Files
rea/src/domain/javascript/javascriptSemanticAnalysis.require_links.test.ts
T
hhangandhhang 86fab11fbe fix(javascript): preserve import-equals declaration and export evidence (#2062)
* fix(javascript): retain TypeScript import-equals dependencies

* test(mcp): align completion checks with warm provider state

* fix(javascript): propagate require origins through exported aliases

* fix(javascript): normalize equivalent CTS binding origins

---------

Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
2026-10-13 06:04:21 +09:00

233 lines
6.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import {
origin,
topLevelBinding,
} from "./javascriptSemanticAnalysis.fixture.js";
describe("TypeScript import-equals links", () => {
it("retains runtime external import-equals as a require dependency", () => {
const links = analyzeJavaScriptSemantics(`
import dependency = require("./dependency.cjs");
import type types = require("./types");
import alias = Namespace.Dependency;
`).moduleLinks;
expect(links).toEqual([
expect.objectContaining({
kind: "require",
specifier: "./dependency.cjs",
importedName: null,
localName: "dependency",
exportedName: null,
}),
]);
});
it("keeps the exported binding for exported import-equals", () => {
const source = 'export import dependency = require("./dependency");';
const links = analyzeJavaScriptSemantics(source).moduleLinks;
expect(links).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "require",
specifier: "./dependency",
localName: "dependency",
exportedName: null,
}),
expect.objectContaining({
kind: "export",
specifier: null,
localName: "dependency",
exportedName: "dependency",
}),
]),
);
expect(
links.find(({ kind }) => kind === "require")?.location.start,
).toEqual({ line: 1, column: 7 });
});
});
describe("CommonJS dependency links", () => {
it("retains an import-equals binding and its module origin", () => {
const ir = analyzeJavaScriptSemantics(`
import typed = require("dependency");
import type TypeOnly = require("type-only");
namespace Local { export class Marker {} }
import Alias = Local.Marker;
void typed;
`);
expect(topLevelBinding(ir, "typed")).toMatchObject({
kind: "import",
mutable: false,
provenance: {
origins: [{ specifier: "dependency", importedPath: [] }],
},
});
expect(
ir.moduleLinks.filter(({ specifier }) => specifier !== null),
).toEqual([
expect.objectContaining({
kind: "require",
specifier: "dependency",
importedName: null,
localName: "typed",
}),
]);
expect(ir.references.filter(({ name }) => name === "typed")).toHaveLength(
1,
);
});
it.each([
'require("dependency");',
'void require("dependency");',
'consume(require("dependency"));',
'require("dependency").member;',
'require("dependency")[key];',
'const {} = require("dependency");',
'const [] = require("dependency");',
'let assigned; assigned = require("dependency");',
'function run(value = require("dependency")) {}',
'(require("dependency") as unknown);',
])("retains an unbound dependency in %s", (source) => {
const ir = analyzeJavaScriptSemantics(source);
expect(ir.moduleLinks).toEqual([
expect.objectContaining({
kind: "require",
specifier: "dependency",
importedName: null,
localName: null,
exportedName: null,
callableId: null,
}),
]);
});
it.each([
'function run(require) { require("dependency"); }',
'require("dependency"); const require = loader;',
'function run() { require("dependency"); var require; }',
'with (environment) { require("dependency"); }',
"require(dynamicSpecifier);",
])("does not invent a static global dependency in %s", (source) => {
expect(analyzeJavaScriptSemantics(source).moduleLinks).toEqual([]);
});
it("keeps bindings and re-exports without extra dependency links", () => {
const ir = analyzeJavaScriptSemantics(`
const bound = (require("bound") as unknown);
const { left, right } = require("destructured");
module.exports = require("forwarded");
exports.member = require("member").value;
exports.first = exports.second = require("chained");
`);
expect(
ir.moduleLinks.map(({ kind, specifier, localName, exportedName }) => ({
kind,
specifier,
localName,
exportedName,
})),
).toEqual([
{
kind: "require",
specifier: "bound",
localName: "bound",
exportedName: null,
},
{
kind: "require",
specifier: "destructured",
localName: "left",
exportedName: null,
},
{
kind: "require",
specifier: "destructured",
localName: "right",
exportedName: null,
},
{
kind: "commonjs-export",
specifier: "forwarded",
localName: null,
exportedName: "default",
},
{
kind: "commonjs-export",
specifier: "member",
localName: null,
exportedName: "member",
},
{
kind: "commonjs-export",
specifier: "chained",
localName: null,
exportedName: "first",
},
{
kind: "commonjs-export",
specifier: "chained",
localName: null,
exportedName: "second",
},
]);
expect(
ir.bindings.find(({ name }) => name === "bound")?.provenance.origins,
).toEqual([
{ specifier: "bound", importedPath: [], resolutionMode: "require" },
]);
});
it.each([
'require("outer", require("inner"));',
'const bound = require("outer", require("inner"));',
'module.exports = require("outer", require("inner"));',
])("retains a distinct nested call in %s", (source) => {
const links = analyzeJavaScriptSemantics(source).moduleLinks;
expect(links.map(({ specifier }) => specifier)).toEqual(["outer", "inner"]);
expect(links[1]).toMatchObject({
kind: "require",
importedName: null,
localName: null,
exportedName: null,
});
});
});
it("preserves unshadowed require origins through aliases and assignments", () => {
const ir = analyzeJavaScriptSemantics(`
const { ipcMain: bus } = require("electron");
const forwarded = bus;
let assigned;
assigned = require("electron").ipcMain;
{ const require = localLoader; require("./local.js"); }
`);
for (const name of ["bus", "forwarded"])
expect(origin(topLevelBinding(ir, name))).toEqual({
specifier: "electron",
importedPath: ["ipcMain"],
resolutionMode: "require",
});
expect(topLevelBinding(ir, "assigned").provenance).toMatchObject({
status: "ambiguous",
origins: [{ specifier: "electron", importedPath: ["ipcMain"] }],
});
expect(ir.moduleLinks.filter(({ kind }) => kind === "require")).toEqual([
expect.objectContaining({
specifier: "electron",
importedName: "ipcMain",
localName: "bus",
}),
expect.objectContaining({
specifier: "electron",
importedName: null,
localName: null,
}),
]);
});