mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 02:45:58 +00:00
* fix(javascript): preserve selected path host resolution reasons Classify ELOOP and ENAMETOOLONG while resolving the caller-selected input so the path and host code stay on the invalid-request issue. Refs #5623 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): count summary coverage in module units Use the module population for both examined and total counts. Non-module graph nodes remain in the application coverage section. Refs #5464 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): connect direct property reads to return and argument flow Record the unwrapped value expression and alias the object projection's read occurrence so a direct member read stays in backward provenance. Refs #5585, #5648 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): count return wrappers against expression depth Evaluate the original return expression so erased TypeScript and Flow wrappers stay inside the expression-depth budget. Keep the unwrapped node for identity and value ranges. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): describe ELOOP as unresolved symlink traversal A host can return ELOOP for a cycle or for a chain past its traversal limit. Keep the selected path and code without asserting that a cycle exists. Co-authored-by: morluto <morluto@users.noreply.github.com> * test(javascript): skip symlink traversal fixtures on Windows Keep the overlong-name case portable. Windows does not produce ELOOP for these fixtures, matching the other path-failure tests. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): describe ENAMETOOLONG as a path or component limit realpath reports that code for a long component and for a long full path. Keep the selected path and host code without claiming only a filename was too long. Co-authored-by: morluto <morluto@users.noreply.github.com> * style(javascript): format return-depth and object-read changes oxfmt rejected the return-depth test and the object-read index. No behavior change. Co-authored-by: morluto <morluto@users.noreply.github.com> * test(ida): expect unchanged invalid requests to be nonretryable The shared projector no longer marks invalid_request retryable. This lookup assertion was not updated with that contract. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
290 lines
9.0 KiB
TypeScript
290 lines
9.0 KiB
TypeScript
import * as t from "@babel/types";
|
|
|
|
import {
|
|
semanticContainer,
|
|
semanticPropertyPointer,
|
|
} from "./javascriptSemanticSlots.js";
|
|
|
|
import type {
|
|
JavaScriptSemanticBinding,
|
|
JavaScriptSemanticCallable,
|
|
JavaScriptSemanticModuleLink,
|
|
JavaScriptSemanticReturnSite,
|
|
} from "./javascriptSemanticIr.js";
|
|
import type { JavaScriptSemanticValue } from "./javascriptSemanticValueTypes.js";
|
|
import type {
|
|
ProjectedPropertyCoverage,
|
|
ProjectedReturnField,
|
|
} from "./javascriptExportShapeComparisonSchemas.js";
|
|
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
|
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
|
|
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
|
|
import {
|
|
resolveSemanticBindingState,
|
|
type JavaScriptSemanticAnalysisState,
|
|
type JavaScriptSemanticBindingState,
|
|
} from "./javascriptSemanticState.js";
|
|
import { evaluateSemanticExpression } from "./javascriptSemanticValues.js";
|
|
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
|
import { semanticReturnCoverage } from "./javascriptSemanticCoverage.js";
|
|
import { semanticCallableResultExpressions } from "./javascriptSemanticCallableResults.js";
|
|
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
|
|
|
|
interface ReturnExpression {
|
|
readonly node: t.Node | null;
|
|
readonly location: JavaScriptSemanticReturnSite["location"];
|
|
}
|
|
|
|
/** Evaluate direct returns while excluding every nested callable. */
|
|
export const collectSemanticReturns = (
|
|
state: JavaScriptSemanticAnalysisState,
|
|
parserPartial: boolean,
|
|
): JavaScriptSemanticCallable[] => {
|
|
return state.callables.map((callable) => {
|
|
const node = state.callableNodesById.get(callable.callableId);
|
|
if (node === undefined)
|
|
return {
|
|
...callable,
|
|
returnCoverage: {
|
|
status: "partial" as const,
|
|
retainedCount: 0,
|
|
omittedCount: null,
|
|
},
|
|
};
|
|
const expressions = directReturnExpressions(node);
|
|
const sites: JavaScriptSemanticReturnSite[] = [];
|
|
for (const expression of expressions) {
|
|
const returned =
|
|
expression.node === null
|
|
? null
|
|
: unwrapJavaScriptExpression(expression.node).node;
|
|
sites.push({
|
|
returnSiteId: semanticReturnSiteId(
|
|
callable.callableId,
|
|
expression.location,
|
|
),
|
|
location: expression.location,
|
|
identityReferenceLocation:
|
|
returned !== null && t.isIdentifier(returned)
|
|
? range(returned)
|
|
: null,
|
|
valueExpressionLocation: returned === null ? null : range(returned),
|
|
value:
|
|
expression.node === null
|
|
? { status: "unknown", reason: "Return has no value." }
|
|
: evaluateSemanticExpression(expression.node, state),
|
|
});
|
|
}
|
|
return {
|
|
...callable,
|
|
returnSites: sites,
|
|
returnCoverage: semanticReturnCoverage(sites.length, parserPartial),
|
|
};
|
|
});
|
|
};
|
|
|
|
const semanticReturnSiteId = (
|
|
callableId: string,
|
|
location: JavaScriptSemanticReturnSite["location"],
|
|
): string =>
|
|
[
|
|
callableId,
|
|
"return",
|
|
String(location.start.line),
|
|
String(location.start.column),
|
|
String(location.end.line),
|
|
String(location.end.column),
|
|
].join(":");
|
|
|
|
/** Finalize exported bindings only when lexical resolution is unique and exact. */
|
|
export const resolveSemanticModuleCallables = (
|
|
state: JavaScriptSemanticAnalysisState,
|
|
callables: readonly JavaScriptSemanticCallable[],
|
|
bindings: readonly JavaScriptSemanticBinding[],
|
|
): JavaScriptSemanticModuleLink[] => {
|
|
const callableIds = new Set(callables.map(({ callableId }) => callableId));
|
|
const provenanceByBinding = new Map(
|
|
bindings.map(({ bindingId, provenance }) => [bindingId, provenance]),
|
|
);
|
|
return state.moduleLinks.map((link) => {
|
|
if (link.kind === "type-import") return { ...link, callableId: null };
|
|
// Named exports follow their lexical binding, including every assignment.
|
|
// A declaration's ID or a callable's display name cannot prove that link.
|
|
const captured = state.defaultExportExpressions.get(link);
|
|
if (link.localName === null && captured === undefined)
|
|
return link.callableId !== null && callableIds.has(link.callableId)
|
|
? link
|
|
: { ...link, callableId: null };
|
|
const binding = state.moduleLinkBindings.get(link);
|
|
const resolved =
|
|
captured !== undefined
|
|
? callableIdsForNode(captured, state, callableIds, new Set(), true)
|
|
: binding === undefined
|
|
? []
|
|
: callableIdsForBinding(binding, state, callableIds, new Set());
|
|
const provenance =
|
|
binding === undefined || link.specifier !== null
|
|
? undefined
|
|
: provenanceByBinding.get(binding.bindingId);
|
|
const resolutionMode =
|
|
provenance?.status === "module"
|
|
? provenance.origins[0].resolutionMode
|
|
: undefined;
|
|
return {
|
|
...link,
|
|
...(resolutionMode !== undefined ? { resolutionMode } : {}),
|
|
callableId: resolved.length === 1 ? (resolved[0] ?? null) : null,
|
|
};
|
|
});
|
|
};
|
|
|
|
const directReturnExpressions = (callable: t.Node): ReturnExpression[] =>
|
|
semanticCallableResultExpressions(callable)
|
|
.filter(({ kind }) => kind === "return")
|
|
.map(({ node, site }) => ({ node, location: range(site) }));
|
|
|
|
const callableIdsForBinding = (
|
|
binding: JavaScriptSemanticBindingState,
|
|
state: JavaScriptSemanticAnalysisState,
|
|
admitted: ReadonlySet<string>,
|
|
seen: ReadonlySet<string>,
|
|
read?: t.Node,
|
|
): string[] => {
|
|
// A single initializer does not prove an assignment executes or replace
|
|
// the unknown incoming value of a parameter/catch binding.
|
|
if (
|
|
seen.has(binding.bindingId) ||
|
|
(read === undefined && binding.initializers.length !== 1) ||
|
|
binding.definitions.some(
|
|
({ kind }) =>
|
|
(read === undefined && kind === "assignment") ||
|
|
kind === "parameter" ||
|
|
kind === "catch",
|
|
)
|
|
)
|
|
return [];
|
|
const selected =
|
|
read === undefined
|
|
? binding
|
|
: semanticBindingAtRead(binding, read, state.parentsByNode).binding;
|
|
const initializer = selected.initializers[0];
|
|
if (
|
|
initializer === undefined ||
|
|
selected.initializers.length !== 1 ||
|
|
initializer.projection.length > 0 ||
|
|
state.conditionallySkippedExpressions.has(initializer.node)
|
|
)
|
|
return [];
|
|
return callableIdsForNode(
|
|
initializer.node,
|
|
state,
|
|
admitted,
|
|
new Set([...seen, binding.bindingId]),
|
|
read !== undefined,
|
|
);
|
|
};
|
|
|
|
const callableIdsForNode = (
|
|
node: t.Node,
|
|
state: JavaScriptSemanticAnalysisState,
|
|
admitted: ReadonlySet<string>,
|
|
seen: ReadonlySet<string>,
|
|
snapshot = false,
|
|
): string[] => {
|
|
const unwrapped = unwrapJavaScriptExpression(node).node;
|
|
if (unwrapped !== node)
|
|
return callableIdsForNode(unwrapped, state, admitted, seen, snapshot);
|
|
const direct = semanticCallableIdForNode(node);
|
|
if (direct !== null && admitted.has(direct)) return [direct];
|
|
if (t.isIdentifier(node)) {
|
|
const binding = resolveSemanticBindingState(state, node, node.name);
|
|
return binding === undefined
|
|
? []
|
|
: callableIdsForBinding(
|
|
binding,
|
|
state,
|
|
admitted,
|
|
seen,
|
|
snapshot ? node : undefined,
|
|
);
|
|
}
|
|
return [];
|
|
};
|
|
|
|
/** Flatten a semantic value to bounded JSON-pointer leaves for graph projection. */
|
|
export const flattenSemanticReturnValue = (
|
|
value: JavaScriptSemanticValue,
|
|
): {
|
|
readonly fields: readonly ProjectedReturnField[];
|
|
readonly propertyCoverage: readonly ProjectedPropertyCoverage[];
|
|
} => {
|
|
const fields: ProjectedReturnField[] = [];
|
|
const propertyCoverage: ProjectedPropertyCoverage[] = [];
|
|
flattenValue(value, "", fields, propertyCoverage);
|
|
fields.sort((left, right) => compareUnicodeCodePoints(left.path, right.path));
|
|
propertyCoverage.sort((left, right) =>
|
|
compareUnicodeCodePoints(left.path, right.path),
|
|
);
|
|
return { fields, propertyCoverage };
|
|
};
|
|
|
|
const flattenValue = (
|
|
value: JavaScriptSemanticValue,
|
|
path: string,
|
|
fields: ProjectedReturnField[],
|
|
coverage: ProjectedPropertyCoverage[],
|
|
presence: "present" | "absent" | "unknown-coverage" = "present",
|
|
): void => {
|
|
if (presence !== "present") {
|
|
fields.push({
|
|
path,
|
|
presence,
|
|
state: "unknown",
|
|
value: null,
|
|
reason:
|
|
"reason" in value ? value.reason : "Property presence is uncertain.",
|
|
});
|
|
return;
|
|
}
|
|
if (value.status === "literal") {
|
|
fields.push({
|
|
path,
|
|
presence,
|
|
state: "literal",
|
|
value: value.value,
|
|
reason: null,
|
|
});
|
|
return;
|
|
}
|
|
if (value.status === "union") {
|
|
fields.push({
|
|
path,
|
|
presence,
|
|
state: "union",
|
|
value: [...value.values],
|
|
reason: null,
|
|
});
|
|
return;
|
|
}
|
|
if ("reason" in value) {
|
|
fields.push({
|
|
path,
|
|
presence,
|
|
state: "unknown",
|
|
value: null,
|
|
reason: value.reason,
|
|
});
|
|
return;
|
|
}
|
|
const container = semanticContainer(value);
|
|
coverage.push({ path, ...container.coverage });
|
|
for (const slot of container.slots)
|
|
flattenValue(
|
|
slot.value,
|
|
`${path}${semanticPropertyPointer([slot.name])}`,
|
|
fields,
|
|
coverage,
|
|
slot.presence,
|
|
);
|
|
};
|