|
1 | 1 | import fs from 'node:fs'
|
| 2 | +import path from 'node:path' |
2 | 3 | import { setTimeout } from 'node:timers/promises'
|
3 | 4 |
|
4 | 5 | import { jest } from '@jest/globals'
|
5 | 6 | import * as getTsconfig from 'get-tsconfig'
|
6 | 7 |
|
7 |
| -import { TEST_FILENAME, testFilePath } from '../utils.js' |
| 8 | +import { |
| 9 | + FIXTURES_PATH, |
| 10 | + TEST_FILENAME, |
| 11 | + testContext, |
| 12 | + testFilePath, |
| 13 | +} from '../utils.js' |
8 | 14 |
|
9 | 15 | import type { ChildContext, RuleContext } from 'eslint-plugin-import-x'
|
10 | 16 | import {
|
11 | 17 | ExportMap,
|
12 | 18 | isMaybeUnambiguousModule,
|
13 | 19 | } from 'eslint-plugin-import-x/utils'
|
| 20 | +import type { getTsconfigWithContext as getTsconfigWithContext_ } from 'eslint-plugin-import-x/utils' |
14 | 21 |
|
15 | 22 | function jsdocTests(parseContext: ChildContext, lineEnding: string) {
|
16 | 23 | describe('deprecated imports', () => {
|
@@ -110,12 +117,18 @@ function jsdocTests(parseContext: ChildContext, lineEnding: string) {
|
110 | 117 | })
|
111 | 118 | }
|
112 | 119 |
|
| 120 | +const createContext = ( |
| 121 | + parserOptions: RuleContext['languageOptions']['parserOptions'] = {}, |
| 122 | +): RuleContext => ({ |
| 123 | + ...testContext(), |
| 124 | + parserOptions, |
| 125 | +}) |
| 126 | + |
113 | 127 | describe('ExportMap', () => {
|
114 | 128 | const fakeContext = {
|
115 |
| - physicalFilename: TEST_FILENAME, |
116 |
| - settings: {}, |
| 129 | + ...testContext(), |
117 | 130 | parserPath: '@babel/eslint-parser',
|
118 |
| - } as RuleContext |
| 131 | + } |
119 | 132 |
|
120 | 133 | it('handles ExportAllDeclaration', () => {
|
121 | 134 | const imports = ExportMap.get('./export-all', fakeContext)!
|
@@ -514,4 +527,104 @@ describe('ExportMap', () => {
|
514 | 527 | })
|
515 | 528 | }
|
516 | 529 | })
|
| 530 | + |
| 531 | + describe('getTsconfigWithContext', () => { |
| 532 | + let spied: jest.Mock |
| 533 | + |
| 534 | + let getTsconfigWithContext: typeof getTsconfigWithContext_ |
| 535 | + |
| 536 | + const mockTsConfig: getTsconfig.TsConfigJsonResolved = { |
| 537 | + compilerOptions: { esModuleInterop: true }, |
| 538 | + } |
| 539 | + |
| 540 | + beforeEach(async () => { |
| 541 | + jest.resetModules() |
| 542 | + spied = jest.fn().mockReturnValue({ config: mockTsConfig }) |
| 543 | + jest.unstable_mockModule('get-tsconfig', () => ({ |
| 544 | + ...getTsconfig, |
| 545 | + getTsconfig: spied, |
| 546 | + })) |
| 547 | + ;({ getTsconfigWithContext } = await import( |
| 548 | + 'eslint-plugin-import-x/utils' |
| 549 | + )) |
| 550 | + }) |
| 551 | + |
| 552 | + afterAll(() => { |
| 553 | + spied.mockRestore() |
| 554 | + }) |
| 555 | + |
| 556 | + test('caches and returns the result for the same context', () => { |
| 557 | + // First call should use getTsconfig |
| 558 | + const result1 = getTsconfigWithContext(fakeContext) |
| 559 | + expect(spied).toHaveBeenCalledTimes(1) |
| 560 | + expect(result1).toBe(mockTsConfig) |
| 561 | + |
| 562 | + // Second call should use the cache |
| 563 | + const result2 = getTsconfigWithContext(fakeContext) |
| 564 | + expect(spied).toHaveBeenCalledTimes(1) // Still 1 |
| 565 | + expect(result2).toBe(mockTsConfig) |
| 566 | + }) |
| 567 | + |
| 568 | + // TODO: enable in next major |
| 569 | + test.skip('falls back to cwd when tsconfigRootDir is not provided', () => { |
| 570 | + getTsconfigWithContext(fakeContext) |
| 571 | + expect(spied).toHaveBeenCalledWith(FIXTURES_PATH) |
| 572 | + }) |
| 573 | + |
| 574 | + test('uses tsconfigRootDir when provided', () => { |
| 575 | + const tsconfigRootDir = '/custom/root/dir' |
| 576 | + getTsconfigWithContext(createContext({ tsconfigRootDir })) |
| 577 | + expect(spied).toHaveBeenCalledWith(tsconfigRootDir) |
| 578 | + }) |
| 579 | + |
| 580 | + test('resolves single project string path', () => { |
| 581 | + const tsconfigRootDir = '/custom/root/dir' |
| 582 | + const project = 'tsconfig.custom.json' |
| 583 | + getTsconfigWithContext(createContext({ tsconfigRootDir, project })) |
| 584 | + expect(spied).toHaveBeenCalledWith(path.resolve(tsconfigRootDir, project)) |
| 585 | + }) |
| 586 | + |
| 587 | + test('uses physicalFilename when project is true', () => { |
| 588 | + const context = createContext({ project: true }) |
| 589 | + getTsconfigWithContext(context) |
| 590 | + expect(spied).toHaveBeenCalledWith(TEST_FILENAME) |
| 591 | + }) |
| 592 | + |
| 593 | + test('tries multiple projects until finding a valid one', () => { |
| 594 | + const tsconfigRootDir = '/custom/root/dir' |
| 595 | + const projects = ['invalid.json', 'also-invalid.json', 'valid.json'] |
| 596 | + const context = createContext({ tsconfigRootDir, project: projects }) |
| 597 | + |
| 598 | + // Mock first two calls to return null (not found) |
| 599 | + spied |
| 600 | + .mockReturnValueOnce(null) |
| 601 | + .mockReturnValueOnce(null) |
| 602 | + .mockReturnValueOnce({ config: mockTsConfig }) |
| 603 | + |
| 604 | + const result = getTsconfigWithContext(context) |
| 605 | + |
| 606 | + // Should have tried all three paths |
| 607 | + expect(spied).toHaveBeenNthCalledWith( |
| 608 | + 1, |
| 609 | + path.resolve(tsconfigRootDir, projects[0]), |
| 610 | + ) |
| 611 | + expect(spied).toHaveBeenNthCalledWith( |
| 612 | + 2, |
| 613 | + path.resolve(tsconfigRootDir, projects[1]), |
| 614 | + ) |
| 615 | + expect(spied).toHaveBeenNthCalledWith( |
| 616 | + 3, |
| 617 | + path.resolve(tsconfigRootDir, projects[2]), |
| 618 | + ) |
| 619 | + |
| 620 | + expect(result).toBe(mockTsConfig) |
| 621 | + }) |
| 622 | + |
| 623 | + test('returns undefined when no tsconfig is found', () => { |
| 624 | + // Mock getTsconfig to return null (not found) |
| 625 | + spied.mockReturnValue(null) |
| 626 | + const result = getTsconfigWithContext(fakeContext) |
| 627 | + expect(result).toBeUndefined() |
| 628 | + }) |
| 629 | + }) |
517 | 630 | })
|
0 commit comments