Do no path canonicalization during config parsing (#20311)

* Do no canonicalization during config parsing

* Add test from issue

* Apply code review feedback
This commit is contained in:
Wesley Wigham 2018-01-30 14:16:44 -08:00 committed by GitHub
parent b0ea899d13
commit 6219be6144
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 26 deletions

View File

@ -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<Diagnostic>,
): 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<Diagnostic>
): 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<Diagnostic>
): 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 {
<string>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<Diagnostic>,
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<Diagnostic>,
): 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]) {

View File

@ -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, []);
});
});
}