diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 9c30cb87a4d..b254bfc1031 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1425,9 +1425,9 @@ namespace ts { } function directoryOfCombinedPath(fileName: string, basePath: string) { - // Use the `identity` function to avoid canonicalizing the path, as it must remain noncanonical + // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical // until consistient casing errors are reported - return getDirectoryPath(toPath(fileName, basePath, identity)); + return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); } /** @@ -1452,8 +1452,7 @@ namespace ts { Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); const errors: Diagnostic[] = []; - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, getCanonicalFileName, resolutionStack, errors); + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors); const { raw } = parsedConfig; const options = extend(existingOptions, parsedConfig.options || {}); options.configFilePath = configFileName; @@ -1547,7 +1546,10 @@ namespace ts { raw: any; options?: CompilerOptions; typeAcquisition?: TypeAcquisition; - extendedConfigPath?: Path; + /** + * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet + */ + extendedConfigPath?: string; } function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { @@ -1564,12 +1566,11 @@ namespace ts { host: ParseConfigHost, basePath: string, configFileName: string, - getCanonicalFileName: GetCanonicalFileName, - resolutionStack: Path[], + resolutionStack: string[], errors: Push, ): ParsedTsconfig { basePath = normalizeSlashes(basePath); - const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); + const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); if (resolutionStack.indexOf(resolvedPath) >= 0) { errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); @@ -1577,14 +1578,13 @@ namespace ts { } const ownConfig = json ? - parseOwnConfigOfJson(json, host, basePath, getCanonicalFileName, configFileName, errors) : - parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, getCanonicalFileName, configFileName, errors); + parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : + parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, configFileName, errors); if (ownConfig.extendedConfigPath) { // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, getCanonicalFileName, - resolutionStack, errors); + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors); if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { const baseRaw = extendedConfig.raw; const raw = ownConfig.raw; @@ -1612,7 +1612,6 @@ namespace ts { json: any, host: ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, configFileName: string | undefined, errors: Push ): ParsedTsconfig { @@ -1625,7 +1624,7 @@ namespace ts { // It should be removed in future releases - use typeAcquisition instead. const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); - let extendedConfigPath: Path; + let extendedConfigPath: string; if (json.extends) { if (!isString(json.extends)) { @@ -1633,7 +1632,7 @@ namespace ts { } else { const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, getCanonicalFileName, errors, createCompilerDiagnostic); + extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); } } return { raw: json, options, typeAcquisition, extendedConfigPath }; @@ -1643,13 +1642,12 @@ namespace ts { sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, configFileName: string | undefined, errors: Push ): ParsedTsconfig { const options = getDefaultCompilerOptions(configFileName); let typeAcquisition: TypeAcquisition, typingOptionstypeAcquisition: TypeAcquisition; - let extendedConfigPath: Path; + let extendedConfigPath: string; const optionsIterator: JsonConversionNotifier = { onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { @@ -1670,7 +1668,6 @@ namespace ts { value, host, newBase, - getCanonicalFileName, errors, (message, arg0) => createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) @@ -1712,7 +1709,6 @@ namespace ts { extendedConfig: string, host: ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, errors: Push, createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { extendedConfig = normalizeSlashes(extendedConfig); @@ -1721,9 +1717,9 @@ namespace ts { errors.push(createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig)); return undefined; } - let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); + let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { - extendedConfigPath = `${extendedConfigPath}.json` as Path; + extendedConfigPath = `${extendedConfigPath}.json`; if (!host.fileExists(extendedConfigPath)) { errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); return undefined; @@ -1734,11 +1730,10 @@ namespace ts { function getExtendedConfig( sourceFile: JsonSourceFile, - extendedConfigPath: Path, + extendedConfigPath: string, host: ts.ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, - resolutionStack: Path[], + resolutionStack: string[], errors: Push, ): ParsedTsconfig | undefined { const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); @@ -1752,14 +1747,14 @@ namespace ts { const extendedDirname = getDirectoryPath(extendedConfigPath); const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, - getBaseFileName(extendedConfigPath), getCanonicalFileName, resolutionStack, errors); + getBaseFileName(extendedConfigPath), resolutionStack, errors); if (sourceFile) { sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); } if (isSuccessfulParsedTsconfig(extendedConfig)) { // Update the paths to reflect base path - const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); + const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity); const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); const mapPropertiesInRawIfNotUndefined = (propertyName: string) => { if (raw[propertyName]) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fee21fcdb2d..42199c895b1 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6795,4 +6795,48 @@ namespace ts.projectSystem { } }); }); + + describe("tsserverProjectSystem forceConsistentCasingInFileNames", () => { + it("works when extends is specified with a case insensitive file system", () => { + const rootPath = "/Users/username/dev/project"; + const file1: FileOrFolder = { + path: `${rootPath}/index.ts`, + content: 'import {x} from "file2";', + }; + const file2: FileOrFolder = { + path: `${rootPath}/file2.js`, + content: "", + }; + const file2Dts: FileOrFolder = { + path: `${rootPath}/types/file2/index.d.ts`, + content: "export declare const x: string;", + }; + const tsconfigAll: FileOrFolder = { + path: `${rootPath}/tsconfig.all.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: ".", + paths: { file2: ["./file2.js"] }, + typeRoots: ["./types"], + forceConsistentCasingInFileNames: true, + }, + }), + }; + const tsconfig: FileOrFolder = { + path: `${rootPath}/tsconfig.json`, + content: JSON.stringify({ extends: "./tsconfig.all.json" }), + }; + + const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); + const session = createSession(host); + + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); + }); }