diff --git a/e2e/package-lock.json b/e2e/package-lock.json index e31c5ae3fb..2ed7b5b62a 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -8,6 +8,7 @@ "devDependencies": { "@formatjs/ts-transformer": "^3.13.32", "@testing-library/react": "^14.3.1", + "@types/jest": "^29.5.14", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.5", "esbuild": "~0.25.1", diff --git a/e2e/package.json b/e2e/package.json index 1e0fd09bfd..18a9a7e723 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -4,6 +4,7 @@ "devDependencies": { "@formatjs/ts-transformer": "^3.13.32", "@testing-library/react": "^14.3.1", + "@types/jest": "^29.5.14", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.5", "esbuild": "~0.25.1", diff --git a/e2e/tsconfig-projects/jest-compiler-cjs.config.ts b/e2e/tsconfig-projects/jest-compiler-cjs.config.ts new file mode 100644 index 0000000000..55dc0216b0 --- /dev/null +++ b/e2e/tsconfig-projects/jest-compiler-cjs.config.ts @@ -0,0 +1,8 @@ +import { type JestConfigWithTsJest, TS_JS_TRANSFORM_PATTERN } from 'ts-jest' + +export default { + displayName: 'tsconfig-projects-compiler-cjs', + transform: { + [TS_JS_TRANSFORM_PATTERN]: 'ts-jest', + }, +} satisfies JestConfigWithTsJest diff --git a/e2e/tsconfig-projects/jest-compiler-esm.config.ts b/e2e/tsconfig-projects/jest-compiler-esm.config.ts new file mode 100644 index 0000000000..1e87d33263 --- /dev/null +++ b/e2e/tsconfig-projects/jest-compiler-esm.config.ts @@ -0,0 +1,14 @@ +import { type JestConfigWithTsJest, TS_JS_TRANSFORM_PATTERN } from 'ts-jest' + +export default { + displayName: 'tsconfig-projects-compiler-esm', + extensionsToTreatAsEsm: ['.ts'], + transform: { + [TS_JS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + useESM: true, + }, + ], + }, +} satisfies JestConfigWithTsJest diff --git a/e2e/tsconfig-projects/jest-transpiler-cjs.config.ts b/e2e/tsconfig-projects/jest-transpiler-cjs.config.ts new file mode 100644 index 0000000000..eda29af617 --- /dev/null +++ b/e2e/tsconfig-projects/jest-transpiler-cjs.config.ts @@ -0,0 +1,8 @@ +import { type JestConfigWithTsJest, TS_JS_TRANSFORM_PATTERN } from 'ts-jest' + +export default { + displayName: 'tsconfig-projects-transpiler-cjs', + transform: { + [TS_JS_TRANSFORM_PATTERN]: 'ts-jest', + }, +} satisfies JestConfigWithTsJest diff --git a/e2e/tsconfig-projects/jest-transpiler-esm.config.ts b/e2e/tsconfig-projects/jest-transpiler-esm.config.ts new file mode 100644 index 0000000000..cf72978cdd --- /dev/null +++ b/e2e/tsconfig-projects/jest-transpiler-esm.config.ts @@ -0,0 +1,14 @@ +import { type JestConfigWithTsJest, TS_JS_TRANSFORM_PATTERN } from 'ts-jest' + +export default { + displayName: 'tsconfig-projects-transpiler-esm', + extensionsToTreatAsEsm: ['.ts'], + transform: { + [TS_JS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + useESM: true, + }, + ], + }, +} satisfies JestConfigWithTsJest diff --git a/e2e/tsconfig-projects/package.json b/e2e/tsconfig-projects/package.json new file mode 100644 index 0000000000..1a7271cc79 --- /dev/null +++ b/e2e/tsconfig-projects/package.json @@ -0,0 +1,4 @@ +{ + "name": "tsconfig-projects", + "private": true +} diff --git a/e2e/tsconfig-projects/src/foo.spec.ts b/e2e/tsconfig-projects/src/foo.spec.ts new file mode 100644 index 0000000000..8673761b75 --- /dev/null +++ b/e2e/tsconfig-projects/src/foo.spec.ts @@ -0,0 +1,7 @@ +import { foo } from './foo' + +describe('foo', () => { + it('should return "foo"', () => { + expect(foo()).toEqual('foo') + }) +}) diff --git a/e2e/tsconfig-projects/src/foo.ts b/e2e/tsconfig-projects/src/foo.ts new file mode 100644 index 0000000000..6de1f5a8d6 --- /dev/null +++ b/e2e/tsconfig-projects/src/foo.ts @@ -0,0 +1,3 @@ +export function foo() { + return 'foo' +} diff --git a/e2e/tsconfig-projects/tsconfig.json b/e2e/tsconfig-projects/tsconfig.json new file mode 100644 index 0000000000..754af78189 --- /dev/null +++ b/e2e/tsconfig-projects/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": [], + "references": [ + { "path": "./tsconfig.src.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/e2e/tsconfig-projects/tsconfig.spec.json b/e2e/tsconfig-projects/tsconfig.spec.json new file mode 100644 index 0000000000..578f384ecf --- /dev/null +++ b/e2e/tsconfig-projects/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "module": "Node16", + "typeRoots": ["../node_modules"], + "types": ["@types/jest", "@types/node"], + }, + "include": ["src/**/*.spec.ts"] +} diff --git a/e2e/tsconfig-projects/tsconfig.src.json b/e2e/tsconfig-projects/tsconfig.src.json new file mode 100644 index 0000000000..cfa57a5d0e --- /dev/null +++ b/e2e/tsconfig-projects/tsconfig.src.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "Node16" + }, + "include": ["src"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/monorepo-app/project-1/tsconfig-esm.json b/examples/monorepo-app/project-1/tsconfig-esm.json index e186ef50ee..ae9e2760a0 100644 --- a/examples/monorepo-app/project-1/tsconfig-esm.json +++ b/examples/monorepo-app/project-1/tsconfig-esm.json @@ -1,3 +1,7 @@ { - "extends": "../tsconfig-esm.base.json" + "extends": "../tsconfig-esm.json", + "compilerOptions": { + "types": ["jest"] + }, + "include": ["test"] } diff --git a/examples/monorepo-app/project-1/tsconfig.json b/examples/monorepo-app/project-1/tsconfig.json index 4eb37fee05..d1df2ac7c2 100644 --- a/examples/monorepo-app/project-1/tsconfig.json +++ b/examples/monorepo-app/project-1/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "../tsconfig.base.json" -} + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + }, + "include": ["test"] +} \ No newline at end of file diff --git a/examples/monorepo-app/project-2/tsconfig-esm.json b/examples/monorepo-app/project-2/tsconfig-esm.json index e186ef50ee..ae9e2760a0 100644 --- a/examples/monorepo-app/project-2/tsconfig-esm.json +++ b/examples/monorepo-app/project-2/tsconfig-esm.json @@ -1,3 +1,7 @@ { - "extends": "../tsconfig-esm.base.json" + "extends": "../tsconfig-esm.json", + "compilerOptions": { + "types": ["jest"] + }, + "include": ["test"] } diff --git a/examples/monorepo-app/project-2/tsconfig.json b/examples/monorepo-app/project-2/tsconfig.json index 4eb37fee05..6a0aa61c4d 100644 --- a/examples/monorepo-app/project-2/tsconfig.json +++ b/examples/monorepo-app/project-2/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "../tsconfig.base.json" + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + }, + "include": ["test"] } diff --git a/examples/monorepo-app/shared/tsconfig.json b/examples/monorepo-app/shared/tsconfig.json new file mode 100644 index 0000000000..da0b3122d4 --- /dev/null +++ b/examples/monorepo-app/shared/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["."] +} \ No newline at end of file diff --git a/examples/monorepo-app/tsconfig-esm.base.json b/examples/monorepo-app/tsconfig-esm.json similarity index 59% rename from examples/monorepo-app/tsconfig-esm.base.json rename to examples/monorepo-app/tsconfig-esm.json index e963c9857b..d0c6c10a5b 100644 --- a/examples/monorepo-app/tsconfig-esm.base.json +++ b/examples/monorepo-app/tsconfig-esm.json @@ -1,7 +1,8 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "compilerOptions": { "module": "ESNext", "esModuleInterop": true - } + }, + "include": [], } diff --git a/examples/monorepo-app/tsconfig.base.json b/examples/monorepo-app/tsconfig.base.json deleted file mode 100644 index 4d4d32b47d..0000000000 --- a/examples/monorepo-app/tsconfig.base.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@tsconfig/strictest/tsconfig.json", - "compilerOptions": { - "target": "ESNext", - "module": "Node16", - "lib": ["ESNext", "dom"], - "types": ["jest"], - "isolatedModules": false - } -} diff --git a/examples/monorepo-app/tsconfig.json b/examples/monorepo-app/tsconfig.json new file mode 100644 index 0000000000..e0d827e2e8 --- /dev/null +++ b/examples/monorepo-app/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@tsconfig/strictest/tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "target": "ESNext", + "module": "Node16", + "lib": ["ESNext", "dom"], + "isolatedModules": false + }, + "include": [], + "references": [ + { + "path": "./project-1/tsconfig.json" + }, + { + "path": "./project-2/tsconfig.json" + }, + { + "path": "./shared/tsconfig.json" + } + ] +} diff --git a/src/legacy/config/config-set.spec.ts b/src/legacy/config/config-set.spec.ts index b5bb83df32..c9b5f07d56 100644 --- a/src/legacy/config/config-set.spec.ts +++ b/src/legacy/config/config-set.spec.ts @@ -789,7 +789,6 @@ describe('_resolveTsConfig', () => { expect(conf.options.configFilePath).toBeUndefined() expect(findConfig).not.toHaveBeenCalled() expect(readConfig.mock.calls[0][0]).toBe('/foo/tsconfig.bar.json') - expect(parseConfig).not.toHaveBeenCalled() }) }) @@ -917,7 +916,6 @@ describe('_resolveTsConfig', () => { const conf = cs.parsedTsConfig expect(conf.options.path).toBe(tscfgPathStub) - expect(findConfig).not.toHaveBeenCalled() expect(readConfig.mock.calls[0][0]).toBe(tscfgPathStub) expect(parseConfig.mock.calls[0][2]).toBe('/foo') expect(parseConfig.mock.calls[0][4]).toBe(tscfgPathStub) diff --git a/src/legacy/config/config-set.ts b/src/legacy/config/config-set.ts index 7386aa1709..f543e5811d 100644 --- a/src/legacy/config/config-set.ts +++ b/src/legacy/config/config-set.ts @@ -574,9 +574,7 @@ export class ConfigSet { let basePath = normalizeSlashes(this.rootDir) const ts = this.compilerModule // Read project configuration when available. - this.tsconfigFilePath = resolvedConfigFile - ? normalizeSlashes(resolvedConfigFile) - : ts.findConfigFile(normalizeSlashes(this.rootDir), ts.sys.fileExists) + this.tsconfigFilePath = this._findTsconfigFile(resolvedConfigFile) if (this.tsconfigFilePath) { this.logger.debug({ tsConfigFileName: this.tsconfigFilePath }, 'readTsConfig(): reading', this.tsconfigFilePath) @@ -596,7 +594,56 @@ export class ConfigSet { } // parse json, merge config extending others, ... - return ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, this.tsconfigFilePath) + return this._parseTsconfig(config, basePath, this.tsconfigFilePath) + } + + protected _findTsconfigFile(resolvedConfigFile?: string) { + const ts = this.compilerModule + + const configFileName: string | undefined = resolvedConfigFile + ? normalizeSlashes(resolvedConfigFile) + : ts.findConfigFile(normalizeSlashes(this.rootDir), ts.sys.fileExists) + + const newTsconfigFile = this._findReferenceTsconfig(configFileName) + + return newTsconfigFile ?? configFileName + } + + protected _findReferenceTsconfig(tsconfigFileName?: string): string | undefined { + const ts = this.compilerModule + + if (!tsconfigFileName) return + + const parsedTsconfig = this._parseTsconfig( + ts.readConfigFile(tsconfigFileName, ts.sys.readFile).config || {}, + dirname(tsconfigFileName), + tsconfigFileName, + ) + + if (this._includesTestFilesInConfig(parsedTsconfig)) return tsconfigFileName + + if (parsedTsconfig.projectReferences) { + for (const ref of parsedTsconfig.projectReferences) { + const filePath = ts.resolveProjectReferencePath(ref) + + if (ts.sys.fileExists(filePath)) { + const newTsconfigFileName = this._findReferenceTsconfig(ts.resolveProjectReferencePath(ref)) + if (newTsconfigFileName) return newTsconfigFileName + } + } + } + + return + } + + protected _includesTestFilesInConfig(parsedConfig?: ts.ParsedCommandLine) { + return parsedConfig?.fileNames?.length ? parsedConfig.fileNames.some((path) => this.isTestFile(path)) : false + } + + protected _parseTsconfig(config: unknown, basePath: string, configFileName = this.tsconfigFilePath) { + const ts = this.compilerModule + + return ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName) } isTestFile(fileName: string): boolean {