perf(javascript): reuse parsed source across analyses
This commit is contained in:
parent
674364443d
commit
adc4852f3a
|
|
@ -1,16 +1,18 @@
|
|||
import { createHash } from "node:crypto";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "../domain/javascriptStaticAnalysis.js";
|
||||
import { analyzeJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
|
||||
import { analyzeParsedJavaScriptStaticSource } from "../domain/javascriptStaticAnalysis.js";
|
||||
import { analyzeParsedJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
|
||||
import {
|
||||
DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS,
|
||||
type JavaScriptSemanticLimits,
|
||||
} from "../domain/javascriptSemanticIr.js";
|
||||
import { parseJavaScriptSource } from "../domain/javascriptSourceParser.js";
|
||||
import type {
|
||||
JavaScriptSourceRange,
|
||||
JavaScriptSourcePoint,
|
||||
JavaScriptStaticAnalysis,
|
||||
} from "../domain/javascriptStaticAnalysisTypes.js";
|
||||
import { failedJavaScriptStaticAnalysis } from "../domain/javascriptStaticAnalysisHelpers.js";
|
||||
import type {
|
||||
JavaScriptArtifactFile,
|
||||
JavaScriptArtifactFileSet,
|
||||
|
|
@ -142,13 +144,24 @@ const analyzeArtifactFile = (
|
|||
state.truncatedScopes += 1;
|
||||
return;
|
||||
}
|
||||
const analysis = analyzeJavaScriptStaticSource(file.text.value, {
|
||||
maxAstNodes: remainingNodes,
|
||||
maxFindings: remainingFindings,
|
||||
maxModules: remainingModules,
|
||||
deadline: context.deadline,
|
||||
now: context.now,
|
||||
});
|
||||
const parsed = parseJavaScriptSource(file.text.value);
|
||||
if (parsed === null) {
|
||||
const analysis = failedJavaScriptStaticAnalysis();
|
||||
state.files.push({ file, javascript: analysis, semantic: null });
|
||||
state.parseFailures += 1;
|
||||
return;
|
||||
}
|
||||
const analysis = analyzeParsedJavaScriptStaticSource(
|
||||
file.text.value,
|
||||
parsed,
|
||||
{
|
||||
maxAstNodes: remainingNodes,
|
||||
maxFindings: remainingFindings,
|
||||
maxModules: remainingModules,
|
||||
deadline: context.deadline,
|
||||
now: context.now,
|
||||
},
|
||||
);
|
||||
const staticFindings = findingCount(analysis);
|
||||
const semanticFindingBudget = Math.max(
|
||||
0,
|
||||
|
|
@ -164,7 +177,7 @@ const analyzeArtifactFile = (
|
|||
};
|
||||
const semantics =
|
||||
analysis.parse_status === "complete" || analysis.parse_status === "partial"
|
||||
? analyzeJavaScriptSemantics(file.text.value, semanticLimits)
|
||||
? analyzeParsedJavaScriptSemantics(parsed, semanticLimits)
|
||||
: null;
|
||||
state.files.push({
|
||||
file,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { parse } from "@babel/parser";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import {
|
||||
|
|
@ -41,6 +40,10 @@ import {
|
|||
propertyName,
|
||||
range,
|
||||
} from "./javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
parseJavaScriptSource,
|
||||
type ParsedJavaScriptSource,
|
||||
} from "./javascriptSourceParser.js";
|
||||
|
||||
interface BindPatternInput {
|
||||
readonly pattern: t.Node;
|
||||
|
|
@ -68,18 +71,19 @@ interface AddBindingInput {
|
|||
export const analyzeJavaScriptSemantics = (
|
||||
source: string,
|
||||
inputLimits: Partial<JavaScriptSemanticLimits> = {},
|
||||
): JavaScriptSemanticIr => {
|
||||
const file = parseJavaScriptSource(source);
|
||||
return file === null
|
||||
? failedJavaScriptSemanticIr()
|
||||
: analyzeParsedJavaScriptSemantics(file, inputLimits);
|
||||
};
|
||||
|
||||
/** Recover semantics from an already parsed JavaScript artifact. */
|
||||
export const analyzeParsedJavaScriptSemantics = (
|
||||
file: ParsedJavaScriptSource,
|
||||
inputLimits: Partial<JavaScriptSemanticLimits> = {},
|
||||
): JavaScriptSemanticIr => {
|
||||
const limits = { ...DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS, ...inputLimits };
|
||||
let file: ReturnType<typeof parse>;
|
||||
try {
|
||||
file = parse(source, {
|
||||
sourceType: "unambiguous",
|
||||
errorRecovery: true,
|
||||
plugins: ["jsx", "typescript"],
|
||||
});
|
||||
} catch {
|
||||
return failedJavaScriptSemanticIr();
|
||||
}
|
||||
const state = createState(file.program, limits);
|
||||
collectDefinitions(file.program, state);
|
||||
const references = collectSemanticReferences(file.program, state);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { parse } from "@babel/parser";
|
||||
|
||||
/** Babel AST produced by REA's inert JavaScript parser boundary. */
|
||||
export type ParsedJavaScriptSource = ReturnType<typeof parse>;
|
||||
|
||||
/** Parse JavaScript or TypeScript once without retaining unused comment nodes. */
|
||||
export const parseJavaScriptSource = (
|
||||
source: string,
|
||||
): ParsedJavaScriptSource | null => {
|
||||
try {
|
||||
return parse(source, {
|
||||
sourceType: "unambiguous",
|
||||
errorRecovery: true,
|
||||
attachComment: false,
|
||||
plugins: ["jsx", "typescript"],
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { parse } from "@babel/parser";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
|
||||
|
|
@ -29,22 +28,28 @@ import type {
|
|||
JavaScriptStaticAnalysis,
|
||||
JavaScriptStaticAnalysisLimits,
|
||||
} from "./javascriptStaticAnalysisTypes.js";
|
||||
import {
|
||||
parseJavaScriptSource,
|
||||
type ParsedJavaScriptSource,
|
||||
} from "./javascriptSourceParser.js";
|
||||
|
||||
/** Parse one bounded JavaScript artifact and recover static structure only. */
|
||||
export const analyzeJavaScriptStaticSource = (
|
||||
source: string,
|
||||
limits: JavaScriptStaticAnalysisLimits,
|
||||
): JavaScriptStaticAnalysis => {
|
||||
let file: ReturnType<typeof parse>;
|
||||
try {
|
||||
file = parse(source, {
|
||||
sourceType: "unambiguous",
|
||||
errorRecovery: true,
|
||||
plugins: ["jsx", "typescript"],
|
||||
});
|
||||
} catch {
|
||||
return failedJavaScriptStaticAnalysis();
|
||||
}
|
||||
const file = parseJavaScriptSource(source);
|
||||
return file === null
|
||||
? failedJavaScriptStaticAnalysis()
|
||||
: analyzeParsedJavaScriptStaticSource(source, file, limits);
|
||||
};
|
||||
|
||||
/** Recover static structure from an already parsed JavaScript artifact. */
|
||||
export const analyzeParsedJavaScriptStaticSource = (
|
||||
source: string,
|
||||
file: ParsedJavaScriptSource,
|
||||
limits: JavaScriptStaticAnalysisLimits,
|
||||
): JavaScriptStaticAnalysis => {
|
||||
const accumulator = createJavaScriptAnalysisAccumulator();
|
||||
traverseStaticSource(source, file, accumulator, limits);
|
||||
addSourceMapDirectives(source, accumulator, limits.maxFindings);
|
||||
|
|
@ -53,7 +58,7 @@ export const analyzeJavaScriptStaticSource = (
|
|||
|
||||
const traverseStaticSource = (
|
||||
source: string,
|
||||
file: ReturnType<typeof parse>,
|
||||
file: ParsedJavaScriptSource,
|
||||
accumulator: AnalysisAccumulator,
|
||||
limits: JavaScriptStaticAnalysisLimits,
|
||||
): void => {
|
||||
|
|
@ -76,7 +81,7 @@ const traverseStaticSource = (
|
|||
|
||||
const finalizeStaticAnalysis = (
|
||||
source: string,
|
||||
file: ReturnType<typeof parse>,
|
||||
file: ParsedJavaScriptSource,
|
||||
accumulator: AnalysisAccumulator,
|
||||
limits: JavaScriptStaticAnalysisLimits,
|
||||
): JavaScriptStaticAnalysis => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue