mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #5462 from Microsoft/useResolvedFileNameWhenResolvingImports
Use dedicated type to store paths
This commit is contained in:
+29
-19
@@ -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
@@ -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
@@ -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
@@ -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}[];
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user