Prototype resolving to JS when go-to-def aliases all resolve to ambient declarations

This commit is contained in:
Andrew Branch
2022-02-25 11:50:11 -08:00
parent aa920c00f2
commit 81ec4fd493
10 changed files with 236 additions and 50 deletions

View File

@@ -882,6 +882,13 @@ namespace ts {
description: Diagnostics.Allow_accessing_UMD_globals_from_modules,
defaultValueDescription: false,
},
{
name: "noDtsResolution",
type: "boolean",
affectsModuleResolution: true,
category: Diagnostics.Modules,
defaultValueDescription: false,
},
// Source Maps
{

View File

@@ -69,7 +69,8 @@ namespace ts {
JavaScript, /** '.js' or '.jsx' */
Json, /** '.json' */
TSConfig, /** '.json' with `tsconfig` used instead of `index` */
DtsOnly /** Only '.d.ts' */
DtsOnly, /** Only '.d.ts' */
TypeScriptButNotDts,
}
interface PathAndPackageId {
@@ -1289,7 +1290,19 @@ namespace ts {
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference);
let extensions;
if (lookupConfig) {
extensions = tsconfigExtensions;
}
else if (compilerOptions.noDtsResolution) {
extensions = [Extensions.TypeScriptButNotDts];
if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript);
if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json);
}
else {
extensions = compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions;
}
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, redirectedReference);
}
function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations {
@@ -1298,6 +1311,11 @@ namespace ts {
const failedLookupLocations: string[] = [];
// conditions are only used by the node12/nodenext resolver - there's no priority order in the list,
//it's essentially a set (priority is determined by object insertion order in the object we look at).
const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"];
if (compilerOptions.noDtsResolution) {
conditions.pop();
}
const state: ModuleResolutionState = {
compilerOptions,
host,
@@ -1305,7 +1323,7 @@ namespace ts {
failedLookupLocations,
packageJsonInfoCache: cache,
features,
conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"]
conditions,
};
const result = forEach(extensions, ext => tryResolve(ext));
@@ -1532,20 +1550,22 @@ namespace ts {
default: return tryExtension(Extension.Dts);
}
case Extensions.TypeScript:
case Extensions.TypeScriptButNotDts:
const useDts = extensions === Extensions.TypeScript;
switch (originalExtension) {
case Extension.Mjs:
case Extension.Mts:
case Extension.Dmts:
return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts);
return tryExtension(Extension.Mts) || (useDts ? tryExtension(Extension.Dmts) : undefined);
case Extension.Cjs:
case Extension.Cts:
case Extension.Dcts:
return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts);
return tryExtension(Extension.Cts) || (useDts ? tryExtension(Extension.Dcts) : undefined);
case Extension.Json:
candidate += Extension.Json;
return tryExtension(Extension.Dts);
return useDts ? tryExtension(Extension.Dts) : undefined;
default:
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || (useDts ? tryExtension(Extension.Dts) : undefined);
}
case Extensions.JavaScript:
switch (originalExtension) {
@@ -1802,6 +1822,7 @@ namespace ts {
switch (extensions) {
case Extensions.JavaScript:
case Extensions.Json:
case Extensions.TypeScriptButNotDts:
packageFile = readPackageJsonMainField(jsonContent, candidate, state);
break;
case Extensions.TypeScript:
@@ -1888,6 +1909,8 @@ namespace ts {
return extension === Extension.Json;
case Extensions.TypeScript:
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts;
case Extensions.TypeScriptButNotDts:
return extension === Extension.Ts || extension === Extension.Tsx;
case Extensions.DtsOnly:
return extension === Extension.Dts;
}

View File

@@ -4646,7 +4646,6 @@ namespace ts {
/* @internal */
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
/* @internal */
export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration;
@@ -6129,6 +6128,8 @@ namespace ts {
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
/*@internal*/
noDtsResolution?: boolean;
noUncheckedIndexedAccess?: boolean;
out?: string;
outDir?: string;

View File

@@ -3114,21 +3114,31 @@ namespace ts {
// export = <EntityNameExpression>
// export default <EntityNameExpression>
// module.exports = <EntityNameExpression>
// {<Identifier>}
// {name: <EntityNameExpression>}
// module.exports.x = <EntityNameExpression>
// const x = require("...")
// const { x } = require("...")
// const x = require("...").y
// const { x } = require("...").y
export function isAliasSymbolDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
if (node.kind === SyntaxKind.ImportEqualsDeclaration ||
node.kind === SyntaxKind.NamespaceExportDeclaration ||
node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name ||
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.NamespaceExport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment)
) {
return true;
}
return isInJSFile(node) && (
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) ||
isPropertyAccessExpression(node) && isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAliasableExpression(node.parent.right) ||
node.kind === SyntaxKind.ShorthandPropertyAssignment ||
node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer);
isPropertyAccessExpression(node)
&& isBinaryExpression(node.parent)
&& node.parent.left === node
&& node.parent.operatorToken.kind === SyntaxKind.EqualsToken
&& isAliasableExpression(node.parent.right));
}
export function getAliasDeclarationFromName(node: EntityName): Declaration | undefined {
@@ -3139,6 +3149,7 @@ namespace ts {
case SyntaxKind.ExportSpecifier:
case SyntaxKind.ExportAssignment:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceExport:
return node.parent as Declaration;
case SyntaxKind.QualifiedName:
do {

View File

@@ -5,6 +5,7 @@ namespace ts.server {
Configured,
External,
AutoImportProvider,
Auxiliary,
}
/* @internal */
@@ -1768,6 +1769,28 @@ namespace ts.server {
getIncompleteCompletionsCache() {
return this.projectService.getIncompleteCompletionsCache();
}
withAuxiliaryProjectForFiles(fileNames: string[], cb: (project: AuxiliaryProject) => void) {
const options: CompilerOptions = {
...this.getCompilerOptions(),
noDtsResolution: true,
allowJs: true,
maxNodeModuleJsDepth: 3,
diagnostics: false,
skipLibCheck: true,
sourceMap: false,
types: ts.emptyArray,
lib: ts.emptyArray,
noLib: true,
};
const project = new AuxiliaryProject(this.projectService, this.documentRegistry, options);
for (const fileName of fileNames) {
project.addRoot(this.getScriptInfo(fileName)!);
}
project.updateGraph();
cb(project);
project.close();
}
}
function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): SortedReadonlyArray<string> {
@@ -1904,6 +1927,34 @@ namespace ts.server {
}
}
export class AuxiliaryProject extends Project {
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, parentCompilerOptions: CompilerOptions) {
const options: CompilerOptions = {
...parentCompilerOptions,
resolveJsOnly: true,
allowJs: true,
maxNodeModuleJsDepth: 3,
diagnostics: false,
skipLibCheck: true,
sourceMap: false,
types: ts.emptyArray,
lib: ts.emptyArray,
noLib: true,
};
super("js-analysis",
ProjectKind.Auxiliary,
projectService,
documentRegistry,
/*hasExplicitListOfFiles*/ false,
/*lastFileExceededProgramSize*/ undefined,
options,
/*compileOnSaveEnabled*/ false,
/*watchOptions*/ undefined,
projectService.host,
/*currentDirectory*/ undefined);
}
}
export class AutoImportProviderProject extends Project {
/*@internal*/
private static readonly maxDependencies = 10;

View File

@@ -1239,6 +1239,36 @@ namespace ts.server {
}
private getDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan {
// const { file, project } = this.getFileAndProject(args);
// const position = this.getPositionInFile(args, file);
// const scriptInfo = Debug.checkDefined(project.getScriptInfo(file));
// const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position);
// if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) {
// return {
// definitions: emptyArray,
// textSpan: undefined! // TODO: GH#18217
// };
// }
// const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project);
// const { textSpan } = unmappedDefinitionAndBoundSpan;
// if (simplifiedResult) {
// return {
// definitions: this.mapDefinitionInfo(definitions, project),
// textSpan: toProtocolTextSpan(textSpan, scriptInfo)
// };
// }
// return {
// definitions: definitions.map(Session.mapToOriginalLocation),
// textSpan,
// };
// }
// private getSourceDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan {
const { file, project } = this.getFileAndProject(args);
const position = this.getPositionInFile(args, file);
const scriptInfo = Debug.checkDefined(project.getScriptInfo(file));
@@ -1252,7 +1282,17 @@ namespace ts.server {
};
}
const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project);
const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice();
const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient);
if (needsJsResolution) {
project.withAuxiliaryProjectForFiles([file], auxiliaryProject => {
const jsDefinitions = auxiliaryProject.getLanguageService().getDefinitionAndBoundSpan(file, position);
for (const jsDefinition of jsDefinitions?.definitions || emptyArray) {
pushIfUnique(definitions, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start);
}
});
}
const { textSpan } = unmappedDefinitionAndBoundSpan;
if (simplifiedResult) {
@@ -1266,6 +1306,30 @@ namespace ts.server {
definitions: definitions.map(Session.mapToOriginalLocation),
textSpan,
};
// function getLocationsResolvingToDts(symbol: Symbol, checker: TypeChecker) {
// let locations: DocumentPosition[] | undefined;
// while (symbol.flags & SymbolFlags.Alias) {
// const aliasDeclaration = find(symbol.declarations || emptyArray, isAliasSymbolDeclaration);
// if (!aliasDeclaration) break;
// const resolvedSymbol = checker.getImmediateAliasedSymbol(symbol);
// if (!resolvedSymbol) break;
// const hasUnmappedDtsDeclarations = some(resolvedSymbol.declarations, d => {
// const sourceFile = getSourceFileOfNode(d);
// if (!isDeclarationFileName(sourceFile.fileName)) return false;
// const mappedFileName = project.getSourceMapper().tryGetSourcePosition(sourceFile)?.fileName;
// if (mappedFileName && project.projectService.fileExists(toNormalizedPath(mappedFileName))) return false;
// });
// if (hasUnmappedDtsDeclarations) {
// const sourceFile = getSourceFileOfNode(aliasDeclaration);
// locations = append(locations, { fileName: sourceFile.fileName, pos: aliasDeclaration.getStart(sourceFile) });
// }
// symbol = resolvedSymbol;
// }
// return locations;
// }
}
private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput {

View File

@@ -1,6 +1,6 @@
/* @internal */
namespace ts.GoToDefinition {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, aliasesOnly?: boolean): readonly DefinitionInfo[] | undefined {
const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray;
if (resolvedRef?.file) {
@@ -28,18 +28,39 @@ namespace ts.GoToDefinition {
if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) {
const classDecl = node.parent.parent;
const symbol = getSymbol(classDecl, typeChecker);
const { symbol, isAliasTarget } = getSymbol(classDecl, typeChecker);
if (aliasesOnly && !isAliasTarget) return undefined;
const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration);
const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : "";
const sourceFile = node.getSourceFile();
return map(staticBlocks, staticBlock => {
let { pos } = moveRangePastModifiers(staticBlock);
pos = skipTrivia(sourceFile.text, pos);
return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, { start: pos, length: "static".length });
return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, isAliasTarget, { start: pos, length: "static".length });
});
}
const symbol = getSymbol(node, typeChecker);
const { symbol, isAliasTarget } = getSymbol(node, typeChecker);
if (!symbol && isModuleSpecifierLike(node)) {
// We couldn't resolve the symbol as an external module, but it could
// that module resolution succeeded but the target was not a module.
const ref = sourceFile.resolvedModules?.get(node.text, getModeForUsageLocation(sourceFile, node));
if (ref) {
return [{
name: node.text,
fileName: ref.resolvedFileName,
containerName: undefined!,
containerKind: undefined!,
kind: ScriptElementKind.scriptElement,
textSpan: createTextSpan(0, 0),
isAliasTarget: true,
isAmbient: isDeclarationFileName(ref.resolvedFileName),
}];
}
}
if (aliasesOnly && !isAliasTarget) return undefined;
// 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
@@ -50,14 +71,14 @@ namespace ts.GoToDefinition {
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
// Don't go to the component constructor definition for a JSX element, just go to the component definition.
if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) {
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, isAliasTarget);
// For a function, if this is the original function definition, return just sigInfo.
// If this is the original constructor definition, parent is the class.
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
return [sigInfo];
}
else {
const defs = getDefinitionFromSymbol(typeChecker, symbol, node, calledDeclaration) || emptyArray;
const defs = getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget, calledDeclaration) || emptyArray;
// For a 'super()' call, put the signature first, else put the variable first.
return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
}
@@ -70,7 +91,7 @@ namespace ts.GoToDefinition {
// assignment. This case and others are handled by the following code.
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : emptyArray;
const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, isAliasTarget)) : emptyArray;
return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray);
}
@@ -95,7 +116,7 @@ namespace ts.GoToDefinition {
});
}
return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node));
return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget));
}
/**
@@ -198,22 +219,22 @@ namespace ts.GoToDefinition {
return undefined;
}
const symbol = getSymbol(node, typeChecker);
const { symbol, isAliasTarget } = getSymbol(node, typeChecker);
if (!symbol) return undefined;
const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker);
const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node);
const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, isAliasTarget);
// If a function returns 'void' or some other type with no definition, just return the function definition.
const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node);
const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, isAliasTarget);
return typeDefinitions.length ? typeDefinitions
: !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node)
: !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, isAliasTarget)
: undefined;
}
function definitionFromType(type: Type, checker: TypeChecker, node: Node): readonly DefinitionInfo[] {
function definitionFromType(type: Type, checker: TypeChecker, node: Node, isAliasTarget: boolean): readonly DefinitionInfo[] {
return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
t.symbol && getDefinitionFromSymbol(checker, t.symbol, node));
t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, isAliasTarget));
}
function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined {
@@ -228,8 +249,8 @@ namespace ts.GoToDefinition {
return undefined;
}
export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined {
const definitions = getDefinitionAtPosition(program, sourceFile, position);
export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number, aliasesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined {
const definitions = getDefinitionAtPosition(program, sourceFile, position, aliasesOnly);
if (!definitions || definitions.length === 0) {
return undefined;
@@ -255,7 +276,7 @@ namespace ts.GoToDefinition {
return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration));
}
function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined {
function getSymbol(node: Node, checker: TypeChecker) {
const symbol = checker.getSymbolAtLocation(node);
// If this is an alias, and the request came at the declaration location
// get the aliased symbol instead. This allows for goto def on an import e.g.
@@ -264,10 +285,10 @@ namespace ts.GoToDefinition {
if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
const aliased = checker.getAliasedSymbol(symbol);
if (aliased.declarations) {
return aliased;
return { symbol: aliased, isAliasTarget: true };
}
}
return symbol;
return { symbol, isAliasTarget: !!(symbol && isExternalModuleSymbol(symbol)) };
}
// Go to the original declaration for cases:
@@ -296,14 +317,14 @@ namespace ts.GoToDefinition {
}
}
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, declarationNode?: Node): DefinitionInfo[] | undefined {
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, declarationNode?: Node): DefinitionInfo[] | undefined {
// There are cases when you extend a function by adding properties to it afterwards,
// we want to strip those extra properties.
// For deduping purposes, we also want to exclude any declarationNodes if provided.
const filteredDeclarations =
filter(symbol.declarations, d => d !== declarationNode && (!isAssignmentDeclaration(d) || d === symbol.valueDeclaration))
|| undefined;
return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node));
return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, isAliasTarget));
function getConstructSignatureDefinition(): DefinitionInfo[] | undefined {
// Applicable only if we are in a new expression, or we are on a constructor declaration
@@ -331,21 +352,21 @@ namespace ts.GoToDefinition {
return declarations.length
? declarationsWithBody.length !== 0
? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node))
: [createDefinitionInfo(last(declarations), typeChecker, symbol, node)]
: [createDefinitionInfo(last(declarations), typeChecker, symbol, node, isAliasTarget)]
: undefined;
}
}
/** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */
function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo {
function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean): DefinitionInfo {
const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node);
const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : "";
return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName);
return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, isAliasTarget);
}
/** Creates a DefinitionInfo directly from the name of a declaration. */
function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, textSpan?: TextSpan): DefinitionInfo {
function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, isAliasTarget?: boolean, textSpan?: TextSpan): DefinitionInfo {
const sourceFile = declaration.getSourceFile();
if (!textSpan) {
const name = getNameOfDeclaration(declaration) || declaration;
@@ -363,7 +384,9 @@ namespace ts.GoToDefinition {
sourceFile,
FindAllReferences.getContextNode(declaration)
),
isLocal: !isDefinitionVisible(checker, declaration)
isLocal: !isDefinitionVisible(checker, declaration),
isAmbient: !!(declaration.flags & NodeFlags.Ambient),
isAliasTarget,
};
}
@@ -398,8 +421,8 @@ namespace ts.GoToDefinition {
}
}
function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
return createDefinitionInfo(decl, typeChecker, decl.symbol, decl);
function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, isAliasTarget?: boolean): DefinitionInfo {
return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, isAliasTarget);
}
export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined {
@@ -415,6 +438,7 @@ namespace ts.GoToDefinition {
containerName: undefined!,
containerKind: undefined!, // TODO: GH#18217
unverified,
isAliasTarget: true,
};
}

View File

@@ -1751,9 +1751,9 @@ namespace ts {
return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position);
}
function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined {
function getDefinitionAndBoundSpan(fileName: string, position: number, aliasesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined {
synchronizeHostData();
return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position);
return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position, aliasesOnly);
}
function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined {

View File

@@ -468,6 +468,9 @@ namespace ts {
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined;
/*@internal*/
// eslint-disable-next-line @typescript-eslint/unified-signatures
getDefinitionAndBoundSpan(fileName: string, position: number, aliasesOnly: boolean): DefinitionInfoAndBoundSpan | undefined;
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined;
getImplementationAtPosition(fileName: string, position: number): readonly ImplementationLocation[] | undefined;
@@ -1028,6 +1031,8 @@ namespace ts {
containerName: string;
unverified?: boolean;
/* @internal */ isLocal?: boolean;
/* @internal */ isAmbient?: boolean;
/* @internal */ isAliasTarget?: boolean;
}
export interface DefinitionInfoAndBoundSpan {

View File

@@ -1,17 +1,17 @@
/// <reference path='fourslash.ts' />
// @Filename: node_modules/foo/index.js
////{}
//// /*index*/{}
// @Filename: a.ts
////[|import /*foo*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}foo|] from /*fooModule*/"[|{| "isInString": true, "contextRangeIndex": 0 |}foo|]";|]
////[|foo|]();
//// [|import /*foo*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}foo|] from /*fooModule*/"[|{| "isInString": true, "contextRangeIndex": 0 |}foo|]";|]
//// [|foo|]();
goTo.file("a.ts");
verify.numberOfErrorsInCurrentFile(0);
goTo.marker("fooModule");
verify.goToDefinitionIs([]);
verify.goToDefinitionIs(["index"]);
verify.quickInfoIs("");
const [r00, r0, r1, r2] = test.ranges();
verify.singleReferenceGroup('"foo"', [r1]);