Files
codegraph/__tests__/nestjs-provider-same-file.test.ts
Colby Mchenry c38aa9c973 fix(nestjs): the provider convention stays in the reference's feature (#2246)
In a TS/JS module the *Service/*Controller convention picks the file's own
class or the convention file beside it (users.controller.ts → users.service.ts),
never a same-named class elsewhere preferred for its file name; anything
farther is the import resolver's or name matching's.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:15:09 +00:00

55 lines
2.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The NestJS provider convention (`*Service`, `*Controller`, …) picks a
* class by name only from the reference's own file in a TS/JS module —
* another file's comes through an import, the import resolver's. Its
* file-name preference (`*.service.ts`) beat name matching's proximity:
* Nest's own specs' `TransientService` / `TestService` went to same-named
* classes in unrelated integration apps.
*/
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-nest-provider-'));
const files: Record<string, string> = {
'package.json': JSON.stringify({ name: 'nest', private: true, dependencies: { '@nestjs/common': '^10.0.0' } }),
'integration/injector/src/app.module.ts': `import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}
`,
'integration/injector/src/scoped/transient.service.ts': `export class TransientService {}
`,
'packages/core/test/scope/helpers.ts': `export class TransientService {}
`,
'packages/core/test/scope/transient-scope.spec.ts': `class Holder {
constructor(public transient: TransientService) {}
}
`,
};
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 });
});
describe('NestJS provider names', () => {
it('never prefer another file’s class for its file name over a nearer one', () => {
const ids = cg.getNodesInFile('packages/core/test/scope/transient-scope.spec.ts').map((n) => n.id);
const targets = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'references').map((e) => cg.getNode(e.target)!.filePath);
expect(targets).not.toContain('integration/injector/src/scoped/transient.service.ts');
});
});