test(javascript): preserve fixture runtime context for path analysis
This commit is contained in:
1 parent
54a663ddd5
commit
c3ad8fc024
8 files changed
+132
-97
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
|
||||
describe("native addon type-only TypeScript imports", () => {
|
||||
@@ -64,8 +65,23 @@ describe("native addon type-only re-exports", () => {
|
||||
});
|
||||
|
||||
describe("native addon loader provenance", () => {
|
||||
it("distinguishes CommonJS and ESM re-exports", () => {
|
||||
it("does not give ESM fixtures an ambient CommonJS loader", () => {
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
import imported from "./addon.node";
|
||||
const required = require("./other.node");
|
||||
module.exports = require("./exported.node");
|
||||
`);
|
||||
expect(analysis.electron.native_addon_bindings).toEqual([
|
||||
expect.objectContaining({
|
||||
specifier: "./addon.node",
|
||||
binding_kind: "import",
|
||||
module_kind: "import",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("distinguishes CommonJS and ESM re-exports", () => {
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
const required = require("./addon.node#literal.node");
|
||||
module.exports = require("./addon.node#literal.node");
|
||||
import imported from "./addon.node#literal.node";
|
||||
@@ -97,7 +113,7 @@ describe("native addon loader provenance", () => {
|
||||
it.each(["#", "?"])(
|
||||
"classifies require filenames literally for %s",
|
||||
(punctuation) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
const script = require("./addon.node${punctuation}helper.js");
|
||||
import native from "./addon.node${punctuation}helper.js";
|
||||
`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { runInNewContext } from "node:vm";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
|
||||
const cases = [
|
||||
["native setter", "const change=app.setAppPath;", true, [1]],
|
||||
@@ -47,14 +47,14 @@ it.each(cases)(
|
||||
runInNewContext("change.call(app,'./lib');", namespace);
|
||||
expect(changed).toBe(expected);
|
||||
}
|
||||
const exported = analyzeJavaScriptStaticSource(
|
||||
const exported = analyzeCommonJsStaticFixture(
|
||||
`${defining}export{change,app};`,
|
||||
);
|
||||
expect(exported.parse_error_count).toBe(0);
|
||||
expect((exported.electron.app_path_changes ?? []).length > 0).toBe(
|
||||
possible,
|
||||
);
|
||||
const caller = analyzeJavaScriptStaticSource(
|
||||
const caller = analyzeCommonJsStaticFixture(
|
||||
"import{change,app}from'./helper.mjs';change.call(app,'./lib');",
|
||||
);
|
||||
expect(caller.parse_error_count).toBe(0);
|
||||
@@ -63,7 +63,7 @@ it.each(cases)(
|
||||
);
|
||||
|
||||
it("does not treat a type-only exported setter as a runtime escape", () => {
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
"const{app}=require('electron');const change=app.setAppPath;export type{change};",
|
||||
);
|
||||
expect(analysis.parse_error_count).toBe(0);
|
||||
@@ -78,7 +78,7 @@ it.each([
|
||||
] as const)(
|
||||
"selects the published runtime value in %s",
|
||||
(declaration, possible) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`const{app}=require('electron');${declaration}`,
|
||||
);
|
||||
expect(analysis.parse_error_count).toBe(0);
|
||||
@@ -105,7 +105,7 @@ it.each(cases)(
|
||||
runInNewContext("change.call(app,'./lib');", module.exports);
|
||||
expect(changed).toBe(expected);
|
||||
}
|
||||
const analysis = analyzeJavaScriptStaticSource(defining);
|
||||
const analysis = analyzeCommonJsStaticFixture(defining);
|
||||
expect(analysis.parse_error_count).toBe(0);
|
||||
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(
|
||||
possible,
|
||||
@@ -125,7 +125,7 @@ it.each([
|
||||
] as const)(
|
||||
"follows shared runtime export values and receivers in %s",
|
||||
(published, possible) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`const {app}=require('electron');const change=app.setAppPath;${published}`,
|
||||
);
|
||||
expect(analysis.parse_error_count).toBe(0);
|
||||
|
||||
@@ -205,16 +205,17 @@ describe("Electron option evidence", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("does not read an audio or video preload attribute as a preload script", () => {
|
||||
it("selects proven Electron preloads while ignoring media attributes and unrelated properties", () => {
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
jsx("audio", { controls: true, preload: "metadata", src });
|
||||
(0, React.createElement)(\`video\`, { preload: "auto" });
|
||||
jsx("webview", { preload: "./guest.js" });
|
||||
const options = { preload: "./preload.js" };
|
||||
const options = { preload: "./unrelated.js" };
|
||||
const { BrowserWindow } = require("electron");
|
||||
new BrowserWindow({webPreferences: {preload: "./preload.js"}});
|
||||
`);
|
||||
|
||||
expect(analysis.role_paths.map(({ path }) => path)).toEqual([
|
||||
"./guest.js",
|
||||
"./preload.js",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
import { analyzeCommonJsSemanticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
|
||||
it("shares required process writes with globals while keeping earlier observations", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`const proc=require('process');
|
||||
const ir = analyzeCommonJsSemanticFixture(`const proc=require('process');
|
||||
process.cwd(); const before=proc.env.BEFORE;
|
||||
proc.env={}; const after=process.env.AFTER;`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["BEFORE"]);
|
||||
});
|
||||
|
||||
it("does not propagate replacement-object writes from import-equals direct origins", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`import fs = require('fs');
|
||||
const ir = analyzeCommonJsSemanticFixture(`import fs = require('fs');
|
||||
const other=require('fs'); fs={}; fs.readFileSync=custom; other.readFileSync('kept');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
});
|
||||
|
||||
it("retains original import-equals aliases captured before reassignment", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`import fs = require('fs');
|
||||
const ir = analyzeCommonJsSemanticFixture(`import fs = require('fs');
|
||||
const alias=fs; const other=require('fs'); fs={}; alias.readFileSync=custom; other.readFileSync('wrong');`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps computed namespace calls as unknown receiver escapes", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const fs=require('fs'); fs.hook=custom; fs[key](); fs.readFileSync('wrong');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
@@ -31,7 +31,8 @@ it("keeps computed namespace calls as unknown receiver escapes", () => {
|
||||
it.each(["(0, fs.readFileSync)", "(assigned = fs.readFileSync)"])(
|
||||
"checks captured function validity through %s",
|
||||
(value) => {
|
||||
const ir = analyzeJavaScriptSemantics(`const fs=require('fs'); let assigned;
|
||||
const ir =
|
||||
analyzeCommonJsSemanticFixture(`const fs=require('fs'); let assigned;
|
||||
const saved=${value}; fs.readFileSync=custom; const late=${value}; saved('kept'); late('wrong');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
},
|
||||
@@ -41,7 +42,7 @@ it.each(['process.env={MODE:"local"};', "process.argv=[];", "patch(process);"])(
|
||||
"shares unbound process object effects with required aliases: %s",
|
||||
(write) => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`${write} const proc=require('process');
|
||||
analyzeCommonJsSemanticFixture(`${write} const proc=require('process');
|
||||
${write.includes("argv") ? "const arg=proc.argv[2];" : "const mode=proc.env.MODE;"}`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
},
|
||||
@@ -51,14 +52,14 @@ it.each([
|
||||
"function local(process) {process.env={};}",
|
||||
"process={}; process.env={};",
|
||||
])("keeps unrelated process replacements separate: %s", (write) => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`${write} const proc=require('process'); const mode=proc.env.MODE;`,
|
||||
);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["MODE"]);
|
||||
});
|
||||
|
||||
it("preserves exports captured before a later loader overwrite", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs=require('node:fs'); fs.readFileSync('before');
|
||||
const read=fs.readFileSync;
|
||||
require('fs').readFileSync('direct-before');
|
||||
@@ -82,9 +83,9 @@ it.each([
|
||||
])(
|
||||
"keeps repeated or already replaced loader captures uncertain: %s",
|
||||
(source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).configurationOperations).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations,
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -95,7 +96,7 @@ it.each([
|
||||
])(
|
||||
"shares require-mode import-equals effects in both directions: %s",
|
||||
(write, call) => {
|
||||
const ir = analyzeJavaScriptSemantics(`import fs = require('node:fs');
|
||||
const ir = analyzeCommonJsSemanticFixture(`import fs = require('node:fs');
|
||||
const other = require('fs'); other.readFileSync('before'); ${write} ${call}`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual([
|
||||
"before",
|
||||
@@ -110,7 +111,7 @@ it.each([
|
||||
])(
|
||||
"retains captured native function calls after argument escape: %s",
|
||||
(capture) => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`${capture} observe(read); read('kept');`,
|
||||
);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
@@ -118,20 +119,20 @@ it.each([
|
||||
);
|
||||
|
||||
it("distinguishes escaping a method value from escaping its namespace", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`const fs=require('fs');
|
||||
const ir = analyzeCommonJsSemanticFixture(`const fs=require('fs');
|
||||
observe(fs.readFileSync); fs.readFileSync('kept');
|
||||
observe(fs); fs.readFileSync('wrong');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
});
|
||||
|
||||
it("still invalidates escaped process configuration containers", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`const proc=require('process');
|
||||
const ir = analyzeCommonJsSemanticFixture(`const proc=require('process');
|
||||
observe(proc.env); const mode=proc.env.MODE;`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("retains direct require effects without poisoning later cached receivers", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
require('node:fs').readFileSync('first');
|
||||
const fs = require('fs'); fs.readFileSync('second');
|
||||
require('node:fs').createReadStream('stream');
|
||||
@@ -156,7 +157,7 @@ it.each([
|
||||
"require('node:fs').promises.open = custom;",
|
||||
"const fsp = require('fs/promises'); fsp.open = custom;",
|
||||
])("shares fs.promises object writes with fs/promises: %s", (write) => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const before = require('node:fs/promises'); before.open('before');
|
||||
${write}
|
||||
const after = require('node:fs/promises'); after.open('wrong');
|
||||
@@ -169,7 +170,7 @@ it.each([
|
||||
"({ loader: require } = other);",
|
||||
"for (require of loaders) {}",
|
||||
])("rejects provenance from an overwritten ambient loader: %s", (write) => {
|
||||
const ir = analyzeJavaScriptSemantics(`${write}
|
||||
const ir = analyzeCommonJsSemanticFixture(`${write}
|
||||
const fs = require('node:fs'); fs.readFileSync('wrong');
|
||||
require('node:fs').readFileSync('also-wrong');
|
||||
`);
|
||||
@@ -180,7 +181,7 @@ it.each([
|
||||
});
|
||||
|
||||
it("keeps local loader reassignment separate from the ambient loader", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
function local(require) { require = replacement; }
|
||||
const fs = require('node:fs'); fs.readFileSync('kept');
|
||||
`);
|
||||
@@ -188,7 +189,7 @@ it("keeps local loader reassignment separate from the ambient loader", () => {
|
||||
});
|
||||
|
||||
it("keeps wrapper require separate from global-object property writes", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
globalThis.require = replacement;
|
||||
function polyfill(key) { globalThis[key] = replacement; }
|
||||
require('node:fs').readFileSync('kept');
|
||||
@@ -199,7 +200,7 @@ it("keeps wrapper require separate from global-object property writes", () => {
|
||||
it.each(["import fs from 'node:fs';", "const fs = require('node:fs');"])(
|
||||
"retains reads and saved methods before a later write: %s",
|
||||
(declaration) => {
|
||||
const ir = analyzeJavaScriptSemantics(`${declaration}
|
||||
const ir = analyzeCommonJsSemanticFixture(`${declaration}
|
||||
fs.readFileSync('before');
|
||||
const saved = fs.readFileSync;
|
||||
const { readFileSync: destructured } = fs;
|
||||
@@ -220,7 +221,7 @@ it.each([
|
||||
"const { readFileSync: read } = require('fs');",
|
||||
"const read = require('fs').readFileSync;",
|
||||
])("checks cached module writes at the projected capture: %s", (capture) => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('node:fs');
|
||||
const saved = require('fs').readFileSync;
|
||||
fs.readFileSync = custom;
|
||||
@@ -231,7 +232,7 @@ it.each([
|
||||
});
|
||||
|
||||
it("retains captured methods across later escapes without freezing a namespace alias", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('node:fs'); const saved = require('fs').readFileSync;
|
||||
const namespace = require('fs'); patch(fs);
|
||||
const late = require('fs').readFileSync;
|
||||
@@ -241,7 +242,7 @@ it("retains captured methods across later escapes without freezing a namespace a
|
||||
});
|
||||
|
||||
it("keeps writes in repeated callable and loop activations conservative", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('node:fs');
|
||||
function run() { fs.readFileSync('wrong'); fs.readFileSync = custom; }
|
||||
for (;;) { const read = require('fs').readFileSync; read('wrong'); fs.readFileSync = custom; }
|
||||
@@ -250,14 +251,14 @@ it("keeps writes in repeated callable and loop activations conservative", () =>
|
||||
});
|
||||
|
||||
it("does not propagate a reassigned local object's writes into cached exports", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`let a = require('node:fs'); const b = require('node:fs'); a = {}; a.readFileSync = custom; b.readFileSync('kept');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toMatchObject([{ key: "kept" }]);
|
||||
});
|
||||
|
||||
it("shares repeated require mutations for every built-in effect family", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('fs'); const otherFs = require('node:fs'); fs.createReadStream = custom; otherFs.createReadStream('wrong');
|
||||
const cp = require('child_process'); const otherCp = require('node:child_process'); cp.spawn = custom; otherCp.spawn('wrong', []);
|
||||
const timers = require('timers'); const otherTimers = require('node:timers'); timers.setTimeout = custom; otherTimers.setTimeout(callback, 1);
|
||||
@@ -274,7 +275,7 @@ it("shares repeated require mutations for every built-in effect family", () => {
|
||||
it.each(["node:fs", "fs"])(
|
||||
"shares repeated built-in require identity across %s",
|
||||
(specifier) => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const a = require('node:fs'); const b = require('${specifier}'); const alias = b; a.readFileSync = custom; b.createReadStream('kept'); alias.readFileSync('wrong');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
@@ -285,14 +286,14 @@ it.each(["node:fs", "fs"])(
|
||||
);
|
||||
|
||||
it("shares unknown escapes across repeated requires without losing earlier effects", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const a = require('node:fs'); const b = require('fs'); b.readFileSync('before'); patch(a); b.readFileSync('after');`,
|
||||
);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["before"]);
|
||||
});
|
||||
|
||||
it("preserves exact default ESM import mutation paths", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`import fs from 'node:fs'; fs.writeFileSync = custom; fs.readFileSync('kept'); fs.writeFileSync('replaced');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toMatchObject([
|
||||
@@ -301,7 +302,7 @@ it("preserves exact default ESM import mutation paths", () => {
|
||||
});
|
||||
|
||||
it("retains built-in effects before later top-level escapes", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('node:fs'); const cp = require('node:child_process'); const timers = require('node:timers'); const http = require('node:http');
|
||||
fs.readFileSync('before'); fs.createReadStream('before'); cp.spawn('tool', []); timers.setTimeout(callback, 1); http.get('http://example.test');
|
||||
patch(fs); patch(cp); patch(timers); patch(http);
|
||||
@@ -319,7 +320,7 @@ it("retains built-in effects before later top-level escapes", () => {
|
||||
it.each(["env", "argv"])(
|
||||
"does not treat a replaced process %s as configuration",
|
||||
(family) => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const proc = require('node:process'); proc.${family} = replacement; const value = proc.${family}[${family === "env" ? "'MODE'" : "2"}];`,
|
||||
);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
@@ -327,14 +328,14 @@ it.each(["env", "argv"])(
|
||||
);
|
||||
|
||||
it("does not infer temporal safety across repeated callable or loop activations", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const fs = require('node:fs'); function run() { fs.readFileSync('callable'); patch(fs); } for (;;) { fs.readFileSync('loop'); patch(fs); }`,
|
||||
);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not recover a replaced method through a subsequent alias", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const fs = require('node:fs'); fs.readFileSync = custom; const read = fs.readFileSync; read('config.json');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
@@ -345,7 +346,7 @@ it.each([
|
||||
"patch(fs); patch(cp); patch(timers); patch(http);",
|
||||
"const fsAlias = fs; fsAlias.readFileSync = custom; fsAlias.createReadStream = custom; const cpAlias = cp; cpAlias.spawn = custom; timers[key] = custom; delete http.get;",
|
||||
])("excludes replaced or escaped CommonJS methods: %s", (mutation) => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fs = require('node:fs'); const cp = require('node:child_process');
|
||||
const timers = require('node:timers'); const http = require('node:http');
|
||||
${mutation}
|
||||
@@ -360,7 +361,7 @@ it.each([
|
||||
});
|
||||
|
||||
it("preserves unaffected CommonJS methods after sibling property mutation", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const fs = require('node:fs'); fs.writeFileSync = custom; fs.readFileSync('config.json');`,
|
||||
);
|
||||
expect(ir.configurationOperations).toMatchObject([
|
||||
@@ -378,7 +379,7 @@ it.each([
|
||||
const { setTimeout: schedule } = require("timers");
|
||||
const { get } = require("http");`,
|
||||
])("recovers builtin effects from CommonJS provenance: %s", (declarations) => {
|
||||
const ir = analyzeJavaScriptSemantics(`${declarations}
|
||||
const ir = analyzeCommonJsSemanticFixture(`${declarations}
|
||||
const text = read("config.json", "utf8");
|
||||
const stream = fs.createReadStream("config.json"); stream.destroy();
|
||||
const child = launch("tool", []); child.kill("SIGTERM");
|
||||
@@ -412,7 +413,7 @@ it.each([
|
||||
});
|
||||
|
||||
it("does not promote ambiguous or non-namespace CommonJS origins to builtin effects", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
const fake = require("./fs.js"); fake.readFileSync("external");
|
||||
const absentDefault = require("node:fs").default; absentDefault.readFileSync("default");
|
||||
const { constants } = require("node:fs"); constants.createReadStream("nested");
|
||||
@@ -425,7 +426,7 @@ it("does not promote ambiguous or non-namespace CommonJS origins to builtin effe
|
||||
});
|
||||
|
||||
it("does not assign built-in APIs to unrelated or shadowed bindings", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const ir = analyzeCommonJsSemanticFixture(`
|
||||
import fs from "./fs.js";
|
||||
import { constants as constantsFs } from "node:fs";
|
||||
fs.createReadStream("config.json");
|
||||
@@ -443,9 +444,9 @@ it.each([
|
||||
`import proc from 'node:process'; proc.env = {}; const value = process.env.MODE;`,
|
||||
`import fs from 'node:fs'; fs.readFileSync = custom; const other = require('fs'); other.readFileSync('wrong');`,
|
||||
])("keeps delayed or replaced built-in identities unknown: %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).configurationOperations).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations,
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -454,7 +455,7 @@ it.each([
|
||||
`import fs from 'node:fs'; fs.writeFileSync = custom; const other = require('fs'); other.readFileSync('kept');`,
|
||||
])("keeps earlier and independent built-in observations: %s", (source) => {
|
||||
expect(
|
||||
analyzeJavaScriptSemantics(source).configurationOperations.map(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations.map(
|
||||
({ key }) => key,
|
||||
),
|
||||
).toEqual(["kept"]);
|
||||
@@ -469,7 +470,7 @@ it("shares equivalent alias subproblems and repeated intact receiver facts", ()
|
||||
{ length: 128 },
|
||||
(_, index) => `a29.readFileSync('file-${index}');`,
|
||||
);
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
["const fs = require('fs');", ...aliases, ...calls].join("\n"),
|
||||
);
|
||||
expect(ir.configurationOperations).toHaveLength(128);
|
||||
@@ -481,7 +482,7 @@ it.each([
|
||||
`const tls = require('tls'); const socket = tls.connect(443, 'example.test'); socket.end();`,
|
||||
])("retains TLS acquisitions and their release: %s", (source) => {
|
||||
expect(
|
||||
analyzeJavaScriptSemantics(source).resourceOperations.map(
|
||||
analyzeCommonJsSemanticFixture(source).resourceOperations.map(
|
||||
({ kind, method }) => ({ kind, method }),
|
||||
),
|
||||
).toEqual([
|
||||
@@ -497,9 +498,9 @@ it.each([
|
||||
`globalThis.process.env = { MODE: 'local' }; const proc = require('process'); const value = proc.env.MODE;`,
|
||||
`const proc = require('process'); proc.env.MODE = 'local'; const value = proc.env.MODE;`,
|
||||
])("shares writes through the original process object: %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).configurationOperations).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations,
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -519,7 +520,7 @@ it.each([
|
||||
"retains reads before a later sequence operand: %s",
|
||||
(source, expectedKey) => {
|
||||
expect(
|
||||
analyzeJavaScriptSemantics(source).configurationOperations.map(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations.map(
|
||||
({ key }) => key,
|
||||
),
|
||||
).toEqual([expectedKey]);
|
||||
@@ -528,21 +529,21 @@ it.each([
|
||||
|
||||
it("does not use source offsets for destructuring assignment evaluation order", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const fs = require('fs'); const target = {};
|
||||
analyzeCommonJsSemanticFixture(`const fs = require('fs'); const target = {};
|
||||
[target[fs.readFileSync('wrong')]] = (fs.readFileSync = custom, []);`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps process reads after an alias is reassigned away from the global", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`let host = globalThis; host = {process: {env: {}}};
|
||||
analyzeCommonJsSemanticFixture(`let host = globalThis; host = {process: {env: {}}};
|
||||
host.process.env = {}; const proc = require('process'); const value = proc.env.MODE;`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["MODE"]);
|
||||
});
|
||||
|
||||
it("rejects CommonJS require after the wrapper module replaces its delegate", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`module.require = () => ({ readFileSync() {} });
|
||||
analyzeCommonJsSemanticFixture(`module.require = () => ({ readFileSync() {} });
|
||||
const fs = require('node:fs'); fs.readFileSync('unknown');`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
@@ -552,7 +553,7 @@ it.each([
|
||||
`const owner = module; owner.require = () => ({}); const fs = require('fs'); fs.readFileSync('wrong');`,
|
||||
`Object.defineProperty(module, 'require', {value: () => ({})}); const fs = require('fs'); fs.readFileSync('wrong');`,
|
||||
])("tracks only actual wrapper require replacements: %s", (source) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
const ir = analyzeCommonJsSemanticFixture(source);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(
|
||||
source.includes("extra") ? ["kept"] : [],
|
||||
);
|
||||
@@ -560,7 +561,7 @@ it.each([
|
||||
|
||||
it("preserves a saved promises object after deleting its parent export", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const fs = require('fs'); const fsp = fs.promises;
|
||||
analyzeCommonJsSemanticFixture(`const fs = require('fs'); const fsp = fs.promises;
|
||||
delete fs.promises; const handle = fsp.open('kept'); handle.close();`);
|
||||
expect(
|
||||
ir.resourceOperations.map(({ kind, method }) => ({ kind, method })),
|
||||
@@ -571,7 +572,7 @@ it("preserves a saved promises object after deleting its parent export", () => {
|
||||
});
|
||||
|
||||
it("does not infer network acquisition methods from another built-in", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const { open } = require('node:net'); open();`,
|
||||
);
|
||||
expect(ir.resourceOperations).toEqual([]);
|
||||
@@ -579,7 +580,7 @@ it("does not infer network acquisition methods from another built-in", () => {
|
||||
|
||||
it("recognizes nested ESM namespace receivers as original built-ins", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`import * as ns from 'node:fs'; import fs from 'node:fs';
|
||||
analyzeCommonJsSemanticFixture(`import * as ns from 'node:fs'; import fs from 'node:fs';
|
||||
ns.default.readFileSync('first'); fs.readFileSync('second');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual([
|
||||
"first",
|
||||
@@ -589,7 +590,7 @@ it("recognizes nested ESM namespace receivers as original built-ins", () => {
|
||||
|
||||
it("keeps intact fs/promises identity after deleting the fs.promises slot", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const fs=require('fs'); delete fs.promises;
|
||||
analyzeCommonJsSemanticFixture(`const fs=require('fs'); delete fs.promises;
|
||||
const fsp=require('fs/promises'); const handle=fsp.open('kept'); handle.close();`);
|
||||
expect(
|
||||
ir.resourceOperations.map(({ kind, method }) => ({ kind, method })),
|
||||
@@ -604,11 +605,11 @@ it.each([
|
||||
`const fs = require('fs'); patch(fs.promises); require('fs/promises').open('wrong');`,
|
||||
`const fs = require('fs'); patch(fs.promises); require('node:fs/promises').open('wrong');`,
|
||||
])("keeps replaced or escaped promises receivers unknown: %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).resourceOperations).toEqual([]);
|
||||
expect(analyzeCommonJsSemanticFixture(source).resourceOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("follows final sequence expressions for direct built-in method calls", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`const fs = require('fs');
|
||||
const ir = analyzeCommonJsSemanticFixture(`const fs = require('fs');
|
||||
(0, fs.readFileSync)('kept'); fs.readFileSync = custom;
|
||||
(0, fs.readFileSync)('wrong');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
@@ -620,35 +621,35 @@ it.each([
|
||||
])(
|
||||
"shares named process containers with their namespace aliases: %s",
|
||||
(source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).configurationOperations).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(
|
||||
analyzeCommonJsSemanticFixture(source).configurationOperations,
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains uncertainty after a computed write to module's require delegate", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const key = 'require'; module[key] = replacement;
|
||||
analyzeCommonJsSemanticFixture(`const key = 'require'; module[key] = replacement;
|
||||
const fs = require('fs'); fs.readFileSync('wrong');`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("retains a module namespace captured before its binding is reassigned", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`let fs = require('fs'); const saved = fs;
|
||||
analyzeCommonJsSemanticFixture(`let fs = require('fs'); const saved = fs;
|
||||
fs = {}; saved.readFileSync('kept');`);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
});
|
||||
|
||||
it("does not let own fs probe calls hide later builtin reads", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`const fs=require('fs'); fs.existsSync('probe'); fs.readFileSync('kept');`,
|
||||
);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
});
|
||||
|
||||
it("resolves nested ESM default namespace methods", () => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
const ir = analyzeCommonJsSemanticFixture(
|
||||
`import * as ns from 'node:fs'; ns.default.readFileSync('kept');`,
|
||||
);
|
||||
expect(ir.configurationOperations.map(({ key }) => key)).toEqual(["kept"]);
|
||||
@@ -656,21 +657,21 @@ it("resolves nested ESM default namespace methods", () => {
|
||||
|
||||
it("tracks process objects passed through qualified global references", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`patch(globalThis.process); const proc = require('process');
|
||||
analyzeCommonJsSemanticFixture(`patch(globalThis.process); const proc = require('process');
|
||||
const mode = proc.env.MODE;`);
|
||||
expect(ir.configurationOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("checks a saved child object's method when that function is selected", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const fs = require('fs'); const fsp = fs.promises;
|
||||
analyzeCommonJsSemanticFixture(`const fs = require('fs'); const fsp = fs.promises;
|
||||
fs.promises.open = custom; const open = fsp.open; open('wrong');`);
|
||||
expect(ir.resourceOperations).toEqual([]);
|
||||
});
|
||||
|
||||
it("retains an earlier captured function after its object method is replaced", () => {
|
||||
const ir =
|
||||
analyzeJavaScriptSemantics(`const fs = require('fs'); const fsp = fs.promises;
|
||||
analyzeCommonJsSemanticFixture(`const fs = require('fs'); const fsp = fs.promises;
|
||||
const open = fsp.open; fs.promises.open = custom; open('kept');`);
|
||||
expect(ir.resourceOperations).toMatchObject([
|
||||
{ kind: "acquire", method: "open" },
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { parseExpression } from "@babel/parser";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
import { parserPluginsForPath } from "./javascriptSourceParser.js";
|
||||
import {
|
||||
calleeName,
|
||||
@@ -358,7 +358,7 @@ it("decodes a source-relative file URL once before composing a filesystem path",
|
||||
|
||||
it("retains Worker URL references through their separate URL-aware producer", () => {
|
||||
const specifier = "./worker%252Fentry.js?version=1#worker";
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
import { Worker } from "node:worker_threads";
|
||||
new Worker(new URL(${JSON.stringify(specifier)}, import.meta.url));
|
||||
`);
|
||||
@@ -404,7 +404,7 @@ const analyzePathConsumers = (expression: string) => {
|
||||
windowPrefix,
|
||||
windowExpression,
|
||||
utilityExpression,
|
||||
analysis: analyzeJavaScriptStaticSource(
|
||||
analysis: analyzeCommonJsStaticFixture(
|
||||
`${genericPrefix}${property} }; const { BrowserWindow, utilityProcess } = require("electron");\n${windowExpression};\n${utilityExpression};`,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@ import { posix } from "node:path";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
|
||||
it.each(["/other", "relative"])(
|
||||
"keeps a shadowed source-directory parameter dynamic: %s",
|
||||
(directory) => {
|
||||
const actual = posix.resolve("/runtime/base", directory, "decoy.cjs");
|
||||
expect(actual).not.toBe("/source/app/decoy.cjs");
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
const path = require('node:path');
|
||||
const {BrowserWindow, utilityProcess} = require('electron');
|
||||
function build(__dirname) {
|
||||
@@ -75,7 +75,7 @@ it.each(localResolvers)(
|
||||
false,
|
||||
),
|
||||
).toBe("/actual/runtime.cjs");
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
import {BrowserWindow, utilityProcess} from 'electron';
|
||||
${setup}
|
||||
new BrowserWindow({webPreferences:{preload:${expression}}});
|
||||
@@ -126,7 +126,7 @@ it("does not treat a constructed Node resolver as a path call", () => {
|
||||
expect(() => Reflect.construct(posix.resolve, ["/app", "decoy.cjs"])).toThrow(
|
||||
TypeError,
|
||||
);
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
const helper = require('node:path');
|
||||
const {BrowserWindow} = require('electron');
|
||||
new BrowserWindow({webPreferences:{preload:new helper.resolve(runtimePath,__dirname,'decoy.cjs')}});
|
||||
@@ -144,7 +144,7 @@ it.each(nodeResolvers)(
|
||||
posix.resolve("/actual/runtime.cjs", "/fixture/app", "decoy.cjs"),
|
||||
).toBe("/fixture/app/decoy.cjs");
|
||||
const expression = `${callee}(runtimePath, __dirname, "decoy.cjs")`;
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
const {BrowserWindow, utilityProcess} = require('electron');
|
||||
${setup}
|
||||
new BrowserWindow({webPreferences:{preload:${expression}}});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { runInThisContext } from "node:vm";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
|
||||
|
||||
it.each([false, true])(
|
||||
"preserves only untouched authored CommonJS dirname vars: assigned=%s",
|
||||
@@ -25,7 +25,7 @@ it.each([false, true])(
|
||||
expect(fixture.exports).toBe(
|
||||
assigned ? "relative/preload.cjs" : "/source/app/preload.cjs",
|
||||
);
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`${setup}const{BrowserWindow}=require('electron');new BrowserWindow({webPreferences:{preload:path.join(__dirname,'preload.cjs')}});`,
|
||||
);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
@@ -43,7 +43,7 @@ it("keeps local dirname methods from proving a source anchor", () => {
|
||||
"decoy.cjs",
|
||||
),
|
||||
).toBe("/other/decoy.cjs");
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`import path from 'node:path';const helper={dirname:()=>'/other'};const{BrowserWindow}=require('electron');new BrowserWindow({webPreferences:{preload:path.resolve(runtimeBase,helper.dirname(import.meta.url),'decoy.cjs')}});`,
|
||||
);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
@@ -57,7 +57,7 @@ it("retains a proven Node dirname of the decoded module URL", () => {
|
||||
fileURLToPath("file:///source/app/main.mjs", { windows: false }),
|
||||
),
|
||||
).toBe("/source/app");
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`import path from 'node:path';import{fileURLToPath}from'node:url';const{BrowserWindow}=require('electron');new BrowserWindow({webPreferences:{preload:path.resolve(runtimeBase,path.dirname(fileURLToPath(import.meta.url)),'preload.cjs')}});`,
|
||||
);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
@@ -75,7 +75,7 @@ it.each([false, true])(
|
||||
expect(local ? helper.dirname(decoded) : posix.dirname(decoded)).toBe(
|
||||
local ? "/other" : "/source/app",
|
||||
);
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const analysis = analyzeCommonJsStaticFixture(`
|
||||
import path from 'node:path';import*as url from 'node:url';
|
||||
const helper={dirname:()=>'/other'};const{BrowserWindow}=require('electron');
|
||||
new BrowserWindow({webPreferences:{preload:path.resolve(runtimeBase,${local ? "helper" : "path"}.dirname(url.fileURLToPath(import.meta.url)),'preload.cjs')}});
|
||||
@@ -88,7 +88,7 @@ it.each([false, true])(
|
||||
|
||||
it("does not use a URL string as a filesystem source-file anchor", () => {
|
||||
expect(posix.dirname("file:///source/app/main.mjs")).not.toBe("/source/app");
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`import path from 'node:path';const{BrowserWindow}=require('electron');new BrowserWindow({webPreferences:{preload:path.resolve(runtimeBase,path.dirname(import.meta.url),'decoy.cjs')}});`,
|
||||
);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
@@ -99,7 +99,7 @@ it("does not use a URL string as a filesystem source-file anchor", () => {
|
||||
it.each([false, true])(
|
||||
"proves the CommonJS filename dirname owner: local=%s",
|
||||
(local) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`const path=require('node:path');const helper={dirname:()=>'.'};const{BrowserWindow}=require('electron');new BrowserWindow({webPreferences:{preload:path.resolve(runtimeBase,${local ? "helper" : "path"}.dirname(__filename),'decoy.cjs')}});`,
|
||||
);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
@@ -111,7 +111,7 @@ it.each([false, true])(
|
||||
it.each([false, true])(
|
||||
"preserves the utility-process dirname parameter until assigned: assigned=%s",
|
||||
(assigned) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(
|
||||
const analysis = analyzeCommonJsStaticFixture(
|
||||
`var __dirname;${assigned ? "__dirname='relative';" : ""}const path=require('node:path');const{utilityProcess}=require('electron');utilityProcess.fork(path.join(__dirname,'worker.cjs'));`,
|
||||
);
|
||||
expect(analysis.electron.utility_processes).toMatchObject([
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { analyzeParsedJavaScriptSemantics } from "../../src/domain/javascript/javascriptSemanticAnalysis.js";
|
||||
import { analyzeParsedJavaScriptStaticSource } from "../../src/domain/javascript/javascriptStaticAnalysis.js";
|
||||
import { parseJavaScriptSource } from "../../src/domain/javascript/javascriptSourceParser.js";
|
||||
|
||||
// These fixtures model CommonJS TypeScript assets. The filename establishes
|
||||
// wrapper authority while retaining type syntax and inert module declarations.
|
||||
const parseCommonJsFixture = (source: string) => {
|
||||
const parsed = parseJavaScriptSource(source, "fixture.cts");
|
||||
if (parsed === null) throw new Error("CommonJS fixture could not be parsed");
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export const analyzeCommonJsStaticFixture = (source: string) =>
|
||||
analyzeParsedJavaScriptStaticSource(source, parseCommonJsFixture(source));
|
||||
|
||||
export const analyzeCommonJsSemanticFixture = (source: string) =>
|
||||
analyzeParsedJavaScriptSemantics(parseCommonJsFixture(source));
|
||||
Reference in new issue
Block a user