2026-07-22 23:07:51 +00:00
import {
createJavaScriptSemanticGraph ,
2026-10-10 03:14:37 -07:00
sealTransferredJavaScriptSemanticGraphSteps ,
2026-07-22 23:07:51 +00:00
type JavaScriptSemanticGraph ,
2026-10-07 17:16:58 +08:00
} from "../../domain/javascript/javascriptSemanticGraph.js" ;
2026-10-10 03:10:50 +08:00
import type {
JavaScriptSemanticFingerprint ,
JavaScriptSemanticGraphNode ,
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js" ;
2026-07-22 23:07:51 +00:00
import {
JAVASCRIPT_SEMANTIC_RELATION_FAMILIES ,
JAVASCRIPT_SEMANTIC_RELATION_FAMILY ,
2026-10-11 07:22:50 +08:00
JAVASCRIPT_SEMANTIC_RELATIONS ,
2026-10-07 17:16:58 +08:00
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js" ;
2026-07-22 23:07:51 +00:00
import type {
JavaScriptSemanticCallArgument ,
JavaScriptSemanticIr ,
2026-10-07 17:16:58 +08:00
} from "../../domain/javascript/javascriptSemanticIr.js" ;
import { sourceRangesEqual as rangesEqual } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js" ;
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js" ;
2026-07-22 23:07:51 +00:00
import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js" ;
2026-10-07 17:57:33 +08:00
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js" ;
2026-07-24 13:17:38 +00:00
import {
projectSemanticEvents ,
projectSemanticTimers ,
} from "./JavaScriptSemanticGraphAsyncProjection.js" ;
import { projectSemanticChildProcesses } from "./JavaScriptSemanticGraphChildProcessProjection.js" ;
import {
projectSemanticBoundaries ,
projectSemanticConfiguration ,
projectSemanticRequests ,
} from "./JavaScriptSemanticGraphDataProjection.js" ;
import { projectSemanticResources } from "./JavaScriptSemanticGraphResourceProjection.js" ;
import { projectSemanticObjects } from "./JavaScriptSemanticGraphObjectProjection.js" ;
import { projectSemanticValues } from "./JavaScriptSemanticGraphValueProjection.js" ;
2026-10-16 06:06:00 +08:00
import {
inferredSemanticEvidenceAt ,
observedSemanticEvidence ,
unknownSemanticEvidence ,
} from "./JavaScriptSemanticGraphEvidence.js" ;
import {
semanticCoverageResourceLimits ,
semanticResourceLimitCoverage ,
} from "../../domain/javascript/javascriptSemanticCoverage.js" ;
import { semanticResourceLimitReason } from "../../domain/javascript/javascriptSemanticResourceLimits.js" ;
2026-10-11 07:22:50 +08:00
import {
partialApplicationCoverage ,
type ApplicationCoverage ,
} from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js" ;
import type { JsonValue } from "../../domain/jsonValue.js" ;
2026-10-12 09:28:59 +08:00
import {
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS ,
2026-10-17 21:57:31 +08:00
JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM ,
2026-10-12 09:28:59 +08:00
javaScriptAnalysisCoverageLimits ,
} from "../../domain/javascript/javascriptAnalysisResourceControls.js" ;
2026-07-24 13:17:38 +00:00
import { projectSemanticFunctionFingerprints } from "./JavaScriptSemanticGraphFingerprintProjection.js" ;
2026-07-22 23:07:51 +00:00
import {
projectSemanticClosureCaptures ,
projectSemanticFrontiers ,
2026-07-24 13:17:38 +00:00
projectSemanticPromises ,
2026-07-22 23:07:51 +00:00
projectSemanticReturnValues ,
type SemanticFlowProjectionContext ,
} from "./JavaScriptSemanticGraphFlowProjection.js" ;
import {
2026-10-08 16:15:35 +08:00
createSemanticCallableOwnerLookup ,
createSemanticCallSiteLookup ,
createSemanticNodeRangeLookup ,
2026-07-22 23:07:51 +00:00
semanticFamilyStatus ,
} from "./JavaScriptSemanticGraphProjection.js" ;
import {
addSemanticFallbackRoot as addFallbackRoot ,
2026-10-17 21:57:31 +08:00
addSemanticGraphUnknown ,
recordSemanticUnknownOmission ,
2026-10-08 16:15:35 +08:00
retainSemanticGraphNode as retainNode ,
2026-07-22 23:07:51 +00:00
addSemanticGraphRelation as addRelation ,
createSemanticGraphProjectionState as emptyState ,
2026-10-08 16:15:35 +08:00
bindSemanticGraphApplicationNodes ,
2026-07-22 23:07:51 +00:00
type SemanticGraphProjectionState as BuilderState ,
} from "./JavaScriptSemanticGraphConstruction.js" ;
2026-10-17 21:57:31 +08:00
import type {
JavaScriptSemanticFileProjection ,
JavaScriptSemanticProjectionCapacity ,
} from "../../domain/javascript/javascriptSemanticFileProjection.js" ;
2026-10-12 09:28:59 +08:00
import type { ElectronCallPath } from "../../domain/javascript/electronStaticAnalysisTypes.js" ;
import { projectElectronSemanticCallPaths } from "./JavaScriptSemanticGraphElectronProjection.js" ;
2026-07-22 23:07:51 +00:00
interface BuilderInput {
readonly rootArtifactSha256 : string ;
readonly applicationGraph : Pick <
JavaScriptApplicationGraph ,
"graph_id" | "nodes"
> ;
readonly analysis : JavaScriptArtifactAnalysis ;
}
interface FileContext extends SemanticFlowProjectionContext {
readonly file : JavaScriptArtifactFile ;
readonly ir : JavaScriptSemanticIr ;
readonly state : BuilderState ;
readonly moduleNode : JavaScriptSemanticGraphNode ;
readonly bindingNodes : ReadonlyMap < string , JavaScriptSemanticGraphNode >;
readonly callableNodes : ReadonlyMap < string , JavaScriptSemanticGraphNode >;
readonly callSiteNodes : ReadonlyMap < string , JavaScriptSemanticGraphNode >;
readonly returnSiteNodes : ReadonlyMap < string , JavaScriptSemanticGraphNode >;
readonly argumentNodes : Map < string , JavaScriptSemanticGraphNode >;
readonly referenceNodes : JavaScriptSemanticGraphNode [];
readonly callResolutions : ReadonlyMap < string , "candidate" | "resolved" >;
2026-10-08 16:15:35 +08:00
readonly callableOwnerAt : ReturnType <
typeof createSemanticCallableOwnerLookup
> ;
2026-07-22 23:07:51 +00:00
}
2026-10-07 12:22:10 -05:00
/**
* Resource-safety ceiling on retained semantic nodes.
*
* The semantic projection emits one node per AST expression, call site, binding
* and property slot. That is unbounded in input size. A small tree of five files
* already yields on the order of 170,000 nodes, and larger trees yield millions,
* exhausting the heap before the result can be serialized.
*
* This is a real resource-safety constraint, not a presentation preference, so
* the ceiling is applied and reported through the graph's own `coverage` fields
* (`truncated`, `omitted_nodes`, `omitted_relations`, `limits`) rather than
* silently dropping data.
*
2026-10-12 09:28:59 +08:00
* Defaults bound work even when ample heap is available. Callers may raise the
* capacities explicitly; isolated worker heap, bounded record transport and
* parent result admission independently constrain actual retained memory. The
* result uses incremental serialization rather than a single JSON string.
2026-10-07 12:22:10 -05:00
*/
2026-10-12 09:28:59 +08:00
export const SEMANTIC_GRAPH_NODE_CEILING =
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS . semanticNodes ;
2026-10-07 12:22:10 -05:00
/**
* Per-file share of the semantic node budget.
*
* Bound each source file independently so one large bundled library cannot
* consume the whole tree's budget and starve the application's own modules.
*/
2026-10-12 09:28:59 +08:00
export const SEMANTIC_GRAPH_FILE_NODE_CEILING =
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS . semanticNodesPerFile ;
2026-10-07 12:22:10 -05:00
2026-07-22 23:07:51 +00:00
export const buildJavaScriptSemanticGraph = ({
rootArtifactSha256 ,
applicationGraph ,
analysis ,
} : BuilderInput ) : JavaScriptSemanticGraph => {
2026-10-08 16:15:35 +08:00
const projection = createJavaScriptSemanticGraphProjection ();
2026-07-22 23:07:51 +00:00
for ( const analyzed of analysis . files ) {
2026-10-08 16:15:35 +08:00
if ( analyzed . semantic !== null )
2026-10-12 09:28:59 +08:00
projection . projectFile (
analyzed . file ,
analyzed . semantic . ir ,
analyzed . javascript ? . electron . call_paths ,
);
2026-10-12 04:07:14 +08:00
else if (
! analyzed . file . text . included &&
analyzed . file . text . reason === "outside-analysis-scope"
)
projection . recordUnavailableFile (
analyzed . file ,
"Source was not analyzed because it is outside the caller-selected source_paths." ,
null ,
"incomplete-module" ,
);
2026-07-22 23:07:51 +00:00
}
2026-10-10 02:15:51 +08:00
return projection . finish ( rootArtifactSha256 , applicationGraph );
2026-07-22 23:07:51 +00:00
};
2026-10-08 16:15:35 +08:00
/** File-local semantic projection that does not retain consumed source IR. */
export interface JavaScriptSemanticGraphProjection {
2026-10-11 07:22:50 +08:00
/** Node capacity offered to the next independently analyzed source file. */
2026-10-17 21:57:31 +08:00
readonly remainingFileCapacity : () => JavaScriptSemanticProjectionCapacity ;
2026-10-11 07:22:50 +08:00
/** Adopt decoded file-local facts; final validation and proof remain in this realm. */
readonly adoptFileProjection : (
projection : JavaScriptSemanticFileProjection ,
) => void ;
2026-10-16 03:46:00 +08:00
/** Preserve JavaScript-source unknowns; other file kinds have no semantic module. */
2026-10-11 07:22:50 +08:00
readonly recordUnavailableFile : (
file : JavaScriptArtifactFile ,
reason : string ,
limits : Readonly < Record < string , JsonValue >> | null ,
unknownReason : "resource-limit" | "incomplete-module" ,
) => void ;
2026-10-08 16:15:35 +08:00
readonly projectFile : (
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
2026-10-12 09:28:59 +08:00
paths? : readonly ElectronCallPath [],
2026-10-08 16:15:35 +08:00
) => void ;
2026-10-10 03:14:37 -07:00
/** Project one file in steps between its projection passes. */
readonly projectFileSteps : (
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
2026-10-12 09:28:59 +08:00
paths? : readonly ElectronCallPath [],
2026-10-10 03:14:37 -07:00
) => Generator < void , void >;
2026-10-08 16:15:35 +08:00
readonly finish : (
rootArtifactSha256 : string ,
applicationGraph : BuilderInput [ "applicationGraph" ],
) => JavaScriptSemanticGraph ;
2026-10-09 02:40:21 +08:00
readonly finishImmutableSteps : (
rootArtifactSha256 : string ,
applicationGraph : BuilderInput [ "applicationGraph" ],
) => Generator < void , JavaScriptSemanticGraph >;
2026-10-08 16:15:35 +08:00
}
/** Accumulate semantic nodes while each source IR is still file-local. */
2026-10-12 09:28:59 +08:00
export const createJavaScriptSemanticGraphProjection = (
2026-10-17 21:57:31 +08:00
limits : {
readonly totalNodes? : number ;
readonly fileNodes? : number ;
readonly totalUnknowns? : number ;
readonly fileUnknowns? : number ;
} = {},
2026-10-12 09:28:59 +08:00
) : JavaScriptSemanticGraphProjection => {
const nodeCeiling = limits . totalNodes ?? SEMANTIC_GRAPH_NODE_CEILING ;
const fileNodeCeiling = limits . fileNodes ?? SEMANTIC_GRAPH_FILE_NODE_CEILING ;
2026-10-17 21:57:31 +08:00
const unknownCeiling =
limits . totalUnknowns ??
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS . semanticUnknowns ;
const fileUnknownCeiling =
limits . fileUnknowns ??
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS . semanticUnknownsPerFile ;
for ( const value of [
nodeCeiling ,
fileNodeCeiling ,
unknownCeiling ,
fileUnknownCeiling ,
])
2026-10-12 09:28:59 +08:00
if (
! Number . isSafeInteger ( value ) ||
value < 1 ||
2026-10-17 21:57:31 +08:00
value > JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM
2026-10-12 09:28:59 +08:00
)
2026-10-17 21:57:31 +08:00
throw new RangeError ( "Semantic graph requires valid retained capacities" );
2026-10-12 09:28:59 +08:00
const state = emptyState ();
const fingerprints : JavaScriptSemanticFingerprint [] = [];
let truncatedFiles = 0 ;
let unavailableFiles = 0 ;
let resourceTruncatedFiles = 0 ;
const executionLimits = new Map <
string ,
ApplicationCoverage [ "limits" ][ number ]
> ();
const projectSourceSteps = function * (
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
paths : readonly ElectronCallPath [] = [],
) : Generator < void , void > {
if ( state . nodes . size >= nodeCeiling ) {
truncatedFiles += 1 ;
return ;
}
const remainingTreeBudget = nodeCeiling - state . nodes . size ;
state . fileNodeBudget = Math . min ( fileNodeCeiling , remainingTreeBudget );
state . fileNodesDropped = false ;
2026-10-17 21:57:31 +08:00
state . unknownCapacity = Math . min (
unknownCeiling ,
state . unknowns . size + fileUnknownCeiling ,
);
2026-10-12 09:28:59 +08:00
fingerprints . push (...( yield * projectFileSteps ( file , ir , state , paths )));
// Nodes were dropped only if the budget actually blocked creation. A file
// that exactly fills its share ends with a zero budget without dropping
// anything, so the remaining budget alone cannot decide truncation.
if ( state . fileNodesDropped ) truncatedFiles += 1 ;
state . fileNodeBudget = null ;
2026-10-17 21:57:31 +08:00
state . unknownCapacity = null ;
2026-10-12 09:28:59 +08:00
};
const finishWith = < Value >(
factory : ( input : unknown ) => Value ,
rootArtifactSha256 : string ,
applicationGraph : BuilderInput [ "applicationGraph" ],
) : Value => {
bindSemanticGraphApplicationNodes ( state , applicationGraph );
if ( state . roots . size === 0 ) addFallbackRoot ( rootArtifactSha256 , state );
const relations = [... state . relations . values ()];
const unknowns = [... state . unknowns . values ()];
2026-10-17 21:57:31 +08:00
const unknownOmissions = [... state . unknownOmissions . values ()];
const unknownsDropped = unknownOmissions . length > 0 ;
2026-10-12 09:28:59 +08:00
type Family = ( typeof JAVASCRIPT_SEMANTIC_RELATION_FAMILIES )[ number ];
const retainedByFamily = new Map < Family , number >();
const unknownIdsByFamily = new Map < Family , string [] >();
for ( const relation of relations ) {
const family = JAVASCRIPT_SEMANTIC_RELATION_FAMILY [ relation . relation ];
retainedByFamily . set ( family , ( retainedByFamily . get ( family ) ?? 0 ) + 1 );
}
for ( const unknown of unknowns ) {
const identifiers = unknownIdsByFamily . get ( unknown . family );
if ( identifiers === undefined )
unknownIdsByFamily . set ( unknown . family , [ unknown . unknown_id ]);
else identifiers . push ( unknown . unknown_id );
}
const graph = factory ({
schema : "JavaScriptSemanticRelationGraph" ,
root_artifact_sha256 : rootArtifactSha256 ,
application_graph_id : applicationGraph.graph_id ,
root_node_ids : [... state . roots ],
evidence_contexts : state.evidenceContexts.contexts ,
nodes : [... state . nodes . values ()],
relations ,
// The input is transferred to the factory; `fingerprints` is cleared below.
fingerprints : [... fingerprints ],
unknowns ,
coverage : {
status :
2026-10-17 21:57:31 +08:00
truncatedFiles > 0 || unavailableFiles > 0 || unknownsDropped
? "partial"
: "unknown" ,
truncated :
truncatedFiles > 0 || resourceTruncatedFiles > 0 || unknownsDropped ,
...( unknownsDropped
? { omitted_unknown_occurrences : unknownOmissions }
: {}),
2026-10-12 09:28:59 +08:00
omitted_nodes : truncatedFiles > 0 || unavailableFiles > 0 ? null : 0 ,
omitted_relations :
truncatedFiles > 0 || unavailableFiles > 0 ? null : 0 ,
limits : [
... executionLimits . values (),
2026-10-17 21:57:31 +08:00
...( unknownsDropped
? [
{
name : "semantic_graph_unknown_ceiling" ,
value : unknownCeiling ,
unit : "items" as const ,
},
{
name : "semantic_graph_file_unknown_ceiling" ,
value : fileUnknownCeiling ,
unit : "items" as const ,
},
]
: []),
2026-10-12 09:28:59 +08:00
...( truncatedFiles > 0
? [
{
name : "semantic_graph_node_ceiling" ,
value : nodeCeiling ,
unit : "items" as const ,
},
{
name : "semantic_graph_file_node_ceiling" ,
value : fileNodeCeiling ,
unit : "items" as const ,
},
]
: []),
],
families : JAVASCRIPT_SEMANTIC_RELATION_FAMILIES.map (( family ) => ({
family ,
status : semanticFamilyStatus ( family ),
retained_relations : retainedByFamily.get ( family ) ?? 0 ,
omitted_relations :
truncatedFiles > 0 || unavailableFiles > 0 ? null : 0 ,
unknown_ids : unknownIdsByFamily.get ( family ) ?? [],
})),
},
limitations : [
2026-10-17 21:57:31 +08:00
...( unknownsDropped
? [
"Semantic unknown retention reached an effective capacity. Omission coverage counts examined emission occurrences by family and reason, including repeats; it does not count unexamined source frontiers or distinct discarded identities." ,
]
: []),
2026-10-12 09:28:59 +08:00
"The semantic graph contains static syntax observations and conservative relationship candidates; it does not claim runtime execution." ,
"Local data flow does not claim control-flow-sensitive reaching definitions or arbitrary dynamic property resolution." ,
"Promise ownership covers explicit unshadowed Promise construction, static factories, aggregation, chaining, and await syntax only." ,
"Function fingerprints are static candidates; equal digests can remain ambiguous and do not prove behavioral equivalence." ,
"Event extraction covers EventEmitter-style literal registrations, removals, and dispatch candidates; dynamic names remain unknown." ,
"Timer extraction covers global or node:timers scheduling and exact local-handle cancellation." ,
"Child-process extraction covers asynchronous node:child_process creation, literal argv/env/stdio options, exit/error listeners, and kill signals." ,
"Configuration extraction covers process.env, process.argv, node:fs reads, and direct logical defaults." ,
"Request extraction covers fetch, WebSocket, node:http/node:https construction, direct option fields, and exact local response consumers." ,
"Boundary extraction covers unshadowed JSON/global coercions plus parse and validation method candidates." ,
"Resource extraction covers built-in filesystem/network acquisition and exact local close/destroy/end handles." ,
],
});
state . nodes . clear ();
state . relations . clear ();
state . unknowns . clear ();
2026-10-17 21:57:31 +08:00
state . unknownOmissions . clear ();
2026-10-12 09:28:59 +08:00
state . roots . clear ();
state . evidenceContexts . clear ();
fingerprints . length = 0 ;
return graph ;
};
return {
2026-10-17 21:57:31 +08:00
remainingFileCapacity : () => ({
nodes : Math.min (
fileNodeCeiling ,
Math . max ( 0 , nodeCeiling - state . nodes . size ),
),
unknowns : Math.min (
fileUnknownCeiling ,
Math . max ( 0 , unknownCeiling - state . unknowns . size ),
),
}),
2026-10-12 09:28:59 +08:00
adoptFileProjection : ( projection ) => {
const newNodes = projection . nodes . filter (
({ node_id }) => ! state . nodes . has ( node_id ),
);
if (
newNodes . length >
Math . min ( fileNodeCeiling , nodeCeiling - state . nodes . size )
)
throw new RangeError (
"Transferred semantic nodes exceed the offered tree capacity" ,
);
2026-10-17 21:57:31 +08:00
const newUnknowns = projection . unknowns . filter (
({ unknown_id }) => ! state . unknowns . has ( unknown_id ),
);
if (
newUnknowns . length >
Math . min ( fileUnknownCeiling , unknownCeiling - state . unknowns . size )
)
throw new RangeError (
"Transferred semantic unknowns exceed the offered tree capacity" ,
);
for ( const omission of projection . omittedUnknownOccurrences ?? [])
recordSemanticUnknownOmission ( state , omission );
2026-10-12 09:28:59 +08:00
for ( const context of projection . evidenceContexts )
state . evidenceContexts . adopt ( context );
for ( const node of newNodes ) state . nodes . set ( node . node_id , node );
for ( const relation of projection . relations )
state . relations . set ( relation . relation_id , relation );
2026-10-17 21:57:31 +08:00
for ( const unknown of newUnknowns )
2026-10-12 09:28:59 +08:00
state . unknowns . set ( unknown . unknown_id , unknown );
for ( const root of projection . roots ) state . roots . add ( root );
fingerprints . push (... projection . fingerprints );
2026-10-17 21:57:31 +08:00
if ( projection . nodesTruncated ) truncatedFiles += 1 ;
2026-10-12 09:28:59 +08:00
},
recordUnavailableFile : ( file , reason , limits , unknownReason ) => {
2026-10-16 03:46:00 +08:00
// Both full and incremental construction enter here. Metadata/native
// processing gaps belong to the application graph, never JavaScript IR.
if ( file . kind !== "javascript" ) return ;
2026-10-12 09:28:59 +08:00
const retainedLimits = javaScriptAnalysisCoverageLimits ( limits );
for ( const limit of retainedLimits ) {
executionLimits . set (
JSON . stringify ([ limit . name , limit . value , limit . unit ]),
limit ,
);
2026-10-08 16:15:35 +08:00
}
state . fileNodeBudget = Math . min (
2026-10-12 09:28:59 +08:00
fileNodeCeiling ,
Math . max ( 0 , nodeCeiling - state . nodes . size ),
2026-10-08 16:15:35 +08:00
);
2026-10-12 09:28:59 +08:00
const module = retainNode ( state , file , {
kind : "module" ,
roleKey : "module" ,
location : null ,
label : file.path ,
functionNodeId : null ,
});
2026-10-08 16:15:35 +08:00
state . fileNodeBudget = null ;
2026-10-12 09:28:59 +08:00
if ( module !== null ) state . roots . add ( module . node_id );
const evidence = {
... observedSemanticEvidence ( file , null ),
authority : "unknown" as const ,
state : "unknown" as const ,
confidence : "unknown" as const ,
coverage : partialApplicationCoverage ( retainedLimits , null ),
limitations : [ reason ],
};
2026-10-17 21:57:31 +08:00
state . unknownCapacity = Math . min (
unknownCeiling ,
state . unknowns . size + fileUnknownCeiling ,
);
2026-10-12 09:28:59 +08:00
for ( const family of JAVASCRIPT_SEMANTIC_RELATION_FAMILIES ) {
2026-10-17 21:57:31 +08:00
addSemanticGraphUnknown ( state , {
node_id : module?.node_id ?? null ,
family ,
relation_kinds : JAVASCRIPT_SEMANTIC_RELATIONS.filter (
( relation ) =>
JAVASCRIPT_SEMANTIC_RELATION_FAMILY [ relation ] === family ,
),
reason : unknownReason ,
detail : reason ,
candidate_node_ids : [],
evidence ,
});
2026-10-12 09:28:59 +08:00
}
2026-10-17 21:57:31 +08:00
state . unknownCapacity = null ;
2026-10-12 09:28:59 +08:00
unavailableFiles += 1 ;
if ( unknownReason === "resource-limit" && retainedLimits . length > 0 )
resourceTruncatedFiles += 1 ;
},
projectFile : ( file , ir , paths ) => {
const steps = projectSourceSteps ( file , ir , paths );
while ( steps . next (). done !== true );
},
projectFileSteps : projectSourceSteps ,
finish : ( rootArtifactSha256 , applicationGraph ) =>
finishWith (
createJavaScriptSemanticGraph ,
rootArtifactSha256 ,
applicationGraph ,
),
finishImmutableSteps : ( rootArtifactSha256 , applicationGraph ) =>
finishWith (
sealTransferredJavaScriptSemanticGraphSteps ,
rootArtifactSha256 ,
applicationGraph ,
),
2026-10-08 16:15:35 +08:00
};
2026-10-12 09:28:59 +08:00
};
2026-10-08 16:15:35 +08:00
2026-10-17 21:57:31 +08:00
/** Project one source with the parent's remaining retained capacities, retaining no full IR. */
2026-10-11 07:22:50 +08:00
export function * projectJavaScriptSemanticFileSteps (
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
2026-10-17 21:57:31 +08:00
capacity : JavaScriptSemanticProjectionCapacity ,
2026-10-12 09:28:59 +08:00
paths : readonly ElectronCallPath [] = [],
2026-10-11 07:22:50 +08:00
) : Generator < void , JavaScriptSemanticFileProjection > {
if (
2026-10-17 21:57:31 +08:00
[ capacity . nodes , capacity . unknowns ]. some (
( value ) =>
! Number . isSafeInteger ( value ) ||
value < 0 ||
value > JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM ,
)
2026-10-11 07:22:50 +08:00
)
throw new RangeError (
2026-10-17 21:57:31 +08:00
"File semantic projection requires valid offered retained capacities" ,
2026-10-11 07:22:50 +08:00
);
const state = emptyState ();
2026-10-17 21:57:31 +08:00
state . fileNodeBudget = capacity . nodes ;
state . unknownCapacity = capacity . unknowns ;
2026-10-11 07:22:50 +08:00
state . fileNodesDropped = false ;
2026-10-12 09:28:59 +08:00
const fingerprints = yield * projectFileSteps ( file , ir , state , paths );
2026-10-11 07:22:50 +08:00
return {
roots : [... state . roots ],
evidenceContexts : state.evidenceContexts.contexts ,
nodes : [... state . nodes . values ()],
relations : [... state . relations . values ()],
unknowns : [... state . unknowns . values ()],
fingerprints ,
2026-10-17 21:57:31 +08:00
...( state . unknownOmissions . size > 0
? { omittedUnknownOccurrences : [... state . unknownOmissions . values ()] }
: {}),
nodesTruncated : Boolean ( state . fileNodesDropped ),
2026-10-11 07:22:50 +08:00
};
}
2026-10-10 03:14:37 -07:00
function * projectFileSteps (
2026-07-22 23:07:51 +00:00
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
state : BuilderState ,
2026-10-12 09:28:59 +08:00
paths : readonly ElectronCallPath [] = [],
2026-10-10 03:14:37 -07:00
) : Generator < void , JavaScriptSemanticFingerprint [] > {
yield ;
2026-10-08 16:15:35 +08:00
const moduleNode = retainNode ( state , file , {
kind : "module" ,
roleKey : "module" ,
location : null ,
label : file.path ,
functionNodeId : null ,
});
2026-07-24 13:17:38 +00:00
if ( moduleNode === null ) return [];
2026-07-22 23:07:51 +00:00
state . roots . add ( moduleNode . node_id );
2026-10-16 06:06:00 +08:00
projectFileResourceLimits ({ file , ir , state , moduleNode });
2026-10-12 09:28:59 +08:00
yield * projectElectronSemanticCallPaths ( file , paths , state );
2026-10-10 04:20:50 -07:00
// Node creation keeps source order, which the per-file node budget relies
// on; yields between nodes do not change it.
const callableNodes = new Map < string , JavaScriptSemanticGraphNode >();
for ( const [ index , callable ] of ir . callables . entries ()) {
if ( index % 64 === 0 ) yield ;
const node = retainNode ( state , file , {
kind : "function" ,
roleKey : `callable: ${ callable . callableId } ` ,
location : callable.location ,
label : callable.name ,
functionNodeId : null ,
// The label is display text; keep the exact name, which may be "".
properties : { name : callable.name },
});
if ( node !== null ) callableNodes . set ( callable . callableId , node );
}
2026-10-08 16:15:35 +08:00
const callableOwnerAt = createSemanticCallableOwnerLookup ( ir , callableNodes );
2026-10-10 04:20:50 -07:00
const bindingNodes = new Map < string , JavaScriptSemanticGraphNode >();
for ( const [ index , binding ] of ir . bindings . entries ()) {
if ( index % 64 === 0 ) yield ;
const location = binding . definitions [ 0 ] ? . location ?? null ;
const kind = binding . kind === "parameter" ? "parameter" : "binding" ;
const owner = callableOwnerAt ( location );
const node = retainNode ( state , file , {
kind ,
roleKey : `binding: ${ binding . bindingId } ` ,
location ,
label : binding.name ,
functionNodeId : owner?.node_id ?? null ,
});
if ( node !== null ) bindingNodes . set ( binding . bindingId , node );
}
const returnSiteNodes = yield * createReturnSiteNodes (
file ,
ir ,
callableNodes ,
state ,
);
const callSiteNodes = yield * createCallSiteNodes (
file ,
ir ,
callableNodes ,
state ,
2026-07-22 23:07:51 +00:00
);
2026-10-08 16:15:35 +08:00
const definitions : Omit < FileContext , "referenceNodesWithin" > = {
2026-07-22 23:07:51 +00:00
file ,
ir ,
state ,
moduleNode ,
bindingNodes ,
callableNodes ,
callSiteNodes ,
returnSiteNodes ,
argumentNodes : new Map (),
referenceNodes : [],
callResolutions : new Map (
ir . callSites . map (( call ) => [
call . callSiteId ,
call . resolution === "exact" ? "resolved" : "candidate" ,
]),
),
2026-10-08 16:15:35 +08:00
callableOwnerAt ,
callSiteAt : createSemanticCallSiteLookup ( ir , callSiteNodes ),
};
2026-10-10 04:20:50 -07:00
yield * projectDefinitionsAndReferences ( definitions );
2026-10-08 16:15:35 +08:00
const context : FileContext = {
... definitions ,
referenceNodesWithin : createSemanticNodeRangeLookup (
definitions . referenceNodes ,
),
2026-07-22 23:07:51 +00:00
};
2026-10-10 03:14:37 -07:00
// Each pass completes before the next starts, so yielding between passes
// keeps projection order and results unchanged.
for ( const project of FILE_PROJECTION_PASSES ) {
yield ;
2026-10-10 04:20:50 -07:00
const steps = project ( context );
if ( steps !== undefined ) while ( steps . next (). done !== true ) yield ;
2026-10-10 03:14:37 -07:00
}
yield ;
2026-10-10 03:10:50 +08:00
return projectSemanticFunctionFingerprints (
file ,
ir ,
callableNodes ,
state . evidenceContexts ,
);
2026-10-10 03:14:37 -07:00
}
2026-07-22 23:07:51 +00:00
2026-10-16 06:06:00 +08:00
const projectFileResourceLimits = (
context : Pick < FileContext , "file" | "ir" | "state" | "moduleNode" >,
) : void => {
// Getter limits describe query effects without tagged values; other limits
// already project from binding or return-site values and must not be duplicated.
for ( const limit of semanticCoverageResourceLimits ( context . ir . coverage )) {
if ( limit !== "getter-expansion" ) continue ;
2026-10-17 21:57:31 +08:00
addSemanticGraphUnknown ( context . state , {
node_id : context.moduleNode.node_id ,
family : "data-flow" ,
relation_kinds : [ "defines" ],
reason : "resource-limit" ,
detail : semanticResourceLimitReason ( limit ),
candidate_node_ids : [],
evidence : {
... unknownSemanticEvidence ( context . file , null ),
coverage : partialApplicationCoverage (
semanticResourceLimitCoverage ([ limit ]),
null ,
),
limitations : [ semanticResourceLimitReason ( limit )],
2026-10-16 06:06:00 +08:00
},
2026-10-17 21:57:31 +08:00
});
2026-10-16 06:06:00 +08:00
}
};
2026-10-10 04:20:50 -07:00
function * createReturnSiteNodes (
2026-07-22 23:07:51 +00:00
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
callables : ReadonlyMap < string , JavaScriptSemanticGraphNode >,
state : BuilderState ,
2026-10-10 04:20:50 -07:00
) : Generator < void , Map < string , JavaScriptSemanticGraphNode >> {
2026-07-22 23:07:51 +00:00
const result = new Map < string , JavaScriptSemanticGraphNode >();
2026-10-10 04:20:50 -07:00
for ( const [ index , callable ] of ir . callables . entries ()) {
if ( index % 64 === 0 ) yield ;
2026-07-22 23:07:51 +00:00
const owner = callables . get ( callable . callableId );
if ( owner === undefined ) continue ;
for ( const site of callable . returnSites ) {
const identifier = site . returnSiteId ;
2026-10-08 16:15:35 +08:00
const node = retainNode ( state , file , {
kind : "return-site" ,
roleKey : identifier ,
location : site.location ,
label : "return" ,
functionNodeId : owner.node_id ,
});
2026-07-22 23:07:51 +00:00
if ( node !== null ) result . set ( identifier , node );
}
}
return result ;
2026-10-10 04:20:50 -07:00
}
2026-07-22 23:07:51 +00:00
2026-10-10 04:20:50 -07:00
function * createCallSiteNodes (
2026-07-22 23:07:51 +00:00
file : JavaScriptArtifactFile ,
ir : JavaScriptSemanticIr ,
callables : ReadonlyMap < string , JavaScriptSemanticGraphNode >,
state : BuilderState ,
2026-10-10 04:20:50 -07:00
) : Generator < void , Map < string , JavaScriptSemanticGraphNode >> {
2026-07-22 23:07:51 +00:00
const result = new Map < string , JavaScriptSemanticGraphNode >();
2026-10-10 04:20:50 -07:00
for ( const [ index , call ] of ir . callSites . entries ()) {
if ( index % 64 === 0 ) yield ;
2026-07-22 23:07:51 +00:00
const owner =
call . callerCallableId === null
? null
: ( callables . get ( call . callerCallableId ) ? . node_id ?? null );
2026-10-08 16:15:35 +08:00
const node = retainNode ( state , file , {
kind : "call-site" ,
roleKey : `call: ${ call . callSiteId } ` ,
location : call.location ,
label : call.kind ,
functionNodeId : owner ,
});
2026-07-22 23:07:51 +00:00
if ( node !== null ) result . set ( call . callSiteId , node );
}
return result ;
2026-10-10 04:20:50 -07:00
}
2026-07-22 23:07:51 +00:00
2026-10-10 04:20:50 -07:00
function * projectDefinitionsAndReferences (
2026-10-08 16:15:35 +08:00
context : Omit < FileContext , "referenceNodesWithin" >,
2026-10-10 04:20:50 -07:00
) : Generator < void , void > {
for ( const [ bindingIndex , binding ] of context . ir . bindings . entries ()) {
if ( bindingIndex % 64 === 0 ) yield ;
2026-07-22 23:07:51 +00:00
const bindingNode = context . bindingNodes . get ( binding . bindingId );
if ( bindingNode === undefined ) continue ;
for ( const [ index , definition ] of binding . definitions . entries ()) {
2026-10-08 16:15:35 +08:00
const definitionNode = retainNode ( context . state , context . file , {
kind : "expression" ,
roleKey : `definition: ${ binding . bindingId } : ${ String ( index ) } ` ,
location : definition.location ,
label : definition.kind ,
functionNodeId : bindingNode.function_node_id ,
});
2026-07-22 23:07:51 +00:00
addRelation ( context . state , {
source : definitionNode ,
target : bindingNode ,
relation : "defines" ,
resolution : "resolved" ,
});
}
}
for ( const [ index , reference ] of context . ir . references . entries ()) {
2026-10-10 04:20:50 -07:00
if ( index % 64 === 0 ) yield ;
2026-10-08 16:15:35 +08:00
const expression = retainNode ( context . state , context . file , {
kind : "expression" ,
roleKey : `reference: ${ String ( index ) } : ${ reference . role } : ${ reference . name } ` ,
location : reference.location ,
label : reference.name ,
functionNodeId :
context.callableOwnerAt ( reference . location ) ? . node_id ?? null ,
});
2026-07-22 23:07:51 +00:00
const binding =
reference . bindingId === null
? undefined
: context . bindingNodes . get ( reference . bindingId );
if ( expression !== null ) context . referenceNodes . push ( expression );
if ( binding === undefined || expression === null ) continue ;
if ( reference . role === "read" )
addRelation ( context . state , {
source : binding ,
target : expression ,
relation : "reads" ,
resolution : "resolved" ,
evidence : inferredSemanticEvidenceAt ( context . file , reference . location ),
});
2026-10-14 16:53:16 +08:00
else if ( reference . role === "write" )
2026-07-22 23:07:51 +00:00
addRelation ( context . state , {
source : expression ,
target : binding ,
relation : "writes" ,
resolution : "resolved" ,
});
}
2026-10-10 04:20:50 -07:00
}
2026-07-22 23:07:51 +00:00
const projectCalls = ( context : FileContext ) : void => {
for ( const call of context . ir . callSites ) {
const callNode = context . callSiteNodes . get ( call . callSiteId );
if ( callNode === undefined ) continue ;
for ( const callableId of call . calleeCallableIds ) {
const callable = context . callableNodes . get ( callableId );
addRelation ( context . state , {
source : callNode ,
target : callable ,
relation : "calls" ,
resolution : call.resolution === "exact" ? "resolved" : "candidate" ,
});
}
for ( const argument of call . arguments ) {
const argumentNode = createArgumentNode (
context ,
callNode ,
call . callSiteId ,
argument ,
);
if ( argumentNode !== null )
context . argumentNodes . set (
` ${ call . callSiteId } \ u0000 ${ String ( argument . index ) } ` ,
argumentNode ,
);
2026-10-08 16:15:35 +08:00
const references = context . referenceNodesWithin ( argument . location );
2026-07-22 23:07:51 +00:00
for ( const reference of references )
addRelation ( context . state , {
source : reference ,
target : argumentNode ,
relation : "aliases" ,
resolution :
references.length === 1 &&
rangesEqual ( reference . identity . source_range , argument . location )
? "resolved"
: "candidate" ,
});
}
}
for ( const flow of context . ir . argumentFlows ) {
const argument = context . argumentNodes . get (
` ${ flow . callSiteId } \ u0000 ${ String ( flow . argumentIndex ) } ` ,
);
const parameter = context . bindingNodes . get ( flow . parameterBindingId );
addRelation ( context . state , {
source : argument ,
target : parameter ,
relation : "argument-to-parameter" ,
resolution : context.callResolutions.get ( flow . callSiteId ) ?? "candidate" ,
});
}
for ( const flow of context . ir . callReturnFlows )
addRelation ( context . state , {
source : context.returnSiteNodes.get ( flow . returnSiteId ),
target : context.callSiteNodes.get ( flow . callSiteId ),
relation : "returns-to-call" ,
resolution : context.callResolutions.get ( flow . callSiteId ) ?? "candidate" ,
});
for ( const flow of context . ir . callResultFlows )
addRelation ( context . state , {
source : context.callSiteNodes.get ( flow . callSiteId ),
target : context.bindingNodes.get ( flow . bindingId ),
relation : "aliases" ,
resolution : context.callResolutions.get ( flow . callSiteId ) ?? "candidate" ,
});
projectSemanticReturnValues ( context );
};
const createArgumentNode = (
context : FileContext ,
callNode : JavaScriptSemanticGraphNode ,
callSiteId : string ,
argument : JavaScriptSemanticCallArgument ,
) : JavaScriptSemanticGraphNode | null =>
2026-10-08 16:15:35 +08:00
retainNode ( context . state , context . file , {
kind : "expression" ,
roleKey : `argument: ${ callSiteId } : ${ String ( argument . index ) } ` ,
location : argument.location ,
label : argument.spread
? "spread argument"
: `argument ${ String ( argument . index ) } ` ,
functionNodeId : callNode.function_node_id ,
});
2026-10-10 03:14:37 -07:00
/** Per-file projection passes, run in this order with a yield between each. */
2026-10-10 04:20:50 -07:00
const FILE_PROJECTION_PASSES : readonly ((
context : FileContext ,
) => Iterator < void , void > | void )[] = [
2026-10-10 03:14:37 -07:00
projectSemanticValues ,
projectSemanticObjects ,
projectCalls ,
projectSemanticPromises ,
projectSemanticEvents ,
projectSemanticTimers ,
projectSemanticChildProcesses ,
projectSemanticConfiguration ,
projectSemanticRequests ,
projectSemanticBoundaries ,
projectSemanticResources ,
projectSemanticClosureCaptures ,
projectSemanticFrontiers ,
];