mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 19:35:56 +00:00
feat(javascript): report observed export property presence (#1160)
* feat(javascript): report observed export property presence Extend compare_javascript_export_shapes so each retained return variant lists observed property names and each change reports presence independently of value resolution. Complete parent coverage classifies one-sided names as added or removed even when static values stay unknown. Fixes #725 * test(javascript): match unknown export-shape values by availability Nested availability objects include a reason when values stay unresolved. Assert availability through objectContaining so presence-only added and removed rows still verify unknown values. * fix(javascript): preserve uncertain export property presence and locations * fix(javascript): normalize presence and preserve local property coverage * fix(javascript): require present discriminants for return pairing * fix(javascript): normalize retained shapes before comparison * refactor(javascript): share return projection with executable examples * fix(javascript): localize uncertainty for missing mutated slots * fix(javascript): bind advertised export examples to exact artifacts * fix(javascript): keep uncertain property flow as candidates --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
1 parent
04aef6df33
commit
45e3edc17f
25 files changed
+1552
-113
No files matched your search
@@ -5,19 +5,28 @@ import {
|
||||
import type {
|
||||
JavaScriptExportShapeComparisonChange,
|
||||
JavaScriptExportShapeComparisonResult,
|
||||
ProjectedExportReturnShapes,
|
||||
JavaScriptExportShapePropertyInventory,
|
||||
} from "./javascriptExportShapeComparisonSchemas.js";
|
||||
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
|
||||
import type { SelectedJavaScriptExport } from "./javascriptExportShapeSelection.js";
|
||||
import type {
|
||||
SelectedJavaScriptExport,
|
||||
RetainedJavaScriptExportShapes,
|
||||
} from "./javascriptExportShapeSelection.js";
|
||||
|
||||
type Primitive = string | number | boolean | null;
|
||||
type Shape = ProjectedExportReturnShapes["static_return_shapes"][number];
|
||||
type Shape = RetainedJavaScriptExportShapes["shapes"][number];
|
||||
type Field = Shape["fields"][number];
|
||||
type SelectorResult = JavaScriptExportShapeComparisonResult["left"];
|
||||
type ValueAvailability = JavaScriptExportShapeComparisonChange["left"];
|
||||
type PropertyPresence =
|
||||
JavaScriptExportShapeComparisonChange["presence"]["left"];
|
||||
type Discriminant = NonNullable<
|
||||
JavaScriptExportShapeComparisonChange["discriminant"]
|
||||
>;
|
||||
const UNKNOWN_COVERAGE_PRESENCE = {
|
||||
left: "unknown-coverage",
|
||||
right: "unknown-coverage",
|
||||
} as const satisfies JavaScriptExportShapeComparisonChange["presence"];
|
||||
|
||||
interface VariantPair {
|
||||
readonly leftIndex: number;
|
||||
@@ -112,12 +121,62 @@ export const buildJavaScriptExportShapeChanges = (
|
||||
return [...paired, ...unpairedLeft, ...unpairedRight];
|
||||
};
|
||||
|
||||
/** List observed property names per retained variant, including unknown values. */
|
||||
export const buildJavaScriptExportShapeInventories = (input: {
|
||||
readonly pairing: JavaScriptExportShapePairing;
|
||||
readonly leftShapes: readonly Shape[];
|
||||
readonly rightShapes: readonly Shape[];
|
||||
}): JavaScriptExportShapePropertyInventory[] => {
|
||||
const leftPairs = new Map(
|
||||
input.pairing.pairs.map((pair) => [pair.leftIndex, pair]),
|
||||
);
|
||||
const rightPairs = new Map(
|
||||
input.pairing.pairs.map((pair) => [pair.rightIndex, pair]),
|
||||
);
|
||||
return [
|
||||
...input.leftShapes.map((shape, index) =>
|
||||
inventoryFor("left", index, shape, leftPairs.get(index)),
|
||||
),
|
||||
...input.rightShapes.map((shape, index) =>
|
||||
inventoryFor("right", index, shape, rightPairs.get(index)),
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
const inventoryFor = (
|
||||
side: JavaScriptExportShapePropertyInventory["side"],
|
||||
variantIndex: number,
|
||||
shape: Shape,
|
||||
pair: VariantPair | undefined,
|
||||
): JavaScriptExportShapePropertyInventory => ({
|
||||
side,
|
||||
variant_index: variantIndex,
|
||||
discriminant: pair?.discriminant ?? null,
|
||||
paired: pair !== undefined,
|
||||
source_range: shape.source_range,
|
||||
properties: uniqueSorted([
|
||||
...shape.fields
|
||||
.filter((field) => fieldPresence(shape, field, field.path) === "present")
|
||||
.map(({ path }) => path),
|
||||
...shape.property_coverage.map(({ path }) => path),
|
||||
]).filter((path) => path !== ""),
|
||||
property_coverage: shape.property_coverage.map(({ path, status }) => ({
|
||||
path,
|
||||
status,
|
||||
})),
|
||||
});
|
||||
|
||||
/** Report whether any retained shape lacks complete property coverage. */
|
||||
export const hasPartialJavaScriptExportPropertyCoverage = (
|
||||
shapes: readonly Shape[],
|
||||
): boolean =>
|
||||
shapes.some((shape) =>
|
||||
shape.property_coverage.some(({ status }) => status === "partial"),
|
||||
shapes.some(
|
||||
(shape) =>
|
||||
shape.property_coverage.some(({ status }) => status === "partial") ||
|
||||
shape.fields.some(
|
||||
(field) =>
|
||||
fieldPresence(shape, field, field.path) === "unknown-coverage",
|
||||
),
|
||||
);
|
||||
|
||||
interface DiscriminantOccurrence {
|
||||
@@ -131,6 +190,7 @@ const discriminantOccurrences = (
|
||||
const output = new Map<string, DiscriminantOccurrence>();
|
||||
shapes.forEach((shape, index) => {
|
||||
for (const field of shape.fields) {
|
||||
if (fieldPresence(shape, field, field.path) !== "present") continue;
|
||||
const value = literalValue(field);
|
||||
if (!value.found) continue;
|
||||
const discriminant = { path: field.path, value: value.value };
|
||||
@@ -204,6 +264,7 @@ const selectionUnknownChange = (
|
||||
status: "unknown",
|
||||
path: "",
|
||||
discriminant: null,
|
||||
presence: UNKNOWN_COVERAGE_PRESENCE,
|
||||
left: selectionAvailability(input.leftSelection.selection, "left"),
|
||||
right: selectionAvailability(input.rightSelection.selection, "right"),
|
||||
left_source_range: null,
|
||||
@@ -221,6 +282,7 @@ const emptyShapeUnknownChange = (
|
||||
status: "unknown",
|
||||
path: "",
|
||||
discriminant: null,
|
||||
presence: UNKNOWN_COVERAGE_PRESENCE,
|
||||
left: emptyShapeAvailability(input.leftShapes, "left"),
|
||||
right: emptyShapeAvailability(input.rightShapes, "right"),
|
||||
left_source_range: null,
|
||||
@@ -261,6 +323,10 @@ const diffPair = (
|
||||
status,
|
||||
path,
|
||||
discriminant,
|
||||
presence: {
|
||||
left: fieldPresence(left, leftField, path),
|
||||
right: fieldPresence(right, rightField, path),
|
||||
},
|
||||
left: fieldAvailability(leftField),
|
||||
right: fieldAvailability(rightField),
|
||||
left_source_range: left.source_range,
|
||||
@@ -288,6 +354,7 @@ const fieldAtPath = (
|
||||
return undefined;
|
||||
return {
|
||||
path,
|
||||
presence: "present",
|
||||
state: "unknown",
|
||||
value: null,
|
||||
reason:
|
||||
@@ -312,24 +379,40 @@ const fieldChangeStatus = ({
|
||||
}: FieldChangeInput):
|
||||
| JavaScriptExportShapeComparisonChange["status"]
|
||||
| null => {
|
||||
if (leftField !== undefined && rightField !== undefined) {
|
||||
if (
|
||||
canonicalExportShapeValue(leftField) ===
|
||||
const leftPresence = fieldPresence(leftShape, leftField, path);
|
||||
const rightPresence = fieldPresence(rightShape, rightField, path);
|
||||
if (
|
||||
leftField !== undefined &&
|
||||
rightField !== undefined &&
|
||||
canonicalExportShapeValue(leftField) ===
|
||||
canonicalExportShapeValue(rightField)
|
||||
)
|
||||
return null;
|
||||
)
|
||||
return null;
|
||||
if (leftPresence === "absent" && rightPresence === "absent") return null;
|
||||
if (
|
||||
leftPresence === "unknown-coverage" ||
|
||||
rightPresence === "unknown-coverage"
|
||||
)
|
||||
return "unknown";
|
||||
if (leftPresence === "absent") return "added";
|
||||
if (rightPresence === "absent") return "removed";
|
||||
if (leftField !== undefined && rightField !== undefined) {
|
||||
return leftField.state === "unknown" || rightField.state === "unknown"
|
||||
? "unknown"
|
||||
: "changed";
|
||||
}
|
||||
const present = leftField ?? rightField;
|
||||
if (present === undefined) return null;
|
||||
const parent = parentPointer(path);
|
||||
const parentsComplete =
|
||||
propertyCoverageComplete(leftShape, parent) &&
|
||||
propertyCoverageComplete(rightShape, parent);
|
||||
if (present.state === "unknown" || !parentsComplete) return "unknown";
|
||||
return leftField === undefined ? "added" : "removed";
|
||||
return null;
|
||||
};
|
||||
|
||||
const fieldPresence = (
|
||||
shape: Shape,
|
||||
field: Field | undefined,
|
||||
path: string,
|
||||
): PropertyPresence => {
|
||||
if (field !== undefined) return field.presence;
|
||||
return propertyCoverageComplete(shape, parentPointer(path))
|
||||
? "absent"
|
||||
: "unknown-coverage";
|
||||
};
|
||||
|
||||
const propertyCoverageComplete = (shape: Shape, path: string | null): boolean =>
|
||||
@@ -345,7 +428,8 @@ const parentPointer = (path: string): string | null => {
|
||||
};
|
||||
|
||||
const fieldAvailability = (field: Field | undefined): ValueAvailability => {
|
||||
if (field === undefined) return { availability: "absent" };
|
||||
if (field === undefined || field.presence === "absent")
|
||||
return { availability: "absent" };
|
||||
if (field.state === "unknown")
|
||||
return {
|
||||
availability: "unknown",
|
||||
@@ -390,6 +474,7 @@ const unpairedChange = (
|
||||
status: "unknown",
|
||||
path: "",
|
||||
discriminant: null,
|
||||
presence: UNKNOWN_COVERAGE_PRESENCE,
|
||||
left: {
|
||||
availability: "unknown",
|
||||
reason:
|
||||
|
||||
Reference in new issue
Block a user