mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
fix(javascript)!: preserve operator influence in semantic traces (#7939)
* fix(javascript)!: preserve operator influence in semantic traces * test(javascript): align semantic influence regressions * style(javascript): format semantic IR * test(javascript): follow literal argument seed influence * fix(javascript): keep one literal node per operator operand Reuse an existing literal for operator influence, and treat TypeScript assertion wrappers as transparent when deciding whether a call result is used. * test(mcp): assert bundle path issues by field compare_bundles reports each relative path as an invalid_format issue instead of one joined diagnostic sentence. * fix(javascript): index operator literals by range and initializer Repeated string operands rescanned every retained literal and every initializer flow. Lookup now uses those indexes, and a 1200-declaration file still projects one literal per operand. * fix(javascript): keep literal and call facts on their own occurrences Operator literals match their own source range, so a nested function does not inherit the enclosing declaration's literal. A direct argument still reaches the call, while the assigned result follows modeled returns. for-loop initializers and updates are discarded call results. * test(javascript): expect conditional operand literals beside binding values A conditional binding keeps one candidate literal per union value and one operator literal per operand token. Those are different facts, so the aggregate payload check counts four nodes and compares literal seeds in query order. * perf(javascript): index call return flows by call site projectCalls filtered every call return for each assigned result. Group those flows once while projecting returns-to-call edges. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
6fa48f201f
commit
5c05eb88e5
24 files changed
+1384
-39
No files matched your search
@@ -18,11 +18,20 @@ import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.
|
||||
import { createSemanticCallableIdStack } from "./javascriptSemanticCallableStack.js";
|
||||
import { traverseJavaScriptValueAstSteps } from "./javascriptSemanticTraversal.js";
|
||||
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
|
||||
import {
|
||||
readExactJavaScriptLiteral,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import { semanticPrimitiveSet } from "./javascriptSemanticPrimitives.js";
|
||||
import { JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS } from "./javascriptAnalysisResourceControls.js";
|
||||
|
||||
type DerivedSemanticAnalysis = Pick<
|
||||
JavaScriptSemanticIr,
|
||||
| "assignmentFlows"
|
||||
| "expressionFlows"
|
||||
| "initializerFlows"
|
||||
| "literalReturns"
|
||||
| "expressionFlowsTruncated"
|
||||
| "argumentFlows"
|
||||
| "callResultFlows"
|
||||
| "callReturnFlows"
|
||||
@@ -53,6 +62,7 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
callables: readonly JavaScriptSemanticCallable[],
|
||||
references: readonly JavaScriptSemanticReference[],
|
||||
parserPartial: boolean,
|
||||
expressionNodeBudget: number = JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS.semanticNodesPerFile,
|
||||
): Generator<void, DerivedSemanticAnalysis> {
|
||||
const calls = collectJavaScriptSemanticCalls(program, state, callables);
|
||||
yield;
|
||||
@@ -97,6 +107,13 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
state,
|
||||
callables,
|
||||
);
|
||||
const expressionFlows = yield* collectExpressionFlowsSteps(
|
||||
program,
|
||||
state,
|
||||
callables,
|
||||
expressionNodeBudget,
|
||||
);
|
||||
const initializerFlows = expressionFlows.initializers;
|
||||
const functionFingerprints =
|
||||
yield* collectJavaScriptSemanticFingerprintsSteps({
|
||||
state,
|
||||
@@ -113,6 +130,10 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
});
|
||||
return {
|
||||
assignmentFlows,
|
||||
expressionFlows: expressionFlows.expressions,
|
||||
initializerFlows,
|
||||
literalReturns: expressionFlows.literalReturns,
|
||||
expressionFlowsTruncated: expressionFlows.truncated,
|
||||
...calls,
|
||||
promiseOperations,
|
||||
...asyncEffects,
|
||||
@@ -124,6 +145,164 @@ export function* collectJavaScriptDerivedSemanticsSteps(
|
||||
};
|
||||
}
|
||||
|
||||
const isInfluenceExpression = (node: t.Node): boolean =>
|
||||
t.isBinaryExpression(node) ||
|
||||
t.isLogicalExpression(node) ||
|
||||
t.isUnaryExpression(node) ||
|
||||
t.isTemplateLiteral(node) ||
|
||||
t.isConditionalExpression(node) ||
|
||||
t.isSequenceExpression(node);
|
||||
|
||||
/** One cooperative pass retains only immediate value operands, not subtree ranges. */
|
||||
function* collectExpressionFlowsSteps(
|
||||
program: t.Program,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
callables: readonly JavaScriptSemanticCallable[],
|
||||
budget: number,
|
||||
): Generator<
|
||||
void,
|
||||
{
|
||||
expressions: JavaScriptSemanticIr["expressionFlows"];
|
||||
initializers: JavaScriptSemanticIr["initializerFlows"];
|
||||
literalReturns: JavaScriptSemanticIr["literalReturns"];
|
||||
truncated: boolean;
|
||||
}
|
||||
> {
|
||||
const expressions: JavaScriptSemanticIr["expressionFlows"][number][] = [];
|
||||
const initializers: JavaScriptSemanticIr["initializerFlows"][number][] = [];
|
||||
const literalReturns: JavaScriptSemanticIr["literalReturns"][number][] = [];
|
||||
const candidates: { node: t.Node; ownerCallableId: string | null }[] = [];
|
||||
let truncated = false;
|
||||
const stack = createSemanticCallableIdStack(
|
||||
new Set(callables.map((item) => item.callableId)),
|
||||
);
|
||||
yield* traverseJavaScriptValueAstSteps(program, state.erasedSyntaxNodes, {
|
||||
enter: (node, parent) => {
|
||||
stack.enter(node, parent);
|
||||
const returnValue = t.isReturnStatement(node)
|
||||
? node.argument
|
||||
: t.isArrowFunctionExpression(node) && !t.isBlockStatement(node.body)
|
||||
? node.body
|
||||
: null;
|
||||
if (returnValue != null) {
|
||||
const expression = unwrapJavaScriptExpression(returnValue).node;
|
||||
const exact = readExactJavaScriptLiteral(expression);
|
||||
if (exact.found) {
|
||||
const value = semanticPrimitiveSet([exact.value]);
|
||||
if (value.status === "literal" && literalReturns.length < budget)
|
||||
literalReturns.push({
|
||||
location: range(expression),
|
||||
value: value.value,
|
||||
});
|
||||
else if (value.status === "literal") truncated = true;
|
||||
else if (
|
||||
value.status === "unknown" &&
|
||||
value.resourceLimit === "primitive-bytes"
|
||||
)
|
||||
state.valueResourceLimits.add("primitive-bytes");
|
||||
}
|
||||
}
|
||||
if (
|
||||
t.isVariableDeclarator(node) &&
|
||||
t.isIdentifier(node.id) &&
|
||||
node.init != null
|
||||
) {
|
||||
const init = unwrapJavaScriptExpression(node.init).node;
|
||||
if (isInfluenceExpression(init)) {
|
||||
if (initializers.length < budget)
|
||||
initializers.push({ target: range(node.id), source: range(init) });
|
||||
else truncated = true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
isInfluenceExpression(node) &&
|
||||
!(t.isTemplateLiteral(node) && t.isTaggedTemplateExpression(parent))
|
||||
)
|
||||
if (candidates.length < budget)
|
||||
candidates.push({
|
||||
node,
|
||||
ownerCallableId: stack.callables.at(-1) ?? null,
|
||||
});
|
||||
else truncated = true;
|
||||
},
|
||||
exit: (node) => stack.exit(node),
|
||||
});
|
||||
let processedOperands = 0;
|
||||
for (const [
|
||||
candidateIndex,
|
||||
{ node, ownerCallableId },
|
||||
] of candidates.entries()) {
|
||||
if (candidateIndex % 64 === 0) yield;
|
||||
let operator: string;
|
||||
let values: readonly t.Node[];
|
||||
if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) {
|
||||
operator = node.operator;
|
||||
values = [node.left, node.right];
|
||||
} else if (t.isUnaryExpression(node)) {
|
||||
operator = node.operator;
|
||||
values = [node.argument];
|
||||
} else if (t.isTemplateLiteral(node)) {
|
||||
operator = "template";
|
||||
values = node.expressions;
|
||||
} else if (t.isConditionalExpression(node)) {
|
||||
operator = "?:";
|
||||
values = [node.test, node.consequent, node.alternate];
|
||||
} else if (t.isSequenceExpression(node)) {
|
||||
operator = ",";
|
||||
values = node.expressions;
|
||||
} else continue;
|
||||
const operands: JavaScriptSemanticIr["expressionFlows"][number]["operands"][number][] =
|
||||
[];
|
||||
for (const [index, value] of values.entries()) {
|
||||
if (processedOperands >= budget) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
if (processedOperands % 64 === 0) yield;
|
||||
processedOperands += 1;
|
||||
const conditional =
|
||||
(t.isLogicalExpression(node) && index === 1) ||
|
||||
(t.isConditionalExpression(node) && index > 0);
|
||||
// Earlier sequence expressions may change the final result via side effects.
|
||||
const unsupported =
|
||||
(t.isUnaryExpression(node) &&
|
||||
(node.operator === "void" || node.operator === "delete")) ||
|
||||
(t.isSequenceExpression(node) && index !== values.length - 1);
|
||||
const operand = unwrapJavaScriptExpression(value).node;
|
||||
const exact = readExactJavaScriptLiteral(operand);
|
||||
const literal = exact.found ? semanticPrimitiveSet([exact.value]) : null;
|
||||
if (
|
||||
literal?.status === "unknown" &&
|
||||
literal.resourceLimit === "primitive-bytes"
|
||||
)
|
||||
state.valueResourceLimits.add("primitive-bytes");
|
||||
operands.push({
|
||||
location: range(operand),
|
||||
conditional,
|
||||
kind: unsupported
|
||||
? "unsupported"
|
||||
: t.isIdentifier(operand)
|
||||
? "reference"
|
||||
: literal?.status === "literal"
|
||||
? "literal"
|
||||
: isInfluenceExpression(operand)
|
||||
? "expression"
|
||||
: "unsupported",
|
||||
...(!unsupported && literal?.status === "literal"
|
||||
? { literal: literal.value }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
expressions.push({
|
||||
location: range(node),
|
||||
operator,
|
||||
ownerCallableId,
|
||||
operands,
|
||||
});
|
||||
}
|
||||
return { expressions, initializers, literalReturns, truncated };
|
||||
}
|
||||
|
||||
/** Keep assignment syntax in the IR so graph consumers never reparse source. */
|
||||
function* collectAssignmentFlowsSteps(
|
||||
program: t.Program,
|
||||
|
||||
Reference in new issue
Block a user