fix(javascript): preserve path reasons, module coverage, and property provenance (#5651)
* 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>
This commit is contained in:
12 files changed
+315
-7
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
import { javaScriptSemanticNodeId } from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import type { JavaScriptSemanticIr } from "../../domain/javascript/javascriptSemanticIr.js";
|
||||
import { sourceRangesEqual } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js";
|
||||
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
@@ -190,6 +191,57 @@ const addPromiseUnknown = (
|
||||
});
|
||||
};
|
||||
|
||||
interface ObjectReadIndex {
|
||||
readonly state: SemanticGraphProjectionState;
|
||||
readonly nodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>;
|
||||
}
|
||||
|
||||
const objectReadIndexes = new WeakMap<JavaScriptSemanticIr, ObjectReadIndex>();
|
||||
|
||||
const objectReadRangeKey = (location: JavaScriptSourceRange): string =>
|
||||
`${String(location.start.line)}:${String(location.start.column)}:${String(location.end.line)}:${String(location.end.column)}`;
|
||||
|
||||
/**
|
||||
* The object projection owns a member read's occurrence. Index it once per
|
||||
* file so return and argument flow can connect that expression without
|
||||
* scanning every property read again.
|
||||
*/
|
||||
const objectReadOccurrences = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
): ReadonlyMap<string, JavaScriptSemanticGraphNode> => {
|
||||
const cached = objectReadIndexes.get(context.ir);
|
||||
if (cached !== undefined && cached.state === context.state)
|
||||
return cached.nodes;
|
||||
const nodes = new Map<string, JavaScriptSemanticGraphNode>();
|
||||
for (const operation of context.ir.objectOperations) {
|
||||
if (operation.kind !== "read") continue;
|
||||
const node = context.state.nodes.get(
|
||||
javaScriptSemanticNodeId({
|
||||
kind: "expression",
|
||||
identity: {
|
||||
artifact_sha256: context.file.sha256,
|
||||
module_path: context.file.path,
|
||||
source_range: operation.location,
|
||||
role_key: operation.objectOperationId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
if (node !== undefined)
|
||||
nodes.set(objectReadRangeKey(operation.location), node);
|
||||
}
|
||||
objectReadIndexes.set(context.ir, { state: context.state, nodes });
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/** Return the retained read occurrence for one unwrapped member expression. */
|
||||
export const semanticObjectReadOccurrence = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
location: JavaScriptSourceRange | null,
|
||||
): JavaScriptSemanticGraphNode | undefined =>
|
||||
location === null
|
||||
? undefined
|
||||
: objectReadOccurrences(context).get(objectReadRangeKey(location));
|
||||
|
||||
/** Link direct return expressions to their retained semantic references. */
|
||||
export const projectSemanticReturnValues = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
@@ -212,6 +264,15 @@ export const projectSemanticReturnValues = (
|
||||
? "resolved"
|
||||
: "candidate",
|
||||
});
|
||||
addSemanticGraphRelation(context.state, {
|
||||
source: semanticObjectReadOccurrence(
|
||||
context,
|
||||
site.valueExpressionLocation,
|
||||
),
|
||||
target: returnNode,
|
||||
relation: "aliases",
|
||||
resolution: "resolved",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user