expose type reference resolution for external consumption

This commit is contained in:
Vladimir Matveev
2016-04-01 12:41:01 -07:00
parent 75922c4056
commit aaaa9c0895
13 changed files with 639 additions and 285 deletions
+4 -4
View File
@@ -312,19 +312,19 @@ namespace ts {
}
},
{
name: "librarySearchPaths",
name: "typesSearchPaths",
type: "list",
isTSConfigOnly: true,
element: {
name: "librarySearchPaths",
name: "typesSearchPaths",
type: "string",
isFilePath: true
}
},
{
name: "traceModuleResolution",
name: "traceResolution",
type: "boolean",
description: Diagnostics.Enable_tracing_of_the_module_resolution_process
description: Diagnostics.Enable_tracing_of_the_name_resolution_process
},
{
name: "allowJs",
+29 -5
View File
@@ -2517,7 +2517,7 @@
"category": "Message",
"code": 6084
},
"Enable tracing of the module resolution process.": {
"Enable tracing of the name resolution process.": {
"category": "Message",
"code": 6085
},
@@ -2565,7 +2565,7 @@
"category": "Message",
"code": 6096
},
"File '{0}' exist - use it as a module resolution result.": {
"File '{0}' exist - use it as a name resolution result.": {
"category": "Message",
"code": 6097
},
@@ -2577,11 +2577,11 @@
"category": "Message",
"code": 6099
},
"'package.json' does not have 'typings' field.": {
"'package.json' does not have 'types' field.": {
"category": "Message",
"code": 6100
},
"'package.json' has 'typings' field '{0}' that references '{1}'.": {
"'package.json' has '{0}' field '{1}' that references '{2}'.": {
"category": "Message",
"code": 6101
},
@@ -2597,7 +2597,7 @@
"category": "Message",
"code": 6104
},
"Expected type of 'typings' field in 'package.json' to be 'string', got '{0}'.": {
"Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": {
"category": "Message",
"code": 6105
},
@@ -2637,6 +2637,30 @@
"category": "Error",
"code": 6114
},
"======== Resolving type reference directive '{0}' from '{1}' with compilation root dir '{2}'. ========": {
"category": "Message",
"code": 6115
},
"Resolving using primary search paths...": {
"category": "Message",
"code": 6116
},
"Resolving from node_modules folder...": {
"category": "Message",
"code": 6117
},
"======== Type reference directive '{0}' was successfully resolved to '{1}', primary: {2}. ========": {
"category": "Message",
"code": 6118
},
"======== Type reference directive '{0}' was not resolved. ========": {
"category": "Message",
"code": 6119
},
"Resolving with primary search path '{0}'": {
"category": "Message",
"code": 6120
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
+4 -4
View File
@@ -5505,7 +5505,7 @@ namespace ts {
function processReferenceComments(sourceFile: SourceFile): void {
const triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, LanguageVariant.Standard, sourceText);
const referencedFiles: FileReference[] = [];
const referencedLibraries: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const amdDependencies: { path: string; name: string }[] = [];
let amdModuleName: string;
@@ -5532,8 +5532,8 @@ namespace ts {
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const diagnosticMessage = referencePathMatchResult.diagnosticMessage;
if (fileReference) {
if (referencePathMatchResult.isLibraryReference) {
referencedLibraries.push(fileReference);
if (referencePathMatchResult.isTypeReferenceDirective) {
typeReferenceDirectives.push(fileReference);
}
else {
referencedFiles.push(fileReference);
@@ -5569,7 +5569,7 @@ namespace ts {
}
sourceFile.referencedFiles = referencedFiles;
sourceFile.referencedLibraries = referencedLibraries;
sourceFile.typeReferenceDirectives = typeReferenceDirectives;
sourceFile.amdDependencies = amdDependencies;
sourceFile.moduleName = amdModuleName;
}
+337 -198
View File
@@ -13,9 +13,9 @@ namespace ts {
const emptyArray: any[] = [];
const defaultLibrarySearchPaths = <Path[]>[
"typings/",
"types/",
"node_modules/",
"node_modules/@typings/",
"node_modules/@types/",
];
export const version = "1.9.0";
@@ -41,13 +41,64 @@ namespace ts {
return normalizePath(referencedFileName);
}
export function computeCompilationRoot(rootFileNames: string[], currentDirectory: string, options: CompilerOptions, getCanonicalFileName: (fileName: string) => string): string {
const rootDirOrConfigFilePath = options.rootDir || (options.configFilePath && getDirectoryPath(options.configFilePath));
return rootDirOrConfigFilePath
? getNormalizedAbsolutePath(rootDirOrConfigFilePath, currentDirectory)
: computeCommonSourceDirectoryOfFilenames(rootFileNames, currentDirectory, getCanonicalFileName);
}
function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: (fileName: string) => string): string {
let commonPathComponents: string[];
const failed = forEach(fileNames, sourceFile => {
// Each file contributes into common source file path
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
sourcePathComponents.pop(); // The base file name is not part of the common directory path
if (!commonPathComponents) {
// first file
commonPathComponents = sourcePathComponents;
return;
}
for (let i = 0, n = Math.min(commonPathComponents.length, sourcePathComponents.length); i < n; i++) {
if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) {
if (i === 0) {
// Failed to find any common path component
return true;
}
// New common path found that is 0 -> i-1
commonPathComponents.length = i;
break;
}
}
// If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents
if (sourcePathComponents.length < commonPathComponents.length) {
commonPathComponents.length = sourcePathComponents.length;
}
});
// A common path can not be found when paths span multiple drives on windows, for example
if (failed) {
return "";
}
if (!commonPathComponents) { // Can happen when all input files are .d.ts files
return currentDirectory;
}
return getNormalizedPathFromPathComponents(commonPathComponents);
}
function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void;
function trace(host: ModuleResolutionHost, message: DiagnosticMessage): void {
host.trace(formatMessage.apply(undefined, arguments));
}
function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean {
return compilerOptions.traceModuleResolution && host.trace !== undefined;
return compilerOptions.traceResolution && host.trace !== undefined;
}
function startsWith(str: string, prefix: string): boolean {
@@ -97,6 +148,155 @@ namespace ts {
skipTsx: boolean;
}
function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string {
let jsonContent: { typings?: string, types?: string };
try {
const jsonText = state.host.readFile(packageJsonPath);
jsonContent = jsonText ? <{ typings?: string, types?: string }>JSON.parse(jsonText) : {};
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
jsonContent = {};
}
let typesFile: string;
let fieldName: string;
// first try to read content of 'typings' section (backward compatibility)
if (jsonContent.typings) {
if (typeof jsonContent.typings === "string") {
fieldName = "typings";
typesFile = jsonContent.typings;
}
else {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings);
}
}
}
// then read 'types'
if (!typesFile && jsonContent.types) {
if (typeof jsonContent.types === "string") {
fieldName = "types";
typesFile = jsonContent.types;
}
else {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types);
}
}
}
if (typesFile) {
const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath);
}
return typesFilePath;
}
return undefined;
}
function tryLoadTypeDeclarationFile(searchPath: string, failedLookupLocations: string[], state: ModuleResolutionState) {
let typesFile: string;
const packageJsonPath = combinePaths(searchPath, "package.json");
if (state.host.fileExists(packageJsonPath)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
typesFile = tryReadTypesSection(packageJsonPath, searchPath, state);
if (!typesFile) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_types_field);
}
}
}
else {
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_does_not_exist, packageJsonPath);
}
failedLookupLocations.push(packageJsonPath);
}
if (!typesFile) {
typesFile = "index.d.ts";
}
const combinedPath = normalizePath(combinePaths(searchPath, typesFile));
if (state.host.fileExists(combinedPath)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, combinedPath);
}
return combinedPath;
}
else {
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_does_not_exist, combinedPath);
}
failedLookupLocations.push(combinedPath);
return undefined;
}
}
function getEffectiveTypesPrimarySearchPaths(options: CompilerOptions): string[] {
if (options.typesSearchPaths) {
return options.typesSearchPaths;
}
return options.configFilePath
? [getDirectoryPath(options.configFilePath)].concat(defaultLibrarySearchPaths)
: defaultLibrarySearchPaths;
}
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string, compilationRoot: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(options, host);
const moduleResolutionState: ModuleResolutionState = {
compilerOptions: options,
host: host,
skipTsx: true,
traceEnabled
};
if (traceEnabled) {
trace(host, Diagnostics.Resolving_type_reference_directive_0_from_1_with_compilation_root_dir_2, typeReferenceDirectiveName, containingFile, compilationRoot);
}
const failedLookupLocations: string[] = [];
// Check primary library paths
for (const searchPath of getEffectiveTypesPrimarySearchPaths(options)) {
const primaryPath = combinePaths(compilationRoot, searchPath);
if (traceEnabled) {
trace(host, Diagnostics.Resolving_with_primary_search_path_0, primaryPath);
}
const resolvedFile = tryLoadTypeDeclarationFile(combinePaths(primaryPath, typeReferenceDirectiveName), failedLookupLocations, moduleResolutionState);
if (resolvedFile) {
if (traceEnabled) {
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, true);
}
return {
resolvedTypeReferenceDirective: { primary: true, resolvedFileName: resolvedFile },
failedLookupLocations
};
}
}
if (traceEnabled) {
trace(host, Diagnostics.Resolving_from_node_modules_folder);
}
// check secondary locations
const resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, getDirectoryPath(containingFile), failedLookupLocations, moduleResolutionState);
if (traceEnabled) {
if (resolvedFile) {
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false);
}
else {
trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName);
}
}
return {
resolvedTypeReferenceDirective: resolvedFile
? { primary: false, resolvedFileName: resolvedFile }
: undefined,
failedLookupLocations
};
}
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
if (traceEnabled) {
@@ -422,6 +622,13 @@ namespace ts {
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
*/
function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string {
if (!onlyRecordFailures) {
// check if containig folder exists - if it doesn't then just record failures for all supported extensions without disk probing
const directory = getDirectoryPath(candidate);
if (directory) {
onlyRecordFailures = !directoryProbablyExists(directory, state.host);
}
}
return forEach(extensions, tryLoad);
function tryLoad(ext: string): string {
@@ -431,7 +638,7 @@ namespace ts {
const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext;
if (!onlyRecordFailures && state.host.fileExists(fileName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_module_resolution_result, fileName);
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName);
}
return fileName;
}
@@ -452,36 +659,16 @@ namespace ts {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
let jsonContent: { typings?: string };
try {
const jsonText = state.host.readFile(packageJsonPath);
jsonContent = jsonText ? <{ typings?: string }>JSON.parse(jsonText) : { typings: undefined };
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
jsonContent = { typings: undefined };
}
if (jsonContent.typings) {
if (typeof jsonContent.typings === "string") {
const typingsFile = normalizePath(combinePaths(candidate, jsonContent.typings));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_typings_field_0_that_references_1, jsonContent.typings, typingsFile);
}
const result = loadModuleFromFile(typingsFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typingsFile), state.host), state);
if (result) {
return result;
}
}
else if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_typings_field_in_package_json_to_be_string_got_0, typeof jsonContent.typings);
const typesFile = tryReadTypesSection(packageJsonPath, candidate, state);
if (typesFile) {
const result = loadModuleFromFile(typesFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typesFile), state.host), state);
if (result) {
return result;
}
}
else {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_typings_field);
trace(state.host, Diagnostics.package_json_does_not_have_types_field);
}
}
}
@@ -496,20 +683,31 @@ namespace ts {
return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state);
}
function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// Load only typescript files irrespective of allowJs option if loading from node modules
let result = loadModuleFromFile(candidate, supportedTypeScriptExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
if (result) {
return result;
}
result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
if (result) {
return result;
}
}
function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
directory = normalizeSlashes(directory);
while (true) {
const baseName = getBaseFileName(directory);
if (baseName !== "node_modules") {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// Load only typescript files irrespective of allowJs option if loading from node modules
let result = loadModuleFromFile(candidate, supportedTypeScriptExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
if (result) {
return result;
}
result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
const result =
// first: try to load module as-is
loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state) ||
// second: try to load module from the scope '@types'
loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state);
if (result) {
return result;
}
@@ -696,54 +894,67 @@ namespace ts {
}
}
function loadWithLocalCache<T>(names: string[], containingFile: string, loader: (name: string, containingFile: string) => T): T[] {
if (names.length === 0) {
return [];
}
const resolutions: T[] = [];
const cache: Map<T> = {};
for (const name of names) {
let result: T;
if (hasProperty(cache, name)) {
result = cache[name];
}
else {
result = loader(name, containingFile);
cache[name] = result;
}
resolutions.push(result);
}
return resolutions;
}
export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program {
let program: Program;
let files: SourceFile[] = [];
let fileProcessingDiagnostics = createDiagnosticCollection();
const currentDirectory = host.getCurrentDirectory();
const resolvedLibraries: Map<ResolvedLibrary> = {};
let libraryRoot =
(options.rootDir && ts.toPath(options.rootDir, currentDirectory, host.getCanonicalFileName)) ||
(options.configFilePath && getDirectoryPath(getNormalizedAbsolutePath(options.configFilePath, currentDirectory)));
if (libraryRoot === undefined) {
libraryRoot = computeCommonSourceDirectoryOfFilenames(rootNames);
}
const programDiagnostics = createDiagnosticCollection();
let commonSourceDirectory: string;
let diagnosticsProducingTypeChecker: TypeChecker;
let noDiagnosticsTypeChecker: TypeChecker;
let classifiableNames: Map<string>;
let resolvedTypeReferenceDirectives: Map<ResolvedTypeReferenceDirective> = {};
let fileProcessingDiagnostics = createDiagnosticCollection();
let skipDefaultLib = options.noLib;
const programDiagnostics = createDiagnosticCollection();
const currentDirectory = host.getCurrentDirectory();
const supportedExtensions = getSupportedExtensions(options);
const start = new Date().getTime();
host = host || createCompilerHost(options);
const compilationRoot = computeCompilationRoot(rootNames, currentDirectory, options, getCanonicalFileName);
// Map storing if there is emit blocking diagnostics for given input
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);
const resolveModuleNamesWorker = host.resolveModuleNames
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
: ((moduleNames: string[], containingFile: string) => {
const resolvedModuleNames: ResolvedModule[] = [];
// resolveModuleName does not store any results between calls.
// lookup is a local cache to avoid resolving the same module name several times
const lookup: Map<ResolvedModule> = {};
for (const moduleName of moduleNames) {
let resolvedName: ResolvedModule;
if (hasProperty(lookup, moduleName)) {
resolvedName = lookup[moduleName];
}
else {
resolvedName = resolveModuleName(moduleName, containingFile, options, host).resolvedModule;
lookup[moduleName] = resolvedName;
}
resolvedModuleNames.push(resolvedName);
}
return resolvedModuleNames;
});
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModule[];
if (host.resolveModuleNames) {
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile);
}
else {
const loader = (moduleName: string, containingFile: string) => resolveModuleName(moduleName, containingFile, options, host).resolvedModule;
resolveModuleNamesWorker = (moduleNames, containingFile) => loadWithLocalCache(moduleNames, containingFile, loader);
}
let resolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[];
if (host.resolveTypeReferenceDirectives) {
resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile);
}
else {
const loader = (typesRef: string, containingFile: string) => resolveTypeReferenceDirective(typesRef, containingFile, compilationRoot, options, host).resolvedTypeReferenceDirective;
resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader);
}
const filesByName = createFileMap<SourceFile>();
// stores 'filename -> file association' ignoring case
@@ -759,7 +970,10 @@ namespace ts {
(oldOptions.target !== options.target) ||
(oldOptions.noLib !== options.noLib) ||
(oldOptions.jsx !== options.jsx) ||
(oldOptions.allowJs !== options.allowJs)) {
(oldOptions.allowJs !== options.allowJs) ||
(oldOptions.rootDir !== options.rootDir) ||
(oldOptions.typesSearchPaths !== options.typesSearchPaths) ||
(oldOptions.configFilePath !== options.configFilePath)) {
oldProgram = undefined;
}
}
@@ -808,7 +1022,8 @@ namespace ts {
getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(),
getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(),
getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(),
getFileProcessingDiagnostics: () => fileProcessingDiagnostics
getFileProcessingDiagnostics: () => fileProcessingDiagnostics,
resolvedTypeReferenceDirectives
};
verifyCompilerOptions();
@@ -901,26 +1116,33 @@ namespace ts {
return false;
}
if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) {
// 'types' references has changed
return false;
}
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
if (resolveModuleNamesWorker) {
const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral);
const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory));
const resolutions = resolveModuleNamesWorker(moduleNames, newSourceFilePath);
// ensure that module resolution results are still correct
for (let i = 0; i < moduleNames.length; i++) {
const newResolution = resolutions[i];
const oldResolution = getResolvedModule(oldSourceFile, moduleNames[i]);
const resolutionChanged = oldResolution
? !newResolution ||
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
: newResolution;
if (resolutionChanged) {
return false;
}
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
if (resolutionsChanged) {
return false;
}
}
// pass the cache of module resolutions from the old source file
if (resolveTypeReferenceDirectiveNamesWorker) {
const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, x => x.fileName);
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath);
// ensure that types resolutions are still correct
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
if (resolutionsChanged) {
return false;
}
}
// pass the cache of module/types resolutions from the old source file
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
modifiedSourceFiles.push(newSourceFile);
}
else {
@@ -943,6 +1165,7 @@ namespace ts {
for (const modifiedFile of modifiedSourceFiles) {
fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile);
}
resolvedTypeReferenceDirectives = oldProgram.resolvedTypeReferenceDirectives;
oldProgram.structureIsReused = true;
return true;
@@ -1510,8 +1733,8 @@ namespace ts {
const basePath = getDirectoryPath(fileName);
if (!options.noResolve) {
processReferencedFiles(file, basePath, /*isDefaultLib*/ isDefaultLib);
processReferencedLibraries(file, libraryRoot);
processReferencedFiles(file, basePath, isDefaultLib);
processTypeReferenceDirectives(file, compilationRoot);
}
// always process imported modules to record module name resolutions
@@ -1535,97 +1758,57 @@ namespace ts {
});
}
function findLibraryDefinition(searchPath: string) {
let typingFilename = "index.d.ts";
const packageJsonPath = combinePaths(searchPath, "package.json");
if (host.fileExists(packageJsonPath)) {
let package: { typings?: string } = {};
try {
package = JSON.parse(host.readFile(packageJsonPath));
}
catch (e) { }
function processTypeReferenceDirectives(file: SourceFile, compilationRoot: string) {
const typeDirectives = map(file.typeReferenceDirectives, l => l.fileName);
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName);
if (package.typings) {
typingFilename = package.typings;
}
}
const combinedPath = normalizePath(combinePaths(searchPath, typingFilename));
return host.fileExists(combinedPath) ? combinedPath : undefined;
}
function processReferencedLibraries(file: SourceFile, compilationRoot: string) {
const primarySearchPaths = map(getEffectiveLibraryPrimarySearchPaths(), path => combinePaths(compilationRoot, path));
const failedSearchPaths: string[] = [];
const moduleResolutionState: ModuleResolutionState = {
compilerOptions: options,
host: host,
skipTsx: true,
traceEnabled: false
};
for (const ref of file.referencedLibraries) {
for (let i = 0; i < typeDirectives.length; i++) {
const ref = file.typeReferenceDirectives[i];
const resolvedTypeReferenceDirective = resolutions[i];
// store resolved type directive on the file
setResolvedTypeReferenceDirective(file, ref.fileName, resolvedTypeReferenceDirective);
// If we already found this library as a primary reference, or failed to find it, nothing to do
const previousResolution = resolvedLibraries[ref.fileName];
const previousResolution = resolvedTypeReferenceDirectives[ref.fileName];
if (previousResolution && (previousResolution.primary || (previousResolution.resolvedFileName === undefined))) {
continue;
}
let foundIt = false;
// Check primary library paths
for (const primaryPath of primarySearchPaths) {
const searchPath = combinePaths(primaryPath, ref.fileName);
const resolvedFile = findLibraryDefinition(searchPath);
if (resolvedFile) {
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: resolvedFile };
processSourceFile(resolvedFile, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
foundIt = true;
break;
let saveResolution = true;
if (resolvedTypeReferenceDirective) {
if (resolvedTypeReferenceDirective.primary) {
// resolved from the primary path
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
}
}
// Check secondary library paths
if (!foundIt) {
const secondaryResult = loadModuleFromNodeModules(ref.fileName, file.fileName, failedSearchPaths, moduleResolutionState);
if (secondaryResult) {
foundIt = true;
else {
// If we already resolved to this file, it must have been a secondary reference. Check file contents
// for sameness and possibly issue an error
if (previousResolution) {
const otherFileText = host.readFile(secondaryResult);
const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName);
if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) {
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos,
Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_the_typings_folder_to_resolve_this_conflict,
ref.fileName,
secondaryResult,
resolvedTypeReferenceDirective.resolvedFileName,
previousResolution.resolvedFileName));
}
// don't overwrite previous resolution result
saveResolution = false;
}
else {
// First resolution of this library
resolvedLibraries[ref.fileName] = { primary: false, resolvedFileName: secondaryResult };
processSourceFile(secondaryResult, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
}
}
}
if (!foundIt) {
else {
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos, Diagnostics.Cannot_find_name_0, ref.fileName));
// Create an entry as a primary lookup result so we don't keep doing this
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: undefined };
}
if (saveResolution) {
resolvedTypeReferenceDirectives[ref.fileName] = resolvedTypeReferenceDirective;
}
}
}
function getEffectiveLibraryPrimarySearchPaths(): Path[] {
return <Path[]>(options.librarySearchPaths ||
(options.configFilePath ?
[options.configFilePath].concat(defaultLibrarySearchPaths) :
defaultLibrarySearchPaths));
}
function getCanonicalFileName(fileName: string): string {
return host.getCanonicalFileName(fileName);
}
@@ -1672,50 +1855,6 @@ namespace ts {
return;
}
function computeCommonSourceDirectoryOfFilenames(fileNames: string[]): string {
let commonPathComponents: string[];
const failed = forEach(fileNames, sourceFile => {
// Each file contributes into common source file path
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
sourcePathComponents.pop(); // The base file name is not part of the common directory path
if (!commonPathComponents) {
// first file
commonPathComponents = sourcePathComponents;
return;
}
for (let i = 0, n = Math.min(commonPathComponents.length, sourcePathComponents.length); i < n; i++) {
if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) {
if (i === 0) {
// Failed to find any common path component
return true;
}
// New common path found that is 0 -> i-1
commonPathComponents.length = i;
break;
}
}
// If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents
if (sourcePathComponents.length < commonPathComponents.length) {
commonPathComponents.length = sourcePathComponents.length;
}
});
// A common path can not be found when paths span multiple drives on windows, for example
if (failed) {
return "";
}
if (!commonPathComponents) { // Can happen when all input files are .d.ts files
return currentDirectory;
}
return getNormalizedPathFromPathComponents(commonPathComponents);
}
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
const fileNames: string[] = [];
for (const file of sourceFiles) {
@@ -1723,7 +1862,7 @@ namespace ts {
fileNames.push(file.fileName);
}
}
return computeCommonSourceDirectoryOfFilenames(fileNames);
return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName);
}
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
+21 -3
View File
@@ -1536,7 +1536,7 @@ namespace ts {
amdDependencies: AmdDependency[];
moduleName: string;
referencedFiles: FileReference[];
referencedLibraries: FileReference[];
typeReferenceDirectives: FileReference[];
languageVariant: LanguageVariant;
isDeclarationFile: boolean;
@@ -1584,6 +1584,7 @@ namespace ts {
// It is used to resolve module names in the checker.
// Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead
/* @internal */ resolvedModules: Map<ResolvedModule>;
/* @internal */ resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
/* @internal */ imports: LiteralExpression[];
/* @internal */ moduleAugmentations: LiteralExpression[];
}
@@ -1660,6 +1661,7 @@ namespace ts {
/* @internal */ getTypeCount(): number;
/* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection;
/* @internal */ resolvedTypeReferenceDirectives: Map<ResolvedTypeReferenceDirective>;
// For testing purposes only.
/* @internal */ structureIsReused?: boolean;
}
@@ -2416,7 +2418,7 @@ namespace ts {
jsx?: JsxEmit;
reactNamespace?: string;
listFiles?: boolean;
librarySearchPaths?: string[];
typesSearchPaths?: string[];
locale?: string;
mapRoot?: string;
module?: ModuleKind;
@@ -2455,7 +2457,7 @@ namespace ts {
baseUrl?: string;
paths?: PathSubstitutions;
rootDirs?: RootPaths;
traceModuleResolution?: boolean;
traceResolution?: boolean;
allowSyntheticDefaultImports?: boolean;
allowJs?: boolean;
noImplicitUseStrict?: boolean;
@@ -2760,6 +2762,18 @@ namespace ts {
failedLookupLocations: string[];
}
export interface ResolvedTypeReferenceDirective {
// True if the type declaration file was found in a primary lookup location
primary: boolean;
// The location of the .d.ts file we located, or undefined if resolution failed
resolvedFileName?: string;
}
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
failedLookupLocations: string[];
}
export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
getCancellationToken?(): CancellationToken;
@@ -2779,6 +2793,10 @@ namespace ts {
* 'throw new Error("NotImplemented")'
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
/**
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
*/
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
}
export interface TextSpan {
+41 -4
View File
@@ -6,7 +6,7 @@ namespace ts {
fileReference?: FileReference;
diagnosticMessage?: DiagnosticMessage;
isNoDefaultLib?: boolean;
isLibraryReference?: boolean;
isTypeReferenceDirective?: boolean;
}
export interface SynthesizedNode extends Node {
@@ -119,6 +119,43 @@ namespace ts {
sourceFile.resolvedModules[moduleNameText] = resolvedModule;
}
export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void {
if (!sourceFile.resolvedTypeReferenceDirectiveNames) {
sourceFile.resolvedTypeReferenceDirectiveNames = {};
}
sourceFile.resolvedTypeReferenceDirectiveNames[typeReferenceDirectiveName] = resolvedTypeReferenceDirective;
}
/* @internal */
export function moduleResolutionIsEqualTo(oldResolution: ResolvedModule, newResolution: ResolvedModule): boolean {
return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport;
}
/* @internal */
export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean {
return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary;
}
/* @internal */
export function hasChangesInResolutions<T>(names: string[], newResolutions: T[], oldResolutions: Map<T>, comparer: (oldResolution: T, newResolution: T) => boolean): boolean {
if (names.length !== newResolutions.length) {
return false;
}
for (let i = 0; i < names.length; i++) {
const newResolution = newResolutions[i];
const oldResolution = oldResolutions && hasProperty(oldResolutions, names[i]) ? oldResolutions[names[i]] : undefined;
const changed =
oldResolution
? !newResolution || !comparer(oldResolution, newResolution)
: newResolution;
if (changed) {
return true;
}
}
return false;
}
// Returns true if this node contains a parse error anywhere underneath it.
export function containsParseError(node: Node): boolean {
aggregateChildData(node);
@@ -499,7 +536,7 @@ namespace ts {
}
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
export let fullTripleSlashReferenceLibraryRegEx = /^(\/\/\/\s*<reference\s+library\s*=\s*)('|")(.+?)\2.*?\/>/;
export let fullTripleSlashReferenceTypeReferenceDirectiveRegEx = /^(\/\/\/\s*<reference\s+types\s*=\s*)('|")(.+?)\2.*?\/>/;
export let fullTripleSlashAMDReferencePathRegEx = /^(\/\/\/\s*<amd-dependency\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
export function isTypeNode(node: Node): boolean {
@@ -1551,7 +1588,7 @@ namespace ts {
}
else {
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
const refLibResult = !refMatchResult && fullTripleSlashReferenceLibraryRegEx.exec(comment);
const refLibResult = !refMatchResult && fullTripleSlashReferenceTypeReferenceDirectiveRegEx.exec(comment);
if (refMatchResult || refLibResult) {
const start = commentRange.pos;
const end = commentRange.end;
@@ -1562,7 +1599,7 @@ namespace ts {
fileName: (refMatchResult || refLibResult)[3]
},
isNoDefaultLib: false,
isLibraryReference: !!refLibResult
isTypeReferenceDirective: !!refLibResult
};
}
+1 -1
View File
@@ -144,7 +144,7 @@ class CompilerBaselineRunner extends RunnerBase {
});
it (`Correct module resolution tracing for ${fileName}`, () => {
if (options.traceModuleResolution) {
if (options.traceResolution) {
Harness.Baseline.runBaseline("Correct sourcemap content for " + fileName, justName.replace(/\.tsx?$/, ".trace.json"), () => {
return JSON.stringify(result.traceResults || [], undefined, 4);
});
+5 -5
View File
@@ -222,7 +222,7 @@ namespace FourSlash {
function tryAdd(path: string) {
const inputFile = inputFiles[path];
if (inputFile && !Harness.isLibraryFile(path)) {
languageServiceAdapterHost.addScript(path, inputFile);
languageServiceAdapterHost.addScript(path, inputFile, /*isRootFile*/ true);
return true;
}
}
@@ -268,7 +268,7 @@ namespace FourSlash {
if (startResolveFileRef) {
// Add the entry-point file itself into the languageServiceShimHost
this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content);
this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content, /*isRootFile*/ true);
const resolvedResult = languageServiceAdapter.getPreProcessedFileInfo(startResolveFileRef.fileName, startResolveFileRef.content);
const referencedFiles: ts.FileReference[] = resolvedResult.referencedFiles;
@@ -292,18 +292,18 @@ namespace FourSlash {
// Check if no-default-lib flag is false and if so add default library
if (!resolvedResult.isLibFile) {
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName,
Harness.Compiler.getDefaultLibrarySourceFile().text);
Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false);
}
}
else {
// resolveReference file-option is not specified then do not resolve any files and include all inputFiles
ts.forEachKey(this.inputFiles, fileName => {
if (!Harness.isLibraryFile(fileName)) {
this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName]);
this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName], /*isRootFile*/ true);
}
});
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName,
Harness.Compiler.getDefaultLibrarySourceFile().text);
Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false);
}
this.formatCodeOptions = {
+6 -1
View File
@@ -65,6 +65,11 @@ namespace Utils {
return Buffer ? (new Buffer(s)).toString("utf8") : s;
}
export function byteLength(s: string, encoding?: string): number {
// stub implementation if Buffer is not available (in-browser case)
return Buffer ? Buffer.byteLength(s, encoding) : s.length;
}
export function evalFile(fileContents: string, fileName: string, nodeContext?: any) {
const environment = getExecutionEnvironment();
switch (environment) {
@@ -1042,7 +1047,7 @@ namespace Harness {
options.newLine);
let traceResults: string[];
if (options.traceModuleResolution) {
if (options.traceResolution) {
traceResults = [];
compilerHost.trace = text => traceResults.push(text);
}
+39 -7
View File
@@ -9,7 +9,7 @@ namespace Harness.LanguageService {
public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = [];
private lineMap: number[] = undefined;
constructor(public fileName: string, public content: string) {
constructor(public fileName: string, public content: string, public isRootFile: boolean) {
this.setContent(content);
}
@@ -135,7 +135,13 @@ namespace Harness.LanguageService {
public getFilenames(): string[] {
const fileNames: string[] = [];
ts.forEachKey(this.fileNameToScript, (fileName) => { fileNames.push(fileName); });
ts.forEachValue(this.fileNameToScript, (scriptInfo) => {
if (scriptInfo.isRootFile) {
// only include root files here
// usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir.
fileNames.push(scriptInfo.fileName);
}
});
return fileNames;
}
@@ -143,8 +149,8 @@ namespace Harness.LanguageService {
return ts.lookUp(this.fileNameToScript, fileName);
}
public addScript(fileName: string, content: string): void {
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
public addScript(fileName: string, content: string, isRootFile: boolean): void {
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content, isRootFile);
}
public editScript(fileName: string, start: number, end: number, newText: string) {
@@ -177,7 +183,7 @@ namespace Harness.LanguageService {
getCompilationSettings() { return this.settings; }
getCancellationToken() { return this.cancellationToken; }
getCurrentDirectory(): string { return ""; }
getDefaultLibFileName(): string { return ""; }
getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; }
getScriptFileNames(): string[] { return this.getFilenames(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
const script = this.getScriptInfo(fileName);
@@ -210,6 +216,7 @@ namespace Harness.LanguageService {
private nativeHost: NativeLanguageServiceHost;
public getModuleResolutionsForFile: (fileName: string) => string;
public getTypeReferenceDirectiveResolutionsForFile: (fileName: string) => string;
constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
super(cancellationToken, options);
@@ -236,12 +243,28 @@ namespace Harness.LanguageService {
}
return JSON.stringify(imports);
};
this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => {
const scriptInfo = this.getScriptInfo(fileName);
const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false);
const resolutions: ts.Map<ts.ResolvedTypeReferenceDirective> = {};
const rootFiles = this.getFilenames();
const currentDirectory = this.getCurrentDirectory();
const settings = this.nativeHost.getCompilationSettings();
const compilationRoot = ts.computeCompilationRoot(rootFiles, currentDirectory, settings, ts.createGetCanonicalFileName(false));
for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) {
const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, compilationRoot, settings, moduleResolutionHost);
if (resolutionInfo.resolvedTypeReferenceDirective.resolvedFileName) {
resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective;
}
}
return JSON.stringify(resolutions);
};
}
}
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); }
editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); }
positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); }
@@ -442,6 +465,7 @@ namespace Harness.LanguageService {
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo {
let shimResult: {
referencedFiles: ts.IFileReference[];
typeReferenceDirectives: ts.IFileReference[];
importedFiles: ts.IFileReference[];
isLibFile: boolean;
};
@@ -453,7 +477,8 @@ namespace Harness.LanguageService {
referencedFiles: [],
importedFiles: [],
ambientExternalModules: [],
isLibFile: shimResult.isLibFile
isLibFile: shimResult.isLibFile,
typeReferenceDirectives: []
};
ts.forEach(shimResult.referencedFiles, refFile => {
@@ -472,6 +497,13 @@ namespace Harness.LanguageService {
});
});
ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => {
convertResult.importedFiles.push({
fileName: typeRefDirective.path,
pos: typeRefDirective.position,
end: typeRefDirective.position + typeRefDirective.length
});
});
return convertResult;
}
}
+53 -17
View File
@@ -81,8 +81,14 @@ namespace ts.server {
}
}
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations {
lastCheckTime: number;
interface Timestamped {
lastCheckTime?: number;
}
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations, Timestamped {
}
interface TimestampedResolvedTypeReferenceDirective extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, Timestamped {
}
export class LSHost implements ts.LanguageServiceHost {
@@ -90,13 +96,17 @@ namespace ts.server {
compilationSettings: ts.CompilerOptions;
filenameToScript: ts.FileMap<ScriptInfo>;
roots: ScriptInfo[] = [];
compilationRoot: string;
private resolvedModuleNames: ts.FileMap<Map<TimestampedResolvedModule>>;
private resolvedTypeReferenceDirectives: ts.FileMap<Map<TimestampedResolvedTypeReferenceDirective>>;
private moduleResolutionHost: ts.ModuleResolutionHost;
private getCanonicalFileName: (fileName: string) => string;
constructor(public host: ServerHost, public project: Project) {
this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<TimestampedResolvedTypeReferenceDirective>>();
this.filenameToScript = createFileMap<ScriptInfo>();
this.moduleResolutionHost = {
fileExists: fileName => this.fileExists(fileName),
@@ -105,46 +115,51 @@ namespace ts.server {
};
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
private resolveNamesWithLocalCache<T extends Timestamped & { failedLookupLocations: string[] }, R>(
names: string[],
containingFile: string,
cache: ts.FileMap<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResult: (s: T) => R): R[] {
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const currentResolutionsInFile = this.resolvedModuleNames.get(path);
const newResolutions: Map<TimestampedResolvedModule> = {};
const resolvedModules: ResolvedModule[] = [];
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = {};
const resolvedModules: R[] = [];
const compilerOptions = this.getCompilationSettings();
for (const moduleName of moduleNames) {
for (const name of names) {
// check if this is a duplicate entry in the list
let resolution = lookUp(newResolutions, moduleName);
let resolution = lookUp(newResolutions, name);
if (!resolution) {
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, moduleName);
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name);
if (moduleResolutionIsValid(existingResolution)) {
// ok, it is safe to use existing module resolution results
// ok, it is safe to use existing name resolution results
resolution = existingResolution;
}
else {
resolution = <TimestampedResolvedModule>resolveModuleName(moduleName, containingFile, compilerOptions, this.moduleResolutionHost);
resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost);
resolution.lastCheckTime = Date.now();
newResolutions[moduleName] = resolution;
newResolutions[name] = resolution;
}
}
ts.Debug.assert(resolution !== undefined);
resolvedModules.push(resolution.resolvedModule);
resolvedModules.push(getResult(resolution));
}
// replace old results with a new one
this.resolvedModuleNames.set(path, newResolutions);
cache.set(path, newResolutions);
return resolvedModules;
function moduleResolutionIsValid(resolution: TimestampedResolvedModule): boolean {
function moduleResolutionIsValid(resolution: T): boolean {
if (!resolution) {
return false;
}
if (resolution.resolvedModule) {
if (getResult(resolution)) {
// TODO: consider checking failedLookupLocations
// TODO: use lastCheckTime to track expiration for module name resolution
return true;
@@ -156,6 +171,20 @@ namespace ts.server {
}
}
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
if (this.compilationRoot === undefined) {
this.compilationRoot = computeCompilationRoot(this.getScriptFileNames(), this.getCurrentDirectory(), this.getCompilationSettings(), this.getCanonicalFileName);
}
const loader = (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => {
return resolveTypeReferenceDirective(name, containingFile, this.compilationRoot, options, this.moduleResolutionHost);
};
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, loader, m => m.resolvedTypeReferenceDirective);
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule);
}
getDefaultLibFileName() {
const nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath()));
return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
@@ -172,6 +201,7 @@ namespace ts.server {
this.compilationSettings = opt;
// conservatively assume that changing compiler options might affect module resolution strategy
this.resolvedModuleNames.clear();
this.resolvedTypeReferenceDirectives.clear();
}
lineAffectsRefs(filename: string, line: number) {
@@ -212,6 +242,7 @@ namespace ts.server {
if (!info.isOpen) {
this.filenameToScript.remove(info.path);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
}
@@ -231,6 +262,8 @@ namespace ts.server {
if (!this.filenameToScript.contains(info.path)) {
this.filenameToScript.set(info.path, info);
this.roots.push(info);
// reset compilation root
this.compilationRoot = undefined;
}
}
@@ -239,6 +272,9 @@ namespace ts.server {
this.filenameToScript.remove(info.path);
this.roots = copyListRemovingItem(info, this.roots);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
// reset compilation root
this.compilationRoot = undefined;
}
}
+50 -15
View File
@@ -125,6 +125,7 @@ namespace ts {
}
export interface PreProcessedFileInfo {
referencedFiles: FileReference[];
typeReferenceDirectives: FileReference[];
importedFiles: FileReference[];
ambientExternalModules: string[];
isLibFile: boolean;
@@ -792,7 +793,7 @@ namespace ts {
public amdDependencies: { name: string; path: string }[];
public moduleName: string;
public referencedFiles: FileReference[];
public referencedLibraries: FileReference[];
public typeReferenceDirectives: FileReference[];
public syntacticDiagnostics: Diagnostic[];
public referenceDiagnostics: Diagnostic[];
@@ -814,6 +815,7 @@ namespace ts {
public identifiers: Map<string>;
public nameTable: Map<number>;
public resolvedModules: Map<ResolvedModule>;
public resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
public imports: LiteralExpression[];
public moduleAugmentations: LiteralExpression[];
private namedDeclarations: Map<Declaration[]>;
@@ -1040,6 +1042,7 @@ namespace ts {
* host specific questions using 'getScriptSnapshot'.
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
directoryExists?(directoryName: string): boolean;
}
@@ -2147,6 +2150,7 @@ namespace ts {
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const importedFiles: FileReference[] = [];
let ambientExternalModules: { ref: FileReference, depth: number }[];
let isNoDefaultLib = false;
@@ -2175,7 +2179,11 @@ namespace ts {
isNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const fileReference = referencePathMatchResult.fileReference;
if (fileReference) {
referencedFiles.push(fileReference);
const collection = referencePathMatchResult.isTypeReferenceDirective
? typeReferenceDirectives
: referencedFiles;
collection.push(fileReference);
}
}
});
@@ -2476,7 +2484,7 @@ namespace ts {
importedFiles.push(decl.ref);
}
}
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
}
else {
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
@@ -2494,7 +2502,7 @@ namespace ts {
}
}
}
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
}
}
@@ -2831,7 +2839,7 @@ namespace ts {
getCurrentDirectory: () => currentDirectory,
fileExists: (fileName): boolean => {
// stub missing host functionality
Debug.assert(!host.resolveModuleNames);
Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives);
return hostCache.getOrCreateEntry(fileName) !== undefined;
},
readFile: (fileName): string => {
@@ -2840,7 +2848,7 @@ namespace ts {
return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
},
directoryExists: directoryName => {
Debug.assert(!host.resolveModuleNames);
Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives);
return directoryProbablyExists(directoryName, host);
}
};
@@ -2851,6 +2859,11 @@ namespace ts {
if (host.resolveModuleNames) {
compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile);
}
if (host.resolveTypeReferenceDirectives) {
compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => {
return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile);
};
}
const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program);
@@ -4671,6 +4684,26 @@ namespace ts {
}
}
function findReferenceInPosition(refs: FileReference[], pos: number): FileReference {
for (const ref of refs) {
if (ref.pos <= pos && pos < ref.end) {
return ref;
}
}
return undefined;
}
function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
return {
fileName: targetFileName,
textSpan: createTextSpanFromBounds(0, 0),
kind: ScriptElementKind.scriptElement,
name: name,
containerName: undefined,
containerKind: undefined
};
}
/// Goto definition
function getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
synchronizeHostData();
@@ -4690,18 +4723,20 @@ namespace ts {
}
/// Triple slash reference comments
const comment = forEach(sourceFile.referencedFiles, r => (r.pos <= position && position < r.end) ? r : undefined);
const comment = findReferenceInPosition(sourceFile.referencedFiles, position);
if (comment) {
const referenceFile = tryResolveScriptReference(program, sourceFile, comment);
if (referenceFile) {
return [{
fileName: referenceFile.fileName,
textSpan: createTextSpanFromBounds(0, 0),
kind: ScriptElementKind.scriptElement,
name: comment.fileName,
containerName: undefined,
containerKind: undefined
}];
return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)];
}
return undefined;
}
// Type reference directives
const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
if (typeReferenceDirective) {
const referenceFile = lookUp(program.resolvedTypeReferenceDirectives, typeReferenceDirective.fileName);
if (referenceFile && referenceFile.resolvedFileName) {
return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)];
}
return undefined;
}
+49 -21
View File
@@ -67,6 +67,7 @@ namespace ts {
useCaseSensitiveFileNames?(): boolean;
getModuleResolutionsForFile?(fileName: string): string;
getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string;
directoryExists(directoryName: string): boolean;
}
@@ -281,6 +282,7 @@ namespace ts {
private tracingEnabled = false;
public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModule[];
public resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[];
public directoryExists: (directoryName: string) => boolean;
constructor(private shimHost: LanguageServiceShimHost) {
@@ -298,6 +300,12 @@ namespace ts {
if ("directoryExists" in this.shimHost) {
this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName);
}
if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) {
this.resolveTypeReferenceDirectives = (typeDirectiveNames: string[], containingFile: string) => {
const typeDirectivesForFile = <Map<ResolvedTypeReferenceDirective>>JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile));
return map(typeDirectiveNames, name => lookUp(typeDirectivesForFile, name));
};
}
}
public log(s: string): void {
@@ -899,6 +907,7 @@ namespace ts {
class CoreServicesShimObject extends ShimBase implements CoreServicesShim {
private logPerformance = false;
private getCanonicalFileName = createGetCanonicalFileName(false);
constructor(factory: ShimFactory, public logger: Logger, private host: CoreServicesShimHostAdapter) {
super(factory);
@@ -919,38 +928,57 @@ namespace ts {
});
}
public computeCompilationRoot(filesJson: string, currentDirectory: string, compilerOptionsJson: string): string {
return this.forwardJSONCall("computeCompilationRoot", () => {
const files = <string[]>JSON.parse(filesJson);
const compilerOptions = <CompilerOptions>JSON.parse(compilerOptionsJson);
return computeCompilationRoot(files, currentDirectory, compilerOptions, this.getCanonicalFileName);
});
}
public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilationRoot: string, compilerOptionsJson: string): string {
return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => {
const compilerOptions = <CompilerOptions>JSON.parse(compilerOptionsJson);
const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilationRoot, compilerOptions, this.host);
return {
resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined,
primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true,
failedLookupLocations: result.failedLookupLocations
};
});
}
public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
return this.forwardJSONCall(
"getPreProcessedFileInfo('" + fileName + "')",
() => {
// for now treat files as JavaScript
const result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
const convertResult = {
referencedFiles: <IFileReference[]>[],
importedFiles: <IFileReference[]>[],
return {
referencedFiles: this.convertFileReferences(result.referencedFiles),
importedFiles: this.convertFileReferences(result.importedFiles),
ambientExternalModules: result.ambientExternalModules,
isLibFile: result.isLibFile
isLibFile: result.isLibFile,
typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives)
};
forEach(result.referencedFiles, refFile => {
convertResult.referencedFiles.push({
path: normalizePath(refFile.fileName),
position: refFile.pos,
length: refFile.end - refFile.pos
});
});
forEach(result.importedFiles, importedFile => {
convertResult.importedFiles.push({
path: normalizeSlashes(importedFile.fileName),
position: importedFile.pos,
length: importedFile.end - importedFile.pos
});
});
return convertResult;
});
}
private convertFileReferences(refs: FileReference[]): IFileReference[] {
if (!refs) {
return undefined;
}
const result: IFileReference[] = [];
for (const ref of refs) {
result.push({
path: normalizeSlashes(ref.fileName),
position: ref.pos,
length: ref.end - ref.pos
});
}
return result;
}
public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
return this.forwardJSONCall(
`getTSConfigFileInfo('${fileName}')`,