mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
c4babfce6a
* fix(js): a CommonJS require and a module path import the file they name
A `require('./application')` was only a call, so a CommonJS file depended
on nothing at file level: Express's lib/express.js showed no imports and
impact missed the files it loads. Local require specifiers are now read
after extraction (both the wasm extractor and the kernel, including its
deferred-decode transport) as `imports` refs from the file, and a JS/TS
module path resolves to its file the way the runtime finds it —
extensions, index files, aliases — which also lands an extensionless
`import './polyfills'` on its file instead of its own import node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): read require imports inside each extractor, so kernel parity holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
/**
|
||
* A CommonJS `require('./x')` is a file import, as ESM's `import` is: Express's
|
||
* `lib/express.js` requires `./application`, `./request` and `./response`, and
|
||
* the index said it imported nothing — an empty Imports rail in the viewer and
|
||
* no file dependency — because those bindings are used as values, never
|
||
* called. A module path also resolves the way the runtime finds it, so an
|
||
* extensionless ESM side-effect import (`import './polyfills'`) reaches its
|
||
* file instead of stopping at its own import statement. A package
|
||
* (`require('express')`) stays outside the index.
|
||
*/
|
||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||
import * as fs from 'fs';
|
||
import * as os from 'os';
|
||
import * as path from 'path';
|
||
import { CodeGraph } from '../src';
|
||
|
||
let root = '';
|
||
let cg: CodeGraph;
|
||
|
||
beforeAll(async () => {
|
||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cjs-require-'));
|
||
const files: Record<string, string> = {
|
||
'package.json': JSON.stringify({ name: 'web', main: 'index.js' }),
|
||
'index.js': `module.exports = require('./lib/express');
|
||
`,
|
||
'lib/express.js': `var EventEmitter = require('events').EventEmitter;
|
||
var mixin = require('merge-descriptors');
|
||
var proto = require('./application');
|
||
var req = require('./request');
|
||
|
||
function createApplication() {
|
||
var app = function () {};
|
||
mixin(app, EventEmitter.prototype, false);
|
||
mixin(app, proto, false);
|
||
app.request = Object.create(req);
|
||
return app;
|
||
}
|
||
|
||
module.exports = createApplication;
|
||
`,
|
||
'lib/application.js': `var app = exports = module.exports = {};
|
||
app.init = function init() {};
|
||
`,
|
||
'lib/request.js': `var req = Object.create({});
|
||
module.exports = req;
|
||
`,
|
||
'test/app.js': `var express = require('..');
|
||
var assert = require("assert");
|
||
`,
|
||
'src/main.ts': `import './polyfills';
|
||
import helper = require('./helper');
|
||
export const value = helper.run();
|
||
`,
|
||
'src/polyfills.ts': `export {};
|
||
`,
|
||
'src/helper.ts': `export function run(): number {
|
||
return 1;
|
||
}
|
||
`,
|
||
};
|
||
for (const [rel, content] of Object.entries(files)) {
|
||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||
fs.writeFileSync(path.join(root, rel), content);
|
||
}
|
||
cg = await CodeGraph.init(root, { index: true });
|
||
});
|
||
|
||
afterAll(() => {
|
||
cg?.close();
|
||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
const importedFiles = (file: string) => {
|
||
const ids = cg.getNodesInFile(file).map((n) => n.id);
|
||
return [...new Set(cg.getOutgoingEdgesFrom(ids, ['imports'])
|
||
.map((e) => cg.getNode(e.target)!.filePath)
|
||
.filter((p) => p !== file))].sort();
|
||
};
|
||
|
||
describe('CommonJS require', () => {
|
||
it('imports the files a module requires, used as values or not, and no package', () => {
|
||
expect(importedFiles('lib/express.js')).toEqual(['lib/application.js', 'lib/request.js']);
|
||
expect(cg.getFileDependencies('lib/express.js')).toEqual(expect.arrayContaining(['lib/application.js', 'lib/request.js']));
|
||
});
|
||
|
||
it('follows a directory require to its package entry', () => {
|
||
expect(importedFiles('index.js')).toEqual(['lib/express.js']);
|
||
expect(importedFiles('test/app.js')).toEqual(['index.js']);
|
||
});
|
||
});
|
||
|
||
describe('a module path', () => {
|
||
it('reaches an extensionless side-effect import’s file', () => {
|
||
expect(importedFiles('src/main.ts')).toContain('src/polyfills.ts');
|
||
});
|
||
|
||
it('reaches a TypeScript import-equals file once', () => {
|
||
const ids = cg.getNodesInFile('src/main.ts').map((n) => n.id);
|
||
const toHelper = cg.getOutgoingEdgesFrom(ids, ['imports'])
|
||
.filter((e) => cg.getNode(e.target)!.filePath === 'src/helper.ts' && cg.getNode(e.target)!.kind === 'file');
|
||
expect(toHelper.length).toBeLessThanOrEqual(1);
|
||
});
|
||
});
|