diff --git a/__tests__/fixtures/a/tsconfig.json b/__tests__/fixtures/a/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/a/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/built-ins/tsconfig.json b/__tests__/fixtures/built-ins/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/built-ins/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/directive/tsconfig.json b/__tests__/fixtures/directive/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/directive/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/entry/tsconfig.json b/__tests__/fixtures/entry/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/entry/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/feature-storybook-ref/tsconfig.json b/__tests__/fixtures/feature-storybook-ref/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/feature-storybook-ref/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/framework/tsconfig.json b/__tests__/fixtures/framework/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/framework/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/modules/tsconfig.json b/__tests__/fixtures/modules/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/modules/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/snapshots/tsconfig.json b/__tests__/fixtures/snapshots/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/snapshots/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/tsconfigs/decls/one.d.ts b/__tests__/fixtures/tsconfigs/decls/one.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/decls/tsconfig.json b/__tests__/fixtures/tsconfigs/decls/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/tsconfigs/decls/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/tsconfigs/decls/two.d.ts b/__tests__/fixtures/tsconfigs/decls/two.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/extends/extends.ts b/__tests__/fixtures/tsconfigs/extends/extends.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/extends/tsconfig.json b/__tests__/fixtures/tsconfigs/extends/tsconfig.json new file mode 100644 index 0000000..3b5fde2 --- /dev/null +++ b/__tests__/fixtures/tsconfigs/extends/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../project/tsconfig.json", + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/tsconfigs/glob/glob.ts b/__tests__/fixtures/tsconfigs/glob/glob.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/glob/tsconfig.json b/__tests__/fixtures/tsconfigs/glob/tsconfig.json new file mode 100644 index 0000000..10ec1eb --- /dev/null +++ b/__tests__/fixtures/tsconfigs/glob/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["../decls/*.d.ts", "./**/*.*"] +} diff --git a/__tests__/fixtures/tsconfigs/mixed/decl.d.ts b/__tests__/fixtures/tsconfigs/mixed/decl.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/mixed/source.ts b/__tests__/fixtures/tsconfigs/mixed/source.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/mixed/tsconfig.json b/__tests__/fixtures/tsconfigs/mixed/tsconfig.json new file mode 100644 index 0000000..1ab6580 --- /dev/null +++ b/__tests__/fixtures/tsconfigs/mixed/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["./**/*.*"] +} diff --git a/__tests__/fixtures/tsconfigs/project/nested/nested.ts b/__tests__/fixtures/tsconfigs/project/nested/nested.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/project/project.ts b/__tests__/fixtures/tsconfigs/project/project.ts new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/tsconfigs/project/tsconfig.json b/__tests__/fixtures/tsconfigs/project/tsconfig.json new file mode 100644 index 0000000..4aee49e --- /dev/null +++ b/__tests__/fixtures/tsconfigs/project/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["../decls/one.d.ts", "./**/*.*"] +} diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index d1b3e63..eb2388d 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -499,4 +499,39 @@ describe('dependencyTree', () => { ).rejects.toThrowErrorMatchingSnapshot(); }); }); + + describe('tsconfig', () => { + beforeEach(() => { + dependencyTree = new DependencyTree([fixture('tsconfigs')]); + }); + + it('adds files in `include` from nearest tsconfig.json', async () => { + expect.hasAssertions(); + const result = await dependencyTree.gather(); + const decl1 = fixture('tsconfigs', 'decls', 'one.d.ts'); + const decl2 = fixture('tsconfigs', 'decls', 'two.d.ts'); + expect(result).toStrictEqual({ + missing: new Map(), + resolved: new Map([ + [fixture('tsconfigs', 'project', 'project.ts'), new Set([decl1])], + [ + fixture('tsconfigs', 'project', 'nested', 'nested.ts'), + new Set([decl1]), + ], + [fixture('tsconfigs', 'glob', 'glob.ts'), new Set([decl1, decl2])], + // "includes" is overwritten in the child config + [fixture('tsconfigs', 'extends', 'extends.ts'), new Set([])], + // decls in the same dir that includes './**.*.*' depend on one another + [fixture('tsconfigs', 'decls', 'one.d.ts'), new Set([decl2])], + [fixture('tsconfigs', 'decls', 'two.d.ts'), new Set([decl1])], + // files can depend on decls in the same dir + [ + fixture('tsconfigs', 'mixed', 'source.ts'), + new Set([fixture('tsconfigs', 'mixed', 'decl.d.ts')]), + ], + [fixture('tsconfigs', 'mixed', 'decl.d.ts'), new Set([])], + ]), + }); + }); + }); }); diff --git a/src/processors/typescript.ts b/src/processors/typescript.ts index b5c7296..f45d3d8 100644 --- a/src/processors/typescript.ts +++ b/src/processors/typescript.ts @@ -104,6 +104,16 @@ export class TypeScriptFileProcessor implements FileProcessor { dependencyTree: DependencyTree, ): Promise> { const importedFiles = new Set(); + + // TypeScript files can *implicitly* depend on .d.ts files. We discover + // these files by extracting them from the nearest tsconfig.json file. + // These do not need to be processed further since they have already been fully + // resolved by the typescript compiler. + const includes = await this.includesFromNearestTsconfigFile(file); + for (const include of includes) { + importedFiles.add(include); + } + const filesList = ts .preProcessFile(contents, true, true) .importedFiles.map(({ fileName }) => fileName); @@ -189,6 +199,51 @@ export class TypeScriptFileProcessor implements FileProcessor { }, ); + private async includesFromNearestTsconfigFile(file: Path): Promise { + const tsconfigPath = ts.findConfigFile( + path.dirname(file), + ts.sys.fileExists, + ); + if (!tsconfigPath) { + throw new Error( + `could not find a tsconfig file for '${path.relative( + this.rootDir, + file, + )}'`, + ); + } + if (!tsconfigPath.startsWith(this.rootDir)) { + throw new Error( + `invalid tsconfig file '${path.relative( + this.rootDir, + tsconfigPath, + )}' found for '${path.relative( + this.rootDir, + file, + )}'. expected a tsconfig file inside the project root '${ + this.rootDir + }'`, + ); + } + + const json = ts.parseJsonText( + tsconfigPath, + await fs.promises.readFile(tsconfigPath, 'utf-8'), + ); + const parsed = ts.parseJsonSourceFileConfigFileContent( + json, + ts.sys, + path.dirname(tsconfigPath), + ); + return parsed.fileNames.filter( + (fileName) => + // only include .d.ts files. all other references should be made using `import` or `require`. + fileName.endsWith('.d.ts') && + // files do not depend on themselves + fileName !== file, + ); + } + // Finds an implicit 'import' in the entry point object literal, like: // // export const entryPoint: DynamicEntryPoint = {