Skip to content

Commit 43b17c1

Browse files
committed
Merge pull request #5462 from Microsoft/useResolvedFileNameWhenResolvingImports
Use dedicated type to store paths
2 parents 067e1cc + 83919f0 commit 43b17c1

File tree

8 files changed

+159
-116
lines changed

8 files changed

+159
-116
lines changed

src/compiler/core.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,55 @@ namespace ts {
1717
True = -1
1818
}
1919

20-
export function createFileMap<T>(getCanonicalFileName: (fileName: string) => string): FileMap<T> {
20+
export function createFileMap<T>(keyMapper?: (key: string) => string): FileMap<T> {
2121
let files: Map<T> = {};
2222
return {
2323
get,
2424
set,
2525
contains,
2626
remove,
27-
clear,
28-
forEachValue: forEachValueInMap
27+
forEachValue: forEachValueInMap,
28+
clear
2929
};
3030

31-
function set(fileName: string, value: T) {
32-
files[normalizeKey(fileName)] = value;
31+
function forEachValueInMap(f: (key: Path, value: T) => void) {
32+
for (let key in files) {
33+
f(<Path>key, files[key]);
34+
}
3335
}
3436

35-
function get(fileName: string) {
36-
return files[normalizeKey(fileName)];
37+
// path should already be well-formed so it does not need to be normalized
38+
function get(path: Path): T {
39+
return files[toKey(path)];
3740
}
3841

39-
function contains(fileName: string) {
40-
return hasProperty(files, normalizeKey(fileName));
42+
function set(path: Path, value: T) {
43+
files[toKey(path)] = value;
4144
}
4245

43-
function remove (fileName: string) {
44-
let key = normalizeKey(fileName);
45-
delete files[key];
46+
function contains(path: Path) {
47+
return hasProperty(files, toKey(path));
4648
}
4749

48-
function forEachValueInMap(f: (value: T) => void) {
49-
forEachValue(files, f);
50-
}
51-
52-
function normalizeKey(key: string) {
53-
return getCanonicalFileName(normalizeSlashes(key));
50+
function remove(path: Path) {
51+
const key = toKey(path);
52+
delete files[key];
5453
}
5554

5655
function clear() {
5756
files = {};
5857
}
58+
59+
function toKey(path: Path): string {
60+
return keyMapper ? keyMapper(path) : path;
61+
}
62+
}
63+
64+
export function toPath(fileName: string, basePath: string, getCanonicalFileName: (path: string) => string): Path {
65+
const nonCanonicalizedPath = isRootedDiskPath(fileName)
66+
? normalizePath(fileName)
67+
: getNormalizedAbsolutePath(fileName, basePath);
68+
return <Path>getCanonicalFileName(nonCanonicalizedPath);
5969
}
6070

6171
export const enum Comparison {

src/compiler/program.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ namespace ts {
346346
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
347347
: ((moduleNames: string[], containingFile: string) => map(moduleNames, moduleName => resolveModuleName(moduleName, containingFile, options, host).resolvedModule));
348348

349-
let filesByName = createFileMap<SourceFile>(getCanonicalFileName);
349+
let filesByName = createFileMap<SourceFile>();
350350
// stores 'filename -> file association' ignoring case
351351
// used to track cases when two file names differ only in casing
352352
let filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap<SourceFile>(fileName => fileName.toLowerCase()) : undefined;
@@ -384,7 +384,7 @@ namespace ts {
384384

385385
program = {
386386
getRootFileNames: () => rootNames,
387-
getSourceFile: getSourceFile,
387+
getSourceFile,
388388
getSourceFiles: () => files,
389389
getCompilerOptions: () => options,
390390
getSyntacticDiagnostics,
@@ -435,7 +435,7 @@ namespace ts {
435435

436436
// check if program source files has changed in the way that can affect structure of the program
437437
let newSourceFiles: SourceFile[] = [];
438-
let normalizedAbsoluteFileNames: string[] = [];
438+
let filePaths: Path[] = [];
439439
let modifiedSourceFiles: SourceFile[] = [];
440440

441441
for (let oldSourceFile of oldProgram.getSourceFiles()) {
@@ -444,8 +444,8 @@ namespace ts {
444444
return false;
445445
}
446446

447-
const normalizedAbsolutePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
448-
normalizedAbsoluteFileNames.push(normalizedAbsolutePath);
447+
newSourceFile.path = oldSourceFile.path;
448+
filePaths.push(newSourceFile.path);
449449

450450
if (oldSourceFile !== newSourceFile) {
451451
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
@@ -469,7 +469,7 @@ namespace ts {
469469

470470
if (resolveModuleNamesWorker) {
471471
let moduleNames = map(newSourceFile.imports, name => name.text);
472-
let resolutions = resolveModuleNamesWorker(moduleNames, normalizedAbsolutePath);
472+
let resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory));
473473
// ensure that module resolution results are still correct
474474
for (let i = 0; i < moduleNames.length; ++i) {
475475
let newResolution = resolutions[i];
@@ -500,7 +500,7 @@ namespace ts {
500500

501501
// update fileName -> file mapping
502502
for (let i = 0, len = newSourceFiles.length; i < len; ++i) {
503-
filesByName.set(normalizedAbsoluteFileNames[i], newSourceFiles[i]);
503+
filesByName.set(filePaths[i], newSourceFiles[i]);
504504
}
505505

506506
files = newSourceFiles;
@@ -570,7 +570,7 @@ namespace ts {
570570
}
571571

572572
function getSourceFile(fileName: string): SourceFile {
573-
return filesByName.get(getNormalizedAbsolutePath(fileName, currentDirectory));
573+
return filesByName.get(toPath(fileName, currentDirectory, getCanonicalFileName));
574574
}
575575

576576
function getDiagnosticsHelper(
@@ -741,7 +741,7 @@ namespace ts {
741741
diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1;
742742
diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"];
743743
}
744-
else if (!findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd)) {
744+
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd)) {
745745
diagnostic = Diagnostics.File_0_not_found;
746746
diagnosticArgument = [fileName];
747747
}
@@ -751,13 +751,13 @@ namespace ts {
751751
}
752752
}
753753
else {
754-
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd);
754+
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd);
755755
if (!nonTsFile) {
756756
if (options.allowNonTsExtensions) {
757757
diagnostic = Diagnostics.File_0_not_found;
758758
diagnosticArgument = [fileName];
759759
}
760-
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, getNormalizedAbsolutePath(fileName + extension, currentDirectory), isDefaultLib, refFile, refPos, refEnd))) {
760+
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd))) {
761761
diagnostic = Diagnostics.File_0_not_found;
762762
fileName += ".ts";
763763
diagnosticArgument = [fileName];
@@ -786,7 +786,7 @@ namespace ts {
786786
}
787787

788788
// Get source file from normalized fileName
789-
function findSourceFile(fileName: string, normalizedAbsolutePath: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
789+
function findSourceFile(fileName: string, normalizedAbsolutePath: Path, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
790790
if (filesByName.contains(normalizedAbsolutePath)) {
791791
const file = filesByName.get(normalizedAbsolutePath);
792792
// try to check if we've already seen this file but with a different casing in path
@@ -811,6 +811,8 @@ namespace ts {
811811

812812
filesByName.set(normalizedAbsolutePath, file);
813813
if (file) {
814+
file.path = normalizedAbsolutePath;
815+
814816
if (host.useCaseSensitiveFileNames()) {
815817
// for case-sensitive file systems check if we've already seen some file with similar filename ignoring case
816818
const existingFile = filesByNameIgnoreCase.get(normalizedAbsolutePath);
@@ -865,14 +867,7 @@ namespace ts {
865867
let resolution = resolutions[i];
866868
setResolvedModule(file, moduleNames[i], resolution);
867869
if (resolution && !options.noResolve) {
868-
const absoluteImportPath = isRootedDiskPath(resolution.resolvedFileName)
869-
? resolution.resolvedFileName
870-
: getNormalizedAbsolutePath(resolution.resolvedFileName, currentDirectory);
871-
872-
// convert an absolute import path to path that is relative to current directory
873-
// this was host still can locate it but files names in user output will be shorter (and thus look nicer).
874-
const relativePath = getRelativePathToDirectoryOrUrl(currentDirectory, absoluteImportPath, currentDirectory, getCanonicalFileName, false);
875-
const importedFile = findSourceFile(relativePath, absoluteImportPath, /* isDefaultLib */ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
870+
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /* isDefaultLib */ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
876871

877872
if (importedFile && resolution.isExternalLibraryImport) {
878873
if (!isExternalModule(importedFile)) {

src/compiler/tsc.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,16 @@ namespace ts {
8181
return <string>diagnostic.messageText;
8282
}
8383

84-
function reportDiagnostic(diagnostic: Diagnostic) {
84+
function reportDiagnostic(diagnostic: Diagnostic, host: CompilerHost) {
8585
let output = "";
8686

8787
if (diagnostic.file) {
8888
let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
89-
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
89+
const relativeFileName = host
90+
? convertToRelativePath(diagnostic.file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName))
91+
: diagnostic.file.fileName;
92+
93+
output += `${ relativeFileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
9094
}
9195

9296
let category = DiagnosticCategory[diagnostic.category].toLowerCase();
@@ -95,9 +99,9 @@ namespace ts {
9599
sys.write(output);
96100
}
97101

98-
function reportDiagnostics(diagnostics: Diagnostic[]) {
102+
function reportDiagnostics(diagnostics: Diagnostic[], host: CompilerHost) {
99103
for (let i = 0; i < diagnostics.length; i++) {
100-
reportDiagnostic(diagnostics[i]);
104+
reportDiagnostic(diagnostics[i], host);
101105
}
102106
}
103107

@@ -166,7 +170,7 @@ namespace ts {
166170

167171
if (commandLine.options.locale) {
168172
if (!isJSONSupported()) {
169-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"));
173+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* compilerHost */ undefined);
170174
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
171175
}
172176
validateLocaleAndSetLanguage(commandLine.options.locale, commandLine.errors);
@@ -175,7 +179,7 @@ namespace ts {
175179
// If there are any errors due to command line parsing and/or
176180
// setting up localization, report them and quit.
177181
if (commandLine.errors.length > 0) {
178-
reportDiagnostics(commandLine.errors);
182+
reportDiagnostics(commandLine.errors, compilerHost);
179183
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
180184
}
181185

@@ -185,7 +189,7 @@ namespace ts {
185189
}
186190

187191
if (commandLine.options.version) {
188-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, ts.version));
192+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, ts.version), /* compilerHost */ undefined);
189193
return sys.exit(ExitStatus.Success);
190194
}
191195

@@ -197,12 +201,12 @@ namespace ts {
197201

198202
if (commandLine.options.project) {
199203
if (!isJSONSupported()) {
200-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"));
204+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* compilerHost */ undefined);
201205
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
202206
}
203207
configFileName = normalizePath(combinePaths(commandLine.options.project, "tsconfig.json"));
204208
if (commandLine.fileNames.length !== 0) {
205-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
209+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* compilerHost */ undefined);
206210
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
207211
}
208212
}
@@ -220,7 +224,7 @@ namespace ts {
220224
// Firefox has Object.prototype.watch
221225
if (commandLine.options.watch && commandLine.options.hasOwnProperty("watch")) {
222226
if (!sys.watchFile) {
223-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
227+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* compilerHost */ undefined);
224228
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
225229
}
226230
if (configFileName) {
@@ -256,7 +260,7 @@ namespace ts {
256260
let configObject = result.config;
257261
let configParseResult = parseJsonConfigFileContent(configObject, sys, getDirectoryPath(configFileName));
258262
if (configParseResult.errors.length > 0) {
259-
reportDiagnostics(configParseResult.errors);
263+
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
260264
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
261265
return;
262266
}
@@ -463,7 +467,7 @@ namespace ts {
463467
}
464468
}
465469

466-
reportDiagnostics(diagnostics);
470+
reportDiagnostics(diagnostics, compilerHost);
467471

468472
// If the user doesn't want us to emit, then we're done at this point.
469473
if (compilerOptions.noEmit) {
@@ -474,7 +478,7 @@ namespace ts {
474478

475479
// Otherwise, emit and report any errors we ran into.
476480
let emitOutput = program.emit();
477-
reportDiagnostics(emitOutput.diagnostics);
481+
reportDiagnostics(emitOutput.diagnostics, compilerHost);
478482

479483
// If the emitter didn't emit anything, then pass that value along.
480484
if (emitOutput.emitSkipped) {
@@ -587,7 +591,7 @@ namespace ts {
587591
let currentDirectory = sys.getCurrentDirectory();
588592
let file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
589593
if (sys.fileExists(file)) {
590-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file));
594+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* compilerHost */ undefined);
591595
}
592596
else {
593597
let compilerOptions = extend(options, defaultInitCompilerOptions);
@@ -602,7 +606,7 @@ namespace ts {
602606
}
603607

604608
sys.writeFile(file, JSON.stringify(configurations, undefined, 4));
605-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file));
609+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* compilerHost */ undefined);
606610
}
607611

608612
return;

src/compiler/types.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ namespace ts {
33
[index: string]: T;
44
}
55

6+
// branded string type used to store absolute, normalized and canonicalized paths
7+
// arbitrary file name can be converted to Path via toPath function
8+
export type Path = string & { __pathBrand: any };
9+
610
export interface FileMap<T> {
7-
get(fileName: string): T;
8-
set(fileName: string, value: T): void;
9-
contains(fileName: string): boolean;
10-
remove(fileName: string): void;
11-
forEachValue(f: (v: T) => void): void;
11+
get(fileName: Path): T;
12+
set(fileName: Path, value: T): void;
13+
contains(fileName: Path): boolean;
14+
remove(fileName: Path): void;
15+
16+
forEachValue(f: (key: Path, v: T) => void): void;
1217
clear(): void;
1318
}
1419

@@ -1250,6 +1255,7 @@ namespace ts {
12501255
endOfFileToken: Node;
12511256

12521257
fileName: string;
1258+
/* internal */ path: Path;
12531259
text: string;
12541260

12551261
amdDependencies: {path: string; name: string}[];

src/compiler/utilities.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1357,7 +1357,6 @@ namespace ts {
13571357
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) {
13581358
if (!host.getCompilerOptions().noResolve) {
13591359
let referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName);
1360-
referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCurrentDirectory());
13611360
return host.getSourceFile(referenceFileName);
13621361
}
13631362
}
@@ -2183,6 +2182,12 @@ namespace ts {
21832182
return result;
21842183
}
21852184

2185+
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
2186+
return !isRootedDiskPath(absoluteOrRelativePath)
2187+
? absoluteOrRelativePath
2188+
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /* isAbsolutePathAnUrl */ false);
2189+
}
2190+
21862191
const carriageReturnLineFeed = "\r\n";
21872192
const lineFeed = "\n";
21882193
export function getNewLineCharacter(options: CompilerOptions): string {

0 commit comments

Comments
 (0)