Skip to content

Commit 6219be6

Browse files
authored
Do no path canonicalization during config parsing (microsoft#20311)
* Do no canonicalization during config parsing * Add test from issue * Apply code review feedback
1 parent b0ea899 commit 6219be6

2 files changed

Lines changed: 65 additions & 26 deletions

File tree

src/compiler/commandLineParser.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,9 +1425,9 @@ namespace ts {
14251425
}
14261426

14271427
function directoryOfCombinedPath(fileName: string, basePath: string) {
1428-
// Use the `identity` function to avoid canonicalizing the path, as it must remain noncanonical
1428+
// Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical
14291429
// until consistient casing errors are reported
1430-
return getDirectoryPath(toPath(fileName, basePath, identity));
1430+
return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath));
14311431
}
14321432

14331433
/**
@@ -1452,8 +1452,7 @@ namespace ts {
14521452
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
14531453
const errors: Diagnostic[] = [];
14541454

1455-
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
1456-
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, getCanonicalFileName, resolutionStack, errors);
1455+
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors);
14571456
const { raw } = parsedConfig;
14581457
const options = extend(existingOptions, parsedConfig.options || {});
14591458
options.configFilePath = configFileName;
@@ -1547,7 +1546,10 @@ namespace ts {
15471546
raw: any;
15481547
options?: CompilerOptions;
15491548
typeAcquisition?: TypeAcquisition;
1550-
extendedConfigPath?: Path;
1549+
/**
1550+
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
1551+
*/
1552+
extendedConfigPath?: string;
15511553
}
15521554

15531555
function isSuccessfulParsedTsconfig(value: ParsedTsconfig) {
@@ -1564,27 +1566,25 @@ namespace ts {
15641566
host: ParseConfigHost,
15651567
basePath: string,
15661568
configFileName: string,
1567-
getCanonicalFileName: GetCanonicalFileName,
1568-
resolutionStack: Path[],
1569+
resolutionStack: string[],
15691570
errors: Push<Diagnostic>,
15701571
): ParsedTsconfig {
15711572
basePath = normalizeSlashes(basePath);
1572-
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
1573+
const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath);
15731574

15741575
if (resolutionStack.indexOf(resolvedPath) >= 0) {
15751576
errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> ")));
15761577
return { raw: json || convertToObject(sourceFile, errors) };
15771578
}
15781579

15791580
const ownConfig = json ?
1580-
parseOwnConfigOfJson(json, host, basePath, getCanonicalFileName, configFileName, errors) :
1581-
parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, getCanonicalFileName, configFileName, errors);
1581+
parseOwnConfigOfJson(json, host, basePath, configFileName, errors) :
1582+
parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, configFileName, errors);
15821583

15831584
if (ownConfig.extendedConfigPath) {
15841585
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
15851586
resolutionStack = resolutionStack.concat([resolvedPath]);
1586-
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, getCanonicalFileName,
1587-
resolutionStack, errors);
1587+
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors);
15881588
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
15891589
const baseRaw = extendedConfig.raw;
15901590
const raw = ownConfig.raw;
@@ -1612,7 +1612,6 @@ namespace ts {
16121612
json: any,
16131613
host: ParseConfigHost,
16141614
basePath: string,
1615-
getCanonicalFileName: GetCanonicalFileName,
16161615
configFileName: string | undefined,
16171616
errors: Push<Diagnostic>
16181617
): ParsedTsconfig {
@@ -1625,15 +1624,15 @@ namespace ts {
16251624
// It should be removed in future releases - use typeAcquisition instead.
16261625
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName);
16271626
json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
1628-
let extendedConfigPath: Path;
1627+
let extendedConfigPath: string;
16291628

16301629
if (json.extends) {
16311630
if (!isString(json.extends)) {
16321631
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
16331632
}
16341633
else {
16351634
const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath;
1636-
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, getCanonicalFileName, errors, createCompilerDiagnostic);
1635+
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic);
16371636
}
16381637
}
16391638
return { raw: json, options, typeAcquisition, extendedConfigPath };
@@ -1643,13 +1642,12 @@ namespace ts {
16431642
sourceFile: JsonSourceFile,
16441643
host: ParseConfigHost,
16451644
basePath: string,
1646-
getCanonicalFileName: GetCanonicalFileName,
16471645
configFileName: string | undefined,
16481646
errors: Push<Diagnostic>
16491647
): ParsedTsconfig {
16501648
const options = getDefaultCompilerOptions(configFileName);
16511649
let typeAcquisition: TypeAcquisition, typingOptionstypeAcquisition: TypeAcquisition;
1652-
let extendedConfigPath: Path;
1650+
let extendedConfigPath: string;
16531651

16541652
const optionsIterator: JsonConversionNotifier = {
16551653
onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) {
@@ -1670,7 +1668,6 @@ namespace ts {
16701668
<string>value,
16711669
host,
16721670
newBase,
1673-
getCanonicalFileName,
16741671
errors,
16751672
(message, arg0) =>
16761673
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
@@ -1712,7 +1709,6 @@ namespace ts {
17121709
extendedConfig: string,
17131710
host: ParseConfigHost,
17141711
basePath: string,
1715-
getCanonicalFileName: GetCanonicalFileName,
17161712
errors: Push<Diagnostic>,
17171713
createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) {
17181714
extendedConfig = normalizeSlashes(extendedConfig);
@@ -1721,9 +1717,9 @@ namespace ts {
17211717
errors.push(createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig));
17221718
return undefined;
17231719
}
1724-
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
1720+
let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath);
17251721
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) {
1726-
extendedConfigPath = `${extendedConfigPath}.json` as Path;
1722+
extendedConfigPath = `${extendedConfigPath}.json`;
17271723
if (!host.fileExists(extendedConfigPath)) {
17281724
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
17291725
return undefined;
@@ -1734,11 +1730,10 @@ namespace ts {
17341730

17351731
function getExtendedConfig(
17361732
sourceFile: JsonSourceFile,
1737-
extendedConfigPath: Path,
1733+
extendedConfigPath: string,
17381734
host: ts.ParseConfigHost,
17391735
basePath: string,
1740-
getCanonicalFileName: GetCanonicalFileName,
1741-
resolutionStack: Path[],
1736+
resolutionStack: string[],
17421737
errors: Push<Diagnostic>,
17431738
): ParsedTsconfig | undefined {
17441739
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
@@ -1752,14 +1747,14 @@ namespace ts {
17521747

17531748
const extendedDirname = getDirectoryPath(extendedConfigPath);
17541749
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
1755-
getBaseFileName(extendedConfigPath), getCanonicalFileName, resolutionStack, errors);
1750+
getBaseFileName(extendedConfigPath), resolutionStack, errors);
17561751
if (sourceFile) {
17571752
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles);
17581753
}
17591754

17601755
if (isSuccessfulParsedTsconfig(extendedConfig)) {
17611756
// Update the paths to reflect base path
1762-
const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName);
1757+
const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity);
17631758
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
17641759
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
17651760
if (raw[propertyName]) {

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6795,4 +6795,48 @@ namespace ts.projectSystem {
67956795
}
67966796
});
67976797
});
6798+
6799+
describe("tsserverProjectSystem forceConsistentCasingInFileNames", () => {
6800+
it("works when extends is specified with a case insensitive file system", () => {
6801+
const rootPath = "/Users/username/dev/project";
6802+
const file1: FileOrFolder = {
6803+
path: `${rootPath}/index.ts`,
6804+
content: 'import {x} from "file2";',
6805+
};
6806+
const file2: FileOrFolder = {
6807+
path: `${rootPath}/file2.js`,
6808+
content: "",
6809+
};
6810+
const file2Dts: FileOrFolder = {
6811+
path: `${rootPath}/types/file2/index.d.ts`,
6812+
content: "export declare const x: string;",
6813+
};
6814+
const tsconfigAll: FileOrFolder = {
6815+
path: `${rootPath}/tsconfig.all.json`,
6816+
content: JSON.stringify({
6817+
compilerOptions: {
6818+
baseUrl: ".",
6819+
paths: { file2: ["./file2.js"] },
6820+
typeRoots: ["./types"],
6821+
forceConsistentCasingInFileNames: true,
6822+
},
6823+
}),
6824+
};
6825+
const tsconfig: FileOrFolder = {
6826+
path: `${rootPath}/tsconfig.json`,
6827+
content: JSON.stringify({ extends: "./tsconfig.all.json" }),
6828+
};
6829+
6830+
const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false });
6831+
const session = createSession(host);
6832+
6833+
openFilesForSession([file1], session);
6834+
const projectService = session.getProjectService();
6835+
6836+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
6837+
6838+
const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
6839+
assert.deepEqual(diagnostics, []);
6840+
});
6841+
});
67986842
}

0 commit comments

Comments
 (0)