fix(javascript): retain standalone GraphQL source operations

Retain named operations in GraphQL Source bodies with locationOffset metadata.
Use final property ownership and parsed lexical facts to suppress copies only
when a recovered document, its containers and source aliases have proven
private metadata ownership. Preserve independent observations after writes,
exports, opaque uses, classic-script global exposure or direct evaluation.

Share interpretation across application and captured-web analysis. Build
optional reference indexes once per source and checkpoint examined records,
including suppressed findings, to retain cooperative cancellation.

Fixes #3311

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
Ysopwnable
2026-10-15 15:59:42 +08:00
committed by GitHub
parent c1fd496e6a
commit 6688ed6db7
13 changed files with 986 additions and 234 deletions
@@ -27,6 +27,7 @@ import type {
JavaScriptSemanticAnalysisState,
JavaScriptSemanticBindingState,
JavaScriptSemanticScopeState,
JavaScriptBindingIndex,
} from "./javascriptSemanticState.js";
import {
currentSemanticScope,
@@ -243,46 +244,37 @@ export interface JavaScriptReceiverFacts {
readonly rpcClientRoots: ReadonlySet<t.Node>;
}
/**
* Resolve only the lexical receiver identities needed to distinguish browser
* `window.open` from XHR and global storage `.open` calls, configuration
* `.get` reads from HTTP client calls, and tRPC client roots (excluding
* overwritten bindings). This deliberately builds definitions without running
* semantic value or provenance analysis.
*/
export const classifyParsedJavaScriptReceivers = (
file: ParsedJavaScriptSource,
): JavaScriptReceiverFacts =>
completeSemanticSteps(classifyParsedJavaScriptReceiversSteps(file));
/** Classify receivers while yielding during lexical and AST scans. */
export function* classifyParsedJavaScriptReceiversSteps(
file: ParsedJavaScriptSource,
): Generator<void, JavaScriptReceiverFacts> {
const state = createState(file.program);
yield* collectDefinitionsSteps(file.program, state);
return yield* classifyDefinitionReceiversSteps(file.program, state);
export interface JavaScriptReceiverIndex extends JavaScriptBindingIndex {
readonly receivers: JavaScriptReceiverFacts;
}
/**
* Index bindings and classify receivers from one definitions pass. Receiver
* facts read the definitions-only phase, so they are classified before the
* same state is extended with ambient writes.
* facts read the definitions-only phase. Consumers request ambient writes and
* lexical references only when needed; each optional index is built once.
*/
export function* indexJavaScriptBindingsWithReceiversSteps(
program: t.Program,
): Generator<
void,
{
readonly bindings: JavaScriptSemanticAnalysisState;
readonly receivers: JavaScriptReceiverFacts;
}
> {
): Generator<void, JavaScriptReceiverIndex> {
const state = createState(program);
yield* collectDefinitionsSteps(program, state);
const receivers = yield* classifyDefinitionReceiversSteps(program, state);
yield* indexJavaScriptAmbientWritesSteps(program, state);
return { bindings: state, receivers };
let ambientIndexed = false;
let references: readonly JavaScriptSemanticReference[] | undefined;
return {
sourceType: program.sourceType,
bindings: state,
receivers,
*indexAmbientWritesSteps() {
if (ambientIndexed) return;
yield* indexJavaScriptAmbientWritesSteps(program, state);
ambientIndexed = true;
},
*referencesSteps() {
references ??= yield* collectSemanticReferencesSteps(program, state);
return references;
},
};
}
function* classifyDefinitionReceiversSteps(