Skip to content

Use dedicated type to store paths #5462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 30, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,55 @@ namespace ts {
True = -1
}

export function createFileMap<T>(getCanonicalFileName: (fileName: string) => string): FileMap<T> {
export function createFileMap<T>(keyMapper?: (key: string) => string): FileMap<T> {
let files: Map<T> = {};
return {
get,
set,
contains,
remove,
clear,
forEachValue: forEachValueInMap
forEachValue: forEachValueInMap,
clear
};

function set(fileName: string, value: T) {
files[normalizeKey(fileName)] = value;
function forEachValueInMap(f: (key: Path, value: T) => void) {
for (let key in files) {
f(<Path>key, files[key]);
}
}

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

function contains(fileName: string) {
return hasProperty(files, normalizeKey(fileName));
function set(path: Path, value: T) {
files[toKey(path)] = value;
}

function remove (fileName: string) {
let key = normalizeKey(fileName);
delete files[key];
function contains(path: Path) {
return hasProperty(files, toKey(path));
}

function forEachValueInMap(f: (value: T) => void) {
forEachValue(files, f);
}

function normalizeKey(key: string) {
return getCanonicalFileName(normalizeSlashes(key));
function remove(path: Path) {
const key = toKey(path);
delete files[key];
}

function clear() {
files = {};
}

function toKey(path: Path): string {
return keyMapper ? keyMapper(path) : path;
}
}

export function toPath(fileName: string, basePath: string, getCanonicalFileName: (path: string) => string): Path {
const nonCanonicalizedPath = isRootedDiskPath(fileName)
? normalizePath(fileName)
: getNormalizedAbsolutePath(fileName, basePath);
return <Path>getCanonicalFileName(nonCanonicalizedPath);
}

export const enum Comparison {
Expand Down
35 changes: 15 additions & 20 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ namespace ts {
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
: ((moduleNames: string[], containingFile: string) => map(moduleNames, moduleName => resolveModuleName(moduleName, containingFile, options, host).resolvedModule));

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

program = {
getRootFileNames: () => rootNames,
getSourceFile: getSourceFile,
getSourceFile,
getSourceFiles: () => files,
getCompilerOptions: () => options,
getSyntacticDiagnostics,
Expand Down Expand Up @@ -435,7 +435,7 @@ namespace ts {

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

for (let oldSourceFile of oldProgram.getSourceFiles()) {
Expand All @@ -444,8 +444,8 @@ namespace ts {
return false;
}

const normalizedAbsolutePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
normalizedAbsoluteFileNames.push(normalizedAbsolutePath);
newSourceFile.path = oldSourceFile.path;
filePaths.push(newSourceFile.path);

if (oldSourceFile !== newSourceFile) {
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
Expand All @@ -469,7 +469,7 @@ namespace ts {

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

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

files = newSourceFiles;
Expand Down Expand Up @@ -570,7 +570,7 @@ namespace ts {
}

function getSourceFile(fileName: string): SourceFile {
return filesByName.get(getNormalizedAbsolutePath(fileName, currentDirectory));
return filesByName.get(toPath(fileName, currentDirectory, getCanonicalFileName));
}

function getDiagnosticsHelper(
Expand Down Expand Up @@ -741,7 +741,7 @@ namespace ts {
diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1;
diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"];
}
else if (!findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd)) {
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd)) {
diagnostic = Diagnostics.File_0_not_found;
diagnosticArgument = [fileName];
}
Expand All @@ -751,13 +751,13 @@ namespace ts {
}
}
else {
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd);
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd);
if (!nonTsFile) {
if (options.allowNonTsExtensions) {
diagnostic = Diagnostics.File_0_not_found;
diagnosticArgument = [fileName];
}
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, getNormalizedAbsolutePath(fileName + extension, currentDirectory), isDefaultLib, refFile, refPos, refEnd))) {
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd))) {
diagnostic = Diagnostics.File_0_not_found;
fileName += ".ts";
diagnosticArgument = [fileName];
Expand Down Expand Up @@ -786,7 +786,7 @@ namespace ts {
}

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

filesByName.set(normalizedAbsolutePath, file);
if (file) {
file.path = normalizedAbsolutePath;

if (host.useCaseSensitiveFileNames()) {
// for case-sensitive file systems check if we've already seen some file with similar filename ignoring case
const existingFile = filesByNameIgnoreCase.get(normalizedAbsolutePath);
Expand Down Expand Up @@ -865,14 +867,7 @@ namespace ts {
let resolution = resolutions[i];
setResolvedModule(file, moduleNames[i], resolution);
if (resolution && !options.noResolve) {
const absoluteImportPath = isRootedDiskPath(resolution.resolvedFileName)
? resolution.resolvedFileName
: getNormalizedAbsolutePath(resolution.resolvedFileName, currentDirectory);

// convert an absolute import path to path that is relative to current directory
// this was host still can locate it but files names in user output will be shorter (and thus look nicer).
const relativePath = getRelativePathToDirectoryOrUrl(currentDirectory, absoluteImportPath, currentDirectory, getCanonicalFileName, false);
const importedFile = findSourceFile(relativePath, absoluteImportPath, /* isDefaultLib */ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /* isDefaultLib */ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);

if (importedFile && resolution.isExternalLibraryImport) {
if (!isExternalModule(importedFile)) {
Expand Down
34 changes: 19 additions & 15 deletions src/compiler/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,16 @@ namespace ts {
return <string>diagnostic.messageText;
}

function reportDiagnostic(diagnostic: Diagnostic) {
function reportDiagnostic(diagnostic: Diagnostic, host: CompilerHost) {
let output = "";

if (diagnostic.file) {
let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
const relativeFileName = host
? convertToRelativePath(diagnostic.file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName))
: diagnostic.file.fileName;

output += `${ relativeFileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
}

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

function reportDiagnostics(diagnostics: Diagnostic[]) {
function reportDiagnostics(diagnostics: Diagnostic[], host: CompilerHost) {
for (let i = 0; i < diagnostics.length; i++) {
reportDiagnostic(diagnostics[i]);
reportDiagnostic(diagnostics[i], host);
}
}

Expand Down Expand Up @@ -166,7 +170,7 @@ namespace ts {

if (commandLine.options.locale) {
if (!isJSONSupported()) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* compilerHost */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
validateLocaleAndSetLanguage(commandLine.options.locale, commandLine.errors);
Expand All @@ -175,7 +179,7 @@ namespace ts {
// If there are any errors due to command line parsing and/or
// setting up localization, report them and quit.
if (commandLine.errors.length > 0) {
reportDiagnostics(commandLine.errors);
reportDiagnostics(commandLine.errors, compilerHost);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}

Expand All @@ -185,7 +189,7 @@ namespace ts {
}

if (commandLine.options.version) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, ts.version));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, ts.version), /* compilerHost */ undefined);
return sys.exit(ExitStatus.Success);
}

Expand All @@ -197,12 +201,12 @@ namespace ts {

if (commandLine.options.project) {
if (!isJSONSupported()) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* compilerHost */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
configFileName = normalizePath(combinePaths(commandLine.options.project, "tsconfig.json"));
if (commandLine.fileNames.length !== 0) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* compilerHost */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
Expand All @@ -220,7 +224,7 @@ namespace ts {
// Firefox has Object.prototype.watch
if (commandLine.options.watch && commandLine.options.hasOwnProperty("watch")) {
if (!sys.watchFile) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* compilerHost */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (configFileName) {
Expand Down Expand Up @@ -256,7 +260,7 @@ namespace ts {
let configObject = result.config;
let configParseResult = parseJsonConfigFileContent(configObject, sys, getDirectoryPath(configFileName));
if (configParseResult.errors.length > 0) {
reportDiagnostics(configParseResult.errors);
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
Expand Down Expand Up @@ -463,7 +467,7 @@ namespace ts {
}
}

reportDiagnostics(diagnostics);
reportDiagnostics(diagnostics, compilerHost);

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

// Otherwise, emit and report any errors we ran into.
let emitOutput = program.emit();
reportDiagnostics(emitOutput.diagnostics);
reportDiagnostics(emitOutput.diagnostics, compilerHost);

// If the emitter didn't emit anything, then pass that value along.
if (emitOutput.emitSkipped) {
Expand Down Expand Up @@ -587,7 +591,7 @@ namespace ts {
let currentDirectory = sys.getCurrentDirectory();
let file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
if (sys.fileExists(file)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* compilerHost */ undefined);
}
else {
let compilerOptions = extend(options, defaultInitCompilerOptions);
Expand All @@ -602,7 +606,7 @@ namespace ts {
}

sys.writeFile(file, JSON.stringify(configurations, undefined, 4));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* compilerHost */ undefined);
}

return;
Expand Down
16 changes: 11 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ namespace ts {
[index: string]: T;
}

// branded string type used to store absolute, normalized and canonicalized paths
// arbitrary file name can be converted to Path via toPath function
export type Path = string & { __pathBrand: any };

export interface FileMap<T> {
get(fileName: string): T;
set(fileName: string, value: T): void;
contains(fileName: string): boolean;
remove(fileName: string): void;
forEachValue(f: (v: T) => void): void;
get(fileName: Path): T;
set(fileName: Path, value: T): void;
contains(fileName: Path): boolean;
remove(fileName: Path): void;

forEachValue(f: (key: Path, v: T) => void): void;
clear(): void;
}

Expand Down Expand Up @@ -1250,6 +1255,7 @@ namespace ts {
endOfFileToken: Node;

fileName: string;
/* internal */ path: Path;
text: string;

amdDependencies: {path: string; name: string}[];
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1357,7 +1357,6 @@ namespace ts {
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) {
if (!host.getCompilerOptions().noResolve) {
let referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName);
referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCurrentDirectory());
return host.getSourceFile(referenceFileName);
}
}
Expand Down Expand Up @@ -2183,6 +2182,12 @@ namespace ts {
return result;
}

export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
return !isRootedDiskPath(absoluteOrRelativePath)
? absoluteOrRelativePath
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /* isAbsolutePathAnUrl */ false);
}

const carriageReturnLineFeed = "\r\n";
const lineFeed = "\n";
export function getNewLineCharacter(options: CompilerOptions): string {
Expand Down
Loading