Files
codegraph/__tests__/commonjs-require-calls.test.ts
Colby Mchenry e77ca83621 fix(js): resolve calls through CommonJS require bindings (#2189)
Four gaps left CommonJS calls on the local alias variable - or on the wrong
function:

- `require('../')` up to the project root produced an empty relative path, so
  its `/index.js` candidate was never found;
- a default import never followed `module.exports = X` /
  `exports = module.exports = X` / `module.exports = require('./lib/x')`;
- only the first declarator of `var a = require('x'), b = require('y')` was a
  binding, so `b()` - a package call - stayed on the local variable;
- `require('./utils').member` was read as the module's DEFAULT export: every
  such alias in express resolved to utils' first exported function
  (`normalizeType`). It is now the named member, and `exports.name =
  function impl(...)`, `module.exports.name = impl` and `module.exports = { a,
  b: c }` bind names the export index could not see.

express -1087 +727 (707 `express()` calls reach createApplication; supertest /
node:assert calls no longer edge to the local variable), eslint -750 +827
(`new RuleTester()` reaches the class, not another test's variable), mocha
-83 +90 (`run(...)` no longer lands in another spec file), koa 65 wrong
`request` edges -> `response`, body-parser +20. zod, trpc, hono, typeorm,
axios unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:49:15 +00:00

105 lines
3.1 KiB
TypeScript

/**
* Calls through CommonJS `require` bindings (express's shape):
* - `var express = require('../')` from `test/` is the project root's
* `index.js`, whose `module.exports = require('./lib/express')` forwards to
* `exports = module.exports = createApplication` — `express()` is that call;
* - each declarator of a list is a binding: `var express = require('../'),
* request = require('supertest')` — `request(app)` is the package's, no edge;
* - `require('./utils').setCharset` is the module's `setCharset`, written
* `exports.setCharset = function setCharset(…)`, not the module's first
* export (every such alias in express went to `normalizeType`).
*/
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', devDependencies: { supertest: '^6' } }),
'index.js': `'use strict';
module.exports = require('./lib/express');
`,
'lib/express.js': `'use strict';
exports = module.exports = createApplication;
function createApplication() {
return {};
}
`,
'lib/utils.js': `'use strict';
exports.normalizeType = function (type) {
return type;
};
exports.setCharset = function setCharset(type, charset) {
return type + charset;
};
`,
'lib/response.js': `'use strict';
var normalizeType = require('./utils').normalizeType;
var setCharset = require('./utils').setCharset;
function send(body) {
return setCharset(normalizeType(body), 'utf-8');
}
module.exports = { send };
`,
'test/app.js': `'use strict';
var express = require('../')
, request = require('supertest');
describe('app', function () {
it('responds', function () {
var app = express();
request(app);
});
});
`,
};
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 });
});
/** `file: qualified name` of every call edge out of a file. */
function callsFrom(file: string): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => {
const t = cg.getNode(e.target)!;
return `${t.filePath}: ${t.qualifiedName}`;
})
.sort();
}
describe('calls through CommonJS require bindings', () => {
it('follow `module.exports` through the root index to the exported function; an outside package is no edge', () => {
expect(callsFrom('test/app.js')).toEqual(['lib/express.js: createApplication']);
});
it('read a `require(…).member` alias as that member', () => {
expect(callsFrom('lib/response.js')).toEqual(['lib/utils.js: normalizeType', 'lib/utils.js: setCharset']);
});
});