Support go-to-definition for imports of arbitrary files (#42539)

* Support go-to-definition for imports of scripts and arbitrary files

* Support go-to-definition for non-existent files

* Add missing file property

* Use `isExternalModuleNameRelative` instead of `!pathIsBareSpecifier`

* Add partial semantic test

* Combine with symbol search for non-source-file file references

* Fix and accept API baselines

* Fix useless or

* A definition is unverified if the file path was a guess, even if a source file has that path
This commit is contained in:
Andrew Branch
2021-03-01 15:15:09 -08:00
committed by GitHub
parent aa67b16e99
commit 4b67b4a7bc
15 changed files with 164 additions and 27 deletions
+2 -2
View File
@@ -624,7 +624,7 @@ namespace ts.FindAllReferences {
}
if (isSourceFile(node)) {
const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program);
if (!resolvedRef) {
if (!resolvedRef?.file) {
return undefined;
}
const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol);
@@ -656,7 +656,7 @@ namespace ts.FindAllReferences {
if (!symbol) {
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
if (!options.implementations && isStringLiteralLike(node)) {
if (isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ true) || isExternalModuleReference(node.parent) || isImportDeclaration(node.parent) || isImportCall(node.parent)) {
if (isModuleSpecifierLike(node)) {
const fileIncludeReasons = program.getFileIncludeReasons();
const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text)?.resolvedFileName;
const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined;
+30 -9
View File
@@ -2,8 +2,10 @@
namespace ts.GoToDefinition {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
if (resolvedRef) {
return [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.file.fileName)];
const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray;
if (resolvedRef?.file) {
// If `file` is missing, do a symbol-based lookup as well
return fileReferenceDefinition;
}
const node = getTouchingPropertyName(sourceFile, position);
@@ -25,7 +27,7 @@ namespace ts.GoToDefinition {
// Could not find a symbol e.g. node is string or number keyword,
// or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
if (!symbol) {
return getDefinitionInfoForIndexSignatures(node, typeChecker);
return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker));
}
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
@@ -76,7 +78,7 @@ namespace ts.GoToDefinition {
});
}
return getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node);
return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node));
}
/**
@@ -111,24 +113,42 @@ namespace ts.GoToDefinition {
}
}
export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, file: SourceFile } | undefined {
export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined {
const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position);
if (referencePath) {
const file = program.getSourceFileFromReference(sourceFile, referencePath);
return file && { reference: referencePath, file };
return file && { reference: referencePath, fileName: file.fileName, file, unverified: false };
}
const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
if (typeReferenceDirective) {
const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName);
const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217
return file && { reference: typeReferenceDirective, file };
return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false };
}
const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position);
if (libReferenceDirective) {
const file = program.getLibFileFromReference(libReferenceDirective);
return file && { reference: libReferenceDirective, file };
return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false };
}
if (sourceFile.resolvedModules?.size) {
const node = getTokenAtPosition(sourceFile, position);
if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text)) {
const verifiedFileName = sourceFile.resolvedModules.get(node.text)?.resolvedFileName;
const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text);
return {
file: program.getSourceFile(fileName),
fileName,
reference: {
pos: node.getStart(),
end: node.getEnd(),
fileName: node.text
},
unverified: !!verifiedFileName,
};
}
}
return undefined;
@@ -318,7 +338,7 @@ namespace ts.GoToDefinition {
return find(refs, ref => textRangeContainsPositionInclusive(ref, pos));
}
function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo {
return {
fileName: targetFileName,
textSpan: createTextSpanFromBounds(0, 0),
@@ -326,6 +346,7 @@ namespace ts.GoToDefinition {
name,
containerName: undefined!,
containerKind: undefined!, // TODO: GH#18217
unverified,
};
}
+12 -1
View File
@@ -2492,6 +2492,17 @@ namespace ts {
return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName);
}
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
// Go to Definition supports returning a zero-length span at position 0 for
// non-existent files. We need to special-case the conversion of position 0
// to avoid a crash trying to get the text for that file, since this function
// otherwise assumes that 'fileName' is the name of a file that exists.
if (position === 0) {
return { line: 0, character: 0 };
}
return sourceMapper.toLineColumnOffset(fileName, position);
}
function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined {
synchronizeHostData();
const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position));
@@ -2567,7 +2578,7 @@ namespace ts {
getAutoImportProvider,
getApplicableRefactors,
getEditsForRefactor,
toLineColumnOffset: sourceMapper.toLineColumnOffset,
toLineColumnOffset,
getSourceMapper: () => sourceMapper,
clearSourceMapperCache: () => sourceMapper.clearCache(),
prepareCallHierarchy,
+1
View File
@@ -991,6 +991,7 @@ namespace ts {
name: string;
containerKind: ScriptElementKind;
containerName: string;
unverified?: boolean;
/* @internal */ isLocal?: boolean;
}
+8
View File
@@ -1906,6 +1906,14 @@ namespace ts {
});
}
export function isModuleSpecifierLike(node: Node): node is StringLiteralLike {
return isStringLiteralLike(node) && (
isExternalModuleReference(node.parent) ||
isImportDeclaration(node.parent) ||
isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false) && node.parent.arguments[0] === node ||
isImportCall(node.parent) && node.parent.arguments[0] === node);
}
export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier };
export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName {