From 1d3f328ec1713e502464f8eff4323bbf4feac05f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 31 Jan 2018 12:09:12 -0800 Subject: [PATCH] Do no path canonicalization during config parsing (#20311) (#21498) * Do no canonicalization during config parsing * Add test from issue * Apply code review feedback --- src/compiler/commandLineParser.ts | 47 +++++++++---------- .../unittests/tsserverProjectSystem.ts | 44 +++++++++++++++++ 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index eb9f1e3cf34..238bd87a3da 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1419,9 +1419,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)); } /** @@ -1446,8 +1446,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; @@ -1541,7 +1540,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) { @@ -1558,12 +1560,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(" -> "))); @@ -1571,14 +1572,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; @@ -1606,7 +1606,6 @@ namespace ts { json: any, host: ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, configFileName: string | undefined, errors: Push ): ParsedTsconfig { @@ -1619,7 +1618,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)) { @@ -1627,7 +1626,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 }; @@ -1637,13 +1636,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) { @@ -1664,7 +1662,6 @@ namespace ts { value, host, newBase, - getCanonicalFileName, errors, (message, arg0) => createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) @@ -1706,7 +1703,6 @@ namespace ts { extendedConfig: string, host: ParseConfigHost, basePath: string, - getCanonicalFileName: GetCanonicalFileName, errors: Push, createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { extendedConfig = normalizeSlashes(extendedConfig); @@ -1715,9 +1711,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; @@ -1728,11 +1724,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)); @@ -1746,14 +1741,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, []); + }); + }); }