Merge pull request #5462 from Microsoft/useResolvedFileNameWhenResolvingImports

Use dedicated type to store paths
This commit is contained in:
Vladimir Matveev
2015-10-30 10:09:10 -07:00
8 changed files with 160 additions and 117 deletions
+29 -19
View File
@@ -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);
function contains(path: Path) {
return hasProperty(files, toKey(path));
}
function remove(path: Path) {
const key = toKey(path);
delete files[key];
}
function forEachValueInMap(f: (value: T) => void) {
forEachValue(files, f);
}
function normalizeKey(key: string) {
return getCanonicalFileName(normalizeSlashes(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 {
+15 -20
View File
@@ -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;
@@ -384,7 +384,7 @@ namespace ts {
program = {
getRootFileNames: () => rootNames,
getSourceFile: getSourceFile,
getSourceFile,
getSourceFiles: () => files,
getCompilerOptions: () => options,
getSyntacticDiagnostics,
@@ -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()) {
@@ -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) {
@@ -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];
@@ -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;
@@ -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(
@@ -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];
}
@@ -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];
@@ -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
@@ -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);
@@ -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)) {
+19 -15
View File
@@ -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();
@@ -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);
}
}
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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) {
@@ -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;
}
@@ -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) {
@@ -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) {
@@ -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);
@@ -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;
+11 -5
View File
@@ -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;
}
@@ -1250,6 +1255,7 @@ namespace ts {
endOfFileToken: Node;
fileName: string;
/* internal */ path: Path;
text: string;
amdDependencies: {path: string; name: string}[];
+6 -1
View File
@@ -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);
}
}
@@ -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 {
+12 -5
View File
@@ -234,12 +234,15 @@ class ProjectRunner extends RunnerBase {
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
// convert file name to rooted name
// if filename is not rooted - concat it with project root and then expand project root relative to current directory
let diskFileName = ts.isRootedDiskPath(fileName)
? fileName
: ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName);
: Harness.IO.resolvePath(ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName));
let diskRelativeName = ts.getRelativePathToDirectoryOrUrl(testCase.projectRoot, diskFileName,
getCurrentDirectory(), Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
let currentDirectory = getCurrentDirectory();
// compute file name relative to current directory (expanded project root)
let diskRelativeName = ts.getRelativePathToDirectoryOrUrl(currentDirectory, diskFileName, currentDirectory, Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
if (ts.isRootedDiskPath(diskRelativeName) || diskRelativeName.substr(0, 3) === "../") {
// If the generated output file resides in the parent folder or is rooted path,
// we need to instead create files that can live in the project reference folder
@@ -373,8 +376,12 @@ class ProjectRunner extends RunnerBase {
runTest: testCase.runTest,
bug: testCase.bug,
rootDir: testCase.rootDir,
resolvedInputFiles: ts.map(compilerResult.program.getSourceFiles(), inputFile => inputFile.fileName),
emittedFiles: ts.map(compilerResult.outputFiles, outputFile => outputFile.emittedFileName)
resolvedInputFiles: ts.map(compilerResult.program.getSourceFiles(), inputFile => {
return ts.convertToRelativePath(inputFile.fileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
}),
emittedFiles: ts.map(compilerResult.outputFiles, outputFile => {
return ts.convertToRelativePath(outputFile.emittedFileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
})
};
return resolutionInfo;
+25 -17
View File
@@ -33,8 +33,10 @@ namespace ts.server {
defaultProject: Project; // project to use by default for file
fileWatcher: FileWatcher;
formatCodeOptions = ts.clone(CompilerService.defaultFormatCodeOptions);
path: Path;
constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) {
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
this.svc = ScriptVersionCache.fromString(host, content);
}
@@ -90,11 +92,12 @@ namespace ts.server {
roots: ScriptInfo[] = [];
private resolvedModuleNames: ts.FileMap<Map<TimestampedResolvedModule>>;
private moduleResolutionHost: ts.ModuleResolutionHost;
private getCanonicalFileName: (fileName: string) => string;
constructor(public host: ServerHost, public project: Project) {
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>(getCanonicalFileName);
this.filenameToScript = createFileMap<ScriptInfo>(getCanonicalFileName);
this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>();
this.filenameToScript = createFileMap<ScriptInfo>();
this.moduleResolutionHost = {
fileExists: fileName => this.fileExists(fileName),
readFile: fileName => this.host.readFile(fileName)
@@ -102,7 +105,8 @@ namespace ts.server {
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
let currentResolutionsInFile = this.resolvedModuleNames.get(containingFile);
let path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
let currentResolutionsInFile = this.resolvedModuleNames.get(path);
let newResolutions: Map<TimestampedResolvedModule> = {};
let resolvedModules: ResolvedModule[] = [];
@@ -131,7 +135,7 @@ namespace ts.server {
}
// replace old results with a new one
this.resolvedModuleNames.set(containingFile, newResolutions);
this.resolvedModuleNames.set(path, newResolutions);
return resolvedModules;
function moduleResolutionIsValid(resolution: TimestampedResolvedModule): boolean {
@@ -201,34 +205,35 @@ namespace ts.server {
removeReferencedFile(info: ScriptInfo) {
if (!info.isOpen) {
this.filenameToScript.remove(info.fileName);
this.resolvedModuleNames.remove(info.fileName);
this.filenameToScript.remove(info.path);
this.resolvedModuleNames.remove(info.path);
}
}
getScriptInfo(filename: string): ScriptInfo {
let scriptInfo = this.filenameToScript.get(filename);
let path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName);
let scriptInfo = this.filenameToScript.get(path);
if (!scriptInfo) {
scriptInfo = this.project.openReferencedFile(filename);
if (scriptInfo) {
this.filenameToScript.set(scriptInfo.fileName, scriptInfo);
this.filenameToScript.set(path, scriptInfo);
}
}
return scriptInfo;
}
addRoot(info: ScriptInfo) {
if (!this.filenameToScript.contains(info.fileName)) {
this.filenameToScript.set(info.fileName, info);
if (!this.filenameToScript.contains(info.path)) {
this.filenameToScript.set(info.path, info);
this.roots.push(info);
}
}
removeRoot(info: ScriptInfo) {
if (!this.filenameToScript.contains(info.fileName)) {
this.filenameToScript.remove(info.fileName);
if (!this.filenameToScript.contains(info.path)) {
this.filenameToScript.remove(info.path);
this.roots = copyListRemovingItem(info, this.roots);
this.resolvedModuleNames.remove(info.fileName);
this.resolvedModuleNames.remove(info.path);
}
}
@@ -277,7 +282,8 @@ namespace ts.server {
* @param line 1 based index
*/
lineToTextSpan(filename: string, line: number): ts.TextSpan {
const script: ScriptInfo = this.filenameToScript.get(filename);
let path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const script: ScriptInfo = this.filenameToScript.get(path);
const index = script.snap().index;
const lineInfo = index.lineNumberToInfo(line + 1);
@@ -297,7 +303,8 @@ namespace ts.server {
* @param offset 1 based index
*/
lineOffsetToPosition(filename: string, line: number, offset: number): number {
const script: ScriptInfo = this.filenameToScript.get(filename);
let path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const script: ScriptInfo = this.filenameToScript.get(path);
const index = script.snap().index;
const lineInfo = index.lineNumberToInfo(line);
@@ -310,7 +317,8 @@ namespace ts.server {
* @param offset 1-based index
*/
positionToLineOffset(filename: string, position: number): ILineInfo {
const script: ScriptInfo = this.filenameToScript.get(filename);
let path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const script: ScriptInfo = this.filenameToScript.get(path);
const index = script.snap().index;
const lineOffset = index.charOffsetToLineNumberAndPos(position);
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
+43 -35
View File
@@ -773,6 +773,7 @@ namespace ts {
class SourceFileObject extends NodeObject implements SourceFile {
public _declarationBrand: any;
public fileName: string;
public path: Path;
public text: string;
public scriptSnapshot: IScriptSnapshot;
public lineMap: number[];
@@ -1695,15 +1696,17 @@ namespace ts {
class HostCache {
private fileNameToEntry: FileMap<HostFileInformation>;
private _compilationSettings: CompilerOptions;
private currentDirectory: string;
constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) {
constructor(private host: LanguageServiceHost, private getCanonicalFileName: (fileName: string) => string) {
// script id => script index
this.fileNameToEntry = createFileMap<HostFileInformation>(getCanonicalFileName);
this.currentDirectory = host.getCurrentDirectory();
this.fileNameToEntry = createFileMap<HostFileInformation>();
// Initialize the list with the root file names
let rootFileNames = host.getScriptFileNames();
for (let fileName of rootFileNames) {
this.createEntry(fileName);
this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName));
}
// store the compilation settings
@@ -1714,7 +1717,7 @@ namespace ts {
return this._compilationSettings;
}
private createEntry(fileName: string) {
private createEntry(fileName: string, path: Path) {
let entry: HostFileInformation;
let scriptSnapshot = this.host.getScriptSnapshot(fileName);
if (scriptSnapshot) {
@@ -1725,30 +1728,31 @@ namespace ts {
};
}
this.fileNameToEntry.set(fileName, entry);
this.fileNameToEntry.set(path, entry);
return entry;
}
private getEntry(fileName: string): HostFileInformation {
return this.fileNameToEntry.get(fileName);
private getEntry(path: Path): HostFileInformation {
return this.fileNameToEntry.get(path);
}
private contains(fileName: string): boolean {
return this.fileNameToEntry.contains(fileName);
private contains(path: Path): boolean {
return this.fileNameToEntry.contains(path);
}
public getOrCreateEntry(fileName: string): HostFileInformation {
if (this.contains(fileName)) {
return this.getEntry(fileName);
let path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName)
if (this.contains(path)) {
return this.getEntry(path);
}
return this.createEntry(fileName);
return this.createEntry(fileName, path);
}
public getRootFileNames(): string[] {
let fileNames: string[] = [];
this.fileNameToEntry.forEachValue(value => {
this.fileNameToEntry.forEachValue((path, value) => {
if (value) {
fileNames.push(value.hostFileName);
}
@@ -1757,13 +1761,13 @@ namespace ts {
return fileNames;
}
public getVersion(fileName: string): string {
let file = this.getEntry(fileName);
public getVersion(path: Path): string {
let file = this.getEntry(path);
return file && file.version;
}
public getScriptSnapshot(fileName: string): IScriptSnapshot {
let file = this.getEntry(fileName);
public getScriptSnapshot(path: Path): IScriptSnapshot {
let file = this.getEntry(path);
return file && file.scriptSnapshot;
}
}
@@ -1993,7 +1997,7 @@ namespace ts {
}
export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean): DocumentRegistry {
export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry {
// Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have
// for those settings.
let buckets: Map<FileMap<DocumentRegistryEntry>> = {};
@@ -2007,7 +2011,7 @@ namespace ts {
let key = getKeyFromCompilationSettings(settings);
let bucket = lookUp(buckets, key);
if (!bucket && createIfMissing) {
buckets[key] = bucket = createFileMap<DocumentRegistryEntry>(getCanonicalFileName);
buckets[key] = bucket = createFileMap<DocumentRegistryEntry>();
}
return bucket;
}
@@ -2016,14 +2020,13 @@ namespace ts {
let bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === '_').map(name => {
let entries = lookUp(buckets, name);
let sourceFiles: { name: string; refCount: number; references: string[]; }[] = [];
for (let i in entries) {
let entry = entries.get(i);
entries.forEachValue((key, entry) => {
sourceFiles.push({
name: i,
name: key,
refCount: entry.languageServiceRefCount,
references: entry.owners.slice(0)
});
}
});
sourceFiles.sort((x, y) => y.refCount - x.refCount);
return {
bucket: name,
@@ -2049,7 +2052,8 @@ namespace ts {
acquiring: boolean): SourceFile {
let bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true);
let entry = bucket.get(fileName);
let path = toPath(fileName, currentDirectory, getCanonicalFileName);
let entry = bucket.get(path);
if (!entry) {
Debug.assert(acquiring, "How could we be trying to update a document that the registry doesn't have?");
@@ -2061,7 +2065,7 @@ namespace ts {
languageServiceRefCount: 0,
owners: []
};
bucket.set(fileName, entry);
bucket.set(path, entry);
}
else {
// We have an entry for this file. However, it may be for a different version of
@@ -2089,12 +2093,14 @@ namespace ts {
let bucket = getBucketForCompilationSettings(compilationSettings, false);
Debug.assert(bucket !== undefined);
let entry = bucket.get(fileName);
let path = toPath(fileName, currentDirectory, getCanonicalFileName);
let entry = bucket.get(path);
entry.languageServiceRefCount--;
Debug.assert(entry.languageServiceRefCount >= 0);
if (entry.languageServiceRefCount === 0) {
bucket.remove(fileName);
bucket.remove(path);
}
}
@@ -2556,7 +2562,9 @@ namespace ts {
}
}
export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry = createDocumentRegistry()): LanguageService {
export function createLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
let syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
let ruleProvider: formatting.RulesProvider;
let program: Program;
@@ -2565,6 +2573,7 @@ namespace ts {
let useCaseSensitivefileNames = false;
let cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());
let currentDirectory = host.getCurrentDirectory();
// Check if the localized messages json is set, otherwise query the host for it
if (!localizedDiagnosticMessages && host.getLocalizedDiagnosticMessages) {
localizedDiagnosticMessages = host.getLocalizedDiagnosticMessages();
@@ -2579,8 +2588,7 @@ namespace ts {
let getCanonicalFileName = createGetCanonicalFileName(useCaseSensitivefileNames);
function getValidSourceFile(fileName: string): SourceFile {
fileName = normalizeSlashes(fileName);
let sourceFile = program.getSourceFile(getCanonicalFileName(fileName));
let sourceFile = program.getSourceFile(fileName);
if (!sourceFile) {
throw new Error("Could not find file: '" + fileName + "'.");
}
@@ -2641,7 +2649,7 @@ namespace ts {
getNewLine: () => getNewLineOrDefaultFromHost(host),
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
writeFile: (fileName, data, writeByteOrderMark) => { },
getCurrentDirectory: () => host.getCurrentDirectory(),
getCurrentDirectory: () => currentDirectory,
fileExists: (fileName): boolean => {
// stub missing host functionality
Debug.assert(!host.resolveModuleNames);
@@ -2665,9 +2673,8 @@ namespace ts {
if (program) {
let oldSourceFiles = program.getSourceFiles();
for (let oldSourceFile of oldSourceFiles) {
let fileName = oldSourceFile.fileName;
if (!newProgram.getSourceFile(fileName) || changesInCompilationSettingsAffectSyntax) {
documentRegistry.releaseDocument(fileName, oldSettings);
if (!newProgram.getSourceFile(oldSourceFile.fileName) || changesInCompilationSettingsAffectSyntax) {
documentRegistry.releaseDocument(oldSourceFile.fileName, oldSettings);
}
}
}
@@ -2732,7 +2739,8 @@ namespace ts {
}
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
return sourceFile && sourceFile.version === hostCache.getVersion(sourceFile.fileName);
let path = sourceFile.path || toPath(sourceFile.fileName, currentDirectory, getCanonicalFileName);
return sourceFile && sourceFile.version === hostCache.getVersion(path);
}
function programUpToDate(): boolean {