mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
Resolve ordered descriptor values at the shared JavaScript fact owner and preserve uncertain intrinsic writes instead of stale exports. Normalize ordinary method values and reuse canonical primitive, prototype, ambient identity and ordering facts across consumers. 384 selected tests, format/static/docs checks, all final CI jobs and reviews pass. Actual Node controls cover 35 bundles; one possibly repeated factory explicitly stays unknown. Shipped Obsidian selected-source complete Evidence matches base/fixed CLI and MCP, with coverage and resource measurements in the PR. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
427 lines
13 KiB
TypeScript
427 lines
13 KiB
TypeScript
import * as t from "@babel/types";
|
|
import { expect, it } from "vitest";
|
|
|
|
import {
|
|
semanticMemberChain,
|
|
semanticStaticPropertyKey,
|
|
staticObjectProperty,
|
|
staticObjectPropertyReader,
|
|
staticObjectPropertyNames,
|
|
} from "./javascriptAstValues.js";
|
|
import { SEMANTIC_EXPRESSION_DEPTH_LIMIT } from "./javascriptSemanticResourceLimits.js";
|
|
|
|
it("keeps string spread indices separate from named descriptor fields", () => {
|
|
for (const text of ["", "x", "😀", "x".repeat(1_000_000)]) {
|
|
const original = t.objectProperty(
|
|
t.identifier("value"),
|
|
t.identifier("sender"),
|
|
);
|
|
const object = t.objectExpression([
|
|
original,
|
|
t.spreadElement(t.stringLiteral(text)),
|
|
]);
|
|
const read = staticObjectPropertyReader(object);
|
|
expect(read("value")).toEqual({
|
|
status: "explicit",
|
|
property: original,
|
|
value: original.value,
|
|
});
|
|
expect(read("get")).toEqual({ status: "missing" });
|
|
expect(read("set")).toEqual({ status: "missing" });
|
|
expect(read("0").status).toBe(text.length > 0 ? "dynamic" : "missing");
|
|
expect(read("1").status).toBe(text.length > 1 ? "dynamic" : "missing");
|
|
for (const key of ["00", "-0", "1.0", "length"])
|
|
expect(read(key)).toEqual({ status: "missing" });
|
|
// Nonempty strings keep their indexed inventory partial without expanding
|
|
// it into character-sized AST nodes or a potentially huge key-name array.
|
|
expect(staticObjectPropertyNames(object)).toEqual({
|
|
status: text.length > 0 ? "partial" : "complete",
|
|
names: ["value"],
|
|
});
|
|
}
|
|
});
|
|
|
|
it("keeps numeric overwrite order and constant template string spread keys", () => {
|
|
const property = t.objectProperty(
|
|
t.stringLiteral("0"),
|
|
t.identifier("original"),
|
|
);
|
|
const spread = t.spreadElement(
|
|
t.templateLiteral([t.templateElement({ raw: "x", cooked: "x" }, true)], []),
|
|
);
|
|
expect(
|
|
staticObjectProperty(t.objectExpression([property, spread]), "0").status,
|
|
).toBe("dynamic");
|
|
expect(
|
|
staticObjectProperty(t.objectExpression([spread, property]), "0"),
|
|
).toEqual({ status: "explicit", property, value: property.value });
|
|
});
|
|
|
|
it("keeps literal array indices, holes and unknown iterable suffixes separate from names", () => {
|
|
const value = t.objectProperty(t.identifier("value"), t.identifier("sender"));
|
|
for (const elements of [
|
|
[],
|
|
[null, null],
|
|
[t.numericLiteral(1)],
|
|
[null, t.numericLiteral(1)],
|
|
[null, t.spreadElement(t.identifier("input"))],
|
|
]) {
|
|
const object = t.objectExpression([
|
|
value,
|
|
t.spreadElement(t.arrayExpression(elements)),
|
|
]);
|
|
const read = staticObjectPropertyReader(object);
|
|
expect(read("value")).toEqual({
|
|
status: "explicit",
|
|
property: value,
|
|
value: value.value,
|
|
});
|
|
expect(read("get")).toEqual({ status: "missing" });
|
|
expect(read("set")).toEqual({ status: "missing" });
|
|
expect(read("0").status).toBe(elements[0] == null ? "missing" : "dynamic");
|
|
expect(read("1").status).toBe(elements[1] == null ? "missing" : "dynamic");
|
|
expect(read("100").status).toBe(
|
|
elements.some((entry) => t.isSpreadElement(entry))
|
|
? "dynamic"
|
|
: "missing",
|
|
);
|
|
expect(read("length")).toEqual({ status: "missing" });
|
|
expect(staticObjectPropertyNames(object)).toEqual({
|
|
status: elements.some((entry) => entry != null) ? "partial" : "complete",
|
|
names: ["value"],
|
|
});
|
|
}
|
|
});
|
|
|
|
it("preserves ordinary method data values without executing getter effects", () => {
|
|
const method = (name: string, computed = false) =>
|
|
t.objectMethod(
|
|
"method",
|
|
t.identifier(name),
|
|
[],
|
|
t.blockStatement([]),
|
|
computed,
|
|
);
|
|
const getter = t.objectMethod(
|
|
"get",
|
|
t.identifier("helper"),
|
|
[],
|
|
t.blockStatement([]),
|
|
);
|
|
const setter = t.objectMethod(
|
|
"set",
|
|
t.identifier("helper"),
|
|
[t.identifier("value")],
|
|
t.blockStatement([]),
|
|
);
|
|
for (const [member, status] of [
|
|
[method("helper"), "explicit"],
|
|
[setter, "explicit"],
|
|
[method("stdio"), "explicit"],
|
|
[getter, "dynamic"],
|
|
[method("key", true), "dynamic"],
|
|
] as const) {
|
|
const object = t.objectExpression([
|
|
t.objectProperty(t.identifier("stdio"), t.stringLiteral("inherit")),
|
|
t.spreadElement(t.objectExpression([member])),
|
|
]);
|
|
expect(staticObjectProperty(object, "stdio").status).toBe(status);
|
|
}
|
|
});
|
|
|
|
it("limits unary-plus keys to transparent numeric literals", () => {
|
|
const wrapped = t.tsAsExpression(t.numericLiteral(1), t.tsNumberKeyword());
|
|
expect(semanticStaticPropertyKey(t.unaryExpression("+", wrapped), true)).toBe(
|
|
"1",
|
|
);
|
|
for (const argument of [
|
|
t.stringLiteral("1"),
|
|
t.bigIntLiteral(1n),
|
|
t.identifier("input"),
|
|
])
|
|
expect(
|
|
semanticStaticPropertyKey(t.unaryExpression("+", argument), true),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("bounds nested inline object spreads without losing a shallow final property", () => {
|
|
let object = t.objectExpression([
|
|
t.objectProperty(t.identifier("stdio"), t.stringLiteral("inherit")),
|
|
]);
|
|
for (let depth = 0; depth < SEMANTIC_EXPRESSION_DEPTH_LIMIT; depth++)
|
|
object = t.objectExpression([t.spreadElement(object)]);
|
|
expect(staticObjectProperty(object, "stdio").status).toBe("explicit");
|
|
expect(
|
|
staticObjectProperty(t.objectExpression([t.spreadElement(object)]), "stdio")
|
|
.status,
|
|
).toBe("dynamic");
|
|
});
|
|
|
|
it("collects complete own-key sets through bounded literal spreads", () => {
|
|
const names = staticObjectPropertyNames(
|
|
t.objectExpression([
|
|
t.spreadElement(
|
|
t.objectExpression([
|
|
t.objectProperty(t.identifier("query"), t.objectExpression([])),
|
|
t.objectProperty(t.identifier("shared"), t.numericLiteral(1)),
|
|
]),
|
|
),
|
|
t.objectProperty(t.identifier("direct"), t.objectExpression([])),
|
|
t.objectProperty(t.identifier("shared"), t.numericLiteral(2)),
|
|
]),
|
|
);
|
|
expect(names).toEqual({
|
|
status: "complete",
|
|
names: ["query", "shared", "direct"],
|
|
});
|
|
});
|
|
|
|
it("keeps key enumeration unknown when object membership is unresolved", () => {
|
|
const dynamicSpread = t.identifier("dynamicProperties");
|
|
const computedKey = t.identifier("propertyName");
|
|
const cases = [
|
|
t.objectExpression([
|
|
t.spreadElement(dynamicSpread),
|
|
t.objectProperty(t.identifier("direct"), t.objectExpression([])),
|
|
]),
|
|
t.objectExpression([
|
|
t.objectProperty(computedKey, t.objectExpression([]), true),
|
|
t.objectProperty(t.identifier("direct"), t.objectExpression([])),
|
|
]),
|
|
t.objectExpression([
|
|
t.spreadElement(
|
|
t.objectExpression([
|
|
t.objectMethod(
|
|
"get",
|
|
t.identifier("query"),
|
|
[],
|
|
t.blockStatement([]),
|
|
),
|
|
]),
|
|
),
|
|
]),
|
|
];
|
|
for (const object of cases)
|
|
expect(staticObjectPropertyNames(object).status).toBe("partial");
|
|
});
|
|
|
|
it("retains known keys alongside unresolved object membership", () => {
|
|
expect(
|
|
staticObjectPropertyNames(
|
|
t.objectExpression([
|
|
t.spreadElement(t.identifier("dynamicProperties")),
|
|
t.objectProperty(t.identifier("direct"), t.objectExpression([])),
|
|
t.objectProperty(
|
|
t.identifier("propertyName"),
|
|
t.objectExpression([]),
|
|
true,
|
|
),
|
|
]),
|
|
),
|
|
).toEqual({ status: "partial", names: ["direct"] });
|
|
});
|
|
|
|
it("does not claim a complete key set after exceeding spread depth", () => {
|
|
let object = t.objectExpression([
|
|
t.objectProperty(t.identifier("query"), t.objectExpression([])),
|
|
]);
|
|
for (let depth = 0; depth < SEMANTIC_EXPRESSION_DEPTH_LIMIT; depth++)
|
|
object = t.objectExpression([t.spreadElement(object)]);
|
|
expect(staticObjectPropertyNames(object).status).toBe("complete");
|
|
expect(
|
|
staticObjectPropertyNames(t.objectExpression([t.spreadElement(object)]))
|
|
.status,
|
|
).toBe("partial");
|
|
});
|
|
|
|
it("enumerates a wide inline spread without a variadic call overflow", () => {
|
|
const properties = Array.from({ length: 130_000 }, (_, index) =>
|
|
t.objectProperty(t.identifier(`key${index}`), t.numericLiteral(index)),
|
|
);
|
|
const result = staticObjectPropertyNames(
|
|
t.objectExpression([t.spreadElement(t.objectExpression(properties))]),
|
|
);
|
|
expect(result.status).toBe("complete");
|
|
if (result.status === "complete") {
|
|
expect(result.names).toHaveLength(properties.length);
|
|
expect(result.names[0]).toBe("key0");
|
|
expect(result.names.at(-1)).toBe("key129999");
|
|
}
|
|
});
|
|
|
|
it("excludes prototype setters from own keys but preserves other __proto__ forms", () => {
|
|
const setter = t.objectProperty(
|
|
t.identifier("__proto__"),
|
|
t.objectExpression([
|
|
t.objectProperty(t.identifier("inherited"), t.numericLiteral(1)),
|
|
]),
|
|
);
|
|
expect(
|
|
staticObjectPropertyNames(
|
|
t.objectExpression([
|
|
setter,
|
|
t.objectProperty(t.identifier("own"), t.numericLiteral(2)),
|
|
]),
|
|
),
|
|
).toEqual({ status: "complete", names: ["own"] });
|
|
|
|
const ownForms = [
|
|
t.objectProperty(t.stringLiteral("__proto__"), t.numericLiteral(1), true),
|
|
t.objectProperty(
|
|
t.identifier("__proto__"),
|
|
t.identifier("__proto__"),
|
|
false,
|
|
true,
|
|
),
|
|
t.objectMethod(
|
|
"method",
|
|
t.identifier("__proto__"),
|
|
[],
|
|
t.blockStatement([]),
|
|
),
|
|
];
|
|
for (const member of ownForms)
|
|
expect(staticObjectPropertyNames(t.objectExpression([member]))).toEqual({
|
|
status: "complete",
|
|
names: ["__proto__"],
|
|
});
|
|
});
|
|
|
|
it("keeps inherited lookup uncertain after a top-level prototype setter", () => {
|
|
const setter = t.objectProperty(
|
|
t.identifier("__proto__"),
|
|
t.objectExpression([
|
|
t.objectProperty(t.identifier("inherited"), t.numericLiteral(1)),
|
|
]),
|
|
);
|
|
expect(
|
|
staticObjectProperty(t.objectExpression([setter]), "inherited").status,
|
|
).toBe("dynamic");
|
|
expect(
|
|
staticObjectProperty(
|
|
t.objectExpression([
|
|
setter,
|
|
t.objectProperty(t.identifier("inherited"), t.numericLiteral(2)),
|
|
]),
|
|
"inherited",
|
|
).status,
|
|
).toBe("explicit");
|
|
expect(
|
|
staticObjectProperty(
|
|
t.objectExpression([
|
|
t.objectProperty(t.identifier("inherited"), t.numericLiteral(2)),
|
|
setter,
|
|
]),
|
|
"inherited",
|
|
).status,
|
|
).toBe("explicit");
|
|
const spreadSource = t.objectExpression([
|
|
setter,
|
|
t.objectProperty(t.identifier("visible"), t.numericLiteral(3)),
|
|
]);
|
|
expect(
|
|
staticObjectProperty(
|
|
t.objectExpression([t.spreadElement(spreadSource)]),
|
|
"inherited",
|
|
).status,
|
|
).toBe("missing");
|
|
});
|
|
|
|
it("omits prototype setters copied by literal object spread", () => {
|
|
const source = t.objectExpression([
|
|
t.objectProperty(
|
|
t.identifier("__proto__"),
|
|
t.objectExpression([
|
|
t.objectProperty(t.identifier("hidden"), t.numericLiteral(1)),
|
|
]),
|
|
),
|
|
t.objectProperty(t.identifier("visible"), t.numericLiteral(2)),
|
|
]);
|
|
expect(
|
|
staticObjectPropertyNames(t.objectExpression([t.spreadElement(source)])),
|
|
).toEqual({ status: "complete", names: ["visible"] });
|
|
});
|
|
|
|
it("shares static member paths and stops before unadmitted path expansion", () => {
|
|
const root = t.identifier("registry");
|
|
const selected = t.optionalMemberExpression(
|
|
t.memberExpression(
|
|
t.tsAsExpression(root, t.tsUnknownKeyword()),
|
|
t.stringLiteral("query"),
|
|
true,
|
|
),
|
|
t.identifier("nested"),
|
|
false,
|
|
true,
|
|
);
|
|
expect(semanticMemberChain(selected)).toEqual({
|
|
root,
|
|
members: ["query", "nested"],
|
|
});
|
|
expect(
|
|
semanticMemberChain(t.memberExpression(root, t.identifier("key"), true)),
|
|
).toBeUndefined();
|
|
let admitted = 0;
|
|
expect(semanticMemberChain(selected, () => ++admitted < 2)).toBeUndefined();
|
|
expect(admitted).toBe(2);
|
|
});
|
|
|
|
it("retains exact ordinary method data nodes while keeping accessors unknown", () => {
|
|
const method = t.objectMethod(
|
|
"method",
|
|
t.identifier("value"),
|
|
[],
|
|
t.blockStatement([]),
|
|
);
|
|
const getter = t.objectMethod(
|
|
"get",
|
|
t.identifier("value"),
|
|
[],
|
|
t.blockStatement([]),
|
|
);
|
|
expect(staticObjectProperty(t.objectExpression([method]), "value")).toEqual({
|
|
status: "explicit",
|
|
property: method,
|
|
value: method,
|
|
});
|
|
expect(staticObjectProperty(t.objectExpression([getter]), "value")).toEqual({
|
|
status: "dynamic",
|
|
});
|
|
});
|
|
|
|
it("keeps primitive unary results propertyless and typeof results index-only", () => {
|
|
const value = t.objectProperty(t.identifier("value"), t.identifier("sender"));
|
|
const index = t.objectProperty(
|
|
t.stringLiteral("0"),
|
|
t.stringLiteral("prior"),
|
|
);
|
|
for (const operator of ["+", "-", "~", "!", "void", "delete"] as const) {
|
|
const object = t.objectExpression([
|
|
value,
|
|
index,
|
|
t.spreadElement(t.unaryExpression(operator, t.numericLiteral(1))),
|
|
]);
|
|
expect(staticObjectProperty(object, "value")).toEqual({
|
|
status: "explicit",
|
|
property: value,
|
|
value: value.value,
|
|
});
|
|
expect(staticObjectProperty(object, "0")).toEqual({
|
|
status: "explicit",
|
|
property: index,
|
|
value: index.value,
|
|
});
|
|
}
|
|
const object = t.objectExpression([
|
|
value,
|
|
index,
|
|
t.spreadElement(t.unaryExpression("typeof", t.identifier("input"))),
|
|
]);
|
|
expect(staticObjectProperty(object, "value")).toEqual({
|
|
status: "explicit",
|
|
property: value,
|
|
value: value.value,
|
|
});
|
|
expect(staticObjectProperty(object, "0")).toEqual({ status: "dynamic" });
|
|
});
|