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:
morlutoandCursor Agent authored and GitHub committed 2026-10-29 20:06:55 +08:00
1 parent 6fa48f201f
commit 5c05eb88e5
24 files changed
+1384 -39

No files matched your search

@@ -0,0 +1,396 @@
import {
compareJavaScriptSourcePoints,
javascriptSourceRangeContains,
javascriptSourceRangeKey,
} from "../../domain/javascript/javascriptSourceCoordinates.js";
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
import {
addSemanticGraphRelation,
addSemanticGraphUnknown,
retainSemanticGraphNode,
} from "./JavaScriptSemanticGraphConstruction.js";
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
const sameArtifact = (
node: JavaScriptSemanticGraphNode,
context: SemanticFlowProjectionContext,
): boolean =>
node.identity.module_path === context.file.path &&
node.identity.artifact_sha256 === context.file.sha256;
const literalValueKey = (value: string | number | boolean | null): string =>
`${typeof value}:${JSON.stringify(value)}`;
type IndexedOperatorLiteral = {
readonly node: JavaScriptSemanticGraphNode;
readonly range: JavaScriptSourceRange;
readonly order: number;
};
/**
* Literals are remembered by their own source range and value.
* An initializer that merely contains another occurrence, including a
* nested function body, does not reuse the declaration's literal.
*/
const operatorLiteralIndex = (
context: SemanticFlowProjectionContext,
): {
remember(
node: JavaScriptSemanticGraphNode | null,
): JavaScriptSemanticGraphNode | null;
find(
value: string | number | boolean | null,
location: JavaScriptSourceRange,
): JavaScriptSemanticGraphNode | undefined;
} => {
const exact = new Map<string, IndexedOperatorLiteral>();
const byValue = new Map<string, IndexedOperatorLiteral[]>();
const seen = new Set<string>();
let order = 0;
const remember = (
node: JavaScriptSemanticGraphNode | null,
): JavaScriptSemanticGraphNode | null => {
if (
node === null ||
node.kind !== "literal" ||
!sameArtifact(node, context) ||
seen.has(node.node_id)
)
return node;
const range = node.identity.source_range;
const value = node.properties.value;
if (
range === null ||
(typeof value !== "string" &&
typeof value !== "number" &&
typeof value !== "boolean" &&
value !== null)
)
return node;
seen.add(node.node_id);
const entry = { node, range, order: order++ };
const id = `${literalValueKey(value)}\0${javascriptSourceRangeKey(range)}`;
const group = byValue.get(literalValueKey(value)) ?? [];
group.push(entry);
byValue.set(literalValueKey(value), group);
if (!exact.has(id)) exact.set(id, entry);
return node;
};
for (const node of context.state.nodes.values()) remember(node);
return {
remember,
find(value, location) {
const valueKey = literalValueKey(value);
const operandKey = javascriptSourceRangeKey(location);
let best: IndexedOperatorLiteral | undefined;
const consider = (entry: IndexedOperatorLiteral | undefined): void => {
if (
entry !== undefined &&
(best === undefined || entry.order < best.order)
)
best = entry;
};
consider(exact.get(`${valueKey}\0${operandKey}`));
const group = byValue.get(valueKey) ?? [];
let low = 0;
let high = group.length;
while (low < high) {
const middle = (low + high) >> 1;
const entry = group[middle];
if (
entry !== undefined &&
compareJavaScriptSourcePoints(entry.range.start, location.start) <= 0
)
low = middle + 1;
else high = middle;
}
const candidate = group[low - 1];
if (
candidate !== undefined &&
javascriptSourceRangeContains(candidate.range, location)
)
consider(candidate);
return best?.node;
},
};
};
/** Project immediate operator dependencies without treating them as aliases. */
export function* projectSemanticExpressionInfluence(
context: SemanticFlowProjectionContext & {
readonly argumentNodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>;
},
): Generator<void, void> {
if (context.ir.expressionFlowsTruncated)
context.state.fileNodesDropped = true;
const key = javascriptSourceRangeKey;
const expressions = new Map<string, JavaScriptSemanticGraphNode>();
const usedExpressions = new Set<string>();
const reads = new Map(
context.readNodes.flatMap((node) =>
node.identity.source_range === null
? []
: [[key(node.identity.source_range), node] as const],
),
);
const writes = new Map(
context.writeNodes.flatMap((node) =>
node.identity.source_range === null
? []
: [[key(node.identity.source_range), node] as const],
),
);
const bindings = new Map(
context.ir.bindings.flatMap((binding) => {
const node = context.bindingNodes.get(binding.bindingId);
return node === undefined
? []
: binding.definitions.map(
(definition) => [key(definition.location), node] as const,
);
}),
);
for (const [index, flow] of context.ir.expressionFlows.entries()) {
if (index % 64 === 0) yield;
const owner =
flow.ownerCallableId === null
? null
: (context.callableNodes.get(flow.ownerCallableId)?.node_id ?? null);
const node = retainSemanticGraphNode(context.state, context.file, {
kind: "expression",
roleKey: `operator:${index}`,
location: flow.location,
label: flow.operator,
functionNodeId: owner,
properties: { operator: flow.operator },
});
if (node !== null) expressions.set(key(flow.location), node);
}
const remainingUnknowns =
context.state.unknownCapacity === null
? context.readNodes.length
: Math.max(
0,
context.state.unknownCapacity - context.state.unknowns.size,
);
let unsupportedReads = 0;
let unsupportedOverflow = false;
const orderedReads = context.readNodes
.flatMap((node) =>
node.identity.source_range === null
? []
: [{ node, location: node.identity.source_range }],
)
.sort((left, right) =>
compareJavaScriptSourcePoints(left.location.start, right.location.start),
);
let processedOperands = 0;
let processedReferences = 0;
const literals = operatorLiteralIndex(context);
for (const [index, flow] of context.ir.expressionFlows.entries()) {
if (index % 64 === 0) yield;
const result = expressions.get(key(flow.location));
for (const operand of flow.operands) {
if (processedOperands % 64 === 0) yield;
processedOperands += 1;
if (operand.kind === "unsupported") {
if (result !== undefined)
addSemanticGraphUnknown(context.state, {
node_id: result.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail: "An operator operand has unmodeled value dependencies.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, operand.location),
});
if (!unsupportedOverflow) {
let low = 0,
high = orderedReads.length;
while (low < high) {
const middle = Math.floor((low + high) / 2);
if (
compareJavaScriptSourcePoints(
orderedReads[middle]!.location.start,
operand.location.start,
) < 0
)
low = middle + 1;
else high = middle;
}
for (let offset = low; offset < orderedReads.length; offset++) {
const { node: reference, location } = orderedReads[offset]!;
if (
compareJavaScriptSourcePoints(
location.start,
operand.location.end,
) > 0
)
break;
if (processedReferences % 64 === 0) yield;
processedReferences += 1;
if (processedReferences > remainingUnknowns) {
unsupportedOverflow = true;
break;
}
if (
!javascriptSourceRangeContains(operand.location, location) ||
reference.function_node_id !== result?.function_node_id
)
continue;
if (unsupportedReads >= remainingUnknowns) {
unsupportedOverflow = true;
break;
}
unsupportedReads += 1;
addSemanticGraphUnknown(context.state, {
node_id: reference.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail:
"This read occurs inside an unmodeled operator operand; its influence on the result is unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, operand.location),
});
}
}
continue;
}
const source =
operand.kind === "reference"
? reads.get(key(operand.location))
: operand.kind === "expression"
? expressions.get(key(operand.location))
: (literals.find(operand.literal ?? null, operand.location) ??
literals.remember(
retainSemanticGraphNode(context.state, context.file, {
kind: "literal",
roleKey: `operator-literal:${key(operand.location)}:${JSON.stringify(operand.literal ?? null)}`,
location: operand.location,
label: "operator literal",
functionNodeId: result?.function_node_id ?? null,
properties: { value: operand.literal ?? null },
}) ?? null,
));
if (
operand.kind === "expression" &&
source !== undefined &&
result !== undefined
)
usedExpressions.add(key(operand.location));
addSemanticGraphRelation(context.state, {
source,
target: result,
relation: "influences",
resolution: "resolved",
properties: { conditional: operand.conditional },
});
}
}
// Some operands are calls, members or other constructs whose value owner is
// not yet modeled here. Do not claim complete influence for any query that
// may cross one of these unmodeled operator inputs.
if (unsupportedOverflow)
addSemanticGraphUnknown(context.state, {
node_id: null,
family: "data-flow",
relation_kinds: ["influences"],
reason: "resource-limit",
detail:
"The bounded operator-dependency audit omitted some unmodeled operand reads; influence through them remains unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, null),
});
const connect = (
source: JavaScriptSemanticGraphNode | undefined,
target: JavaScriptSemanticGraphNode | undefined,
conditional = false,
) => {
if (
source !== undefined &&
target !== undefined &&
source.identity.source_range !== null &&
expressions.get(key(source.identity.source_range)) === source
)
usedExpressions.add(key(source.identity.source_range));
addSemanticGraphRelation(context.state, {
source,
target,
relation: "influences",
resolution: "resolved",
properties: { conditional },
});
};
for (const flow of context.ir.initializerFlows)
connect(expressions.get(key(flow.source)), bindings.get(key(flow.target)));
for (const flow of context.ir.assignmentFlows) {
const source = expressions.get(key(flow.source));
const target = writes.get(key(flow.target));
if (source !== undefined && target !== undefined)
usedExpressions.add(key(flow.source));
addSemanticGraphRelation(context.state, {
source,
target,
relation: "influences",
resolution: "resolved",
properties: {
conditional: ["&&=", "||=", "??="].includes(flow.operator),
assignment_operator: flow.operator,
},
});
}
for (const call of context.ir.callSites)
for (const argument of call.arguments) {
if (argument.valueExpressionLocation === null) continue;
connect(
expressions.get(key(argument.valueExpressionLocation)),
context.argumentNodes.get(
`${call.callSiteId}\u0000${String(argument.index)}`,
),
);
}
const literalReturns = new Map(
context.ir.literalReturns.map((item) => [key(item.location), item]),
);
for (const callable of context.ir.callables)
for (const site of callable.returnSites) {
if (site.valueExpressionLocation !== null) {
const target = context.returnSiteNodes.get(site.returnSiteId);
connect(expressions.get(key(site.valueExpressionLocation)), target);
const literal = literalReturns.get(key(site.valueExpressionLocation));
if (literal !== undefined) {
const source = retainSemanticGraphNode(context.state, context.file, {
kind: "literal",
roleKey: `return-literal:${site.returnSiteId}`,
location: literal.location,
label: "return literal",
functionNodeId: target?.function_node_id ?? null,
properties: { value: literal.value },
});
addSemanticGraphRelation(context.state, {
source,
target,
relation: "aliases",
resolution: "resolved",
});
}
}
}
for (const flow of context.ir.expressionFlows) {
const node = expressions.get(key(flow.location));
if (node === undefined || usedExpressions.has(key(flow.location))) continue;
addSemanticGraphUnknown(context.state, {
node_id: node.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail:
"The receiving expression or container for this operator result is not modeled; further influence is unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, flow.location),
});
}
}