diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 39efe7eb0522e..3c65e4410bdbd 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -392,10 +392,12 @@ namespace ts { if (resolved) { const { fileName, packageId } = resolved; const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); + const pathsAreEqual = arePathsEqual(fileName, resolvedFileName, host); resolvedTypeReferenceDirective = { primary, - resolvedFileName, - originalPath: arePathsEqual(fileName, resolvedFileName, host) ? undefined : fileName, + // If the fileName and realpath are differing only in casing prefer fileName so that we can issue correct errors for casing under forceConsistentCasingInFileNames + resolvedFileName: pathsAreEqual ? fileName : resolvedFileName, + originalPath: pathsAreEqual ? undefined : fileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName), }; @@ -1406,8 +1408,10 @@ namespace ts { let resolvedValue = resolved.value; if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { const path = realPath(resolvedValue.path, host, traceEnabled); - const originalPath = arePathsEqual(path, resolvedValue.path, host) ? undefined : resolvedValue.path; - resolvedValue = { ...resolvedValue, path, originalPath }; + const pathsAreEqual = arePathsEqual(path, resolvedValue.path, host); + const originalPath = pathsAreEqual ? undefined : resolvedValue.path; + // If the path and realpath are differing only in casing prefer path so that we can issue correct errors for casing under forceConsistentCasingInFileNames + resolvedValue = { ...resolvedValue, path: pathsAreEqual ? resolvedValue.path : path, originalPath }; } // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 1bd159038861d..02cd25cb7dadb 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -154,6 +154,7 @@ "unittests/tsc/cancellationToken.ts", "unittests/tsc/composite.ts", "unittests/tsc/declarationEmit.ts", + "unittests/tsc/forceConsistentCasingInFileNames.ts", "unittests/tsc/incremental.ts", "unittests/tsc/listFilesOnly.ts", "unittests/tsc/projectReferences.ts", diff --git a/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts new file mode 100644 index 0000000000000..8718af088df8a --- /dev/null +++ b/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts @@ -0,0 +1,18 @@ +namespace ts { + describe("unittests:: tsc:: forceConsistentCasingInFileNames::", () => { + verifyTsc({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "with relative and non relative file resolutions", + commandLineArgs: ["/src/project/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"], + fs: () => loadProjectFromFiles({ + "/src/project/src/struct.d.ts": Utils.dedent` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `, + "/src/project/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }), + }); + }); +} \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts index 60a989f2fa397..69ad855b626df 100644 --- a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts @@ -113,7 +113,7 @@ export const Fragment: unique symbol; path: `${projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, - files: ["node_modules/react/jsx-Runtime/index.d.ts", "index.tsx"] // NB: casing does not match disk + files: ["node_modules/react/Jsx-Runtime/index.d.ts", "index.tsx"] }) } ], { currentDirectory: projectRoot }), diff --git a/tests/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js b/tests/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js new file mode 100644 index 0000000000000..1d71a121bc001 --- /dev/null +++ b/tests/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js @@ -0,0 +1,72 @@ +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; }; + +//// [/src/project/node_modules/fp-ts/lib/struct.d.ts] +export function foo(): void + +//// [/src/project/src/struct.d.ts] +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; + + + + +Output:: +/lib/tsc /src/project/src/struct.d.ts --forceConsistentCasingInFileNames --explainFiles +src/project/src/struct.d.ts:2:22 - error TS1149: File name '/src/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/src/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/src/project/src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/src/project/src/struct.d.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/project/src/struct.d.ts:1:22 + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + File is included via import here. + +src/project/src/struct.d.ts:3:22 - error TS1149: File name '/src/project/src/Struct.d.ts' differs from already included file name '/src/project/src/struct.d.ts' only in casing. + The file is in the program because: + Root file specified for compilation + Imported via "./Struct" from file '/src/project/src/struct.d.ts' + Imported via "./struct" from file '/src/project/src/struct.d.ts' + +3 import * as xs3 from "./Struct"; +   ~~~~~~~~~~ + + src/project/src/struct.d.ts:4:22 + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + File is included via import here. + +lib/lib.d.ts + Default library for target 'es3' +src/project/node_modules/fp-ts/lib/Struct.d.ts + Imported via "fp-ts/lib/Struct" from file 'src/project/src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file 'src/project/src/struct.d.ts' +src/project/src/struct.d.ts + Root file specified for compilation + Imported via "./Struct" from file 'src/project/src/struct.d.ts' + Imported via "./struct" from file 'src/project/src/struct.d.ts' + +Found 2 errors in the same file, starting at: src/project/src/struct.d.ts:2 + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated + + diff --git a/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/jsxImportSource-option-changed.js b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/jsxImportSource-option-changed.js index 5fbffca425c7a..c820b4469c832 100644 --- a/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/jsxImportSource-option-changed.js +++ b/tests/baselines/reference/tscWatch/forceConsistentCasingInFileNames/jsxImportSource-option-changed.js @@ -33,7 +33,7 @@ export const Fragment: unique symbol; export const App = () =>
; //// [/user/username/projects/myproject/tsconfig.json] -{"compilerOptions":{"jsx":"react-jsx","jsxImportSource":"react","forceConsistentCasingInFileNames":true},"files":["node_modules/react/jsx-Runtime/index.d.ts","index.tsx"]} +{"compilerOptions":{"jsx":"react-jsx","jsxImportSource":"react","forceConsistentCasingInFileNames":true},"files":["node_modules/react/Jsx-Runtime/index.d.ts","index.tsx"]} /a/lib/tsc.js --w --p . --explainFiles @@ -41,19 +41,19 @@ Output:: >> Screen clear [12:00:31 AM] Starting compilation in watch mode... -error TS1149: File name '/user/username/projects/myproject/node_modules/react/Jsx-runtime/index.d.ts' differs from already included file name '/user/username/projects/myproject/node_modules/react/jsx-Runtime/index.d.ts' only in casing. +error TS1149: File name '/user/username/projects/myproject/node_modules/react/jsx-runtime/index.d.ts' differs from already included file name '/user/username/projects/myproject/node_modules/react/Jsx-Runtime/index.d.ts' only in casing. The file is in the program because: Part of 'files' list in tsconfig.json Imported via "react/jsx-runtime" from file '/user/username/projects/myproject/index.tsx' with packageId 'react/jsx-runtime/index.d.ts@0.0.1' to import 'jsx' and 'jsxs' factory functions tsconfig.json:1:115 - 1 {"compilerOptions":{"jsx":"react-jsx","jsxImportSource":"react","forceConsistentCasingInFileNames":true},"files":["node_modules/react/jsx-Runtime/index.d.ts","index.tsx"]} + 1 {"compilerOptions":{"jsx":"react-jsx","jsxImportSource":"react","forceConsistentCasingInFileNames":true},"files":["node_modules/react/Jsx-Runtime/index.d.ts","index.tsx"]}    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ File is matched by 'files' list specified here. ../../../../a/lib/lib.d.ts Default library for target 'es3' -node_modules/react/jsx-Runtime/index.d.ts +node_modules/react/Jsx-Runtime/index.d.ts Part of 'files' list in tsconfig.json Imported via "react/jsx-runtime" from file 'index.tsx' with packageId 'react/jsx-runtime/index.d.ts@0.0.1' to import 'jsx' and 'jsxs' factory functions index.tsx @@ -62,12 +62,12 @@ index.tsx -Program root files: ["/user/username/projects/myproject/node_modules/react/jsx-Runtime/index.d.ts","/user/username/projects/myproject/index.tsx"] +Program root files: ["/user/username/projects/myproject/node_modules/react/Jsx-Runtime/index.d.ts","/user/username/projects/myproject/index.tsx"] Program options: {"jsx":4,"jsxImportSource":"react","forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject","explainFiles":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts -/user/username/projects/myproject/node_modules/react/jsx-Runtime/index.d.ts +/user/username/projects/myproject/node_modules/react/Jsx-Runtime/index.d.ts /user/username/projects/myproject/index.tsx No cached semantic diagnostics in the builder:: @@ -81,7 +81,7 @@ WatchedFiles:: /user/username/projects/myproject/tsconfig.json: {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} /user/username/projects/myproject/node_modules/react/jsx-runtime/index.d.ts: - {"fileName":"/user/username/projects/myproject/node_modules/react/jsx-Runtime/index.d.ts","pollingInterval":250} + {"fileName":"/user/username/projects/myproject/node_modules/react/Jsx-Runtime/index.d.ts","pollingInterval":250} /user/username/projects/myproject/index.tsx: {"fileName":"/user/username/projects/myproject/index.tsx","pollingInterval":250} /a/lib/lib.d.ts: