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:
Matt Van Hornandmorluto authored and GitHub committed 2026-10-09 18:30:31 +08:00
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: