diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f04945739b5..33cb44f5327 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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 { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index c63cc791782..4c0e03d0c6d 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -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; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 54936e787bc..0f2672eefdd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 651b29d0dc9..78cf6985faf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3114,21 +3114,31 @@ namespace ts { // export = // export default // module.exports = - // {} - // {name: } + // module.exports.x = + // 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 { diff --git a/src/server/project.ts b/src/server/project.ts index b9ec87f4115..3d72860a772 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -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): SortedReadonlyArray { @@ -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; diff --git a/src/server/session.ts b/src/server/session.ts index 25d9a6fe83b..b87f0bdb9d3 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -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 { diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 2464b6d9672..b4513fe6b75 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -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, }; } diff --git a/src/services/services.ts b/src/services/services.ts index fa56017da5e..5a3a7c21dff 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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 { diff --git a/src/services/types.ts b/src/services/types.ts index 66730a23a53..fed54f2f785 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -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 { diff --git a/tests/cases/fourslash/quickInfoUntypedModuleImport.ts b/tests/cases/fourslash/quickInfoUntypedModuleImport.ts index 2e06c45107e..39450715022 100644 --- a/tests/cases/fourslash/quickInfoUntypedModuleImport.ts +++ b/tests/cases/fourslash/quickInfoUntypedModuleImport.ts @@ -1,17 +1,17 @@ /// // @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]);