diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 74abd84b43665..cc773c39f4bae 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -168,10 +168,10 @@ namespace ts { const originalDirectoryExists = host.directoryExists; const originalCreateDirectory = host.createDirectory; const originalWriteFile = host.writeFile; - const readFileCache = new Map(); - const fileExistsCache = new Map(); - const directoryExistsCache = new Map(); - const sourceFileCache = new Map(); + const readFileCache = new Map(); + const fileExistsCache = new Map(); + const directoryExistsCache = new Map(); + const sourceFileCache = new Map>(); const readFileWithCache = (fileName: string): string | undefined => { const key = toPath(fileName); @@ -196,14 +196,16 @@ namespace ts { return setReadFileCache(key, fileName); }; - const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { + const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => { const key = toPath(fileName); - const value = sourceFileCache.get(key); + const impliedNodeFormat: SourceFile["impliedNodeFormat"] = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions.impliedNodeFormat : undefined; + const forImpliedNodeFormat = sourceFileCache.get(impliedNodeFormat); + const value = forImpliedNodeFormat?.get(key); if (value) return value; - const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); + const sourceFile = getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile); if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { - sourceFileCache.set(key, sourceFile); + sourceFileCache.set(impliedNodeFormat, (forImpliedNodeFormat || new Map()).set(key, sourceFile)); } return sourceFile; } : undefined; @@ -225,13 +227,15 @@ namespace ts { const value = readFileCache.get(key); if (value !== undefined && value !== data) { readFileCache.delete(key); - sourceFileCache.delete(key); + sourceFileCache.forEach(map => map.delete(key)); } else if (getSourceFileWithCache) { - const sourceFile = sourceFileCache.get(key); - if (sourceFile && sourceFile.text !== data) { - sourceFileCache.delete(key); - } + sourceFileCache.forEach(map => { + const sourceFile = map.get(key); + if (sourceFile && sourceFile.text !== data) { + map.delete(key); + } + }); } originalWriteFile.call(host, fileName, data, ...rest); }; diff --git a/src/harness/fakesHosts.ts b/src/harness/fakesHosts.ts index 1868561a34ccc..17fbc39833994 100644 --- a/src/harness/fakesHosts.ts +++ b/src/harness/fakesHosts.ts @@ -41,6 +41,7 @@ namespace fakes { } public write(message: string) { + if (ts.Debug.isDebugging) console.log(message); this.output.push(message); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 5c1d2cea88883..f5979f9c3876d 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -985,6 +985,7 @@ interface Array { length: number; [n: number]: T; }` } write(message: string) { + if (Debug.isDebugging) console.log(message); this.output.push(message); } diff --git a/src/testRunner/unittests/tsbuild/moduleResolution.ts b/src/testRunner/unittests/tsbuild/moduleResolution.ts index 487f83196c36e..772a30262a042 100644 --- a/src/testRunner/unittests/tsbuild/moduleResolution.ts +++ b/src/testRunner/unittests/tsbuild/moduleResolution.ts @@ -86,4 +86,36 @@ namespace ts.tscWatch { commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"], }); }); -} + + describe("unittests:: tsbuild:: moduleResolution:: impliedNodeFormat differs between projects for shared file", () => { + verifyTscWithEdits({ + scenario: "moduleResolution", + subScenario: "impliedNodeFormat differs between projects for shared file", + fs: () => loadProjectFromFiles({ + "/src/projects/a/src/index.ts": "", + "/src/projects/a/tsconfig.json": JSON.stringify({ + compilerOptions: { strict: true } + }), + "/src/projects/b/src/index.ts": Utils.dedent` + import pg from "pg"; + pg.foo(); + `, + "/src/projects/b/tsconfig.json": JSON.stringify({ + compilerOptions: { strict: true, module: "node16" } + }), + "/src/projects/b/package.json": JSON.stringify({ + name: "b", + type: "module" + }), + "/src/projects/node_modules/@types/pg/index.d.ts": "export function foo(): void;", + "/src/projects/node_modules/@types/pg/package.json": JSON.stringify({ + name: "@types/pg", + types: "index.d.ts", + }), + }), + modifyFs: fs => fs.writeFileSync("/lib/lib.es2022.full.d.ts", libFile.content), + commandLineArgs: ["-b", "/src/projects/a", "/src/projects/b", "--verbose", "--traceResolution", "--explainFiles"], + edits: noChangeOnlyRuns + }); + }); +} \ No newline at end of file diff --git a/tests/baselines/reference/tsbuild/moduleResolution/impliedNodeFormat-differs-between-projects-for-shared-file.js b/tests/baselines/reference/tsbuild/moduleResolution/impliedNodeFormat-differs-between-projects-for-shared-file.js new file mode 100644 index 0000000000000..a566a262e4d7a --- /dev/null +++ b/tests/baselines/reference/tsbuild/moduleResolution/impliedNodeFormat-differs-between-projects-for-shared-file.js @@ -0,0 +1,151 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/lib/lib.es2022.full.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/src/projects/a/src/index.ts] + + +//// [/src/projects/a/tsconfig.json] +{"compilerOptions":{"strict":true}} + +//// [/src/projects/b/package.json] +{"name":"b","type":"module"} + +//// [/src/projects/b/src/index.ts] +import pg from "pg"; +pg.foo(); + + +//// [/src/projects/b/tsconfig.json] +{"compilerOptions":{"strict":true,"module":"node16"}} + +//// [/src/projects/node_modules/@types/pg/index.d.ts] +export function foo(): void; + +//// [/src/projects/node_modules/@types/pg/package.json] +{"name":"@types/pg","types":"index.d.ts"} + + + +Output:: +/lib/tsc -b /src/projects/a /src/projects/b --verbose --traceResolution --explainFiles +[12:00:22 AM] Projects in this build: + * src/projects/a/tsconfig.json + * src/projects/b/tsconfig.json + +[12:00:23 AM] Project 'src/projects/a/tsconfig.json' is out of date because output file 'src/projects/a/src/index.js' does not exist + +[12:00:24 AM] Building project '/src/projects/a/tsconfig.json'... + +======== Resolving type reference directive 'pg', containing file '/src/projects/a/__inferred type names__.ts', root directory '/src/projects/node_modules/@types'. ======== +Resolving with primary search path '/src/projects/node_modules/@types'. +Found 'package.json' at '/src/projects/node_modules/@types/pg/package.json'. +'package.json' does not have a 'typesVersions' field. +'package.json' does not have a 'typings' field. +'package.json' has 'types' field 'index.d.ts' that references '/src/projects/node_modules/@types/pg/index.d.ts'. +File '/src/projects/node_modules/@types/pg/index.d.ts' exist - use it as a name resolution result. +Resolving real path for '/src/projects/node_modules/@types/pg/index.d.ts', result '/src/projects/node_modules/@types/pg/index.d.ts'. +======== Type reference directive 'pg' was successfully resolved to '/src/projects/node_modules/@types/pg/index.d.ts', primary: true. ======== +lib/lib.d.ts + Default library for target 'es3' +src/projects/a/src/index.ts + Matched by default include pattern '**/*' +src/projects/node_modules/@types/pg/index.d.ts + Entry point for implicit type library 'pg' +[12:00:26 AM] Project 'src/projects/b/tsconfig.json' is out of date because output file 'src/projects/b/src/index.js' does not exist + +[12:00:27 AM] Building project '/src/projects/b/tsconfig.json'... + +File '/src/projects/b/src/package.json' does not exist. +Found 'package.json' at '/src/projects/b/package.json'. +'package.json' does not have a 'typesVersions' field. +======== Resolving module 'pg' from '/src/projects/b/src/index.ts'. ======== +Module resolution kind is not specified, using 'Node16'. +Resolving in ESM mode with conditions 'node', 'import', 'types'. +File '/src/projects/b/src/package.json' does not exist according to earlier cached lookups. +File '/src/projects/b/package.json' exists according to earlier cached lookups. +Loading module 'pg' from 'node_modules' folder, target file type 'TypeScript'. +Directory '/src/projects/b/src/node_modules' does not exist, skipping all lookups in it. +Directory '/src/projects/b/node_modules' does not exist, skipping all lookups in it. +File '/src/projects/node_modules/@types/pg/package.json' exists according to earlier cached lookups. +'package.json' does not have a 'typings' field. +'package.json' has 'types' field 'index.d.ts' that references '/src/projects/node_modules/@types/pg/index.d.ts'. +File '/src/projects/node_modules/@types/pg/index.d.ts' exist - use it as a name resolution result. +Resolving real path for '/src/projects/node_modules/@types/pg/index.d.ts', result '/src/projects/node_modules/@types/pg/index.d.ts'. +======== Module name 'pg' was successfully resolved to '/src/projects/node_modules/@types/pg/index.d.ts'. ======== +File '/src/projects/node_modules/@types/pg/package.json' exists according to earlier cached lookups. +======== Resolving type reference directive 'pg', containing file '/src/projects/b/__inferred type names__.ts', root directory '/src/projects/node_modules/@types'. ======== +Resolving with primary search path '/src/projects/node_modules/@types'. +File '/src/projects/node_modules/@types/pg/package.json' exists according to earlier cached lookups. +'package.json' does not have a 'typings' field. +'package.json' has 'types' field 'index.d.ts' that references '/src/projects/node_modules/@types/pg/index.d.ts'. +File '/src/projects/node_modules/@types/pg/index.d.ts' exist - use it as a name resolution result. +Resolving real path for '/src/projects/node_modules/@types/pg/index.d.ts', result '/src/projects/node_modules/@types/pg/index.d.ts'. +======== Type reference directive 'pg' was successfully resolved to '/src/projects/node_modules/@types/pg/index.d.ts', primary: true. ======== +File '/lib/package.json' does not exist. +File '/package.json' does not exist. +lib/lib.es2022.full.d.ts + Default library for target 'es2022' +src/projects/node_modules/@types/pg/index.d.ts + Imported via "pg" from file 'src/projects/b/src/index.ts' + Entry point for implicit type library 'pg' + File is CommonJS module because 'src/projects/node_modules/@types/pg/package.json' does not have field "type" +src/projects/b/src/index.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'src/projects/b/package.json' has field "type" with value "module" +exitCode:: ExitStatus.Success + + +//// [/src/projects/a/src/index.js] +"use strict"; + + +//// [/src/projects/b/src/index.js] +import pg from "pg"; +pg.foo(); + + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc -b /src/projects/a /src/projects/b --verbose --traceResolution --explainFiles +[12:00:29 AM] Projects in this build: + * src/projects/a/tsconfig.json + * src/projects/b/tsconfig.json + +[12:00:30 AM] Project 'src/projects/a/tsconfig.json' is up to date because newest input 'src/projects/a/src/index.ts' is older than output 'src/projects/a/src/index.js' + +[12:00:31 AM] Project 'src/projects/b/tsconfig.json' is up to date because newest input 'src/projects/b/src/index.ts' is older than output 'src/projects/b/src/index.js' + +exitCode:: ExitStatus.Success + +