mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -991,6 +991,7 @@ namespace ts {
|
||||
name: string;
|
||||
containerKind: ScriptElementKind;
|
||||
containerName: string;
|
||||
unverified?: boolean;
|
||||
/* @internal */ isLocal?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user