diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 3c65e4410bdbd..9e3b15f09580c 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -2227,7 +2227,7 @@ namespace ts { function toAbsolutePath(path: string | undefined): string | undefined; function toAbsolutePath(path: string | undefined): string | undefined { if (path === undefined) return path; - return hostGetCanonicalFileName({ useCaseSensitiveFileNames })(getNormalizedAbsolutePath(path, state.host.getCurrentDirectory?.())); + return getNormalizedAbsolutePath(path, state.host.getCurrentDirectory?.()); } function combineDirectoryPath(root: string, dir: string) { @@ -2249,7 +2249,7 @@ namespace ts { if ((extensions === Extensions.TypeScript || extensions === Extensions.JavaScript || extensions === Extensions.Json) && (state.compilerOptions.declarationDir || state.compilerOptions.outDir) && finalPath.indexOf("/node_modules/") === -1 - && (state.compilerOptions.configFile ? startsWith(toAbsolutePath(state.compilerOptions.configFile.fileName), scope.packageDirectory) : true) + && (state.compilerOptions.configFile ? containsPath(scope.packageDirectory, toAbsolutePath(state.compilerOptions.configFile.fileName), !useCaseSensitiveFileNames()) : true) ) { // So that all means we'll only try these guesses for files outside `node_modules` in a directory where the `package.json` and `tsconfig.json` are siblings. // Even with all that, we still don't know if the root of the output file structure will be (relative to the package file) @@ -2310,7 +2310,7 @@ namespace ts { for (const commonSourceDirGuess of commonSourceDirGuesses) { const candidateDirectories = getOutputDirectoriesForBaseDirectory(commonSourceDirGuess); for (const candidateDir of candidateDirectories) { - if (startsWith(finalPath, candidateDir)) { + if (containsPath(candidateDir, finalPath, !useCaseSensitiveFileNames())) { // The matched export is looking up something in either the out declaration or js dir, now map the written path back into the source dir and source extension const pathFragment = finalPath.slice(candidateDir.length + 1); // +1 to also remove directory seperator const possibleInputBase = combinePaths(commonSourceDirGuess, pathFragment); diff --git a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts index 69ad855b626df..584b32c33535c 100644 --- a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts @@ -262,5 +262,69 @@ a;b; verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`); verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`); verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`); + + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "with nodeNext resolution", + commandLineArgs: ["--w", "--explainFiles"], + sys: () => createWatchedSystem({ + "/Users/name/projects/web/src/bin.ts": `import { foo } from "yargs";`, + "/Users/name/projects/web/node_modules/@types/yargs/index.d.ts": "export function foo(): void;", + "/Users/name/projects/web/node_modules/@types/yargs/index.d.mts": "export function foo(): void;", + "/Users/name/projects/web/node_modules/@types/yargs/package.json": JSON.stringify({ + name: "yargs", + version: "17.0.12", + exports: { + ".": { + types: { + import: "./index.d.mts", + default: "./index.d.ts" + } + }, + } + }), + "/Users/name/projects/web/tsconfig.json": JSON.stringify({ + compilerOptions: { + moduleResolution: "nodenext", + forceConsistentCasingInFileNames: true, + traceResolution: true, + } + }), + [libFile.path]: libFile.content, + }, { currentDirectory: "/Users/name/projects/web" }), + changes: emptyArray, + }); + + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "self name package reference", + commandLineArgs: ["-w", "--explainFiles"], + sys: () => createWatchedSystem({ + "/Users/name/projects/web/package.json": JSON.stringify({ + name: "@this/package", + type: "module", + exports: { + ".": "./dist/index.js" + } + }), + "/Users/name/projects/web/index.ts": Utils.dedent` + import * as me from "@this/package"; + me.thing(); + export function thing(): void {} + `, + "/Users/name/projects/web/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "nodenext", + outDir: "./dist", + declarationDir: "./types", + composite: true, + forceConsistentCasingInFileNames: true, + traceResolution: true, + } + }), + "/a/lib/lib.esnext.full.d.ts": libFile.content, + }, { currentDirectory: "/Users/name/projects/web" }), + changes: emptyArray, + }); }); } diff --git a/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/self-name-package-reference.js b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/self-name-package-reference.js new file mode 100644 index 0000000000000..02b1358e97ad7 --- /dev/null +++ b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/self-name-package-reference.js @@ -0,0 +1,148 @@ +Input:: +//// [/Users/name/projects/web/package.json] +{"name":"@this/package","type":"module","exports":{".":"./dist/index.js"}} + +//// [/Users/name/projects/web/index.ts] +import * as me from "@this/package"; +me.thing(); +export function thing(): void {} + + +//// [/Users/name/projects/web/tsconfig.json] +{"compilerOptions":{"module":"nodenext","outDir":"./dist","declarationDir":"./types","composite":true,"forceConsistentCasingInFileNames":true,"traceResolution":true}} + +//// [/a/lib/lib.esnext.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; } + + +/a/lib/tsc.js -w --explainFiles +Output:: +>> Screen clear +[12:00:23 AM] Starting compilation in watch mode... + +Found 'package.json' at '/users/name/projects/web/package.json'. +'package.json' does not have a 'typesVersions' field. +======== Resolving module '@this/package' from '/Users/name/projects/web/index.ts'. ======== +Module resolution kind is not specified, using 'NodeNext'. +File '/users/name/projects/web/package.json' exists according to earlier cached lookups. +File '/Users/name/projects/web/index.ts' exist - use it as a name resolution result. +Resolving real path for '/Users/name/projects/web/index.ts', result '/Users/name/projects/web/index.ts'. +======== Module name '@this/package' was successfully resolved to '/Users/name/projects/web/index.ts'. ======== +File '/a/lib/package.json' does not exist. +File '/a/package.json' does not exist. +File '/package.json' does not exist. +../../../../a/lib/lib.esnext.full.d.ts + Default library for target 'esnext' +index.ts + Matched by default include pattern '**/*' + Imported via "@this/package" from file 'index.ts' + File is ECMAScript module because 'package.json' has field "type" with value "module" +[12:00:36 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/Users/name/projects/web/index.ts"] +Program options: {"module":199,"outDir":"/Users/name/projects/web/dist","declarationDir":"/Users/name/projects/web/types","composite":true,"forceConsistentCasingInFileNames":true,"traceResolution":true,"watch":true,"explainFiles":true,"configFilePath":"/Users/name/projects/web/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.esnext.full.d.ts +/Users/name/projects/web/index.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.esnext.full.d.ts +/Users/name/projects/web/index.ts + +Shape signatures in builder refreshed for:: +/a/lib/lib.esnext.full.d.ts (used version) +/users/name/projects/web/index.ts (computed .d.ts during emit) + +WatchedFiles:: +/users/name/projects/web/tsconfig.json: + {"fileName":"/Users/name/projects/web/tsconfig.json","pollingInterval":250} +/users/name/projects/web/index.ts: + {"fileName":"/Users/name/projects/web/index.ts","pollingInterval":250} +/a/lib/lib.esnext.full.d.ts: + {"fileName":"/a/lib/lib.esnext.full.d.ts","pollingInterval":250} +/users/name/projects/web/package.json: + {"fileName":"/Users/name/projects/web/package.json","pollingInterval":250} +/users/name/projects/web/node_modules/@types: + {"fileName":"/Users/name/projects/web/node_modules/@types","pollingInterval":500} + +FsWatches:: + +FsWatchesRecursive:: +/users/name/projects/web: + {"directoryName":"/users/name/projects/web"} + +exitCode:: ExitStatus.undefined + +//// [/Users/name/projects/web/dist/index.js] +import * as me from "@this/package"; +me.thing(); +export function thing() { } + + +//// [/Users/name/projects/web/types/index.d.ts] +export declare function thing(): void; + + +//// [/Users/name/projects/web/dist/tsconfig.tsbuildinfo] +{"program":{"fileNames":["../../../../../a/lib/lib.esnext.full.d.ts","../index.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true,"impliedFormat":1},{"version":"14361483761-import * as me from \"@this/package\";\nme.thing();\nexport function thing(): void {}\n","signature":"-2724770439-export declare function thing(): void;\n","impliedFormat":99}],"options":{"composite":true,"declarationDir":"../types","module":199,"outDir":"./"},"fileIdsList":[[2]],"referencedMap":[[2,1]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"../types/index.d.ts"},"version":"FakeTSVersion"} + +//// [/Users/name/projects/web/dist/tsconfig.tsbuildinfo.readable.baseline.txt] +{ + "program": { + "fileNames": [ + "../../../../../a/lib/lib.esnext.full.d.ts", + "../index.ts" + ], + "fileNamesList": [ + [ + "../index.ts" + ] + ], + "fileInfos": { + "../../../../../a/lib/lib.esnext.full.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true, + "impliedFormat": "commonjs" + }, + "../index.ts": { + "version": "14361483761-import * as me from \"@this/package\";\nme.thing();\nexport function thing(): void {}\n", + "signature": "-2724770439-export declare function thing(): void;\n", + "impliedFormat": "esnext" + } + }, + "options": { + "composite": true, + "declarationDir": "../types", + "module": 199, + "outDir": "./" + }, + "referencedMap": { + "../index.ts": [ + "../index.ts" + ] + }, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../../../a/lib/lib.esnext.full.d.ts", + "../index.ts" + ], + "latestChangedDtsFile": "../types/index.d.ts" + }, + "version": "FakeTSVersion", + "size": 974 +} + diff --git a/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/with-nodeNext-resolution.js b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/with-nodeNext-resolution.js new file mode 100644 index 0000000000000..efcf899a34ffa --- /dev/null +++ b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/with-nodeNext-resolution.js @@ -0,0 +1,141 @@ +Input:: +//// [/Users/name/projects/web/src/bin.ts] +import { foo } from "yargs"; + +//// [/Users/name/projects/web/node_modules/@types/yargs/index.d.ts] +export function foo(): void; + +//// [/Users/name/projects/web/node_modules/@types/yargs/index.d.mts] +export function foo(): void; + +//// [/Users/name/projects/web/node_modules/@types/yargs/package.json] +{"name":"yargs","version":"17.0.12","exports":{".":{"types":{"import":"./index.d.mts","default":"./index.d.ts"}}}} + +//// [/Users/name/projects/web/tsconfig.json] +{"compilerOptions":{"moduleResolution":"nodenext","forceConsistentCasingInFileNames":true,"traceResolution":true}} + +//// [/a/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; } + + +/a/lib/tsc.js --w --explainFiles +Output:: +>> Screen clear +[12:00:35 AM] Starting compilation in watch mode... + +File '/users/name/projects/web/src/package.json' does not exist. +File '/users/name/projects/web/package.json' does not exist. +File '/users/name/projects/package.json' does not exist. +File '/users/name/package.json' does not exist. +File '/users/package.json' does not exist. +File '/package.json' does not exist. +======== Resolving module 'yargs' from '/Users/name/projects/web/src/bin.ts'. ======== +Explicitly specified module resolution kind: 'NodeNext'. +File '/users/name/projects/web/src/package.json' does not exist according to earlier cached lookups. +File '/users/name/projects/web/package.json' does not exist according to earlier cached lookups. +File '/users/name/projects/package.json' does not exist according to earlier cached lookups. +File '/users/name/package.json' does not exist according to earlier cached lookups. +File '/users/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Loading module 'yargs' from 'node_modules' folder, target file type 'TypeScript'. +Directory '/Users/name/projects/web/src/node_modules' does not exist, skipping all lookups in it. +File '/Users/name/projects/web/node_modules/yargs.ts' does not exist. +File '/Users/name/projects/web/node_modules/yargs.tsx' does not exist. +File '/Users/name/projects/web/node_modules/yargs.d.ts' does not exist. +Found 'package.json' at '/Users/name/projects/web/node_modules/@types/yargs/package.json'. +'package.json' does not have a 'typesVersions' field. +File '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts' exist - use it as a name resolution result. +Resolving real path for '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts', result '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts'. +======== Module name 'yargs' was successfully resolved to '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts' with Package ID 'yargs/index.d.ts@17.0.12'. ======== +File '/users/name/projects/web/node_modules/@types/yargs/package.json' exists according to earlier cached lookups. +======== Resolving type reference directive 'yargs', containing file '/Users/name/projects/web/__inferred type names__.ts', root directory '/Users/name/projects/web/node_modules/@types'. ======== +Resolving with primary search path '/Users/name/projects/web/node_modules/@types'. +File '/Users/name/projects/web/node_modules/@types/yargs/package.json' exists according to earlier cached lookups. +'package.json' does not have a 'typings' field. +'package.json' does not have a 'types' field. +File '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts' exist - use it as a name resolution result. +Resolving real path for '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts', result '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts'. +======== Type reference directive 'yargs' was successfully resolved to '/Users/name/projects/web/node_modules/@types/yargs/index.d.ts' with Package ID 'yargs/index.d.ts@17.0.12', primary: true. ======== +File '/a/lib/package.json' does not exist. +File '/a/package.json' does not exist. +File '/package.json' does not exist according to earlier cached lookups. +../../../../a/lib/lib.d.ts + Default library for target 'es3' +node_modules/@types/yargs/index.d.ts + Imported via "yargs" from file 'src/bin.ts' with packageId 'yargs/index.d.ts@17.0.12' + Entry point for implicit type library 'yargs' with packageId 'yargs/index.d.ts@17.0.12' + File is CommonJS module because 'node_modules/@types/yargs/package.json' does not have field "type" +src/bin.ts + Matched by default include pattern '**/*' + File is CommonJS module because 'package.json' was not found +[12:00:38 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/Users/name/projects/web/src/bin.ts"] +Program options: {"moduleResolution":99,"forceConsistentCasingInFileNames":true,"traceResolution":true,"watch":true,"explainFiles":true,"configFilePath":"/Users/name/projects/web/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/Users/name/projects/web/node_modules/@types/yargs/index.d.ts +/Users/name/projects/web/src/bin.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/Users/name/projects/web/node_modules/@types/yargs/index.d.ts +/Users/name/projects/web/src/bin.ts + +Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) +/users/name/projects/web/node_modules/@types/yargs/index.d.ts (used version) +/users/name/projects/web/src/bin.ts (used version) + +WatchedFiles:: +/users/name/projects/web/tsconfig.json: + {"fileName":"/Users/name/projects/web/tsconfig.json","pollingInterval":250} +/users/name/projects/web/src/bin.ts: + {"fileName":"/Users/name/projects/web/src/bin.ts","pollingInterval":250} +/users/name/projects/web/node_modules/@types/yargs/index.d.ts: + {"fileName":"/Users/name/projects/web/node_modules/@types/yargs/index.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} +/users/name/projects/web/node_modules/@types/yargs/package.json: + {"fileName":"/Users/name/projects/web/node_modules/@types/yargs/package.json","pollingInterval":250} +/users/name/projects/web/src/package.json: + {"fileName":"/Users/name/projects/web/src/package.json","pollingInterval":250} +/users/name/projects/web/package.json: + {"fileName":"/Users/name/projects/web/package.json","pollingInterval":250} +/users/name/projects/package.json: + {"fileName":"/Users/name/projects/package.json","pollingInterval":250} + +FsWatches:: +/users/name/projects/web: + {"directoryName":"/Users/name/projects/web"} + +FsWatchesRecursive:: +/users/name/projects/web/src: + {"directoryName":"/users/name/projects/web/src"} +/users/name/projects/web/node_modules: + {"directoryName":"/Users/name/projects/web/node_modules"} +/users/name/projects/web/node_modules/@types: + {"directoryName":"/Users/name/projects/web/node_modules/@types"} +/users/name/projects/web: + {"directoryName":"/users/name/projects/web"} + +exitCode:: ExitStatus.undefined + +//// [/Users/name/projects/web/src/bin.js] +"use strict"; +exports.__esModule = true; + +