feat(javascript): link assignment right-hand sides to their writes (#6206)
* feat(javascript): link assignment right-hand sides to their writes A declaration carries its value forward, but a later assignment to a variable, a property or through a compound operator stopped the forward trace at the right-hand-side read. The semantic IR now records one syntactic assignment flow per simple assignment target (ranges, operator, owning callable), and the graph projection connects each retained read of the right-hand side to the write occurrence through a defines relation. Compound assignments also consume the previous value of the target; the written value itself stays unknown, keeping the boundary of #4342 intact. Fixes #6193 * fix(javascript): preserve computed key assignment ownership * test(javascript): retain nested callback assignment ownership * docs(javascript): record assignment influence verification * test(mcp): isolate mocks and acknowledge cancellation boundaries * test(javascript): retain CLI diagnostics and current-main verification --------- Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
2968d1e932
commit
9df94a62cd
17 files changed
+702
-35
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type * as t from "@babel/types";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import { collectJavaScriptSemanticAsyncEffects } from "./javascriptSemanticAsyncEffects.js";
|
||||
import { collectJavaScriptSemanticCalls } from "./javascriptSemanticCalls.js";
|
||||
@@ -15,8 +15,14 @@ import { collectJavaScriptSemanticObjects } from "./javascriptSemanticObjects.js
|
||||
import { collectJavaScriptSemanticResources } from "./javascriptSemanticResources.js";
|
||||
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js";
|
||||
|
||||
import { createSemanticCallableIdStack } from "./javascriptSemanticCallableStack.js";
|
||||
import { traverseJavaScriptAstSteps } from "./javascriptSemanticTraversal.js";
|
||||
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
|
||||
|
||||
type DerivedSemanticAnalysis = Pick<
|
||||
JavaScriptSemanticIr,
|
||||
| "assignmentFlows"
|
||||
| "argumentFlows"
|
||||
| "callResultFlows"
|
||||
| "callReturnFlows"
|
||||
@@ -86,6 +92,10 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
callables,
|
||||
);
|
||||
yield;
|
||||
const assignmentFlows = yield* collectAssignmentFlowsSteps(
|
||||
program,
|
||||
callables,
|
||||
);
|
||||
const functionFingerprints =
|
||||
yield* collectJavaScriptSemanticFingerprintsSteps({
|
||||
state,
|
||||
@@ -101,6 +111,7 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
parserPartial,
|
||||
});
|
||||
return {
|
||||
assignmentFlows,
|
||||
...calls,
|
||||
promiseOperations,
|
||||
...asyncEffects,
|
||||
@@ -111,3 +122,34 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
functionFingerprints,
|
||||
};
|
||||
}
|
||||
|
||||
/** Keep assignment syntax in the IR so graph consumers never reparse source. */
|
||||
function* collectAssignmentFlowsSteps(
|
||||
program: t.Program,
|
||||
callables: readonly JavaScriptSemanticCallable[],
|
||||
): Generator<void, JavaScriptSemanticIr["assignmentFlows"]> {
|
||||
const flows: JavaScriptSemanticIr["assignmentFlows"][number][] = [];
|
||||
const stack = createSemanticCallableIdStack(
|
||||
new Set(callables.map((item) => item.callableId)),
|
||||
);
|
||||
yield* traverseJavaScriptAstSteps(program, {
|
||||
enter: (node, parent) => {
|
||||
stack.enter(node, parent);
|
||||
if (!t.isAssignmentExpression(node)) return;
|
||||
const target = unwrapJavaScriptExpression(node.left).node;
|
||||
// Destructuring has its own property-selection owner.
|
||||
if (!t.isIdentifier(target) && !t.isMemberExpression(target)) return;
|
||||
const source = unwrapJavaScriptExpression(node.right).node;
|
||||
flows.push({
|
||||
target: range(target),
|
||||
source: range(source),
|
||||
sourceIsReference:
|
||||
t.isIdentifier(source) || t.isMemberExpression(source),
|
||||
operator: node.operator,
|
||||
ownerCallableId: stack.callables.at(-1) ?? null,
|
||||
});
|
||||
},
|
||||
exit: (node) => stack.exit(node),
|
||||
});
|
||||
return flows;
|
||||
}
|
||||
Reference in new issue
Block a user