From 81ec4fd493ebf234ef90edd9cc683278aefca3d6 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 25 Feb 2022 11:50:11 -0800 Subject: [PATCH 01/45] Prototype resolving to JS when go-to-def aliases all resolve to ambient declarations --- src/compiler/commandLineParser.ts | 7 ++ src/compiler/moduleNameResolver.ts | 37 +++++++-- src/compiler/types.ts | 3 +- src/compiler/utilities.ts | 25 ++++-- src/server/project.ts | 51 ++++++++++++ src/server/session.ts | 66 ++++++++++++++- src/services/goToDefinition.ts | 80 ++++++++++++------- src/services/services.ts | 4 +- src/services/types.ts | 5 ++ .../fourslash/quickInfoUntypedModuleImport.ts | 8 +- 10 files changed, 236 insertions(+), 50 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f04945739b54e..33cb44f53278f 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 c63cc7917826d..4c0e03d0c6d53 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 54936e787bc7a..0f2672eefdd45 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 651b29d0dc9be..78cf6985faf45 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 b9ec87f411542..3d72860a772d1 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 25d9a6fe83b59..b87f0bdb9d3ca 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 2464b6d967232..b4513fe6b7584 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 fa56017da5e16..5a3a7c21dffe6 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 66730a23a5343..fed54f2f7852f 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 2e06c45107e71..39450715022b1 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]); From 422d845cc00d4358255eccfe641611393d6533f7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 1 Mar 2022 15:45:31 -0800 Subject: [PATCH 02/45] Add test infrastructure --- src/harness/client.ts | 22 ++++- src/harness/fourslashImpl.ts | 7 ++ src/harness/fourslashInterfaceImpl.ts | 4 + src/server/protocol.ts | 7 ++ src/server/session.ts | 97 +++++++++------------ tests/cases/fourslash/fourslash.ts | 2 + tests/cases/fourslash/server/goToSource1.ts | 13 +++ 7 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource1.ts diff --git a/src/harness/client.ts b/src/harness/client.ts index 537aa7321e597..06b954b2c8397 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -300,7 +300,7 @@ namespace ts.server { getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); + const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); const response = this.processResponse(request); const body = Debug.checkDefined(response.body); // TODO: GH#18217 @@ -334,6 +334,26 @@ namespace ts.server { })); } + getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.SourceDefinitionAndBoundSpan, args); + const response = this.processResponse(request); + const body = Debug.checkDefined(response.body); // TODO: GH#18217 + + return { + definitions: body.definitions.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "", + unverified: entry.unverified, + })), + textSpan: this.decodeSpan(body.textSpan, request.arguments.file) + }; + } + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { const args = this.createFileLocationRequestArgs(fileName, position); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 47ed0e4a1aed8..640e0e0b8220a 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -697,6 +697,13 @@ namespace FourSlash { this.verifyGoToX(arg0, endMarkerNames, () => this.getGoToDefinitionAndBoundSpan()); } + public verifyGoToSourceDefinition(startMarkerNames: ArrayOrSingle, end?: ArrayOrSingle | { file: string, unverified?: boolean }) { + if (this.testType !== FourSlashTestType.Server) { + this.raiseError("goToSourceDefinition may only be used in fourslash/server tests."); + } + this.verifyGoToX(startMarkerNames, end, () => (this.languageService as ts.server.SessionClient).getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition)!); + } + private getGoToDefinition(): readonly ts.DefinitionInfo[] { return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition)!; } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 98b61cf2fb55a..48cae7705f088 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -324,6 +324,10 @@ namespace FourSlashInterface { this.state.verifyGoToType(arg0, endMarkerName); } + public goToSourceDefinition(startMarkerNames: ArrayOrSingle, end: { file: string } | ArrayOrSingle) { + this.state.verifyGoToSourceDefinition(startMarkerNames, end); + } + public goToDefinitionForMarkers(...markerNames: string[]) { this.state.verifyGoToDefinitionForMarkers(markerNames); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ca8dbd1cac63b..56aaf0000443d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -83,6 +83,9 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", + SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", + /* @internal */ + SourceDefinitionAndBoundSpanFull = "sourceDefinitionAndBoundSpan-full", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -904,6 +907,10 @@ namespace ts.server.protocol { readonly command: CommandTypes.DefinitionAndBoundSpan; } + export interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { + readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + } + export interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; } diff --git a/src/server/session.ts b/src/server/session.ts index b87f0bdb9d3ca..81308329e64f2 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1239,36 +1239,6 @@ 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)); @@ -1282,7 +1252,37 @@ namespace ts.server { }; } - const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); + 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)); + + const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); + + if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { + return { + definitions: emptyArray, + textSpan: undefined! // TODO: GH#18217 + }; + } + + let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient); if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { @@ -1293,6 +1293,7 @@ namespace ts.server { }); } + definitions = definitions.filter(d => !d.isAmbient); const { textSpan } = unmappedDefinitionAndBoundSpan; if (simplifiedResult) { @@ -1306,30 +1307,6 @@ 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 { @@ -2750,12 +2727,18 @@ namespace ts.server { [CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => { return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionRequest) => { + [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); }, - [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionRequest) => { + [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 955d87c6f6346..8990c6615a20f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,6 +317,8 @@ declare namespace FourSlashInterface { goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string }): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; diff --git a/tests/cases/fourslash/server/goToSource1.ts b/tests/cases/fourslash/server/goToSource1.ts new file mode 100644 index 0000000000000..c9fbfb98fae3e --- /dev/null +++ b/tests/cases/fourslash/server/goToSource1.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.js +//// export const /*end*/a = "a"; + +// @Filename: /a.d.ts +//// export declare const a: string; + +// @Filename: /index.ts +//// import { a } from "./a"; +//// [|a/*start*/|] + +verify.goToSourceDefinition("start", "end"); From 1b4335289e23258298384df13b909ac511d0b924 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 1 Mar 2022 16:26:55 -0800 Subject: [PATCH 03/45] Start fleshing out test coverage --- src/harness/fourslashImpl.ts | 27 +++++++++++-------- ...ce1.ts => goToSource1_localJsBesideDts.ts} | 0 .../goToSource2_nodeModulesWithTypes.ts | 18 +++++++++++++ .../server/goToSource3_nodeModulesAtTypes.ts | 21 +++++++++++++++ .../server/goToSource4_sameAsGoToDef1.ts | 8 ++++++ .../server/goToSource5_sameAsGoToDef2.ts | 17 ++++++++++++ .../server/goToSource6_sameAsGoToDef3.ts | 24 +++++++++++++++++ 7 files changed, 104 insertions(+), 11 deletions(-) rename tests/cases/fourslash/server/{goToSource1.ts => goToSource1_localJsBesideDts.ts} (100%) create mode 100644 tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts create mode 100644 tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts create mode 100644 tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts create mode 100644 tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts create mode 100644 tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 640e0e0b8220a..7f0d385b7969e 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -771,7 +771,9 @@ namespace FourSlash { } if (endMarkers.length !== definitions.length) { - this.raiseError(`${testName} failed - expected to find ${endMarkers.length} definitions but got ${definitions.length}`); + const markers = definitions.map(d => ({ text: "HERE", fileName: d.fileName, position: d.textSpan.start })); + const actual = this.renderMarkers(markers); + this.raiseError(`${testName} failed - expected to find ${endMarkers.length} definitions but got ${definitions.length}\n\n${actual}`); } ts.zipWith(endMarkers, definitions, (endMarkerOrFileResult, definition, i) => { @@ -781,17 +783,8 @@ namespace FourSlash { ts.Debug.assert(typeof expectedFileName === "string"); const expectedPosition = marker?.position || 0; if (ts.comparePaths(expectedFileName, definition.fileName, /*ignoreCase*/ true) !== ts.Comparison.EqualTo || expectedPosition !== definition.textSpan.start) { - const filesToDisplay = ts.deduplicate([expectedFileName, definition.fileName], ts.equateValues); const markers = [{ text: "EXPECTED", fileName: expectedFileName, position: expectedPosition }, { text: "ACTUAL", fileName: definition.fileName, position: definition.textSpan.start }]; - const text = filesToDisplay.map(fileName => { - const markersToRender = markers.filter(m => m.fileName === fileName).sort((a, b) => b.position - a.position); - let fileContent = this.tryGetFileContent(fileName) || ""; - for (const marker of markersToRender) { - fileContent = fileContent.slice(0, marker.position) + `\x1b[1;4m/*${marker.text}*/\x1b[0;31m` + fileContent.slice(marker.position); - } - return `// @Filename: ${fileName}\n${fileContent}`; - }).join("\n\n"); - + const text = this.renderMarkers(markers); this.raiseError(`${testName} failed for definition ${markerName || expectedFileName} (${i}): expected ${expectedFileName} at ${expectedPosition}, got ${definition.fileName} at ${definition.textSpan.start}\n\n${text}\n`); } if (definition.unverified && (typeof endMarkerOrFileResult === "string" || !endMarkerOrFileResult.unverified)) { @@ -805,6 +798,18 @@ namespace FourSlash { }); } + private renderMarkers(markers: { text: string, fileName: string, position: number }[]) { + const filesToDisplay = ts.deduplicate(markers.map(m => m.fileName), ts.equateValues); + return filesToDisplay.map(fileName => { + const markersToRender = markers.filter(m => m.fileName === fileName).sort((a, b) => b.position - a.position); + let fileContent = this.tryGetFileContent(fileName) || ""; + for (const marker of markersToRender) { + fileContent = fileContent.slice(0, marker.position) + `\x1b[1;4m/*${marker.text}*/\x1b[0;31m` + fileContent.slice(marker.position); + } + return `// @Filename: ${fileName}\n${fileContent}`; + }).join("\n\n"); + } + private verifyDefinitionTextSpan(defs: ts.DefinitionInfoAndBoundSpan, startMarkerName: string) { const range = this.testData.ranges.find(range => this.markerName(range.marker!) === startMarkerName); diff --git a/tests/cases/fourslash/server/goToSource1.ts b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts similarity index 100% rename from tests/cases/fourslash/server/goToSource1.ts rename to tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts diff --git a/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts new file mode 100644 index 0000000000000..a2166786c54cd --- /dev/null +++ b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts @@ -0,0 +1,18 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/foo/package.json +//// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js", "types": "./types/main.d.ts" } + +// @Filename: /node_modules/foo/lib/main.js +//// export const /*end*/a = "a"; + +// @Filename: /node_modules/foo/types/main.d.ts +//// export declare const a: string; + +// @Filename: /index.ts +//// import { a } from "foo"; +//// [|a/*start*/|] + +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts new file mode 100644 index 0000000000000..152d411040a12 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts @@ -0,0 +1,21 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/foo/package.json +//// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js" } + +// @Filename: /node_modules/foo/lib/main.js +//// export const /*end*/a = "a"; + +// @Filename: /node_modules/@types/foo/package.json +//// { "name": "@types/foo", "version": "1.0.0", "types": "./index.d.ts" } + +// @Filename: /node_modules/@types/foo/index.d.ts +//// export declare const a: string; + +// @Filename: /index.ts +//// import { a } from "foo"; +//// [|a/*start*/|] + +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts new file mode 100644 index 0000000000000..dc8f9a47abc51 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts @@ -0,0 +1,8 @@ +/// + +// @Filename: /index.ts +//// import { a/*end*/ } from "./a"; +//// [|a/*start*/|] + +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts new file mode 100644 index 0000000000000..f2b7e2b73457f --- /dev/null +++ b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: /a.ts +//// export const /*end*/a = 'a'; + +// @Filename: /a.d.ts +//// export declare const a: string; + +// @Filename: /a.js +//// export const a = 'a'; + +// @Filename: /b.ts +//// import { a } from './a'; +//// [|a/*start*/|] + +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts new file mode 100644 index 0000000000000..87b7065a17b0a --- /dev/null +++ b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts @@ -0,0 +1,24 @@ +/// + +// @Filename: /node_modules/foo/package.json +//// { "name": "foo", "version": "1.2.3", "typesVersions": { "*": { "*": ["./types/*"] } } } + +// @Filename: /node_modules/foo/src/a.ts +//// export const /*end*/a = 'a'; + +// @Filename: /node_modules/foo/types/a.d.ts +//// export declare const a: string; +//// //# sourceMappingURL=a.d.ts.map + +// @Filename: /node_modules/foo/types/a.d.ts.map +//// {"version":3,"file":"a.d.ts","sourceRoot":"","sources":["../src/a.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE,OAAO,CAAC;;AACvB,wBAAsB"} + +// @Filename: /node_modules/foo/dist/a.js +//// export const a = 'a'; + +// @Filename: /b.ts +//// import { a } from 'foo/a'; +//// [|a/*start*/|] + +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); From 7aeb92f06fb96b85366aafdc92c7cad7de22faa8 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 7 Mar 2022 17:24:20 -0800 Subject: [PATCH 04/45] Fix some go-to-def stuff --- src/compiler/checker.ts | 2 +- src/server/session.ts | 6 +- src/services/goToDefinition.ts | 100 +++++++++++------- src/services/types.ts | 1 + .../goToDefinitionExpandoElementAccess.ts | 2 +- .../goToSource7_conditionallyMinified.ts | 33 ++++++ 6 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cb52a13176aca..fcdac1191ab5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9003,7 +9003,7 @@ namespace ts { } } const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 - type = getUnionType(sourceTypes!, UnionReduction.Subtype); + type = getUnionType(sourceTypes!); } } const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); diff --git a/src/server/session.ts b/src/server/session.ts index 81308329e64f2..708221835a4a0 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1233,6 +1233,8 @@ namespace ts.server { containerName: info.containerName, kind: info.kind, name: info.name, + isAliasTarget: info.isAliasTarget, + failedAliasResolution: info.failedAliasResolution, ...info.unverified && { unverified: info.unverified }, }; }); @@ -1283,7 +1285,7 @@ namespace ts.server { } let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient); + const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { const jsDefinitions = auxiliaryProject.getLanguageService().getDefinitionAndBoundSpan(file, position); @@ -1293,7 +1295,7 @@ namespace ts.server { }); } - definitions = definitions.filter(d => !d.isAmbient); + definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); const { textSpan } = unmappedDefinitionAndBoundSpan; if (simplifiedResult) { diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index b4513fe6b7584..dc6ed645d63d8 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -28,7 +28,7 @@ namespace ts.GoToDefinition { if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { const classDecl = node.parent.parent; - const { symbol, isAliasTarget } = getSymbol(classDecl, typeChecker); + const { symbol, isAliasTarget, failedAliasResolution } = getSymbol(classDecl, typeChecker); if (aliasesOnly && !isAliasTarget) return undefined; const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); @@ -37,11 +37,11 @@ namespace ts.GoToDefinition { return map(staticBlocks, staticBlock => { let { pos } = moveRangePastModifiers(staticBlock); pos = skipTrivia(sourceFile.text, pos); - return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, isAliasTarget, { start: pos, length: "static".length }); + return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, isAliasTarget, failedAliasResolution, { start: pos, length: "static".length }); }); } - const { symbol, isAliasTarget } = getSymbol(node, typeChecker); + const { symbol, isAliasTarget, failedAliasResolution } = 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. @@ -55,6 +55,7 @@ namespace ts.GoToDefinition { kind: ScriptElementKind.scriptElement, textSpan: createTextSpan(0, 0), isAliasTarget: true, + failedAliasResolution, isAmbient: isDeclarationFileName(ref.resolvedFileName), }]; } @@ -71,14 +72,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, isAliasTarget); + const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, isAliasTarget, failedAliasResolution); // 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, isAliasTarget, calledDeclaration) || emptyArray; + const defs = getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget, failedAliasResolution, calledDeclaration) || emptyArray; // For a 'super()' call, put the signature first, else put the variable first. return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; } @@ -91,7 +92,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, isAliasTarget)) : emptyArray; + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, isAliasTarget, failedAliasResolution)) : emptyArray; return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } @@ -116,7 +117,7 @@ namespace ts.GoToDefinition { }); } - return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget)); + return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget, failedAliasResolution)); } /** @@ -219,22 +220,22 @@ namespace ts.GoToDefinition { return undefined; } - const { symbol, isAliasTarget } = getSymbol(node, typeChecker); + const { symbol, isAliasTarget, failedAliasResolution } = 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, isAliasTarget); + const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, isAliasTarget, failedAliasResolution); // 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, isAliasTarget); + const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, isAliasTarget, failedAliasResolution); return typeDefinitions.length ? typeDefinitions - : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, isAliasTarget) + : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, isAliasTarget, failedAliasResolution) : undefined; } - function definitionFromType(type: Type, checker: TypeChecker, node: Node, isAliasTarget: boolean): readonly DefinitionInfo[] { + function definitionFromType(type: Type, checker: TypeChecker, node: Node, isAliasTarget: boolean, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] { return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => - t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, isAliasTarget)); + t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, isAliasTarget, failedAliasResolution)); } function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { @@ -282,13 +283,17 @@ namespace ts.GoToDefinition { // get the aliased symbol instead. This allows for goto def on an import e.g. // import {A, B} from "mod"; // to jump to the implementation directly. + let failedAliasResolution = false; if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { const aliased = checker.getAliasedSymbol(symbol); if (aliased.declarations) { return { symbol: aliased, isAliasTarget: true }; } + else { + failedAliasResolution = true; + } } - return { symbol, isAliasTarget: !!(symbol && isExternalModuleSymbol(symbol)) }; + return { symbol, isAliasTarget: !!(symbol && isExternalModuleSymbol(symbol)), failedAliasResolution }; } // Go to the original declaration for cases: @@ -303,28 +308,44 @@ namespace ts.GoToDefinition { if (node.parent === declaration) { return true; } - switch (declaration.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportEqualsDeclaration: - return true; - case SyntaxKind.ImportSpecifier: - return declaration.parent.kind === SyntaxKind.NamedImports; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - return isInJSFile(declaration) && isVariableDeclarationInitializedToBareOrAccessedRequire(declaration); - default: - return false; + if (declaration.kind === SyntaxKind.NamespaceImport) { + return false; } + return true; + } + + /** + * ```ts + * function f() {} + * f.foo = 0; + * ``` + * + * Here, `f` has two declarations: the function declaration, and the identifier in the next line. + * The latter is a declaration for `f` because it gives `f` the `SymbolFlags.Namespace` meaning so + * it can contain `foo`. However, that declaration is pretty uninteresting and not intuitively a + * "definition" for `f`. Ideally, the question we'd like to answer is "what SymbolFlags does this + * declaration contribute to the symbol for `f`?" If the answer is just `Namespace` and the + * declaration looks like an assignment, that declaration is in no sense a definition for `f`. + * But that information is totally lost during binding and/or symbol merging, so we need to do + * our best to reconstruct it or use other heuristics. This function (and the logic around its + * calling) covers our tests but feels like a hack, and it would be great if someone could come + * up with a more precise definition of what counts as a definition. + */ + function isExpandoDeclaration(node: Declaration): boolean { + if (!isAssignmentDeclaration(node)) return false; + const containingAssignment = findAncestor(node, p => { + if (isAssignmentExpression(p)) return true; + if (!isAssignmentDeclaration(p as Declaration)) return "quit"; + return false; + }) as AssignmentExpression | undefined; + return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property; } - 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, isAliasTarget)); + function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined { + const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration); + const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d)); + const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations; + return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, isAliasTarget, failedAliasResolution)); function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { // Applicable only if we are in a new expression, or we are on a constructor declaration @@ -352,21 +373,21 @@ namespace ts.GoToDefinition { return declarations.length ? declarationsWithBody.length !== 0 ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) - : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, isAliasTarget)] + : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, isAliasTarget, failedAliasResolution)] : undefined; } } /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean): DefinitionInfo { + function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: 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, isAliasTarget); + return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, isAliasTarget, failedAliasResolution); } /** Creates a DefinitionInfo directly from the name of a declaration. */ - function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, isAliasTarget?: boolean, textSpan?: TextSpan): DefinitionInfo { + function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, isAliasTarget?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { const sourceFile = declaration.getSourceFile(); if (!textSpan) { const name = getNameOfDeclaration(declaration) || declaration; @@ -387,6 +408,7 @@ namespace ts.GoToDefinition { isLocal: !isDefinitionVisible(checker, declaration), isAmbient: !!(declaration.flags & NodeFlags.Ambient), isAliasTarget, + failedAliasResolution, }; } @@ -421,8 +443,8 @@ namespace ts.GoToDefinition { } } - function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, isAliasTarget?: boolean): DefinitionInfo { - return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, isAliasTarget); + function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, isAliasTarget?: boolean, failedAliasResolution?: boolean): DefinitionInfo { + return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, isAliasTarget, failedAliasResolution); } export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { diff --git a/src/services/types.ts b/src/services/types.ts index fed54f2f7852f..89cc2d156d62d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1033,6 +1033,7 @@ namespace ts { /* @internal */ isLocal?: boolean; /* @internal */ isAmbient?: boolean; /* @internal */ isAliasTarget?: boolean; + /* @internal */ failedAliasResolution?: boolean; } export interface DefinitionInfoAndBoundSpan { diff --git a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts index 5deeb4c02532e..67efdb195e690 100644 --- a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts +++ b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts @@ -4,4 +4,4 @@ ////f[/*0*/"x"] = 0; ////f[[|/*1*/"x"|]] = 1; -verify.goToDefinition("1", "0"); +verify.goToDefinition("1", ["0", "1"]); diff --git a/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts new file mode 100644 index 0000000000000..6a0e13962ba38 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts @@ -0,0 +1,33 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/react/package.json +//// { "name": "react", "version": "16.8.6", "main": "index.js" } + +// @Filename: /node_modules/react/index.js +//// 'use strict'; +//// +//// if (process.env.NODE_ENV === 'production') { +//// module.exports = require('./cjs/react.production.min.js'); +//// } else { +//// module.exports = require('./cjs/react.development.js'); +//// } + +// @Filename: /node_modules/react/cjs/react.production.min.js +//// 'use strict';exports./*production*/useState=function(a){};exports.version='16.8.6'; + +// @Filename: /node_modules/react/cjs/react.development.js +//// 'use strict'; +//// if (process.env.NODE_ENV !== 'production') { +//// (function() { +//// function useState(initialState) {} +//// exports./*development*/useState = useState; +//// exports.version = '16.8.6'; +//// }()); +//// } + +// @Filename: /index.ts +//// import { [|/*start*/useState|] } from 'react'; + +verify.goToSourceDefinition("start", ["production", "development"]); From f2915e6c806b38351a4f1e1ffce0ab783197dfa6 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 10 Mar 2022 13:46:19 -0800 Subject: [PATCH 05/45] Finish lodash test case --- src/compiler/checker.ts | 6 ++ src/server/session.ts | 102 +++++++++++++++++- src/services/findAllReferences.ts | 24 +++++ src/services/goToDefinition.ts | 4 +- .../server/goToSource8_mapFromAtTypes.ts | 77 +++++++++++++ 5 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fcdac1191ab5a..9de7b21a7fb88 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41488,8 +41488,14 @@ namespace ts { return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; case SyntaxKind.MetaProperty: return checkExpression(node as Expression).symbol; + case SyntaxKind.BinaryExpression: + // See binary expression handling in `getDeclarationFromName` + return getSymbolOfNode(node as BinaryExpression) || getSymbolOfNode((node as BinaryExpression).left); default: + if (isDeclaration(node)) { + return getSymbolOfNode(node); + } return undefined; } } diff --git a/src/server/session.ts b/src/server/session.ts index 708221835a4a0..d658f7e5227d6 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1285,12 +1285,40 @@ namespace ts.server { } let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); + const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); 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 ls = auxiliaryProject.getLanguageService(); + const jsDefinitions = ls.getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true); + if (some(jsDefinitions?.definitions)) { + for (const jsDefinition of jsDefinitions!.definitions) { + pushIfUnique(definitions, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); + } + } + else { + const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); + for (const candidate of ambientCandidates) { + const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); + if (candidateFileName) { + const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); + const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; + if (!scriptInfo) { + continue; + } + if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { + auxiliaryProject.addRoot(scriptInfo); + } + const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; + const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); + const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); + for (const match of matches) { + const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + if (symbol) { + pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); + } + } + } + } } }); } @@ -1309,6 +1337,72 @@ namespace ts.server { definitions: definitions.map(Session.mapToOriginalLocation), textSpan, }; + + function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { + const sourceFile = program.getSourceFile(definition.fileName)!; + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); + if (symbol) { + let parent = symbol.parent; + while (parent && !isExternalModuleSymbol(parent)) { + parent = parent.parent; + } + if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { + // Always CommonJS right now, but who knows in the future + const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); + const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; + if (fileName) { + return fileName; + } + } + const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; + if (fileName) { + return fileName; + } + } + } + + function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { + const nodeModulesPathParts = getNodeModulePathParts(fileName); + if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { + // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up. + const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex); + const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache(); + const compilerOptions = project.getCompilationSettings(); + const packageJson = getPackageScopeForPath(project.toPath(packageDirectory + "/package.json"), packageJsonCache, project, compilerOptions); + if (!packageJson) return undefined; + // Use fake options instead of actual compiler options to avoid following export map if the project uses node12 or nodenext - + // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be + // resolved from the package root under --moduleResolution node + const entrypoints = getEntrypointsFromPackageJsonInfo( + packageJson, + { moduleResolution: ModuleResolutionKind.NodeJs }, + project, + project.getModuleResolutionCache()); + // This substring is correct only because we checked for a single `/node_modules/` at the top. + const packageNamePathPart = fileName.substring( + nodeModulesPathParts.topLevelPackageNameIndex + 1, + nodeModulesPathParts.packageRootIndex); + const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart)); + const path = project.toPath(fileName); + if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { + // This file was the main entrypoint of a package. Try to resolve that same package name with + // the auxiliary project that only resolves to implementation files. + const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + else { + // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package. + const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1); + const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`; + const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + } + // We're not in node_modules, and we only get to this function if non-dts module resolution failed. + // I'm not sure what else I can do here that isn't already covered by that module resolution. + return undefined; + } } private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d4cccf374ec7d..bd2b73101c75c 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1333,6 +1333,30 @@ namespace ts.FindAllReferences { } } + export function getTopMostDeclarationsInFile(declarationName: string, sourceFile: SourceFile): readonly Declaration[] { + const candidates = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, declarationName), getDeclarationFromName); + return candidates.reduce((topMost, decl) => { + const depth = getDepth(decl); + if (!some(topMost.declarations) || depth === topMost.depth) { + topMost.declarations.push(decl); + } + else if (depth < topMost.depth) { + topMost.declarations = [decl]; + } + topMost.depth = depth; + return topMost; + }, { depth: Infinity, declarations: [] as Declaration[] }).declarations; + + function getDepth(declaration: Declaration | undefined) { + let depth = 0; + while (declaration) { + declaration = getContainerNode(declaration); + depth++; + } + return depth; + } + } + export function someSignatureUsage( signature: SignatureDeclaration, sourceFiles: readonly SourceFile[], diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index dc6ed645d63d8..522e3a574c73d 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -44,7 +44,7 @@ namespace ts.GoToDefinition { const { symbol, isAliasTarget, failedAliasResolution } = 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. + // be that module resolution succeeded but the target was not a module. const ref = sourceFile.resolvedModules?.get(node.text, getModeForUsageLocation(sourceFile, node)); if (ref) { return [{ @@ -379,7 +379,7 @@ namespace ts.GoToDefinition { } /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: boolean): DefinitionInfo { + export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: 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) : ""; diff --git a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts new file mode 100644 index 0000000000000..e596fd97d9e0d --- /dev/null +++ b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts @@ -0,0 +1,77 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/lodash/package.json +//// { "name": "lodash", "version": "4.17.15", "main": "./lodash.js" } + +// @Filename: /node_modules/lodash/lodash.js +//// ;(function() { +//// /** +//// * Adds two numbers. +//// * +//// * @static +//// * @memberOf _ +//// * @since 3.4.0 +//// * @category Math +//// * @param {number} augend The first number in an addition. +//// * @param {number} addend The second number in an addition. +//// * @returns {number} Returns the total. +//// * @example +//// * +//// * _.add(6, 4); +//// * // => 10 +//// */ +//// var [|/*variable*/add|] = createMathOperation(function(augend, addend) { +//// return augend + addend; +//// }, 0); +//// +//// function lodash(value) {} +//// lodash.[|/*property*/add|] = add; +//// +//// /** Detect free variable `global` from Node.js. */ +//// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; +//// /** Detect free variable `self`. */ +//// var freeSelf = typeof self == 'object' && self && self.Object === Object && self; +//// /** Used as a reference to the global object. */ +//// var root = freeGlobal || freeSelf || Function('return this')(); +//// /** Detect free variable `exports`. */ +//// var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;//// +//// /** Detect free variable `module`. */ +//// var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; +//// if (freeModule) { +//// // Export for Node.js. +//// (freeModule.exports = _)._ = _; +//// // Export for CommonJS support. +//// freeExports._ = _; +//// } +//// else { +//// // Export to the global object. +//// root._ = _; +//// } +//// }.call(this)); + +// @Filename: /node_modules/@types/lodash/package.json +//// { "name": "@types/lodash", "version": "4.14.97", "types": "index.d.ts" } + +// @Filename: /node_modules/@types/lodash/index.d.ts +//// /// +//// export = _; +//// export as namespace _; +//// declare const _: _.LoDashStatic; +//// declare namespace _ { +//// interface LoDashStatic {} +//// } + +// @Filename: /node_modules/@types/lodash/common/math.d.ts +//// import _ = require("../index"); +//// declare module "../index" { +//// interface LoDashStatic { +//// add(augend: number, addend: number): number; +//// } +//// } + +// @Filename: /index.ts +//// import { [|/*start*/add|] } from 'lodash'; + +verify.goToSourceDefinition("start", ["variable", "property"]); From 309c4bbf590c3c9f7ae1018be66cf9172561c21a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 10 Mar 2022 14:32:44 -0800 Subject: [PATCH 06/45] Make go-to-implementation never return ambient results --- src/compiler/checker.ts | 3 --- src/server/session.ts | 2 +- src/services/findAllReferences.ts | 4 ++-- tests/cases/fourslash/goToImplementationLocal_06.ts | 2 +- tests/cases/fourslash/goToImplementationLocal_07.ts | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9de7b21a7fb88..ac6b5d09b67ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41493,9 +41493,6 @@ namespace ts { return getSymbolOfNode(node as BinaryExpression) || getSymbolOfNode((node as BinaryExpression).left); default: - if (isDeclaration(node)) { - return getSymbolOfNode(node); - } return undefined; } } diff --git a/src/server/session.ts b/src/server/session.ts index d658f7e5227d6..05d514c6672ec 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1312,7 +1312,7 @@ namespace ts.server { const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); for (const match of matches) { - const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); if (symbol) { pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index bd2b73101c75c..139eac1423e84 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -2296,10 +2296,10 @@ namespace ts.FindAllReferences { } function isImplementation(node: Node): boolean { - return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : + return !(node.flags & NodeFlags.Ambient) && ( (isVariableLike(node) ? hasInitializer(node) : isFunctionLikeDeclaration(node) ? !!node.body : - isClassLike(node) || isModuleOrEnumDeclaration(node)); + isClassLike(node) || isModuleOrEnumDeclaration(node))); } export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { diff --git a/tests/cases/fourslash/goToImplementationLocal_06.ts b/tests/cases/fourslash/goToImplementationLocal_06.ts index 90c489bcf5402..923f02a4aae9c 100644 --- a/tests/cases/fourslash/goToImplementationLocal_06.ts +++ b/tests/cases/fourslash/goToImplementationLocal_06.ts @@ -5,4 +5,4 @@ //// declare var [|someVar|]: string; //// someVa/*reference*/r -verify.allRangesAppearInImplementationList("reference"); +verify.implementationListIsEmpty(); diff --git a/tests/cases/fourslash/goToImplementationLocal_07.ts b/tests/cases/fourslash/goToImplementationLocal_07.ts index b24b463e10721..fabee95ceaa4e 100644 --- a/tests/cases/fourslash/goToImplementationLocal_07.ts +++ b/tests/cases/fourslash/goToImplementationLocal_07.ts @@ -5,4 +5,4 @@ //// declare function [|someFunction|](): () => void; //// someFun/*reference*/ction(); -verify.allRangesAppearInImplementationList("reference"); +verify.implementationListIsEmpty(); From 381799d0f104000c676334c8e08fcdff9ccac896 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 10 Mar 2022 16:13:52 -0800 Subject: [PATCH 07/45] Build new functionality into go-to-implementation --- src/harness/client.ts | 20 -- src/harness/fourslashImpl.ts | 14 +- src/harness/fourslashInterfaceImpl.ts | 4 - src/server/protocol.ts | 7 - src/server/session.ts | 251 ++++++++---------- src/services/callHierarchy.ts | 2 +- src/services/documentHighlights.ts | 2 +- src/services/findAllReferences.ts | 50 ++-- src/services/services.ts | 6 +- tests/cases/fourslash/fourslash.ts | 2 - .../server/goToSource1_localJsBesideDts.ts | 6 +- .../goToSource2_nodeModulesWithTypes.ts | 6 +- .../server/goToSource3_nodeModulesAtTypes.ts | 6 +- .../server/goToSource4_sameAsGoToDef1.ts | 8 +- .../server/goToSource5_sameAsGoToDef2.ts | 7 +- .../server/goToSource6_sameAsGoToDef3.ts | 7 +- .../goToSource7_conditionallyMinified.ts | 8 +- .../server/goToSource8_mapFromAtTypes.ts | 8 +- 18 files changed, 177 insertions(+), 237 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index 06b954b2c8397..2040ed2018ab9 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -334,26 +334,6 @@ namespace ts.server { })); } - getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.SourceDefinitionAndBoundSpan, args); - const response = this.processResponse(request); - const body = Debug.checkDefined(response.body); // TODO: GH#18217 - - return { - definitions: body.definitions.map(entry => ({ - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "", - unverified: entry.unverified, - })), - textSpan: this.decodeSpan(body.textSpan, request.arguments.file) - }; - } - getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { const args = this.createFileLocationRequestArgs(fileName, position); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 7f0d385b7969e..520666383a258 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -697,13 +697,6 @@ namespace FourSlash { this.verifyGoToX(arg0, endMarkerNames, () => this.getGoToDefinitionAndBoundSpan()); } - public verifyGoToSourceDefinition(startMarkerNames: ArrayOrSingle, end?: ArrayOrSingle | { file: string, unverified?: boolean }) { - if (this.testType !== FourSlashTestType.Server) { - this.raiseError("goToSourceDefinition may only be used in fourslash/server tests."); - } - this.verifyGoToX(startMarkerNames, end, () => (this.languageService as ts.server.SessionClient).getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition)!); - } - private getGoToDefinition(): readonly ts.DefinitionInfo[] { return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition)!; } @@ -2447,7 +2440,12 @@ namespace FourSlash { assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); } else { - assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); + if (this.testType === FourSlashTestType.Server) { + assert.deepEqual(implementations, [], "Expected implementation list to be empty but implementations returned"); + } + else { + assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); + } } } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 48cae7705f088..98b61cf2fb55a 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -324,10 +324,6 @@ namespace FourSlashInterface { this.state.verifyGoToType(arg0, endMarkerName); } - public goToSourceDefinition(startMarkerNames: ArrayOrSingle, end: { file: string } | ArrayOrSingle) { - this.state.verifyGoToSourceDefinition(startMarkerNames, end); - } - public goToDefinitionForMarkers(...markerNames: string[]) { this.state.verifyGoToDefinitionForMarkers(markerNames); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 56aaf0000443d..ca8dbd1cac63b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -83,9 +83,6 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", - SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", - /* @internal */ - SourceDefinitionAndBoundSpanFull = "sourceDefinitionAndBoundSpan-full", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -907,10 +904,6 @@ namespace ts.server.protocol { readonly command: CommandTypes.DefinitionAndBoundSpan; } - export interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { - readonly command: CommandTypes.SourceDefinitionAndBoundSpan; - } - export interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; } diff --git a/src/server/session.ts b/src/server/session.ts index 05d514c6672ec..c1fed85410d88 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1270,141 +1270,6 @@ namespace ts.server { }; } - 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)); - - const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); - - if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { - return { - definitions: emptyArray, - textSpan: undefined! // TODO: GH#18217 - }; - } - - let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); - if (needsJsResolution) { - project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { - const ls = auxiliaryProject.getLanguageService(); - const jsDefinitions = ls.getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true); - if (some(jsDefinitions?.definitions)) { - for (const jsDefinition of jsDefinitions!.definitions) { - pushIfUnique(definitions, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); - } - } - else { - const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); - for (const candidate of ambientCandidates) { - const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); - if (candidateFileName) { - const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); - const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; - if (!scriptInfo) { - continue; - } - if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { - auxiliaryProject.addRoot(scriptInfo); - } - const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; - const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); - for (const match of matches) { - const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - if (symbol) { - pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); - } - } - } - } - } - }); - } - - definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); - const { textSpan } = unmappedDefinitionAndBoundSpan; - - if (simplifiedResult) { - return { - definitions: this.mapDefinitionInfo(definitions, project), - textSpan: toProtocolTextSpan(textSpan, scriptInfo) - }; - } - - return { - definitions: definitions.map(Session.mapToOriginalLocation), - textSpan, - }; - - function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { - const sourceFile = program.getSourceFile(definition.fileName)!; - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); - if (symbol) { - let parent = symbol.parent; - while (parent && !isExternalModuleSymbol(parent)) { - parent = parent.parent; - } - if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { - // Always CommonJS right now, but who knows in the future - const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); - const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; - if (fileName) { - return fileName; - } - } - const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; - if (fileName) { - return fileName; - } - } - } - - function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { - const nodeModulesPathParts = getNodeModulePathParts(fileName); - if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { - // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up. - const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex); - const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache(); - const compilerOptions = project.getCompilationSettings(); - const packageJson = getPackageScopeForPath(project.toPath(packageDirectory + "/package.json"), packageJsonCache, project, compilerOptions); - if (!packageJson) return undefined; - // Use fake options instead of actual compiler options to avoid following export map if the project uses node12 or nodenext - - // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be - // resolved from the package root under --moduleResolution node - const entrypoints = getEntrypointsFromPackageJsonInfo( - packageJson, - { moduleResolution: ModuleResolutionKind.NodeJs }, - project, - project.getModuleResolutionCache()); - // This substring is correct only because we checked for a single `/node_modules/` at the top. - const packageNamePathPart = fileName.substring( - nodeModulesPathParts.topLevelPackageNameIndex + 1, - nodeModulesPathParts.packageRootIndex); - const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart)); - const path = project.toPath(fileName); - if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { - // This file was the main entrypoint of a package. Try to resolve that same package name with - // the auxiliary project that only resolves to implementation files. - const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile); - return implementationResolution?.resolvedFileName; - } - else { - // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package. - const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1); - const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`; - const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile); - return implementationResolution?.resolvedFileName; - } - } - // We're not in node_modules, and we only get to this function if non-dts module resolution failed. - // I'm not sure what else I can do here that isn't already covered by that module resolution. - return undefined; - } - } - private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { const { file, project } = this.getFileAndProject(args); if (!project.shouldEmitFile(project.getScriptInfo(file))) { @@ -1517,9 +1382,119 @@ namespace ts.server { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project); + + // const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); + const needsJsResolution = !some(implementations); + + if (needsJsResolution) { + project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { + const ls = auxiliaryProject.getLanguageService(); + const jsDefinitions = ls.getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true); + if (some(jsDefinitions?.definitions)) { + for (const jsDefinition of jsDefinitions!.definitions) { + pushIfUnique(implementations, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); + } + } + else { + const ambientDefinitions = this.mapDefinitionInfoLocations( + project.getLanguageService().getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true)?.definitions || emptyArray, + project, + ).filter(d => d.isAmbient && d.isAliasTarget); + for (const candidate of ambientDefinitions) { + const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); + if (candidateFileName) { + const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); + const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; + if (!scriptInfo) { + continue; + } + if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { + auxiliaryProject.addRoot(scriptInfo); + } + const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; + const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); + const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); + for (const match of matches) { + const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + if (symbol) { + pushIfUnique(implementations, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); + } + } + } + } + } + }); + } + return simplifiedResult ? implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) : implementations.map(Session.mapToOriginalLocation); + + function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { + const sourceFile = program.getSourceFile(definition.fileName)!; + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); + if (symbol) { + let parent = symbol.parent; + while (parent && !isExternalModuleSymbol(parent)) { + parent = parent.parent; + } + if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { + // Always CommonJS right now, but who knows in the future + const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); + const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; + if (fileName) { + return fileName; + } + } + const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; + if (fileName) { + return fileName; + } + } + } + + function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { + const nodeModulesPathParts = getNodeModulePathParts(fileName); + if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { + // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up. + const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex); + const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache(); + const compilerOptions = project.getCompilationSettings(); + const packageJson = getPackageScopeForPath(project.toPath(packageDirectory + "/package.json"), packageJsonCache, project, compilerOptions); + if (!packageJson) return undefined; + // Use fake options instead of actual compiler options to avoid following export map if the project uses node12 or nodenext - + // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be + // resolved from the package root under --moduleResolution node + const entrypoints = getEntrypointsFromPackageJsonInfo( + packageJson, + { moduleResolution: ModuleResolutionKind.NodeJs }, + project, + project.getModuleResolutionCache()); + // This substring is correct only because we checked for a single `/node_modules/` at the top. + const packageNamePathPart = fileName.substring( + nodeModulesPathParts.topLevelPackageNameIndex + 1, + nodeModulesPathParts.packageRootIndex); + const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart)); + const path = project.toPath(fileName); + if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { + // This file was the main entrypoint of a package. Try to resolve that same package name with + // the auxiliary project that only resolves to implementation files. + const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + else { + // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package. + const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1); + const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`; + const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + } + // We're not in node_modules, and we only get to this function if non-dts module resolution failed. + // I'm not sure what else I can do here that isn't already covered by that module resolution. + return undefined; + } } private getOccurrences(args: protocol.FileLocationRequestArgs): readonly protocol.OccurrencesResponseItem[] { @@ -2829,12 +2804,6 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); - }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index bfb533b0160b2..19f59d0127ce2 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -347,7 +347,7 @@ namespace ts.CallHierarchy { return []; } const location = getCallHierarchyDeclarationReferenceNode(declaration); - const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined); + const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*sourceMapper*/ undefined, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined); return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : []; } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index ffcec349cc663..021dcabda439d 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -29,7 +29,7 @@ namespace ts { function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*sourceMapper*/ undefined, /*options*/ undefined, sourceFilesSet); if (!referenceEntries) return undefined; const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 139eac1423e84..709af091e72f4 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -204,9 +204,9 @@ namespace ts.FindAllReferences { readonly providePrefixAndSuffixTextForRename?: boolean; } - export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + export function findReferencedSymbols(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position); - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, { use: FindReferencesUse.References }); const checker = program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => @@ -217,10 +217,10 @@ namespace ts.FindAllReferences { }); } - export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { + export function getImplementationsAtPosition(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { const node = getTouchingPropertyName(sourceFile, position); let referenceEntries: Entry[] | undefined; - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); + const entries = getImplementationReferenceEntries(program, sourceMapper, cancellationToken, sourceFiles, node, position); if ( node.parent.kind === SyntaxKind.PropertyAccessExpression @@ -239,7 +239,7 @@ namespace ts.FindAllReferences { continue; } referenceEntries = append(referenceEntries, entry); - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); + const entries = getImplementationReferenceEntries(program, sourceMapper, cancellationToken, sourceFiles, entry.node, entry.node.pos); if (entries) { queue.push(...entries); } @@ -249,7 +249,7 @@ namespace ts.FindAllReferences { return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } - function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { + function getImplementationReferenceEntries(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { if (node.kind === SyntaxKind.SourceFile) { return undefined; } @@ -270,15 +270,15 @@ namespace ts.FindAllReferences { } else { // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, { implementations: true, use: FindReferencesUse.References }); } } export function findReferenceOrRenameEntries( - program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, + program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, sourceMapper: SourceMapper | undefined, options: Options | undefined, convertEntry: ToReferenceOrRenameEntry, ): T[] | undefined { - return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); + return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options)), entry => convertEntry(entry, node, program.getTypeChecker())); } export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; @@ -289,10 +289,11 @@ namespace ts.FindAllReferences { program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, + sourceMapper?: SourceMapper, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), ): readonly Entry[] | undefined { - return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); + return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options, sourceFilesSet)); } function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { @@ -621,7 +622,7 @@ namespace ts.FindAllReferences { /** Encapsulates the core find-all-references algorithm. */ export namespace Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, sourceMapper?: SourceMapper, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { if (options.use === FindReferencesUse.References) { node = getAdjustedReferenceLocation(node); } @@ -682,16 +683,16 @@ namespace ts.FindAllReferences { return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, sourceMapper, cancellationToken, options, sourceFilesSet); if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { return moduleReferences; } const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); const moduleReferencesOfExportTarget = aliasedSymbol && - getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, sourceMapper, cancellationToken, options, sourceFilesSet); - const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); + const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, sourceMapper, options); return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); } @@ -735,7 +736,7 @@ namespace ts.FindAllReferences { return undefined; } - function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { + function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], sourceMapper: SourceMapper | undefined, cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); if (!moduleSourceFile) return undefined; const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); @@ -745,7 +746,7 @@ namespace ts.FindAllReferences { // Continue to get references to 'export ='. const checker = program.getTypeChecker(); symbol = skipAlias(exportEquals, checker); - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); + return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, sourceMapper, options)); } /** @@ -918,13 +919,13 @@ namespace ts.FindAllReferences { } /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { + function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, sourceMapper: SourceMapper | undefined, options: Options): SymbolAndEntries[] { const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; // Compute the meaning from the location and the symbol it references const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); + const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, sourceMapper, searchMeaning, options, result); const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); if (exportSpecifier) { @@ -1065,6 +1066,7 @@ namespace ts.FindAllReferences { readonly specialSearchKind: SpecialSearchKind, readonly checker: TypeChecker, readonly cancellationToken: CancellationToken, + readonly sourceMapper: SourceMapper | undefined, readonly searchMeaning: SemanticMeaning, readonly options: Options, private readonly result: Push) { @@ -1803,7 +1805,7 @@ namespace ts.FindAllReferences { function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { + if (isDeclarationName(refNode) && isImplementation(refNode.parent, state.sourceMapper)) { addReference(refNode); return; } @@ -2295,8 +2297,14 @@ namespace ts.FindAllReferences { return meaning; } - function isImplementation(node: Node): boolean { - return !(node.flags & NodeFlags.Ambient) && ( + function isImplementation(node: Node, sourceMapper: SourceMapper | undefined): boolean { + if (node.flags & NodeFlags.Ambient) { + if (!sourceMapper) return false; + if (!isVariableLike(node) && !isFunctionLikeDeclaration(node) && !isClassLike(node) && !isModuleOrEnumDeclaration(node)) return false; + const source = sourceMapper.tryGetSourcePosition({ fileName: node.getSourceFile().fileName, pos: node.pos }); + return !!source && !isDeclarationFileName(source.fileName); + } + return ( (isVariableLike(node) ? hasInitializer(node) : isFunctionLikeDeclaration(node) ? !!node.body : isClassLike(node) || isModuleOrEnumDeclaration(node))); diff --git a/src/services/services.ts b/src/services/services.ts index 5a3a7c21dffe6..b2e2cf465a42b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1765,7 +1765,7 @@ namespace ts { function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { synchronizeHostData(); - return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + return FindAllReferences.getImplementationsAtPosition(program, sourceMapper, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } /// References and Occurrences @@ -1827,12 +1827,12 @@ namespace ts { ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) : program.getSourceFiles(); - return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); + return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, sourceMapper, options, cb); } function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + return FindAllReferences.findReferencedSymbols(program, sourceMapper, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } function getFileReferences(fileName: string): ReferenceEntry[] { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8990c6615a20f..955d87c6f6346 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,8 +317,6 @@ declare namespace FourSlashInterface { goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; - goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string }): void; - goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; diff --git a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts index c9fbfb98fae3e..8cf54c9ea4cd0 100644 --- a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts +++ b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts @@ -1,13 +1,13 @@ /// // @Filename: /a.js -//// export const /*end*/a = "a"; +//// export const [|a|] = "a"; // @Filename: /a.d.ts //// export declare const a: string; // @Filename: /index.ts //// import { a } from "./a"; -//// [|a/*start*/|] +//// a/*start*/ -verify.goToSourceDefinition("start", "end"); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts index a2166786c54cd..a6384efd75a8a 100644 --- a/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts +++ b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts @@ -6,13 +6,13 @@ //// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js", "types": "./types/main.d.ts" } // @Filename: /node_modules/foo/lib/main.js -//// export const /*end*/a = "a"; +//// export const [|a|] = "a"; // @Filename: /node_modules/foo/types/main.d.ts //// export declare const a: string; // @Filename: /index.ts //// import { a } from "foo"; -//// [|a/*start*/|] +//// a/*start*/ -verify.goToSourceDefinition("start", "end"); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts index 152d411040a12..b79f870fc2b30 100644 --- a/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts +++ b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts @@ -6,7 +6,7 @@ //// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js" } // @Filename: /node_modules/foo/lib/main.js -//// export const /*end*/a = "a"; +//// export const [|a|] = "a"; // @Filename: /node_modules/@types/foo/package.json //// { "name": "@types/foo", "version": "1.0.0", "types": "./index.d.ts" } @@ -16,6 +16,6 @@ // @Filename: /index.ts //// import { a } from "foo"; -//// [|a/*start*/|] +//// a/*start*/ -verify.goToSourceDefinition("start", "end"); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts index dc8f9a47abc51..57396683f2ddc 100644 --- a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts +++ b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts @@ -1,8 +1,8 @@ /// // @Filename: /index.ts -//// import { a/*end*/ } from "./a"; -//// [|a/*start*/|] +//// import { a } from "./a"; +//// a/*start*/ -verify.goToDefinition("start", "end"); -verify.goToSourceDefinition("start", "end"); +goTo.marker("start"); +verify.implementationListIsEmpty(); diff --git a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts index f2b7e2b73457f..8637439ee68bd 100644 --- a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts +++ b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts @@ -1,7 +1,7 @@ /// // @Filename: /a.ts -//// export const /*end*/a = 'a'; +//// export const [|a|] = 'a'; // @Filename: /a.d.ts //// export declare const a: string; @@ -11,7 +11,6 @@ // @Filename: /b.ts //// import { a } from './a'; -//// [|a/*start*/|] +//// a/*start*/ -verify.goToDefinition("start", "end"); -verify.goToSourceDefinition("start", "end"); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts index 87b7065a17b0a..316fde37f4ca4 100644 --- a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts +++ b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts @@ -4,7 +4,7 @@ //// { "name": "foo", "version": "1.2.3", "typesVersions": { "*": { "*": ["./types/*"] } } } // @Filename: /node_modules/foo/src/a.ts -//// export const /*end*/a = 'a'; +//// export const [|a|] = 'a'; // @Filename: /node_modules/foo/types/a.d.ts //// export declare const a: string; @@ -18,7 +18,6 @@ // @Filename: /b.ts //// import { a } from 'foo/a'; -//// [|a/*start*/|] +//// a/*start*/ -verify.goToDefinition("start", "end"); -verify.goToSourceDefinition("start", "end"); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts index 6a0e13962ba38..203b0e6ed9276 100644 --- a/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts +++ b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts @@ -15,19 +15,19 @@ //// } // @Filename: /node_modules/react/cjs/react.production.min.js -//// 'use strict';exports./*production*/useState=function(a){};exports.version='16.8.6'; +//// 'use strict';exports.[|useState|]=function(a){};exports.version='16.8.6'; // @Filename: /node_modules/react/cjs/react.development.js //// 'use strict'; //// if (process.env.NODE_ENV !== 'production') { //// (function() { //// function useState(initialState) {} -//// exports./*development*/useState = useState; +//// exports.[|useState|] = useState; //// exports.version = '16.8.6'; //// }()); //// } // @Filename: /index.ts -//// import { [|/*start*/useState|] } from 'react'; +//// import { /*start*/useState } from 'react'; -verify.goToSourceDefinition("start", ["production", "development"]); +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts index e596fd97d9e0d..3712fb9303cd2 100644 --- a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts +++ b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts @@ -22,12 +22,12 @@ //// * _.add(6, 4); //// * // => 10 //// */ -//// var [|/*variable*/add|] = createMathOperation(function(augend, addend) { +//// var [|add|] = createMathOperation(function(augend, addend) { //// return augend + addend; //// }, 0); //// //// function lodash(value) {} -//// lodash.[|/*property*/add|] = add; +//// lodash.[|add|] = add; //// //// /** Detect free variable `global` from Node.js. */ //// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; @@ -72,6 +72,6 @@ //// } // @Filename: /index.ts -//// import { [|/*start*/add|] } from 'lodash'; +//// import { /*start*/add } from 'lodash'; -verify.goToSourceDefinition("start", ["variable", "property"]); +verify.allRangesAppearInImplementationList("start"); From 072770291cbd0eea2c4af5e821607451697a3d65 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 10 Mar 2022 16:59:53 -0800 Subject: [PATCH 08/45] Update baselines --- src/compiler/checker.ts | 3 --- src/server/session.ts | 11 +++++++---- src/services/findAllReferences.ts | 14 +++++++------- tests/baselines/reference/api/tsserverlibrary.d.ts | 7 ++++++- .../noDtsResolution/tsconfig.json | 5 +++++ .../Default initialized TSConfig/tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../declarationDir-is-specified.js | 1 + .../when-outDir-and-declarationDir-is-specified.js | 1 + .../when-outDir-is-specified.js | 1 + .../with-outFile.js | 1 + ...utFile-is-specified-with-declaration-enabled.js | 1 + .../without-outDir-or-outFile-is-specified.js | 1 + .../cases/fourslash/goToImplementationLocal_08.ts | 3 ++- 21 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 tests/baselines/reference/showConfig/Shows tsconfig for single option/noDtsResolution/tsconfig.json diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac6b5d09b67ba..fcdac1191ab5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41488,9 +41488,6 @@ namespace ts { return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; case SyntaxKind.MetaProperty: return checkExpression(node as Expression).symbol; - case SyntaxKind.BinaryExpression: - // See binary expression handling in `getDeclarationFromName` - return getSymbolOfNode(node as BinaryExpression) || getSymbolOfNode((node as BinaryExpression).left); default: return undefined; diff --git a/src/server/session.ts b/src/server/session.ts index c1fed85410d88..9c5bc628eeee5 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1413,11 +1413,14 @@ namespace ts.server { } const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); + const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); for (const match of matches) { - const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - if (symbol) { - pushIfUnique(implementations, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); + const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const decl = getDeclarationFromName(match); + if (symbol && decl) { + // I think the last argument to this is supposed to be the start node, but it doesn't seem important. + // Callers internal to GoToDefinition already get confused about this. + pushIfUnique(implementations, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl)); } } } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 709af091e72f4..1cb61592858f7 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1335,21 +1335,21 @@ namespace ts.FindAllReferences { } } - export function getTopMostDeclarationsInFile(declarationName: string, sourceFile: SourceFile): readonly Declaration[] { - const candidates = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, declarationName), getDeclarationFromName); + export function getTopMostDeclarationNamesInFile(declarationName: string, sourceFile: SourceFile): readonly Node[] { + const candidates = filter(getPossibleSymbolReferenceNodes(sourceFile, declarationName), name => !!getDeclarationFromName(name)); return candidates.reduce((topMost, decl) => { const depth = getDepth(decl); - if (!some(topMost.declarations) || depth === topMost.depth) { - topMost.declarations.push(decl); + if (!some(topMost.declarationNames) || depth === topMost.depth) { + topMost.declarationNames.push(decl); } else if (depth < topMost.depth) { - topMost.declarations = [decl]; + topMost.declarationNames = [decl]; } topMost.depth = depth; return topMost; - }, { depth: Infinity, declarations: [] as Declaration[] }).declarations; + }, { depth: Infinity, declarationNames: [] as Node[] }).declarationNames; - function getDepth(declaration: Declaration | undefined) { + function getDepth(declaration: Node | undefined) { let depth = 0; while (declaration) { declaration = getContainerNode(declaration); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 255fb6a1305c7..3e5ecc9e35e2c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9789,7 +9789,8 @@ declare namespace ts.server { Inferred = 0, Configured = 1, External = 2, - AutoImportProvider = 3 + AutoImportProvider = 3, + Auxiliary = 4 } function allRootFilesAreJsOrDts(project: Project): boolean; function allFilesAreJsOrDts(project: Project): boolean; @@ -9957,6 +9958,7 @@ declare namespace ts.server { private enableProxy; /** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */ refreshDiagnostics(): void; + withAuxiliaryProjectForFiles(fileNames: string[], cb: (project: AuxiliaryProject) => void): void; } /** * If a file is opened and no tsconfig (or jsconfig) is found, @@ -9974,6 +9976,9 @@ declare namespace ts.server { close(): void; getTypeAcquisition(): TypeAcquisition; } + class AuxiliaryProject extends Project { + constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, parentCompilerOptions: CompilerOptions); + } class AutoImportProviderProject extends Project { private hostProject; private rootFileNames; diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/noDtsResolution/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/noDtsResolution/tsconfig.json new file mode 100644 index 0000000000000..a879d26a750ec --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/noDtsResolution/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "noDtsResolution": true + } +} diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 57e9a2b745187..0dcec91eb0689 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json index bfa516dee13cb..9defe7677b7a5 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index eb149f81bf6cb..8ce575c3e1f86 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 8380fa45a09d6..4d988edbadd58 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 894c1e7e6d727..acabb78c4ffa1 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 0adbea873c55d..c6fd0342ca784 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 57e9a2b745187..0dcec91eb0689 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 6cec9bab660a8..9644a1c7baeb0 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index b26e448a84b9e..008d3e1bd52df 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -33,6 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ "types": ["jquery","mocha"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js index f3473b25e3743..54f54582595b7 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js index e7d593a417cfa..d2f3296186621 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js index 581a85f51836f..b4a4dac85431e 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js index fd153355bec63..9107d29619437 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js index 1f562d6203f6b..379eeaad11302 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js index d3c9002df9d08..5169a5959c1d4 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js @@ -54,6 +54,7 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "noDtsResolution": true, /* noDtsResolution */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/cases/fourslash/goToImplementationLocal_08.ts b/tests/cases/fourslash/goToImplementationLocal_08.ts index b24b463e10721..340aa1ef47814 100644 --- a/tests/cases/fourslash/goToImplementationLocal_08.ts +++ b/tests/cases/fourslash/goToImplementationLocal_08.ts @@ -5,4 +5,5 @@ //// declare function [|someFunction|](): () => void; //// someFun/*reference*/ction(); -verify.allRangesAppearInImplementationList("reference"); +goTo.marker("reference"); +verify.implementationListIsEmpty(); From ebeda0b34d88a146f6324f126637d8ff28fe9a93 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 15:41:32 -0700 Subject: [PATCH 09/45] Two more test cases --- .../server/goToSource10_mapFromAtTypes3.ts | 69 ++++++++++++++++ .../server/goToSource9_mapFromAtTypes2.ts | 79 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts create mode 100644 tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts new file mode 100644 index 0000000000000..be26ec1da259c --- /dev/null +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -0,0 +1,69 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/lodash/package.json +//// { "name": "lodash", "version": "4.17.15", "main": "./lodash.js" } + +// @Filename: /node_modules/lodash/lodash.js +//// ;(function() { +//// /** +//// * Adds two numbers. +//// * +//// * @static +//// * @memberOf _ +//// * @since 3.4.0 +//// * @category Math +//// * @param {number} augend The first number in an addition. +//// * @param {number} addend The second number in an addition. +//// * @returns {number} Returns the total. +//// * @example +//// * +//// * _.add(6, 4); +//// * // => 10 +//// */ +//// var [|add|] = createMathOperation(function(augend, addend) { +//// return augend + addend; +//// }, 0); +//// +//// function lodash(value) {} +//// lodash.[|add|] = add; +//// +//// /** Detect free variable `global` from Node.js. */ +//// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; +//// /** Detect free variable `self`. */ +//// var freeSelf = typeof self == 'object' && self && self.Object === Object && self; +//// /** Used as a reference to the global object. */ +//// var root = freeGlobal || freeSelf || Function('return this')(); +//// /** Detect free variable `exports`. */ +//// var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;//// +//// /** Detect free variable `module`. */ +//// var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; +//// if (freeModule) { +//// // Export for Node.js. +//// (freeModule.exports = _)._ = _; +//// // Export for CommonJS support. +//// freeExports._ = _; +//// } +//// else { +//// // Export to the global object. +//// root._ = _; +//// } +//// }.call(this)); + +// @Filename: /node_modules/@types/lodash/package.json +//// { "name": "@types/lodash", "version": "4.14.97", "types": "index.d.ts" } + +// @Filename: /node_modules/@types/lodash/index.d.ts +//// export = _; +//// export as namespace _; +//// declare const _: _.LoDashStatic; +//// declare namespace _ { +//// interface LoDashStatic {} +//// } + + +// @Filename: /index.ts +//// import { /*start*/add } from 'lodash'; + +verify.allRangesAppearInImplementationList("start"); diff --git a/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts new file mode 100644 index 0000000000000..24da362439789 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts @@ -0,0 +1,79 @@ +/// + +// @moduleResolution: node + +// @Filename: /node_modules/lodash/package.json +//// { "name": "lodash", "version": "4.17.15", "main": "./lodash.js" } + +// @Filename: /node_modules/lodash/lodash.js +//// [||];(function() { +//// /** +//// * Adds two numbers. +//// * +//// * @static +//// * @memberOf _ +//// * @since 3.4.0 +//// * @category Math +//// * @param {number} augend The first number in an addition. +//// * @param {number} addend The second number in an addition. +//// * @returns {number} Returns the total. +//// * @example +//// * +//// * _.add(6, 4); +//// * // => 10 +//// */ +//// var add = createMathOperation(function(augend, addend) { +//// return augend + addend; +//// }, 0); +//// +//// function lodash(value) {} +//// lodash.add = add; +//// +//// /** Detect free variable `global` from Node.js. */ +//// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; +//// /** Detect free variable `self`. */ +//// var freeSelf = typeof self == 'object' && self && self.Object === Object && self; +//// /** Used as a reference to the global object. */ +//// var root = freeGlobal || freeSelf || Function('return this')(); +//// /** Detect free variable `exports`. */ +//// var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;//// +//// /** Detect free variable `module`. */ +//// var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; +//// if (freeModule) { +//// // Export for Node.js. +//// (freeModule.exports = _)._ = _; +//// // Export for CommonJS support. +//// freeExports._ = _; +//// } +//// else { +//// // Export to the global object. +//// root._ = _; +//// } +//// }.call(this)); + +// @Filename: /node_modules/@types/lodash/package.json +//// { "name": "@types/lodash", "version": "4.14.97", "types": "index.d.ts" } + +// @Filename: /node_modules/@types/lodash/index.d.ts +//// /// +//// export = _; +//// export as namespace _; +//// declare const _: _.LoDashStatic; +//// declare namespace _ { +//// interface LoDashStatic {} +//// } + +// @Filename: /node_modules/@types/lodash/common/math.d.ts +//// import _ = require("../index"); +//// declare module "../index" { +//// interface LoDashStatic { +//// add(augend: number, addend: number): number; +//// } +//// } + +// @Filename: /index.ts +//// import /*defaultImport*/_, { /*unresolvableNamedImport*/foo } from /*moduleSpecifier*/'lodash'; + +verify.allRangesAppearInImplementationList("defaultImport"); +verify.allRangesAppearInImplementationList("unresolvableNamedImport"); +verify.allRangesAppearInImplementationList("moduleSpecifier"); From 03ea413d568e3a8048c79f3c4619f278b0b8fd8e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 15:42:50 -0700 Subject: [PATCH 10/45] Refine definition searches for unresolved imports --- src/compiler/types.ts | 3 ++ src/compiler/utilities.ts | 12 +++-- src/server/session.ts | 67 +++++++++++++++++++++------ src/services/codefixes/importFixes.ts | 4 +- src/services/findAllReferences.ts | 17 +++++-- src/services/goToDefinition.ts | 23 +++++++-- src/services/utilities.ts | 14 ++++++ 7 files changed, 111 insertions(+), 29 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0f2672eefdd45..8a869275b4714 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4643,6 +4643,9 @@ namespace ts { /* @internal */ export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitializedTo; + /* @internal */ + export type AnyImportOrBareOrAccessedRequire = AnyImportSyntax | VariableDeclarationInitializedTo; + /* @internal */ export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 78cf6985faf45..496417d42d3ec 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -933,6 +933,10 @@ namespace ts { } } + export function isAnyImportOrBareOrAccessedRequire(node: Node): node is AnyImportOrBareOrAccessedRequire { + return isAnyImportSyntax(node) || isVariableDeclarationInitializedToBareOrAccessedRequire(node); + } + export function isLateVisibilityPaintedStatement(node: Node): node is LateVisibilityPaintedStatement { switch (node.kind) { case SyntaxKind.ImportDeclaration: @@ -2548,14 +2552,14 @@ namespace ts { return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer); } - export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrRequire): string | undefined { + export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire): StringLiteralLike | undefined { switch (node.kind) { case SyntaxKind.VariableDeclaration: - return node.initializer.arguments[0].text; + return findAncestor(node.initializer, (node): node is RequireOrImportCall => isRequireCall(node, /*requireStringLiteralLikeArgument*/ true))?.arguments[0]; case SyntaxKind.ImportDeclaration: - return tryCast(node.moduleSpecifier, isStringLiteralLike)?.text; + return tryCast(node.moduleSpecifier, isStringLiteralLike); case SyntaxKind.ImportEqualsDeclaration: - return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike)?.text; + return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike); default: Debug.assertNever(node); } diff --git a/src/server/session.ts b/src/server/session.ts index 9c5bc628eeee5..7773d164d6459 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1381,18 +1381,24 @@ namespace ts.server { private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly ImplementationLocation[] { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); - const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project); - - // const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); - const needsJsResolution = !some(implementations); - + const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project).slice(); + const needsJsResolution = !length(implementations); if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { const ls = auxiliaryProject.getLanguageService(); const jsDefinitions = ls.getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true); if (some(jsDefinitions?.definitions)) { for (const jsDefinition of jsDefinitions!.definitions) { - pushIfUnique(implementations, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); + if (jsDefinition.unverified) { + const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); + if (some(refined)) { + for (const def of refined || emptyArray) { + pushIfUnique(implementations, definitionInfoToImplementationLocation(def), documentSpansEqual); + } + continue; + } + } + pushIfUnique(implementations, definitionInfoToImplementationLocation(jsDefinition), documentSpansEqual); } } else { @@ -1413,15 +1419,8 @@ namespace ts.server { } const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); - for (const match of matches) { - const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - const decl = getDeclarationFromName(match); - if (symbol && decl) { - // I think the last argument to this is supposed to be the start node, but it doesn't seem important. - // Callers internal to GoToDefinition already get confused about this. - pushIfUnique(implementations, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl)); - } + for (const definition of searchForDeclaration(candidate.name, fileToSearch, auxiliaryProgram)) { + pushIfUnique(implementations, definition, documentSpansEqual); } } } @@ -1433,6 +1432,16 @@ namespace ts.server { implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) : implementations.map(Session.mapToOriginalLocation); + function definitionInfoToImplementationLocation(definition: DefinitionInfo): ImplementationLocation { + return { + fileName: definition.fileName, + textSpan: definition.textSpan, + contextSpan: definition.contextSpan, + kind: definition.kind, + displayParts: [], + }; + } + function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { const sourceFile = program.getSourceFile(definition.fileName)!; const checker = program.getTypeChecker(); @@ -1498,6 +1507,34 @@ namespace ts.server { // I'm not sure what else I can do here that isn't already covered by that module resolution. return undefined; } + + function tryRefineDefinition(definition: DefinitionInfo, program: Program, auxiliaryProgram: Program) { + const fileToSearch = auxiliaryProgram.getSourceFile(definition.fileName); + if (!fileToSearch) { + return undefined; + } + const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position); + const symbol = program.getTypeChecker().getSymbolAtLocation(initialNode); + if (!symbol || !symbol.declarations || some(symbol.declarations, isFreelyNameableImport)) { + return undefined; + } + + const nameToSearch = find(symbol.declarations, isImportSpecifier)?.propertyName?.text || symbol.name; + return searchForDeclaration(nameToSearch, fileToSearch, auxiliaryProgram); + } + + function searchForDeclaration(declarationName: string, fileToSearch: SourceFile, auxiliaryProgram: Program) { + const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(declarationName, fileToSearch); + return mapDefined(matches, match => { + const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const decl = getDeclarationFromName(match); + if (symbol && decl) { + // I think the last argument to this is supposed to be the start node, but it doesn't seem important. + // Callers internal to GoToDefinition already get confused about this. + return GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl); + } + }); + } } private getOccurrences(args: protocol.FileLocationRequestArgs): readonly protocol.OccurrencesResponseItem[] { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index c3e6087839bd9..74217de524e7e 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -453,7 +453,7 @@ namespace ts.codefix { // and it is up to the user to decide which one fits best. return firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => { const namespacePrefix = getNamespaceLikeImportText(declaration); - const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration); + const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration)?.text; if (namespacePrefix && moduleSpecifier) { const moduleSymbol = getTargetModuleFromNamespaceLikeImport(declaration, checker); if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) { @@ -670,7 +670,7 @@ namespace ts.codefix { checker: TypeChecker, compilerOptions: CompilerOptions ): FixAddNewImport | undefined { - const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration); + const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration)?.text; if (moduleSpecifier) { const addAsTypeOnly = useRequire ? AddAsTypeOnly.NotAllowed diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 1cb61592858f7..047f85d266375 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -293,7 +293,18 @@ namespace ts.FindAllReferences { options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), ): readonly Entry[] | undefined { - return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options, sourceFilesSet)); + const allEntries = flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options, sourceFilesSet)); + if (options.implementations) { + return filter(allEntries, entry => { + switch (entry.kind) { + case EntryKind.Span: + return !isDeclarationFileName(entry.fileName); + default: + return Core.isImplementation(entry.node, sourceMapper); + } + }); + } + return allEntries; } function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { @@ -2297,10 +2308,10 @@ namespace ts.FindAllReferences { return meaning; } - function isImplementation(node: Node, sourceMapper: SourceMapper | undefined): boolean { + export function isImplementation(node: Node, sourceMapper: SourceMapper | undefined): boolean { if (node.flags & NodeFlags.Ambient) { if (!sourceMapper) return false; - if (!isVariableLike(node) && !isFunctionLikeDeclaration(node) && !isClassLike(node) && !isModuleOrEnumDeclaration(node)) return false; + if (isPartOfTypeNode(node)) return false; const source = sourceMapper.tryGetSourcePosition({ fileName: node.getSourceFile().fileName, pos: node.pos }); return !!source && !isDeclarationFileName(source.fileName); } diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 522e3a574c73d..d93bfd3884669 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -41,14 +41,26 @@ namespace ts.GoToDefinition { }); } - const { symbol, isAliasTarget, failedAliasResolution } = getSymbol(node, typeChecker); - if (!symbol && isModuleSpecifierLike(node)) { - // We couldn't resolve the symbol as an external module, but it could + let { symbol, isAliasTarget, failedAliasResolution } = getSymbol(node, typeChecker); + let fallbackNode = node; + + if (aliasesOnly && failedAliasResolution) { + // We couldn't resolve the specific import, try on the module specifier. + const importDeclaration = findAncestor(node, isAnyImportOrBareOrAccessedRequire); + const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); + if (moduleSpecifier) { + ({ symbol, isAliasTarget, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker)); + fallbackNode = moduleSpecifier; + } + } + + if (!symbol && isModuleSpecifierLike(fallbackNode)) { + // We couldn't resolve the module specifier as an external module, but it could // be that module resolution succeeded but the target was not a module. - const ref = sourceFile.resolvedModules?.get(node.text, getModeForUsageLocation(sourceFile, node)); + const ref = sourceFile.resolvedModules?.get(fallbackNode.text, getModeForUsageLocation(sourceFile, fallbackNode)); if (ref) { return [{ - name: node.text, + name: fallbackNode.text, fileName: ref.resolvedFileName, containerName: undefined!, containerKind: undefined!, @@ -57,6 +69,7 @@ namespace ts.GoToDefinition { isAliasTarget: true, failedAliasResolution, isAmbient: isDeclarationFileName(ref.resolvedFileName), + unverified: fallbackNode !== node, }]; } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b3fcce814821d..364a41a5076f6 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2399,6 +2399,20 @@ namespace ts { return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; } + /** + * Returns true if `node` is the declaration of `n` in: + * - `import n from "m"` + * - `import * as n from "m"` + * - `import n = require("m")` + * - `const n = require("m")` + */ + export function isFreelyNameableImport(node: Node): node is ImportClause | NamespaceImport | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } | VariableDeclarationInitializedTo { + return node.kind === SyntaxKind.ImportClause + || node.kind === SyntaxKind.NamespaceImport + || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) + || isVariableDeclarationInitializedToRequire(node); + } + export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { // First check to see if the script kind was specified by the host. Chances are the host // may override the default script kind for the file extension. From e5556b8d53a03ac0a6c43dd7c9539ef23e55a911 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 15:47:47 -0700 Subject: [PATCH 11/45] Revert "Build new functionality into go-to-implementation" This reverts commit 381799d0f104000c676334c8e08fcdff9ccac896. --- src/harness/client.ts | 20 ++ src/harness/fourslashImpl.ts | 14 +- src/harness/fourslashInterfaceImpl.ts | 4 + src/server/protocol.ts | 7 + src/server/session.ts | 276 ++++++++++-------- src/services/callHierarchy.ts | 2 +- src/services/documentHighlights.ts | 2 +- src/services/findAllReferences.ts | 61 ++-- src/services/services.ts | 6 +- tests/cases/fourslash/fourslash.ts | 2 + .../server/goToSource1_localJsBesideDts.ts | 6 +- .../goToSource2_nodeModulesWithTypes.ts | 6 +- .../server/goToSource3_nodeModulesAtTypes.ts | 6 +- .../server/goToSource4_sameAsGoToDef1.ts | 8 +- .../server/goToSource5_sameAsGoToDef2.ts | 7 +- .../server/goToSource6_sameAsGoToDef3.ts | 7 +- .../goToSource7_conditionallyMinified.ts | 8 +- .../server/goToSource8_mapFromAtTypes.ts | 8 +- 18 files changed, 248 insertions(+), 202 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index 2040ed2018ab9..06b954b2c8397 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -334,6 +334,26 @@ namespace ts.server { })); } + getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.SourceDefinitionAndBoundSpan, args); + const response = this.processResponse(request); + const body = Debug.checkDefined(response.body); // TODO: GH#18217 + + return { + definitions: body.definitions.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "", + unverified: entry.unverified, + })), + textSpan: this.decodeSpan(body.textSpan, request.arguments.file) + }; + } + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { const args = this.createFileLocationRequestArgs(fileName, position); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 520666383a258..7f0d385b7969e 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -697,6 +697,13 @@ namespace FourSlash { this.verifyGoToX(arg0, endMarkerNames, () => this.getGoToDefinitionAndBoundSpan()); } + public verifyGoToSourceDefinition(startMarkerNames: ArrayOrSingle, end?: ArrayOrSingle | { file: string, unverified?: boolean }) { + if (this.testType !== FourSlashTestType.Server) { + this.raiseError("goToSourceDefinition may only be used in fourslash/server tests."); + } + this.verifyGoToX(startMarkerNames, end, () => (this.languageService as ts.server.SessionClient).getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition)!); + } + private getGoToDefinition(): readonly ts.DefinitionInfo[] { return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition)!; } @@ -2440,12 +2447,7 @@ namespace FourSlash { assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); } else { - if (this.testType === FourSlashTestType.Server) { - assert.deepEqual(implementations, [], "Expected implementation list to be empty but implementations returned"); - } - else { - assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); - } + assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); } } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 98b61cf2fb55a..48cae7705f088 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -324,6 +324,10 @@ namespace FourSlashInterface { this.state.verifyGoToType(arg0, endMarkerName); } + public goToSourceDefinition(startMarkerNames: ArrayOrSingle, end: { file: string } | ArrayOrSingle) { + this.state.verifyGoToSourceDefinition(startMarkerNames, end); + } + public goToDefinitionForMarkers(...markerNames: string[]) { this.state.verifyGoToDefinitionForMarkers(markerNames); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ca8dbd1cac63b..56aaf0000443d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -83,6 +83,9 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", + SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", + /* @internal */ + SourceDefinitionAndBoundSpanFull = "sourceDefinitionAndBoundSpan-full", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -904,6 +907,10 @@ namespace ts.server.protocol { readonly command: CommandTypes.DefinitionAndBoundSpan; } + export interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { + readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + } + export interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; } diff --git a/src/server/session.ts b/src/server/session.ts index 7773d164d6459..133961ba35141 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1270,119 +1270,22 @@ namespace ts.server { }; } - private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { + private getSourceDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { const { file, project } = this.getFileAndProject(args); - if (!project.shouldEmitFile(project.getScriptInfo(file))) { - return { emitSkipped: true, outputFiles: [], diagnostics: [] }; - } - const result = project.getLanguageService().getEmitOutput(file); - return args.richResponse ? - { - ...result, - diagnostics: args.includeLinePosition ? - this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(result.diagnostics) : - result.diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true)) - } : - result; - } - - private mapJSDocTagInfo(tags: JSDocTagInfo[] | undefined, project: Project, richResponse: boolean): protocol.JSDocTagInfo[] { - return tags ? tags.map(tag => ({ - ...tag, - text: richResponse ? this.mapDisplayParts(tag.text, project) : tag.text?.map(part => part.text).join("") - })) : []; - } - - private mapDisplayParts(parts: SymbolDisplayPart[] | undefined, project: Project): protocol.SymbolDisplayPart[] { - if (!parts) { - return []; - } - return parts.map(part => part.kind !== "linkName" ? part : { - ...part, - target: this.toFileSpan((part as JSDocLinkDisplayPart).target.fileName, (part as JSDocLinkDisplayPart).target.textSpan, project), - }); - } - - private mapSignatureHelpItems(items: SignatureHelpItem[], project: Project, richResponse: boolean): protocol.SignatureHelpItem[] { - return items.map(item => ({ - ...item, - documentation: this.mapDisplayParts(item.documentation, project), - parameters: item.parameters.map(p => ({ ...p, documentation: this.mapDisplayParts(p.documentation, project) })), - tags: this.mapJSDocTagInfo(item.tags, project, richResponse), - })); - } + const position = this.getPositionInFile(args, file); + const scriptInfo = Debug.checkDefined(project.getScriptInfo(file)); - private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.DefinitionInfo[] { - return definitions.map(def => ({ ...this.toFileSpanWithContext(def.fileName, def.textSpan, def.contextSpan, project), ...def.unverified && { unverified: def.unverified } })); - } + const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); - /* - * When we map a .d.ts location to .ts, Visual Studio gets confused because there's no associated Roslyn Document in - * the same project which corresponds to the file. VS Code has no problem with this, and luckily we have two protocols. - * This retains the existing behavior for the "simplified" (VS Code) protocol but stores the .d.ts location in a - * set of additional fields, and does the reverse for VS (store the .d.ts location where - * it used to be and stores the .ts location in the additional fields). - */ - private static mapToOriginalLocation(def: T): T { - if (def.originalFileName) { - Debug.assert(def.originalTextSpan !== undefined, "originalTextSpan should be present if originalFileName is"); + if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { return { - ...def as any, - fileName: def.originalFileName, - textSpan: def.originalTextSpan, - targetFileName: def.fileName, - targetTextSpan: def.textSpan, - contextSpan: def.originalContextSpan, - targetContextSpan: def.contextSpan + definitions: emptyArray, + textSpan: undefined! // TODO: GH#18217 }; } - return def; - } - - private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan { - const ls = project.getLanguageService(); - const start = ls.toLineColumnOffset!(fileName, textSpan.start); // TODO: GH#18217 - const end = ls.toLineColumnOffset!(fileName, textSpanEnd(textSpan)); - - return { - file: fileName, - start: { line: start.line + 1, offset: start.character + 1 }, - end: { line: end.line + 1, offset: end.character + 1 } - }; - } - - private toFileSpanWithContext(fileName: string, textSpan: TextSpan, contextSpan: TextSpan | undefined, project: Project): protocol.FileSpanWithContext { - const fileSpan = this.toFileSpan(fileName, textSpan, project); - const context = contextSpan && this.toFileSpan(fileName, contextSpan, project); - return context ? - { ...fileSpan, contextStart: context.start, contextEnd: context.end } : - fileSpan; - } - - private getTypeDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.FileSpanWithContext[] { - const { file, project } = this.getFileAndProject(args); - const position = this.getPositionInFile(args, file); - - const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getTypeDefinitionAtPosition(file, position) || emptyArray, project); - return this.mapDefinitionInfo(definitions, project); - } - - private mapImplementationLocations(implementations: readonly ImplementationLocation[], project: Project): readonly ImplementationLocation[] { - return implementations.map((info): ImplementationLocation => { - const newDocumentSpan = getMappedDocumentSpan(info, project); - return !newDocumentSpan ? info : { - ...newDocumentSpan, - kind: info.kind, - displayParts: info.displayParts, - }; - }); - } - private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly ImplementationLocation[] { - const { file, project } = this.getFileAndProject(args); - const position = this.getPositionInFile(args, file); - const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project).slice(); - const needsJsResolution = !length(implementations); + let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); + const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { const ls = auxiliaryProject.getLanguageService(); @@ -1393,20 +1296,17 @@ namespace ts.server { const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); if (some(refined)) { for (const def of refined || emptyArray) { - pushIfUnique(implementations, definitionInfoToImplementationLocation(def), documentSpansEqual); + pushIfUnique(definitions, def, documentSpansEqual); } continue; } } - pushIfUnique(implementations, definitionInfoToImplementationLocation(jsDefinition), documentSpansEqual); + pushIfUnique(definitions, jsDefinition, documentSpansEqual); } } else { - const ambientDefinitions = this.mapDefinitionInfoLocations( - project.getLanguageService().getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true)?.definitions || emptyArray, - project, - ).filter(d => d.isAmbient && d.isAliasTarget); - for (const candidate of ambientDefinitions) { + const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); + for (const candidate of ambientCandidates) { const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); if (candidateFileName) { const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); @@ -1419,8 +1319,12 @@ namespace ts.server { } const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - for (const definition of searchForDeclaration(candidate.name, fileToSearch, auxiliaryProgram)) { - pushIfUnique(implementations, definition, documentSpansEqual); + const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); + for (const match of matches) { + const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + if (symbol) { + pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); + } } } } @@ -1428,20 +1332,21 @@ namespace ts.server { }); } - return simplifiedResult ? - implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) : - implementations.map(Session.mapToOriginalLocation); + definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); + const { textSpan } = unmappedDefinitionAndBoundSpan; - function definitionInfoToImplementationLocation(definition: DefinitionInfo): ImplementationLocation { + if (simplifiedResult) { return { - fileName: definition.fileName, - textSpan: definition.textSpan, - contextSpan: definition.contextSpan, - kind: definition.kind, - displayParts: [], + definitions: this.mapDefinitionInfo(definitions, project), + textSpan: toProtocolTextSpan(textSpan, scriptInfo) }; } + return { + definitions: definitions.map(Session.mapToOriginalLocation), + textSpan, + }; + function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { const sourceFile = program.getSourceFile(definition.fileName)!; const checker = program.getTypeChecker(); @@ -1537,6 +1442,123 @@ namespace ts.server { } } + private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { + const { file, project } = this.getFileAndProject(args); + if (!project.shouldEmitFile(project.getScriptInfo(file))) { + return { emitSkipped: true, outputFiles: [], diagnostics: [] }; + } + const result = project.getLanguageService().getEmitOutput(file); + return args.richResponse ? + { + ...result, + diagnostics: args.includeLinePosition ? + this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(result.diagnostics) : + result.diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true)) + } : + result; + } + + private mapJSDocTagInfo(tags: JSDocTagInfo[] | undefined, project: Project, richResponse: boolean): protocol.JSDocTagInfo[] { + return tags ? tags.map(tag => ({ + ...tag, + text: richResponse ? this.mapDisplayParts(tag.text, project) : tag.text?.map(part => part.text).join("") + })) : []; + } + + private mapDisplayParts(parts: SymbolDisplayPart[] | undefined, project: Project): protocol.SymbolDisplayPart[] { + if (!parts) { + return []; + } + return parts.map(part => part.kind !== "linkName" ? part : { + ...part, + target: this.toFileSpan((part as JSDocLinkDisplayPart).target.fileName, (part as JSDocLinkDisplayPart).target.textSpan, project), + }); + } + + private mapSignatureHelpItems(items: SignatureHelpItem[], project: Project, richResponse: boolean): protocol.SignatureHelpItem[] { + return items.map(item => ({ + ...item, + documentation: this.mapDisplayParts(item.documentation, project), + parameters: item.parameters.map(p => ({ ...p, documentation: this.mapDisplayParts(p.documentation, project) })), + tags: this.mapJSDocTagInfo(item.tags, project, richResponse), + })); + } + + private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.DefinitionInfo[] { + return definitions.map(def => ({ ...this.toFileSpanWithContext(def.fileName, def.textSpan, def.contextSpan, project), ...def.unverified && { unverified: def.unverified } })); + } + + /* + * When we map a .d.ts location to .ts, Visual Studio gets confused because there's no associated Roslyn Document in + * the same project which corresponds to the file. VS Code has no problem with this, and luckily we have two protocols. + * This retains the existing behavior for the "simplified" (VS Code) protocol but stores the .d.ts location in a + * set of additional fields, and does the reverse for VS (store the .d.ts location where + * it used to be and stores the .ts location in the additional fields). + */ + private static mapToOriginalLocation(def: T): T { + if (def.originalFileName) { + Debug.assert(def.originalTextSpan !== undefined, "originalTextSpan should be present if originalFileName is"); + return { + ...def as any, + fileName: def.originalFileName, + textSpan: def.originalTextSpan, + targetFileName: def.fileName, + targetTextSpan: def.textSpan, + contextSpan: def.originalContextSpan, + targetContextSpan: def.contextSpan + }; + } + return def; + } + + private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan { + const ls = project.getLanguageService(); + const start = ls.toLineColumnOffset!(fileName, textSpan.start); // TODO: GH#18217 + const end = ls.toLineColumnOffset!(fileName, textSpanEnd(textSpan)); + + return { + file: fileName, + start: { line: start.line + 1, offset: start.character + 1 }, + end: { line: end.line + 1, offset: end.character + 1 } + }; + } + + private toFileSpanWithContext(fileName: string, textSpan: TextSpan, contextSpan: TextSpan | undefined, project: Project): protocol.FileSpanWithContext { + const fileSpan = this.toFileSpan(fileName, textSpan, project); + const context = contextSpan && this.toFileSpan(fileName, contextSpan, project); + return context ? + { ...fileSpan, contextStart: context.start, contextEnd: context.end } : + fileSpan; + } + + private getTypeDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.FileSpanWithContext[] { + const { file, project } = this.getFileAndProject(args); + const position = this.getPositionInFile(args, file); + + const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getTypeDefinitionAtPosition(file, position) || emptyArray, project); + return this.mapDefinitionInfo(definitions, project); + } + + private mapImplementationLocations(implementations: readonly ImplementationLocation[], project: Project): readonly ImplementationLocation[] { + return implementations.map((info): ImplementationLocation => { + const newDocumentSpan = getMappedDocumentSpan(info, project); + return !newDocumentSpan ? info : { + ...newDocumentSpan, + kind: info.kind, + displayParts: info.displayParts, + }; + }); + } + + private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly ImplementationLocation[] { + const { file, project } = this.getFileAndProject(args); + const position = this.getPositionInFile(args, file); + const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project); + return simplifiedResult ? + implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) : + implementations.map(Session.mapToOriginalLocation); + } + private getOccurrences(args: protocol.FileLocationRequestArgs): readonly protocol.OccurrencesResponseItem[] { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); @@ -2844,6 +2866,12 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index 19f59d0127ce2..bfb533b0160b2 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -347,7 +347,7 @@ namespace ts.CallHierarchy { return []; } const location = getCallHierarchyDeclarationReferenceNode(declaration); - const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*sourceMapper*/ undefined, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined); + const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined); return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : []; } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 021dcabda439d..ffcec349cc663 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -29,7 +29,7 @@ namespace ts { function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*sourceMapper*/ undefined, /*options*/ undefined, sourceFilesSet); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); if (!referenceEntries) return undefined; const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 047f85d266375..fd4c30eb66c6e 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -204,9 +204,9 @@ namespace ts.FindAllReferences { readonly providePrefixAndSuffixTextForRename?: boolean; } - export function findReferencedSymbols(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position); - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, { use: FindReferencesUse.References }); + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); const checker = program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => @@ -217,10 +217,10 @@ namespace ts.FindAllReferences { }); } - export function getImplementationsAtPosition(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { + export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { const node = getTouchingPropertyName(sourceFile, position); let referenceEntries: Entry[] | undefined; - const entries = getImplementationReferenceEntries(program, sourceMapper, cancellationToken, sourceFiles, node, position); + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); if ( node.parent.kind === SyntaxKind.PropertyAccessExpression @@ -239,7 +239,7 @@ namespace ts.FindAllReferences { continue; } referenceEntries = append(referenceEntries, entry); - const entries = getImplementationReferenceEntries(program, sourceMapper, cancellationToken, sourceFiles, entry.node, entry.node.pos); + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); if (entries) { queue.push(...entries); } @@ -249,7 +249,7 @@ namespace ts.FindAllReferences { return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } - function getImplementationReferenceEntries(program: Program, sourceMapper: SourceMapper, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { + function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { if (node.kind === SyntaxKind.SourceFile) { return undefined; } @@ -270,15 +270,15 @@ namespace ts.FindAllReferences { } else { // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, { implementations: true, use: FindReferencesUse.References }); + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); } } export function findReferenceOrRenameEntries( - program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, sourceMapper: SourceMapper | undefined, options: Options | undefined, + program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, convertEntry: ToReferenceOrRenameEntry, ): T[] | undefined { - return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options)), entry => convertEntry(entry, node, program.getTypeChecker())); + return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); } export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; @@ -289,22 +289,10 @@ namespace ts.FindAllReferences { program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, - sourceMapper?: SourceMapper, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), ): readonly Entry[] | undefined { - const allEntries = flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, sourceMapper, options, sourceFilesSet)); - if (options.implementations) { - return filter(allEntries, entry => { - switch (entry.kind) { - case EntryKind.Span: - return !isDeclarationFileName(entry.fileName); - default: - return Core.isImplementation(entry.node, sourceMapper); - } - }); - } - return allEntries; + return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); } function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { @@ -633,7 +621,7 @@ namespace ts.FindAllReferences { /** Encapsulates the core find-all-references algorithm. */ export namespace Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, sourceMapper?: SourceMapper, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { if (options.use === FindReferencesUse.References) { node = getAdjustedReferenceLocation(node); } @@ -694,16 +682,16 @@ namespace ts.FindAllReferences { return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, sourceMapper, cancellationToken, options, sourceFilesSet); + const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { return moduleReferences; } const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); const moduleReferencesOfExportTarget = aliasedSymbol && - getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, sourceMapper, cancellationToken, options, sourceFilesSet); + getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, sourceMapper, options); + const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); } @@ -747,7 +735,7 @@ namespace ts.FindAllReferences { return undefined; } - function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], sourceMapper: SourceMapper | undefined, cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { + function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); if (!moduleSourceFile) return undefined; const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); @@ -757,7 +745,7 @@ namespace ts.FindAllReferences { // Continue to get references to 'export ='. const checker = program.getTypeChecker(); symbol = skipAlias(exportEquals, checker); - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, sourceMapper, options)); + return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); } /** @@ -930,13 +918,13 @@ namespace ts.FindAllReferences { } /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, sourceMapper: SourceMapper | undefined, options: Options): SymbolAndEntries[] { + function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; // Compute the meaning from the location and the symbol it references const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, sourceMapper, searchMeaning, options, result); + const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); if (exportSpecifier) { @@ -1077,7 +1065,6 @@ namespace ts.FindAllReferences { readonly specialSearchKind: SpecialSearchKind, readonly checker: TypeChecker, readonly cancellationToken: CancellationToken, - readonly sourceMapper: SourceMapper | undefined, readonly searchMeaning: SemanticMeaning, readonly options: Options, private readonly result: Push) { @@ -1816,7 +1803,7 @@ namespace ts.FindAllReferences { function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isDeclarationName(refNode) && isImplementation(refNode.parent, state.sourceMapper)) { + if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { addReference(refNode); return; } @@ -2308,14 +2295,8 @@ namespace ts.FindAllReferences { return meaning; } - export function isImplementation(node: Node, sourceMapper: SourceMapper | undefined): boolean { - if (node.flags & NodeFlags.Ambient) { - if (!sourceMapper) return false; - if (isPartOfTypeNode(node)) return false; - const source = sourceMapper.tryGetSourcePosition({ fileName: node.getSourceFile().fileName, pos: node.pos }); - return !!source && !isDeclarationFileName(source.fileName); - } - return ( + function isImplementation(node: Node): boolean { + return !(node.flags & NodeFlags.Ambient) && ( (isVariableLike(node) ? hasInitializer(node) : isFunctionLikeDeclaration(node) ? !!node.body : isClassLike(node) || isModuleOrEnumDeclaration(node))); diff --git a/src/services/services.ts b/src/services/services.ts index b2e2cf465a42b..5a3a7c21dffe6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1765,7 +1765,7 @@ namespace ts { function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { synchronizeHostData(); - return FindAllReferences.getImplementationsAtPosition(program, sourceMapper, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } /// References and Occurrences @@ -1827,12 +1827,12 @@ namespace ts { ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) : program.getSourceFiles(); - return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, sourceMapper, options, cb); + return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); } function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program, sourceMapper, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } function getFileReferences(fileName: string): ReferenceEntry[] { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 955d87c6f6346..8990c6615a20f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,6 +317,8 @@ declare namespace FourSlashInterface { goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string }): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; diff --git a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts index 8cf54c9ea4cd0..c9fbfb98fae3e 100644 --- a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts +++ b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts @@ -1,13 +1,13 @@ /// // @Filename: /a.js -//// export const [|a|] = "a"; +//// export const /*end*/a = "a"; // @Filename: /a.d.ts //// export declare const a: string; // @Filename: /index.ts //// import { a } from "./a"; -//// a/*start*/ +//// [|a/*start*/|] -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts index a6384efd75a8a..a2166786c54cd 100644 --- a/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts +++ b/tests/cases/fourslash/server/goToSource2_nodeModulesWithTypes.ts @@ -6,13 +6,13 @@ //// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js", "types": "./types/main.d.ts" } // @Filename: /node_modules/foo/lib/main.js -//// export const [|a|] = "a"; +//// export const /*end*/a = "a"; // @Filename: /node_modules/foo/types/main.d.ts //// export declare const a: string; // @Filename: /index.ts //// import { a } from "foo"; -//// a/*start*/ +//// [|a/*start*/|] -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts index b79f870fc2b30..152d411040a12 100644 --- a/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts +++ b/tests/cases/fourslash/server/goToSource3_nodeModulesAtTypes.ts @@ -6,7 +6,7 @@ //// { "name": "foo", "version": "1.0.0", "main": "./lib/main.js" } // @Filename: /node_modules/foo/lib/main.js -//// export const [|a|] = "a"; +//// export const /*end*/a = "a"; // @Filename: /node_modules/@types/foo/package.json //// { "name": "@types/foo", "version": "1.0.0", "types": "./index.d.ts" } @@ -16,6 +16,6 @@ // @Filename: /index.ts //// import { a } from "foo"; -//// a/*start*/ +//// [|a/*start*/|] -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts index 57396683f2ddc..dc8f9a47abc51 100644 --- a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts +++ b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts @@ -1,8 +1,8 @@ /// // @Filename: /index.ts -//// import { a } from "./a"; -//// a/*start*/ +//// import { a/*end*/ } from "./a"; +//// [|a/*start*/|] -goTo.marker("start"); -verify.implementationListIsEmpty(); +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts index 8637439ee68bd..f2b7e2b73457f 100644 --- a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts +++ b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts @@ -1,7 +1,7 @@ /// // @Filename: /a.ts -//// export const [|a|] = 'a'; +//// export const /*end*/a = 'a'; // @Filename: /a.d.ts //// export declare const a: string; @@ -11,6 +11,7 @@ // @Filename: /b.ts //// import { a } from './a'; -//// a/*start*/ +//// [|a/*start*/|] -verify.allRangesAppearInImplementationList("start"); +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts index 316fde37f4ca4..87b7065a17b0a 100644 --- a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts +++ b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts @@ -4,7 +4,7 @@ //// { "name": "foo", "version": "1.2.3", "typesVersions": { "*": { "*": ["./types/*"] } } } // @Filename: /node_modules/foo/src/a.ts -//// export const [|a|] = 'a'; +//// export const /*end*/a = 'a'; // @Filename: /node_modules/foo/types/a.d.ts //// export declare const a: string; @@ -18,6 +18,7 @@ // @Filename: /b.ts //// import { a } from 'foo/a'; -//// a/*start*/ +//// [|a/*start*/|] -verify.allRangesAppearInImplementationList("start"); +verify.goToDefinition("start", "end"); +verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts index 203b0e6ed9276..6a0e13962ba38 100644 --- a/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts +++ b/tests/cases/fourslash/server/goToSource7_conditionallyMinified.ts @@ -15,19 +15,19 @@ //// } // @Filename: /node_modules/react/cjs/react.production.min.js -//// 'use strict';exports.[|useState|]=function(a){};exports.version='16.8.6'; +//// 'use strict';exports./*production*/useState=function(a){};exports.version='16.8.6'; // @Filename: /node_modules/react/cjs/react.development.js //// 'use strict'; //// if (process.env.NODE_ENV !== 'production') { //// (function() { //// function useState(initialState) {} -//// exports.[|useState|] = useState; +//// exports./*development*/useState = useState; //// exports.version = '16.8.6'; //// }()); //// } // @Filename: /index.ts -//// import { /*start*/useState } from 'react'; +//// import { [|/*start*/useState|] } from 'react'; -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", ["production", "development"]); diff --git a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts index 3712fb9303cd2..e596fd97d9e0d 100644 --- a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts +++ b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts @@ -22,12 +22,12 @@ //// * _.add(6, 4); //// * // => 10 //// */ -//// var [|add|] = createMathOperation(function(augend, addend) { +//// var [|/*variable*/add|] = createMathOperation(function(augend, addend) { //// return augend + addend; //// }, 0); //// //// function lodash(value) {} -//// lodash.[|add|] = add; +//// lodash.[|/*property*/add|] = add; //// //// /** Detect free variable `global` from Node.js. */ //// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; @@ -72,6 +72,6 @@ //// } // @Filename: /index.ts -//// import { /*start*/add } from 'lodash'; +//// import { [|/*start*/add|] } from 'lodash'; -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", ["variable", "property"]); From 6104a5cccbd36f4a96eae1d06b4cf79ecf2a596f Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 16:02:42 -0700 Subject: [PATCH 12/45] Fix tests --- src/server/session.ts | 7 ++++--- tests/cases/fourslash/fourslash.ts | 2 +- .../fourslash/server/goToSource10_mapFromAtTypes3.ts | 6 +++--- .../fourslash/server/goToSource4_sameAsGoToDef1.ts | 8 -------- ...sameAsGoToDef2.ts => goToSource5_sameAsGoToDef1.ts} | 0 ...sameAsGoToDef3.ts => goToSource6_sameAsGoToDef2.ts} | 0 .../fourslash/server/goToSource9_mapFromAtTypes2.ts | 10 +++++----- 7 files changed, 13 insertions(+), 20 deletions(-) delete mode 100644 tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts rename tests/cases/fourslash/server/{goToSource5_sameAsGoToDef2.ts => goToSource5_sameAsGoToDef1.ts} (100%) rename tests/cases/fourslash/server/{goToSource6_sameAsGoToDef3.ts => goToSource6_sameAsGoToDef2.ts} (100%) diff --git a/src/server/session.ts b/src/server/session.ts index 133961ba35141..9f4cad83e66ef 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1319,11 +1319,12 @@ namespace ts.server { } const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationsInFile(candidate.name, fileToSearch); + const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); for (const match of matches) { const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - if (symbol) { - pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(match, auxiliaryProgram.getTypeChecker(), symbol, match)); + const decl = getDeclarationFromName(match); + if (symbol && decl) { + pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); } } } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8990c6615a20f..ba1163683d9f5 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,7 +317,7 @@ declare namespace FourSlashInterface { goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; - goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string }): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string, unverified?: boolean }): void; goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts index be26ec1da259c..b7adebebd3f96 100644 --- a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -22,12 +22,12 @@ //// * _.add(6, 4); //// * // => 10 //// */ -//// var [|add|] = createMathOperation(function(augend, addend) { +//// var [|/*variable*/add|] = createMathOperation(function(augend, addend) { //// return augend + addend; //// }, 0); //// //// function lodash(value) {} -//// lodash.[|add|] = add; +//// lodash.[|/*property*/add|] = add; //// //// /** Detect free variable `global` from Node.js. */ //// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; @@ -66,4 +66,4 @@ // @Filename: /index.ts //// import { /*start*/add } from 'lodash'; -verify.allRangesAppearInImplementationList("start"); +verify.goToSourceDefinition("start", ["variable", "property"]); diff --git a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts b/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts deleted file mode 100644 index dc8f9a47abc51..0000000000000 --- a/tests/cases/fourslash/server/goToSource4_sameAsGoToDef1.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -// @Filename: /index.ts -//// import { a/*end*/ } from "./a"; -//// [|a/*start*/|] - -verify.goToDefinition("start", "end"); -verify.goToSourceDefinition("start", "end"); diff --git a/tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts b/tests/cases/fourslash/server/goToSource5_sameAsGoToDef1.ts similarity index 100% rename from tests/cases/fourslash/server/goToSource5_sameAsGoToDef2.ts rename to tests/cases/fourslash/server/goToSource5_sameAsGoToDef1.ts diff --git a/tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts b/tests/cases/fourslash/server/goToSource6_sameAsGoToDef2.ts similarity index 100% rename from tests/cases/fourslash/server/goToSource6_sameAsGoToDef3.ts rename to tests/cases/fourslash/server/goToSource6_sameAsGoToDef2.ts diff --git a/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts index 24da362439789..0c68d4922f2a0 100644 --- a/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts +++ b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts @@ -6,7 +6,7 @@ //// { "name": "lodash", "version": "4.17.15", "main": "./lodash.js" } // @Filename: /node_modules/lodash/lodash.js -//// [||];(function() { +//// ;(function() { //// /** //// * Adds two numbers. //// * @@ -72,8 +72,8 @@ //// } // @Filename: /index.ts -//// import /*defaultImport*/_, { /*unresolvableNamedImport*/foo } from /*moduleSpecifier*/'lodash'; +//// import [|/*defaultImport*/_|], { [|/*unresolvableNamedImport*/foo|] } from [|/*moduleSpecifier*/'lodash'|]; -verify.allRangesAppearInImplementationList("defaultImport"); -verify.allRangesAppearInImplementationList("unresolvableNamedImport"); -verify.allRangesAppearInImplementationList("moduleSpecifier"); +verify.goToSourceDefinition("defaultImport", { file: "/node_modules/lodash/lodash.js", unverified: true }); +verify.goToSourceDefinition("unresolvableNamedImport", { file: "/node_modules/lodash/lodash.js", unverified: true }); +verify.goToSourceDefinition("moduleSpecifier", { file: "/node_modules/lodash/lodash.js", unverified: true }); From 4e64659c833b661229d24594dbb0f5ce0d8ebeef Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 16:17:05 -0700 Subject: [PATCH 13/45] Revert go-to-implementation changes --- src/services/findAllReferences.ts | 4 ++-- tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts | 2 +- tests/cases/fourslash/goToImplementationLocal_06.ts | 2 +- tests/cases/fourslash/goToImplementationLocal_07.ts | 2 +- tests/cases/fourslash/goToImplementationLocal_08.ts | 3 +-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index e9bc04a8cd2ab..6abe7308d9750 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -2313,10 +2313,10 @@ namespace ts.FindAllReferences { } function isImplementation(node: Node): boolean { - return !(node.flags & NodeFlags.Ambient) && ( + return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : (isVariableLike(node) ? hasInitializer(node) : isFunctionLikeDeclaration(node) ? !!node.body : - isClassLike(node) || isModuleOrEnumDeclaration(node))); + isClassLike(node) || isModuleOrEnumDeclaration(node)); } export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { diff --git a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts index 67efdb195e690..5deeb4c02532e 100644 --- a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts +++ b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts @@ -4,4 +4,4 @@ ////f[/*0*/"x"] = 0; ////f[[|/*1*/"x"|]] = 1; -verify.goToDefinition("1", ["0", "1"]); +verify.goToDefinition("1", "0"); diff --git a/tests/cases/fourslash/goToImplementationLocal_06.ts b/tests/cases/fourslash/goToImplementationLocal_06.ts index 923f02a4aae9c..90c489bcf5402 100644 --- a/tests/cases/fourslash/goToImplementationLocal_06.ts +++ b/tests/cases/fourslash/goToImplementationLocal_06.ts @@ -5,4 +5,4 @@ //// declare var [|someVar|]: string; //// someVa/*reference*/r -verify.implementationListIsEmpty(); +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationLocal_07.ts b/tests/cases/fourslash/goToImplementationLocal_07.ts index fabee95ceaa4e..b24b463e10721 100644 --- a/tests/cases/fourslash/goToImplementationLocal_07.ts +++ b/tests/cases/fourslash/goToImplementationLocal_07.ts @@ -5,4 +5,4 @@ //// declare function [|someFunction|](): () => void; //// someFun/*reference*/ction(); -verify.implementationListIsEmpty(); +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationLocal_08.ts b/tests/cases/fourslash/goToImplementationLocal_08.ts index 340aa1ef47814..b24b463e10721 100644 --- a/tests/cases/fourslash/goToImplementationLocal_08.ts +++ b/tests/cases/fourslash/goToImplementationLocal_08.ts @@ -5,5 +5,4 @@ //// declare function [|someFunction|](): () => void; //// someFun/*reference*/ction(); -goTo.marker("reference"); -verify.implementationListIsEmpty(); +verify.allRangesAppearInImplementationList("reference"); From 1cb2ba646c9e80d1ca08b6fc3f6a2219aac44f67 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 16:47:27 -0700 Subject: [PATCH 14/45] Wow a bunch of code was unnecessary --- src/server/session.ts | 92 ------------------- .../server/goToSource10_mapFromAtTypes3.ts | 2 +- 2 files changed, 1 insertion(+), 93 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 3ab144bce2395..e42ad09878888 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1255,32 +1255,6 @@ namespace ts.server { pushIfUnique(definitions, jsDefinition, documentSpansEqual); } } - else { - const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); - for (const candidate of ambientCandidates) { - const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); - if (candidateFileName) { - const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); - const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; - if (!scriptInfo) { - continue; - } - if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { - auxiliaryProject.addRoot(scriptInfo); - } - const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; - const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); - for (const match of matches) { - const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - const decl = getDeclarationFromName(match); - if (symbol && decl) { - pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); - } - } - } - } - } }); } @@ -1299,72 +1273,6 @@ namespace ts.server { textSpan, }; - function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { - const sourceFile = program.getSourceFile(definition.fileName)!; - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); - if (symbol) { - let parent = symbol.parent; - while (parent && !isExternalModuleSymbol(parent)) { - parent = parent.parent; - } - if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { - // Always CommonJS right now, but who knows in the future - const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); - const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; - if (fileName) { - return fileName; - } - } - const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; - if (fileName) { - return fileName; - } - } - } - - function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { - const nodeModulesPathParts = getNodeModulePathParts(fileName); - if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { - // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up. - const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex); - const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache(); - const compilerOptions = project.getCompilationSettings(); - const packageJson = getPackageScopeForPath(project.toPath(packageDirectory + "/package.json"), packageJsonCache, project, compilerOptions); - if (!packageJson) return undefined; - // Use fake options instead of actual compiler options to avoid following export map if the project uses node12 or nodenext - - // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be - // resolved from the package root under --moduleResolution node - const entrypoints = getEntrypointsFromPackageJsonInfo( - packageJson, - { moduleResolution: ModuleResolutionKind.NodeJs }, - project, - project.getModuleResolutionCache()); - // This substring is correct only because we checked for a single `/node_modules/` at the top. - const packageNamePathPart = fileName.substring( - nodeModulesPathParts.topLevelPackageNameIndex + 1, - nodeModulesPathParts.packageRootIndex); - const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart)); - const path = project.toPath(fileName); - if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { - // This file was the main entrypoint of a package. Try to resolve that same package name with - // the auxiliary project that only resolves to implementation files. - const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile); - return implementationResolution?.resolvedFileName; - } - else { - // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package. - const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1); - const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`; - const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile); - return implementationResolution?.resolvedFileName; - } - } - // We're not in node_modules, and we only get to this function if non-dts module resolution failed. - // I'm not sure what else I can do here that isn't already covered by that module resolution. - return undefined; - } - function tryRefineDefinition(definition: DefinitionInfo, program: Program, auxiliaryProgram: Program) { const fileToSearch = auxiliaryProgram.getSourceFile(definition.fileName); if (!fileToSearch) { diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts index b7adebebd3f96..524b44c16c88d 100644 --- a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -64,6 +64,6 @@ // @Filename: /index.ts -//// import { /*start*/add } from 'lodash'; +//// import { [|/*start*/add|] } from 'lodash'; verify.goToSourceDefinition("start", ["variable", "property"]); From 7e57890b16bd61b0dadfb4dc7a8be49efa133bd5 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 15 Mar 2022 09:50:16 -0700 Subject: [PATCH 15/45] Update baselines and go-to-def test --- tests/baselines/reference/api/tsserverlibrary.d.ts | 5 +++++ tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c00d3cde5e715..76b7560d1bc78 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7018,6 +7018,7 @@ declare namespace ts.server.protocol { Rename = "rename", Saveto = "saveto", SignatureHelp = "signatureHelp", + SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -7637,6 +7638,9 @@ declare namespace ts.server.protocol { interface DefinitionAndBoundSpanRequest extends FileLocationRequest { readonly command: CommandTypes.DefinitionAndBoundSpan; } + interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { + readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + } interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; } @@ -10592,6 +10596,7 @@ declare namespace ts.server { private getDefinition; private mapDefinitionInfoLocations; private getDefinitionAndBoundSpan; + private getSourceDefinitionAndBoundSpan; private getEmitOutput; private mapJSDocTagInfo; private mapDisplayParts; diff --git a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts index 5deeb4c02532e..67efdb195e690 100644 --- a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts +++ b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts @@ -4,4 +4,4 @@ ////f[/*0*/"x"] = 0; ////f[[|/*1*/"x"|]] = 1; -verify.goToDefinition("1", "0"); +verify.goToDefinition("1", ["0", "1"]); From 43c01e26a7b4d0490f0aed50b2552ecdf4ef784b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 16 Mar 2022 16:10:12 -0700 Subject: [PATCH 16/45] Fix navigation on symbols that are not aliases but resolve through aliases in chain --- src/server/session.ts | 3 +- src/services/goToDefinition.ts | 58 +++++++++---------- src/services/types.ts | 1 - .../server/goToSource11_propertyOfAlias.ts | 15 +++++ 4 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource11_propertyOfAlias.ts diff --git a/src/server/session.ts b/src/server/session.ts index e42ad09878888..e71425a318a7e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1184,7 +1184,6 @@ namespace ts.server { containerName: info.containerName, kind: info.kind, name: info.name, - isAliasTarget: info.isAliasTarget, failedAliasResolution: info.failedAliasResolution, ...info.unverified && { unverified: info.unverified }, }; @@ -1236,7 +1235,7 @@ namespace ts.server { } let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = !some(definitions, d => !!d.isAliasTarget && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); + const needsJsResolution = !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { const ls = auxiliaryProject.getLanguageService(); diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 1558114e0d5fa..c99ebf75f64fa 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -28,8 +28,7 @@ namespace ts.GoToDefinition { if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { const classDecl = node.parent.parent; - const { symbol, isAliasTarget, failedAliasResolution } = getSymbol(classDecl, typeChecker); - if (aliasesOnly && !isAliasTarget) return undefined; + const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker); const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : ""; @@ -37,11 +36,11 @@ namespace ts.GoToDefinition { return map(staticBlocks, staticBlock => { let { pos } = moveRangePastModifiers(staticBlock); pos = skipTrivia(sourceFile.text, pos); - return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, isAliasTarget, failedAliasResolution, { start: pos, length: "static".length }); + return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, failedAliasResolution, { start: pos, length: "static".length }); }); } - let { symbol, isAliasTarget, failedAliasResolution } = getSymbol(node, typeChecker); + let { symbol, failedAliasResolution } = getSymbol(node, typeChecker); let fallbackNode = node; if (aliasesOnly && failedAliasResolution) { @@ -49,7 +48,7 @@ namespace ts.GoToDefinition { const importDeclaration = findAncestor(node, isAnyImportOrBareOrAccessedRequire); const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); if (moduleSpecifier) { - ({ symbol, isAliasTarget, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker)); + ({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker)); fallbackNode = moduleSpecifier; } } @@ -66,7 +65,6 @@ namespace ts.GoToDefinition { containerKind: undefined!, kind: ScriptElementKind.scriptElement, textSpan: createTextSpan(0, 0), - isAliasTarget: true, failedAliasResolution, isAmbient: isDeclarationFileName(ref.resolvedFileName), unverified: fallbackNode !== node, @@ -74,25 +72,25 @@ namespace ts.GoToDefinition { } } - 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 if (!symbol) { return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); } + if (aliasesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined; + 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, isAliasTarget, failedAliasResolution); + const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, failedAliasResolution); // 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, isAliasTarget, failedAliasResolution, calledDeclaration) || emptyArray; + const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, calledDeclaration) || emptyArray; // For a 'super()' call, put the signature first, else put the variable first. return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; } @@ -105,7 +103,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, isAliasTarget, failedAliasResolution)) : emptyArray; + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, failedAliasResolution)) : emptyArray; return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } @@ -130,7 +128,7 @@ namespace ts.GoToDefinition { }); } - return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, isAliasTarget, failedAliasResolution)); + return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution)); } /** @@ -234,25 +232,25 @@ namespace ts.GoToDefinition { } if (isImportMeta(node.parent) && node.parent.name === node) { - return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*isAliasTarget*/ false, /*failedAliasResolution*/ false); + return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*failedAliasResolution*/ false); } - const { symbol, isAliasTarget, failedAliasResolution } = getSymbol(node, typeChecker); + const { symbol, failedAliasResolution } = 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, isAliasTarget, failedAliasResolution); + const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, failedAliasResolution); // 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, isAliasTarget, failedAliasResolution); + const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, failedAliasResolution); return typeDefinitions.length ? typeDefinitions - : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, isAliasTarget, failedAliasResolution) + : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, failedAliasResolution) : undefined; } - function definitionFromType(type: Type, checker: TypeChecker, node: Node, isAliasTarget: boolean, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] { + function definitionFromType(type: Type, checker: TypeChecker, node: Node, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] { return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => - t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, isAliasTarget, failedAliasResolution)); + t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, failedAliasResolution)); } function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { @@ -304,13 +302,13 @@ namespace ts.GoToDefinition { if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { const aliased = checker.getAliasedSymbol(symbol); if (aliased.declarations) { - return { symbol: aliased, isAliasTarget: true }; + return { symbol: aliased }; } else { failedAliasResolution = true; } } - return { symbol, isAliasTarget: !!(symbol && isExternalModuleSymbol(symbol)), failedAliasResolution }; + return { symbol, failedAliasResolution }; } // Go to the original declaration for cases: @@ -358,11 +356,11 @@ namespace ts.GoToDefinition { return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property; } - function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined { + function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined { const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration); const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d)); const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations; - return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, isAliasTarget, failedAliasResolution)); + return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, failedAliasResolution)); function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { // Applicable only if we are in a new expression, or we are on a constructor declaration @@ -390,21 +388,21 @@ namespace ts.GoToDefinition { return declarations.length ? declarationsWithBody.length !== 0 ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) - : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, isAliasTarget, failedAliasResolution)] + : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, failedAliasResolution)] : undefined; } } /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, isAliasTarget?: boolean, failedAliasResolution?: boolean): DefinitionInfo { + export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: 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, isAliasTarget, failedAliasResolution); + return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, failedAliasResolution); } /** Creates a DefinitionInfo directly from the name of a declaration. */ - function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, isAliasTarget?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { + function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { const sourceFile = declaration.getSourceFile(); if (!textSpan) { const name = getNameOfDeclaration(declaration) || declaration; @@ -424,7 +422,6 @@ namespace ts.GoToDefinition { ), isLocal: !isDefinitionVisible(checker, declaration), isAmbient: !!(declaration.flags & NodeFlags.Ambient), - isAliasTarget, failedAliasResolution, }; } @@ -460,8 +457,8 @@ namespace ts.GoToDefinition { } } - function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, isAliasTarget?: boolean, failedAliasResolution?: boolean): DefinitionInfo { - return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, isAliasTarget, failedAliasResolution); + function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, failedAliasResolution?: boolean): DefinitionInfo { + return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, failedAliasResolution); } export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { @@ -477,7 +474,6 @@ namespace ts.GoToDefinition { containerName: undefined!, containerKind: undefined!, // TODO: GH#18217 unverified, - isAliasTarget: true, }; } diff --git a/src/services/types.ts b/src/services/types.ts index 4d8d3dd0460c8..3b3ab35e62f16 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1037,7 +1037,6 @@ namespace ts { unverified?: boolean; /* @internal */ isLocal?: boolean; /* @internal */ isAmbient?: boolean; - /* @internal */ isAliasTarget?: boolean; /* @internal */ failedAliasResolution?: boolean; } diff --git a/tests/cases/fourslash/server/goToSource11_propertyOfAlias.ts b/tests/cases/fourslash/server/goToSource11_propertyOfAlias.ts new file mode 100644 index 0000000000000..9856d82610946 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource11_propertyOfAlias.ts @@ -0,0 +1,15 @@ +/// + +// @moduleResolution: node + +// @Filename: /a.js +//// export const a = { /*end*/a: 'a' }; + +// @Filename: /a.d.ts +//// export declare const a: { a: string }; + +// @Filename: /b.ts +//// import { a } from './a'; +//// a.[|a/*start*/|] + +verify.goToSourceDefinition("start", "end"); From 34c6cfdebbb9e2506ea5e6d528d2cb57b2cdeb2e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 17:00:43 -0700 Subject: [PATCH 17/45] Temporarily replace go-to-def with new command implementation --- Gulpfile.js | 2 +- src/server/session.ts | 72 +++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index c9d3feff80b01..5e21ac6f38640 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -463,7 +463,7 @@ task("runtests").flags = { }; const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ cmdLineOptions.workers > 1, /*watchMode*/ false); -task("runtests-parallel", series(preBuild, preTest, runTestsParallel, postTest)); +task("runtests-parallel", series(preBuild, preTest, /*runTestsParallel, postTest*/)); task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; task("runtests-parallel").flags = { " --no-lint": "disables lint.", diff --git a/src/server/session.ts b/src/server/session.ts index e71425a318a7e..f310f1f5df2c5 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1191,36 +1191,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)); + + // 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)); @@ -2799,12 +2799,12 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); - }, + // [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + // }, + // [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + // }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, From 7357593d90abc27147d27ae19551339864817943 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 16 Mar 2022 16:17:11 -0700 Subject: [PATCH 18/45] Revert "Temporarily replace go-to-def with new command implementation" This reverts commit 34c6cfdebbb9e2506ea5e6d528d2cb57b2cdeb2e. --- Gulpfile.js | 2 +- src/server/session.ts | 72 +++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index 5e21ac6f38640..c9d3feff80b01 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -463,7 +463,7 @@ task("runtests").flags = { }; const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ cmdLineOptions.workers > 1, /*watchMode*/ false); -task("runtests-parallel", series(preBuild, preTest, /*runTestsParallel, postTest*/)); +task("runtests-parallel", series(preBuild, preTest, runTestsParallel, postTest)); task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; task("runtests-parallel").flags = { " --no-lint": "disables lint.", diff --git a/src/server/session.ts b/src/server/session.ts index f310f1f5df2c5..e71425a318a7e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1191,36 +1191,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)); + + 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)); @@ -2799,12 +2799,12 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, - // [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); - // }, - // [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); - // }, + [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, From d14e43d6e25ada8221ffb86fc05f6216ce029f53 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 21 Mar 2022 10:41:14 -0700 Subject: [PATCH 19/45] Revert "Wow a bunch of code was unnecessary" This reverts commit 1cb2ba646c9e80d1ca08b6fc3f6a2219aac44f67. --- src/server/session.ts | 92 +++++++++++++++++++ .../server/goToSource10_mapFromAtTypes3.ts | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/server/session.ts b/src/server/session.ts index e71425a318a7e..79a2e69d0ceca 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1254,6 +1254,32 @@ namespace ts.server { pushIfUnique(definitions, jsDefinition, documentSpansEqual); } } + else { + const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); + for (const candidate of ambientCandidates) { + const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); + if (candidateFileName) { + const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); + const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; + if (!scriptInfo) { + continue; + } + if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { + auxiliaryProject.addRoot(scriptInfo); + } + const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; + const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); + const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); + for (const match of matches) { + const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const decl = getDeclarationFromName(match); + if (symbol && decl) { + pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); + } + } + } + } + } }); } @@ -1272,6 +1298,72 @@ namespace ts.server { textSpan, }; + function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { + const sourceFile = program.getSourceFile(definition.fileName)!; + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); + if (symbol) { + let parent = symbol.parent; + while (parent && !isExternalModuleSymbol(parent)) { + parent = parent.parent; + } + if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { + // Always CommonJS right now, but who knows in the future + const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); + const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; + if (fileName) { + return fileName; + } + } + const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; + if (fileName) { + return fileName; + } + } + } + + function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { + const nodeModulesPathParts = getNodeModulePathParts(fileName); + if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { + // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up. + const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex); + const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache(); + const compilerOptions = project.getCompilationSettings(); + const packageJson = getPackageScopeForPath(project.toPath(packageDirectory + "/package.json"), packageJsonCache, project, compilerOptions); + if (!packageJson) return undefined; + // Use fake options instead of actual compiler options to avoid following export map if the project uses node12 or nodenext - + // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be + // resolved from the package root under --moduleResolution node + const entrypoints = getEntrypointsFromPackageJsonInfo( + packageJson, + { moduleResolution: ModuleResolutionKind.NodeJs }, + project, + project.getModuleResolutionCache()); + // This substring is correct only because we checked for a single `/node_modules/` at the top. + const packageNamePathPart = fileName.substring( + nodeModulesPathParts.topLevelPackageNameIndex + 1, + nodeModulesPathParts.packageRootIndex); + const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart)); + const path = project.toPath(fileName); + if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { + // This file was the main entrypoint of a package. Try to resolve that same package name with + // the auxiliary project that only resolves to implementation files. + const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + else { + // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package. + const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1); + const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`; + const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile); + return implementationResolution?.resolvedFileName; + } + } + // We're not in node_modules, and we only get to this function if non-dts module resolution failed. + // I'm not sure what else I can do here that isn't already covered by that module resolution. + return undefined; + } + function tryRefineDefinition(definition: DefinitionInfo, program: Program, auxiliaryProgram: Program) { const fileToSearch = auxiliaryProgram.getSourceFile(definition.fileName); if (!fileToSearch) { diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts index 524b44c16c88d..b7adebebd3f96 100644 --- a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -64,6 +64,6 @@ // @Filename: /index.ts -//// import { [|/*start*/add|] } from 'lodash'; +//// import { /*start*/add } from 'lodash'; verify.goToSourceDefinition("start", ["variable", "property"]); From 4e1cf1c1f9231c8e37f2345acf084cfd861b1acd Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 21 Mar 2022 16:41:32 -0700 Subject: [PATCH 20/45] Bring back some deleted code needed for a new test case --- src/server/session.ts | 70 ++++++------------- .../server/goToSource12_callbackParam.ts | 43 ++++++++++++ 2 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource12_callbackParam.ts diff --git a/src/server/session.ts b/src/server/session.ts index 79a2e69d0ceca..ba338633dc83e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1239,9 +1239,12 @@ namespace ts.server { if (needsJsResolution) { project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { const ls = auxiliaryProject.getLanguageService(); - const jsDefinitions = ls.getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true); - if (some(jsDefinitions?.definitions)) { - for (const jsDefinition of jsDefinitions!.definitions) { + const jsDefinitions = ls + .getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true) + ?.definitions + ?.filter(d => toNormalizedPath(d.fileName) !== file); + if (some(jsDefinitions)) { + for (const jsDefinition of jsDefinitions) { if (jsDefinition.unverified) { const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); if (some(refined)) { @@ -1255,27 +1258,24 @@ namespace ts.server { } } else { - const ambientCandidates = definitions.filter(d => d.isAliasTarget && d.isAmbient); + const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient); for (const candidate of ambientCandidates) { - const candidateFileName = getEffectiveFileNameOfDefinition(candidate, project.getLanguageService().getProgram()!); - if (candidateFileName) { - const fileNameToSearch = findImplementationFileFromDtsFileName(candidateFileName, file, auxiliaryProject); - const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; - if (!scriptInfo) { - continue; - } - if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { - auxiliaryProject.addRoot(scriptInfo); - } - const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; - const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); - for (const match of matches) { - const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - const decl = getDeclarationFromName(match); - if (symbol && decl) { - pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); - } + const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, auxiliaryProject); + const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; + if (!scriptInfo) { + continue; + } + if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { + auxiliaryProject.addRoot(scriptInfo); + } + const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; + const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); + const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); + for (const match of matches) { + const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const decl = getDeclarationFromName(match); + if (symbol && decl) { + pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); } } } @@ -1298,30 +1298,6 @@ namespace ts.server { textSpan, }; - function getEffectiveFileNameOfDefinition(definition: DefinitionInfo, program: Program) { - const sourceFile = program.getSourceFile(definition.fileName)!; - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(getTouchingPropertyName(sourceFile, definition.textSpan.start)); - if (symbol) { - let parent = symbol.parent; - while (parent && !isExternalModuleSymbol(parent)) { - parent = parent.parent; - } - if (parent?.declarations && some(parent.declarations, isExternalModuleAugmentation)) { - // Always CommonJS right now, but who knows in the future - const mode = getModeForUsageLocation(sourceFile, find(parent.declarations, isExternalModuleAugmentation)!.name as StringLiteral); - const fileName = sourceFile.resolvedModules?.get(stripQuotes(parent.name), mode)?.resolvedFileName; - if (fileName) { - return fileName; - } - } - const fileName = tryCast(parent?.valueDeclaration, isSourceFile)?.fileName; - if (fileName) { - return fileName; - } - } - } - function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { const nodeModulesPathParts = getNodeModulePathParts(fileName); if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) { diff --git a/tests/cases/fourslash/server/goToSource12_callbackParam.ts b/tests/cases/fourslash/server/goToSource12_callbackParam.ts new file mode 100644 index 0000000000000..d972cb58b3d29 --- /dev/null +++ b/tests/cases/fourslash/server/goToSource12_callbackParam.ts @@ -0,0 +1,43 @@ +/// + +// @moduleResolution: node + +// Actual yargs doesn't work like this because the implementation package's +// main entry exports a small function wrapper function whose return value +// is derived from something imported from another file where all the +// important code lives, and it's not quite clear on what grounds we can +// make the jump from the main entry to that other file. But this test +// demonstrates the need to do some filename-based jumping: regular go-to-def +// on `yargs` goes to the type definition `Yargs`, but a JS-only go-to-def +// simply stops on the callback parameter. So we have to start at the type +// definition and say "well, maybe I can find a JS counterpart to this .d.ts +// and just look for declarations called 'positional' in there." + +// @Filename: /node_modules/@types/yargs/package.json +//// { +//// "name": "@types/yargs", +//// "version": "1.0.0", +//// "types": "./index.d.ts" +//// } + +// @Filename: /node_modules/@types/yargs/index.d.ts +//// export interface Yargs { positional(): Yargs; } +//// export declare function command(command: string, cb: (yargs: Yargs) => void): void; + +// @Filename: /node_modules/yargs/package.json +//// { +//// "name": "yargs", +//// "version": "1.0.0", +//// "main": "index.js" +//// } + +// @Filename: /node_modules/yargs/index.js +//// export function command(cmd, cb) { cb({ /*end*/positional: "This is obviously not even close to realistic" }); } + +// @Filename: /index.ts +//// import { command } from "yargs"; +//// command("foo", yargs => { +//// yargs.[|/*start*/positional|](); +//// }); + +verify.goToSourceDefinition("start", "end"); From 52a79d536f14c7d6cfc17a75122c57eec7309fc1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 22 Mar 2022 09:46:06 -0700 Subject: [PATCH 21/45] Clean up a little --- src/server/editorServices.ts | 2 + src/server/project.ts | 49 +++++++++++-------- src/server/session.ts | 27 ++++------ src/server/utilitiesPublic.ts | 5 ++ src/services/goToDefinition.ts | 17 ++++--- .../reference/api/tsserverlibrary.d.ts | 4 -- tests/cases/fourslash/fourslash.ts | 1 + .../server/goToSource10_mapFromAtTypes3.ts | 2 +- .../server/goToSource12_callbackParam.ts | 2 +- .../server/goToSource8_mapFromAtTypes.ts | 5 +- 10 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 149332bb6f646..967f5c7556179 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -715,6 +715,8 @@ namespace ts.server { readonly newInferredProjectName = createProjectNameFactoryWithCounter(makeInferredProjectName); /*@internal*/ readonly newAutoImportProviderProjectName = createProjectNameFactoryWithCounter(makeAutoImportProviderProjectName); + /*@internal*/ + readonly newSingleCommandProjectName = createProjectNameFactoryWithCounter(makeSingleCommandProjectName); /** * Open files: with value being project root path, and key being Path of the file that is open */ diff --git a/src/server/project.ts b/src/server/project.ts index 8ab26299a3f0f..3fdb4ab7f434f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1775,7 +1775,8 @@ namespace ts.server { return this.projectService.getIncompleteCompletionsCache(); } - withAuxiliaryProjectForFiles(fileNames: string[], cb: (project: AuxiliaryProject) => void) { + /*@internal*/ + withNoDtsResolutionProject(rootFileNames: string[], cb: (project: Project, ensureRoot: (fileName: string) => boolean) => void) { const options: CompilerOptions = { ...this.getCompilerOptions(), noDtsResolution: true, @@ -1788,12 +1789,22 @@ namespace ts.server { lib: ts.emptyArray, noLib: true, }; - const project = new AuxiliaryProject(this.projectService, this.documentRegistry, options); - for (const fileName of fileNames) { + const project = new SingleCommandProject(this.projectService, this.documentRegistry, options); + for (const fileName of rootFileNames) { project.addRoot(this.getScriptInfo(fileName)!); } - project.updateGraph(); - cb(project); + cb( + project, + fileName => { + const info = project.getScriptInfo(fileName); + if (!info) return false; + if (!project.containsScriptInfo(info)) { + project.addRoot(info); + project.updateGraph(); + } + return true; + } + ); project.close(); } } @@ -1932,32 +1943,28 @@ 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", + class SingleCommandProject extends Project { + constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { + super(projectService.newSingleCommandProjectName(), ProjectKind.Auxiliary, projectService, documentRegistry, /*hasExplicitListOfFiles*/ false, /*lastFileExceededProgramSize*/ undefined, - options, + compilerOptions, /*compileOnSaveEnabled*/ false, /*watchOptions*/ undefined, projectService.host, /*currentDirectory*/ undefined); } + + watchDirectoryOfFailedLookupLocation(): FileWatcher { + return noopFileWatcher; + } + + watchTypeRootsDirectory(): FileWatcher { + return noopFileWatcher; + } } export class AutoImportProviderProject extends Project { diff --git a/src/server/session.ts b/src/server/session.ts index ba338633dc83e..838cd231b8ebe 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1237,8 +1237,8 @@ namespace ts.server { let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); const needsJsResolution = !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); if (needsJsResolution) { - project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { - const ls = auxiliaryProject.getLanguageService(); + project.withNoDtsResolutionProject([file], (noDtsProject, ensureRoot) => { + const ls = noDtsProject.getLanguageService(); const jsDefinitions = ls .getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true) ?.definitions @@ -1260,23 +1260,14 @@ namespace ts.server { else { const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient); for (const candidate of ambientCandidates) { - const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, auxiliaryProject); - const scriptInfo = fileNameToSearch ? auxiliaryProject.getScriptInfo(fileNameToSearch) : undefined; - if (!scriptInfo) { + const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject); + if (!fileNameToSearch || !ensureRoot(fileNameToSearch)) { continue; } - if (!auxiliaryProject.containsScriptInfo(scriptInfo)) { - auxiliaryProject.addRoot(scriptInfo); - } - const auxiliaryProgram = auxiliaryProject.getLanguageService().getProgram()!; - const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch!)); - const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(candidate.name, fileToSearch); - for (const match of matches) { - const symbol = match.symbol || auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); - const decl = getDeclarationFromName(match); - if (symbol && decl) { - pushIfUnique(definitions, GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, match)); - } + const auxiliaryProgram = ls.getProgram()!; + const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch)); + for (const match of searchForDeclaration(candidate.name, fileToSearch, auxiliaryProgram)) { + pushIfUnique(definitions, match, documentSpansEqual); } } } @@ -1363,7 +1354,7 @@ namespace ts.server { if (symbol && decl) { // I think the last argument to this is supposed to be the start node, but it doesn't seem important. // Callers internal to GoToDefinition already get confused about this. - return GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl); + return GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl, /*unverified*/ true); } }); } diff --git a/src/server/utilitiesPublic.ts b/src/server/utilitiesPublic.ts index 026162133bcbb..93f0f9ce570db 100644 --- a/src/server/utilitiesPublic.ts +++ b/src/server/utilitiesPublic.ts @@ -122,6 +122,11 @@ namespace ts.server { return `/dev/null/autoImportProviderProject${counter}*`; } + /*@internal*/ + export function makeSingleCommandProjectName(counter: number): string { + return `/dev/null/singleCommandProject${counter}*`; + } + export function createSortedArray(): SortedArray { return [] as any as SortedArray; // TODO: GH#19873 } diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index c99ebf75f64fa..d959ec0226776 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -36,7 +36,7 @@ namespace ts.GoToDefinition { return map(staticBlocks, staticBlock => { let { pos } = moveRangePastModifiers(staticBlock); pos = skipTrivia(sourceFile.text, pos); - return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, failedAliasResolution, { start: pos, length: "static".length }); + return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, /*unverified*/ false, failedAliasResolution, { start: pos, length: "static".length }); }); } @@ -103,7 +103,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, failedAliasResolution)) : emptyArray; + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, /*unverified*/ false, failedAliasResolution)) : emptyArray; return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } @@ -360,7 +360,7 @@ namespace ts.GoToDefinition { const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration); const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d)); const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations; - return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, failedAliasResolution)); + return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)); function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { // Applicable only if we are in a new expression, or we are on a constructor declaration @@ -388,21 +388,21 @@ namespace ts.GoToDefinition { return declarations.length ? declarationsWithBody.length !== 0 ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) - : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, failedAliasResolution)] + : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)] : undefined; } } /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean): DefinitionInfo { + export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, unverified?: boolean, failedAliasResolution?: 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, failedAliasResolution); + return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, unverified, failedAliasResolution); } /** Creates a DefinitionInfo directly from the name of a declaration. */ - function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { + function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, unverified?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { const sourceFile = declaration.getSourceFile(); if (!textSpan) { const name = getNameOfDeclaration(declaration) || declaration; @@ -422,6 +422,7 @@ namespace ts.GoToDefinition { ), isLocal: !isDefinitionVisible(checker, declaration), isAmbient: !!(declaration.flags & NodeFlags.Ambient), + unverified, failedAliasResolution, }; } @@ -458,7 +459,7 @@ namespace ts.GoToDefinition { } function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, failedAliasResolution?: boolean): DefinitionInfo { - return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, failedAliasResolution); + return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, /*unverified*/ false, failedAliasResolution); } export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 76b7560d1bc78..ce73db8c77034 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10025,7 +10025,6 @@ declare namespace ts.server { private enableProxy; /** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */ refreshDiagnostics(): void; - withAuxiliaryProjectForFiles(fileNames: string[], cb: (project: AuxiliaryProject) => void): void; } /** * If a file is opened and no tsconfig (or jsconfig) is found, @@ -10043,9 +10042,6 @@ declare namespace ts.server { close(): void; getTypeAcquisition(): TypeAcquisition; } - class AuxiliaryProject extends Project { - constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, parentCompilerOptions: CompilerOptions); - } class AutoImportProviderProject extends Project { private hostProject; private rootFileNames; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index ba1163683d9f5..a60749c18a5e0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -318,6 +318,7 @@ declare namespace FourSlashInterface { /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string, unverified?: boolean }): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts index b7adebebd3f96..524b44c16c88d 100644 --- a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -64,6 +64,6 @@ // @Filename: /index.ts -//// import { /*start*/add } from 'lodash'; +//// import { [|/*start*/add|] } from 'lodash'; verify.goToSourceDefinition("start", ["variable", "property"]); diff --git a/tests/cases/fourslash/server/goToSource12_callbackParam.ts b/tests/cases/fourslash/server/goToSource12_callbackParam.ts index d972cb58b3d29..948591ba48e5f 100644 --- a/tests/cases/fourslash/server/goToSource12_callbackParam.ts +++ b/tests/cases/fourslash/server/goToSource12_callbackParam.ts @@ -40,4 +40,4 @@ //// yargs.[|/*start*/positional|](); //// }); -verify.goToSourceDefinition("start", "end"); +verify.goToSourceDefinition("start", { marker: "end", unverified: true }); diff --git a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts index e596fd97d9e0d..690ae8b5fa5a6 100644 --- a/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts +++ b/tests/cases/fourslash/server/goToSource8_mapFromAtTypes.ts @@ -74,4 +74,7 @@ // @Filename: /index.ts //// import { [|/*start*/add|] } from 'lodash'; -verify.goToSourceDefinition("start", ["variable", "property"]); +verify.goToSourceDefinition("start", [ + { marker: "variable", unverified: true }, + { marker: "property", unverified: true }, +]); From e011f2df67916e52825128cce49c2363ec03ab4a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 22 Mar 2022 10:18:32 -0700 Subject: [PATCH 22/45] Rename more stuff --- src/compiler/moduleNameResolver.ts | 10 +++++----- src/server/project.ts | 4 ++-- src/server/session.ts | 9 ++++----- src/services/goToDefinition.ts | 10 +++++----- src/services/utilities.ts | 14 -------------- 5 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 547a518490ec2..753312ad60cb3 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -70,7 +70,7 @@ namespace ts { Json, /** '.json' */ TSConfig, /** '.json' with `tsconfig` used instead of `index` */ DtsOnly, /** Only '.d.ts' */ - TypeScriptButNotDts, + TsOnly, /** '.[cm]tsx?' but not .d.ts variants */ } interface PathAndPackageId { @@ -1296,7 +1296,7 @@ namespace ts { extensions = tsconfigExtensions; } else if (compilerOptions.noDtsResolution) { - extensions = [Extensions.TypeScriptButNotDts]; + extensions = [Extensions.TsOnly]; if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript); if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json); } @@ -1551,7 +1551,7 @@ namespace ts { default: return tryExtension(Extension.Dts); } case Extensions.TypeScript: - case Extensions.TypeScriptButNotDts: + case Extensions.TsOnly: const useDts = extensions === Extensions.TypeScript; switch (originalExtension) { case Extension.Mjs: @@ -1823,7 +1823,7 @@ namespace ts { switch (extensions) { case Extensions.JavaScript: case Extensions.Json: - case Extensions.TypeScriptButNotDts: + case Extensions.TsOnly: packageFile = readPackageJsonMainField(jsonContent, candidate, state); break; case Extensions.TypeScript: @@ -1910,7 +1910,7 @@ namespace ts { return extension === Extension.Json; case Extensions.TypeScript: return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; - case Extensions.TypeScriptButNotDts: + case Extensions.TsOnly: return extension === Extension.Ts || extension === Extension.Tsx; case Extensions.DtsOnly: return extension === Extension.Dts; diff --git a/src/server/project.ts b/src/server/project.ts index 3fdb4ab7f434f..6e8df00f01993 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -5,7 +5,7 @@ namespace ts.server { Configured, External, AutoImportProvider, - Auxiliary, + SingleCommand, } /* @internal */ @@ -1946,7 +1946,7 @@ namespace ts.server { class SingleCommandProject extends Project { constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { super(projectService.newSingleCommandProjectName(), - ProjectKind.Auxiliary, + ProjectKind.SingleCommand, projectService, documentRegistry, /*hasExplicitListOfFiles*/ false, diff --git a/src/server/session.ts b/src/server/session.ts index 838cd231b8ebe..a4ccb88a5a82c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1240,7 +1240,7 @@ namespace ts.server { project.withNoDtsResolutionProject([file], (noDtsProject, ensureRoot) => { const ls = noDtsProject.getLanguageService(); const jsDefinitions = ls - .getDefinitionAndBoundSpan(file, position, /*aliasesOnly*/ true) + .getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true) ?.definitions ?.filter(d => toNormalizedPath(d.fileName) !== file); if (some(jsDefinitions)) { @@ -1338,11 +1338,10 @@ namespace ts.server { } const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position); const symbol = program.getTypeChecker().getSymbolAtLocation(initialNode); - if (!symbol || !symbol.declarations || some(symbol.declarations, isFreelyNameableImport)) { - return undefined; - } + const importSpecifier = symbol && getDeclarationOfKind(symbol, SyntaxKind.ImportSpecifier); + if (!importSpecifier) return undefined; - const nameToSearch = find(symbol.declarations, isImportSpecifier)?.propertyName?.text || symbol.name; + const nameToSearch = importSpecifier.propertyName?.text || importSpecifier.name.text; return searchForDeclaration(nameToSearch, fileToSearch, auxiliaryProgram); } diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index d959ec0226776..266e126780a9a 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, aliasesOnly?: boolean): readonly DefinitionInfo[] | undefined { + export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: 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) { @@ -43,7 +43,7 @@ namespace ts.GoToDefinition { let { symbol, failedAliasResolution } = getSymbol(node, typeChecker); let fallbackNode = node; - if (aliasesOnly && failedAliasResolution) { + if (searchOtherFilesOnly && failedAliasResolution) { // We couldn't resolve the specific import, try on the module specifier. const importDeclaration = findAncestor(node, isAnyImportOrBareOrAccessedRequire); const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); @@ -78,7 +78,7 @@ namespace ts.GoToDefinition { return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); } - if (aliasesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined; + if (searchOtherFilesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined; const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); // Don't go to the component constructor definition for a JSX element, just go to the component definition. @@ -265,8 +265,8 @@ namespace ts.GoToDefinition { return undefined; } - export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number, aliasesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { - const definitions = getDefinitionAtPosition(program, sourceFile, position, aliasesOnly); + export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { + const definitions = getDefinitionAtPosition(program, sourceFile, position, searchOtherFilesOnly); if (!definitions || definitions.length === 0) { return undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 3e4c488ed73a9..88836f59c3428 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2399,20 +2399,6 @@ namespace ts { return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; } - /** - * Returns true if `node` is the declaration of `n` in: - * - `import n from "m"` - * - `import * as n from "m"` - * - `import n = require("m")` - * - `const n = require("m")` - */ - export function isFreelyNameableImport(node: Node): node is ImportClause | NamespaceImport | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } | VariableDeclarationInitializedTo { - return node.kind === SyntaxKind.ImportClause - || node.kind === SyntaxKind.NamespaceImport - || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) - || isVariableDeclarationInitializedToRequire(node); - } - export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { // First check to see if the script kind was specified by the host. Chances are the host // may override the default script kind for the file extension. From 05cfd2716048ac760cc79228a5c15e0cd66a0885 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 22 Mar 2022 10:28:05 -0700 Subject: [PATCH 23/45] Update test --- tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts index 524b44c16c88d..6b636839e1781 100644 --- a/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts +++ b/tests/cases/fourslash/server/goToSource10_mapFromAtTypes3.ts @@ -66,4 +66,7 @@ // @Filename: /index.ts //// import { [|/*start*/add|] } from 'lodash'; -verify.goToSourceDefinition("start", ["variable", "property"]); +verify.goToSourceDefinition("start", [ + { marker: "variable", unverified: true }, + { marker: "property", unverified: true }, +]); From b95f496fb468d99d13b9db10effac1a4607d14b0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 22 Mar 2022 10:55:11 -0700 Subject: [PATCH 24/45] Update API baseline --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ce73db8c77034..250f704a78356 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9857,7 +9857,7 @@ declare namespace ts.server { Configured = 1, External = 2, AutoImportProvider = 3, - Auxiliary = 4 + SingleCommand = 4 } function allRootFilesAreJsOrDts(project: Project): boolean; function allFilesAreJsOrDts(project: Project): boolean; From b8838980e050e2fa12d58f45590adeb4f2a83228 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 14 Mar 2022 17:00:43 -0700 Subject: [PATCH 25/45] Temporarily replace go-to-def with new command implementation --- Gulpfile.js | 2 +- src/server/session.ts | 72 +++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index c9d3feff80b01..5e21ac6f38640 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -463,7 +463,7 @@ task("runtests").flags = { }; const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ cmdLineOptions.workers > 1, /*watchMode*/ false); -task("runtests-parallel", series(preBuild, preTest, runTestsParallel, postTest)); +task("runtests-parallel", series(preBuild, preTest, /*runTestsParallel, postTest*/)); task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; task("runtests-parallel").flags = { " --no-lint": "disables lint.", diff --git a/src/server/session.ts b/src/server/session.ts index a4ccb88a5a82c..b8902bd938e71 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1191,36 +1191,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)); + + // 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)); @@ -2857,12 +2857,12 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); - }, + // [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + // }, + // [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + // return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + // }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, From 373bca246da3e7a604b946855ff4b52f514b9532 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 4 Apr 2022 09:38:02 -0700 Subject: [PATCH 26/45] PR review fixes --- src/server/session.ts | 2 +- src/services/findAllReferences.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index b8902bd938e71..f2bea62f2a99b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1248,7 +1248,7 @@ namespace ts.server { if (jsDefinition.unverified) { const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); if (some(refined)) { - for (const def of refined || emptyArray) { + for (const def of refined) { pushIfUnique(definitions, def, documentSpansEqual); } continue; diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 6abe7308d9750..b6732cd66ef64 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1346,8 +1346,8 @@ namespace ts.FindAllReferences { } else if (depth < topMost.depth) { topMost.declarationNames = [decl]; + topMost.depth = depth; } - topMost.depth = depth; return topMost; }, { depth: Infinity, declarationNames: [] as Node[] }).declarationNames; From 3a85dc8972a0211aee6f8b05b3f87b9d4889b0be Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 4 Apr 2022 10:29:18 -0700 Subject: [PATCH 27/45] Fix getTopMostDeclarationNamesInFile --- src/services/findAllReferences.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index b8509bed48cc8..d8f2c4d844781 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1349,6 +1349,7 @@ namespace ts.FindAllReferences { const depth = getDepth(decl); if (!some(topMost.declarationNames) || depth === topMost.depth) { topMost.declarationNames.push(decl); + topMost.depth = depth; } else if (depth < topMost.depth) { topMost.declarationNames = [decl]; From 7dd9db8a1fa4015ac3e08f8583f0005d3de366b1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 4 Apr 2022 10:30:25 -0700 Subject: [PATCH 28/45] Rename local --- src/server/session.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index dd2466693db64..eafd750bfeb13 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1269,9 +1269,9 @@ namespace ts.server { if (!fileNameToSearch || !ensureRoot(fileNameToSearch)) { continue; } - const auxiliaryProgram = ls.getProgram()!; - const fileToSearch = Debug.checkDefined(auxiliaryProgram.getSourceFile(fileNameToSearch)); - for (const match of searchForDeclaration(candidate.name, fileToSearch, auxiliaryProgram)) { + const noDtsProgram = ls.getProgram()!; + const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch)); + for (const match of searchForDeclaration(candidate.name, fileToSearch, noDtsProgram)) { pushIfUnique(definitions, match, documentSpansEqual); } } @@ -1336,8 +1336,8 @@ namespace ts.server { return undefined; } - function tryRefineDefinition(definition: DefinitionInfo, program: Program, auxiliaryProgram: Program) { - const fileToSearch = auxiliaryProgram.getSourceFile(definition.fileName); + function tryRefineDefinition(definition: DefinitionInfo, program: Program, noDtsProgram: Program) { + const fileToSearch = noDtsProgram.getSourceFile(definition.fileName); if (!fileToSearch) { return undefined; } @@ -1347,18 +1347,18 @@ namespace ts.server { if (!importSpecifier) return undefined; const nameToSearch = importSpecifier.propertyName?.text || importSpecifier.name.text; - return searchForDeclaration(nameToSearch, fileToSearch, auxiliaryProgram); + return searchForDeclaration(nameToSearch, fileToSearch, noDtsProgram); } - function searchForDeclaration(declarationName: string, fileToSearch: SourceFile, auxiliaryProgram: Program) { + function searchForDeclaration(declarationName: string, fileToSearch: SourceFile, noDtsProgram: Program) { const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(declarationName, fileToSearch); return mapDefined(matches, match => { - const symbol = auxiliaryProgram.getTypeChecker().getSymbolAtLocation(match); + const symbol = noDtsProgram.getTypeChecker().getSymbolAtLocation(match); const decl = getDeclarationFromName(match); if (symbol && decl) { // I think the last argument to this is supposed to be the start node, but it doesn't seem important. // Callers internal to GoToDefinition already get confused about this. - return GoToDefinition.createDefinitionInfo(decl, auxiliaryProgram.getTypeChecker(), symbol, decl, /*unverified*/ true); + return GoToDefinition.createDefinitionInfo(decl, noDtsProgram.getTypeChecker(), symbol, decl, /*unverified*/ true); } }); } From 0f3b29cc10a4366471e6dd451bca923c991da879 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 4 Apr 2022 10:43:45 -0700 Subject: [PATCH 29/45] Use hash set --- src/server/session.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index eafd750bfeb13..53a345d870035 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1241,7 +1241,10 @@ namespace ts.server { let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); const needsJsResolution = !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); + if (needsJsResolution) { + const definitionSet = createSet(d => d.textSpan.start, documentSpansEqual); + definitionSet.forEach(d => definitionSet.add(d)); project.withNoDtsResolutionProject([file], (noDtsProject, ensureRoot) => { const ls = noDtsProject.getLanguageService(); const jsDefinitions = ls @@ -1254,12 +1257,12 @@ namespace ts.server { const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); if (some(refined)) { for (const def of refined) { - pushIfUnique(definitions, def, documentSpansEqual); + definitionSet.add(def); } continue; } } - pushIfUnique(definitions, jsDefinition, documentSpansEqual); + definitionSet.add(jsDefinition); } } else { @@ -1272,11 +1275,12 @@ namespace ts.server { const noDtsProgram = ls.getProgram()!; const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch)); for (const match of searchForDeclaration(candidate.name, fileToSearch, noDtsProgram)) { - pushIfUnique(definitions, match, documentSpansEqual); + definitionSet.add(match); } } } }); + definitions = arrayFrom(definitionSet.values()); } definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); From 234c13979489cff65328d92172e437cdc2461978 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 4 Apr 2022 10:53:25 -0700 Subject: [PATCH 30/45] Remove option from commandLineParser --- src/compiler/commandLineParser.ts | 7 ------- .../tsConfig/Default initialized TSConfig/tsconfig.json | 1 - .../tsconfig.json | 1 - .../tsconfig.json | 1 - .../tsconfig.json | 1 - .../Initialized TSConfig with files options/tsconfig.json | 1 - .../tsconfig.json | 1 - .../tsconfig.json | 1 - .../tsconfig.json | 1 - .../tsconfig.json | 1 - .../declarationDir-is-specified.js | 1 - .../when-outDir-and-declarationDir-is-specified.js | 1 - .../when-outDir-is-specified.js | 1 - .../with-outFile.js | 1 - ...Dir-or-outFile-is-specified-with-declaration-enabled.js | 1 - .../without-outDir-or-outFile-is-specified.js | 1 - 16 files changed, 22 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f9fde3c9debde..6ed23d16e6b5a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -884,13 +884,6 @@ namespace ts { description: Diagnostics.Allow_accessing_UMD_globals_from_modules, defaultValueDescription: false, }, - { - name: "noDtsResolution", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Modules, - defaultValueDescription: false, - }, { name: "moduleSuffixes", type: "list", diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index fa40c8f6246a7..75dcaeac2e2b9 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json index e0a02a42e25c5..ce36f2a0fcfd7 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index adcd79114160a..a4be57dd3410e 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 6699b070177e1..faa350ae98575 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index e3df4c71823af..09587994ffbcd 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index a59749b4c9029..abb2dfd47088b 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index fa40c8f6246a7..75dcaeac2e2b9 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index cafee09db6365..04aa9196bfc65 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index f41eb2eb41ca8..4eb9bb8aa4b84 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -34,7 +34,6 @@ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ "types": ["jquery","mocha"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js index bfc96b250723f..841002b440019 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js index cd9aed115babd..7e2380200d728 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js index 0e044b98bb44f..18b7c7536b52b 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js index a5c9eb7a98b0d..a665616e7e52a 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js index 7c9ed988c5f28..d17968fa60d58 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js index 44a44b8609c7c..641d0e341de4b 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js @@ -55,7 +55,6 @@ interface Array { length: number; [n: number]: T; } // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "noDtsResolution": true, /* noDtsResolution */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ From 16989305aa46b72e5044ddcf1806727b3f9e3238 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 6 Apr 2022 17:11:41 -0700 Subject: [PATCH 31/45] Keep noDtsResolution project around --- src/server/editorServices.ts | 2 +- src/server/project.ts | 86 ++++++++++--------- src/server/session.ts | 67 ++++++++------- src/server/utilitiesPublic.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 2 +- 5 files changed, 88 insertions(+), 73 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9ecf13094b30c..6f9f057c7c364 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -716,7 +716,7 @@ namespace ts.server { /*@internal*/ readonly newAutoImportProviderProjectName = createProjectNameFactoryWithCounter(makeAutoImportProviderProjectName); /*@internal*/ - readonly newSingleCommandProjectName = createProjectNameFactoryWithCounter(makeSingleCommandProjectName); + readonly newAuxiliaryProjectName = createProjectNameFactoryWithCounter(makeAuxiliaryProjectName); /** * Open files: with value being project root path, and key being Path of the file that is open */ diff --git a/src/server/project.ts b/src/server/project.ts index b1d2460a0cab0..2c90f5aef366b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -5,7 +5,7 @@ namespace ts.server { Configured, External, AutoImportProvider, - SingleCommand, + Auxiliary, } /* @internal */ @@ -210,6 +210,9 @@ namespace ts.server { /*@internal*/ private packageJsonsForAutoImport: Set | undefined; + /*@internal*/ + private noDtsResolutionProject?: AuxiliaryProject | undefined; + /*@internal*/ getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined { return undefined; @@ -812,6 +815,10 @@ namespace ts.server { this.autoImportProviderHost.close(); } this.autoImportProviderHost = undefined; + if (this.noDtsResolutionProject) { + this.noDtsResolutionProject.close(); + } + this.noDtsResolutionProject = undefined; // signal language service to release source files acquired from document registry this.languageService.dispose(); @@ -1783,36 +1790,43 @@ namespace ts.server { } /*@internal*/ - withNoDtsResolutionProject(rootFileNames: string[], cb: (project: Project, ensureRoot: (fileName: string) => boolean) => 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 SingleCommandProject(this.projectService, this.documentRegistry, options); - for (const fileName of rootFileNames) { - project.addRoot(this.getScriptInfo(fileName)!); - } - cb( - project, - fileName => { - const info = project.getScriptInfo(fileName); - if (!info) return false; - if (!project.containsScriptInfo(info)) { - project.addRoot(info); - project.updateGraph(); + getNoDtsResolutionProject(rootFileNames: readonly string[]): Project { + if (!this.noDtsResolutionProject) { + 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, + }; + this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, options); + } + + enumerateInsertsAndDeletes( + rootFileNames.map(toNormalizedPath), + this.noDtsResolutionProject.getRootFiles(), + compareStringsCaseInsensitive, + pathToAdd => { + const info = this.noDtsResolutionProject!.getScriptInfo(pathToAdd); + if (info) { + this.noDtsResolutionProject!.addRoot(info, pathToAdd); } - return true; - } + }, + pathToRemove => { + // It may be preferable to remove roots only once project grows to a certain size? + const info = this.noDtsResolutionProject!.getScriptInfo(pathToRemove); + if (info) { + this.noDtsResolutionProject!.removeRoot(info); + } + }, ); - project.close(); + + return this.noDtsResolutionProject; } } @@ -1954,10 +1968,10 @@ namespace ts.server { } } - class SingleCommandProject extends Project { + class AuxiliaryProject extends Project { constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { - super(projectService.newSingleCommandProjectName(), - ProjectKind.SingleCommand, + super(projectService.newAuxiliaryProjectName(), + ProjectKind.Auxiliary, projectService, documentRegistry, /*hasExplicitListOfFiles*/ false, @@ -1968,14 +1982,6 @@ namespace ts.server { projectService.host, /*currentDirectory*/ undefined); } - - watchDirectoryOfFailedLookupLocation(): FileWatcher { - return noopFileWatcher; - } - - watchTypeRootsDirectory(): FileWatcher { - return noopFileWatcher; - } } export class AutoImportProviderProject extends Project { diff --git a/src/server/session.ts b/src/server/session.ts index 5fe3bbd7f0401..1d0cda407fa0d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1249,41 +1249,40 @@ namespace ts.server { if (needsJsResolution) { const definitionSet = createSet(d => d.textSpan.start, documentSpansEqual); definitionSet.forEach(d => definitionSet.add(d)); - project.withNoDtsResolutionProject([file], (noDtsProject, ensureRoot) => { - const ls = noDtsProject.getLanguageService(); - const jsDefinitions = ls - .getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true) - ?.definitions - ?.filter(d => toNormalizedPath(d.fileName) !== file); - if (some(jsDefinitions)) { - for (const jsDefinition of jsDefinitions) { - if (jsDefinition.unverified) { - const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); - if (some(refined)) { - for (const def of refined) { - definitionSet.add(def); - } - continue; + const noDtsProject = project.getNoDtsResolutionProject([file]); + const ls = noDtsProject.getLanguageService(); + const jsDefinitions = ls + .getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true) + ?.definitions + ?.filter(d => toNormalizedPath(d.fileName) !== file); + if (some(jsDefinitions)) { + for (const jsDefinition of jsDefinitions) { + if (jsDefinition.unverified) { + const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!); + if (some(refined)) { + for (const def of refined) { + definitionSet.add(def); } + continue; } - definitionSet.add(jsDefinition); } + definitionSet.add(jsDefinition); } - else { - const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient); - for (const candidate of ambientCandidates) { - const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject); - if (!fileNameToSearch || !ensureRoot(fileNameToSearch)) { - continue; - } - const noDtsProgram = ls.getProgram()!; - const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch)); - for (const match of searchForDeclaration(candidate.name, fileToSearch, noDtsProgram)) { - definitionSet.add(match); - } + } + else { + const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient); + for (const candidate of ambientCandidates) { + const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject); + if (!fileNameToSearch || !ensureRoot(noDtsProject, fileNameToSearch)) { + continue; + } + const noDtsProgram = ls.getProgram()!; + const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch)); + for (const match of searchForDeclaration(candidate.name, fileToSearch, noDtsProgram)) { + definitionSet.add(match); } } - }); + } definitions = arrayFrom(definitionSet.values()); } @@ -1370,6 +1369,16 @@ namespace ts.server { } }); } + + function ensureRoot(project: Project, fileName: string) { + const info = project.getScriptInfo(fileName); + if (!info) return false; + if (!project.containsScriptInfo(info)) { + project.addRoot(info); + project.updateGraph(); + } + return true; + } } private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { diff --git a/src/server/utilitiesPublic.ts b/src/server/utilitiesPublic.ts index 93f0f9ce570db..7b28a9b17d367 100644 --- a/src/server/utilitiesPublic.ts +++ b/src/server/utilitiesPublic.ts @@ -123,8 +123,8 @@ namespace ts.server { } /*@internal*/ - export function makeSingleCommandProjectName(counter: number): string { - return `/dev/null/singleCommandProject${counter}*`; + export function makeAuxiliaryProjectName(counter: number): string { + return `/dev/null/auxiliaryProject${counter}*`; } export function createSortedArray(): SortedArray { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3e1992fe1ce19..b5b52794381be 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9911,7 +9911,7 @@ declare namespace ts.server { Configured = 1, External = 2, AutoImportProvider = 3, - SingleCommand = 4 + Auxiliary = 4 } function allRootFilesAreJsOrDts(project: Project): boolean; function allFilesAreJsOrDts(project: Project): boolean; From 859ed79cb17517f7c3f08a9e03e0701988b9acfc Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 11:54:08 -0700 Subject: [PATCH 32/45] Handle AuxiliaryProject kind in ScriptInfo getDefaultProject etc. --- src/server/editorServices.ts | 4 +- src/server/project.ts | 4 ++ src/server/scriptInfo.ts | 19 +++++++--- src/testRunner/tsconfig.json | 1 + .../unittests/tsserver/auxiliaryProject.ts | 38 +++++++++++++++++++ 5 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 src/testRunner/unittests/tsserver/auxiliaryProject.ts diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6f9f057c7c364..aabab6117f708 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2299,7 +2299,7 @@ namespace ts.server { configFileExistenceInfo.config.watchedDirectoriesStale = undefined; } - private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject | AutoImportProviderProject, files: T[], propertyReader: FilePropertyReader) { + private updateNonInferredProjectFiles(project: Project, files: T[], propertyReader: FilePropertyReader) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap = new Map(); @@ -3591,7 +3591,7 @@ namespace ts.server { const toRemoveScriptInfos = new Map(this.filenameToScriptInfo); this.filenameToScriptInfo.forEach(info => { // If script info is open or orphan, retain it and its dependencies - if (!info.isScriptOpen() && info.isOrphan() && !info.isContainedByAutoImportProvider()) { + if (!info.isScriptOpen() && info.isOrphan() && !info.isContainedByBackgroundProject()) { // Otherwise if there is any source info that is alive, this alive too if (!info.sourceMapFilePath) return; let sourceInfos: Set | undefined; diff --git a/src/server/project.ts b/src/server/project.ts index 2c90f5aef366b..6ec0b6197e5c0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1982,6 +1982,10 @@ namespace ts.server { projectService.host, /*currentDirectory*/ undefined); } + + isOrphan(): boolean { + return true; + } } export class AutoImportProviderProject extends Project { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index a26fcc6748932..dedd30fe19b93 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -501,7 +501,7 @@ namespace ts.server { case 0: return Errors.ThrowNoProject(); case 1: - return ensureNotAutoImportProvider(this.containingProjects[0]); + return ensurePrimaryProjectKind(this.containingProjects[0]); default: // If this file belongs to multiple projects, below is the order in which default project is used // - for open script info, its default configured project during opening is default if info is part of it @@ -536,7 +536,7 @@ namespace ts.server { firstInferredProject = project; } } - return ensureNotAutoImportProvider(defaultConfiguredProject || + return ensurePrimaryProjectKind(defaultConfiguredProject || firstNonSourceOfProjectReferenceRedirect || firstConfiguredProject || firstExternalProject || @@ -622,8 +622,10 @@ namespace ts.server { } /*@internal*/ - isContainedByAutoImportProvider() { - return some(this.containingProjects, p => p.projectKind === ProjectKind.AutoImportProvider); + isContainedByBackgroundProject() { + return some( + this.containingProjects, + p => p.projectKind === ProjectKind.AutoImportProvider || p.projectKind === ProjectKind.Auxiliary); } /** @@ -669,8 +671,13 @@ namespace ts.server { } } - function ensureNotAutoImportProvider(project: Project | undefined) { - if (!project || project.projectKind === ProjectKind.AutoImportProvider) { + /** + * Throws an error if `project` is an AutoImportProvider or AuxiliaryProject, + * which are used in the background by other Projects and should never be + * reported as the default project for a ScriptInfo. + */ + function ensurePrimaryProjectKind(project: Project | undefined) { + if (!project || project.projectKind === ProjectKind.AutoImportProvider || project.projectKind === ProjectKind.Auxiliary) { return Errors.ThrowNoProject(); } return project; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 2caa9873f29dd..599aaa19a8615 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -169,6 +169,7 @@ "unittests/tscWatch/watchEnvironment.ts", "unittests/tsserver/applyChangesToOpenFiles.ts", "unittests/tsserver/autoImportProvider.ts", + "unittests/tsserver/auxiliaryProject.ts", "unittests/tsserver/cachingFileSystemInformation.ts", "unittests/tsserver/cancellationToken.ts", "unittests/tsserver/compileOnSave.ts", diff --git a/src/testRunner/unittests/tsserver/auxiliaryProject.ts b/src/testRunner/unittests/tsserver/auxiliaryProject.ts new file mode 100644 index 0000000000000..dc4cd36d99131 --- /dev/null +++ b/src/testRunner/unittests/tsserver/auxiliaryProject.ts @@ -0,0 +1,38 @@ +namespace ts.projectSystem { + const aTs: File = { + path: "/a.ts", + content: `import { B } from "./b";` + }; + const bDts: File = { + path: "/b.d.ts", + content: `export declare class B {}` + }; + const bJs: File = { + path: "/b.js", + content: `export class B {}` + }; + describe("unittests:: tsserver:: auxiliaryProject", () => { + it("AuxiliaryProject does not remove scrips from InferredProject", () => { + const host = createServerHost([aTs, bDts, bJs]); + const session = createSession(host); + const projectService = session.getProjectService(); + openFilesForSession([aTs], session); + + checkNumberOfInferredProjects(projectService, 1); + const inferredProject = projectService.inferredProjects[0]; + const auxProject = inferredProject.getNoDtsResolutionProject([aTs.path]); + auxProject.updateGraph(); + + const bJsScriptInfo = Debug.checkDefined(projectService.getScriptInfo(bJs.path)); + assert(bJsScriptInfo.isOrphan()); + assert(bJsScriptInfo.isContainedByBackgroundProject()); + assert.deepEqual(bJsScriptInfo.containingProjects, [auxProject]); + assert.throws(() => bJsScriptInfo.getDefaultProject()); + + openFilesForSession([bJs], session); + assert(!bJsScriptInfo.isOrphan()); + assert(bJsScriptInfo.isContainedByBackgroundProject()); + assert.equal(bJsScriptInfo.getDefaultProject().projectKind, server.ProjectKind.Inferred); + }); + }); +} From 7be215af96a984323094d2cdef62a0295ab46aac Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 12:05:34 -0700 Subject: [PATCH 33/45] Do not run updateGraph in the background for AuxiliaryProject --- src/server/editorServices.ts | 16 +++++++++------- src/server/project.ts | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index aabab6117f708..d40294989ee68 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -991,13 +991,15 @@ namespace ts.server { private delayUpdateProjectGraph(project: Project) { project.markAsDirty(); - const projectName = project.getProjectName(); - this.pendingProjectUpdates.set(projectName, project); - this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { - if (this.pendingProjectUpdates.delete(projectName)) { - updateProjectIfDirty(project); - } - }); + if (project.projectKind !== ProjectKind.AutoImportProvider && project.projectKind !== ProjectKind.Auxiliary) { + const projectName = project.getProjectName(); + this.pendingProjectUpdates.set(projectName, project); + this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { + if (this.pendingProjectUpdates.delete(projectName)) { + updateProjectIfDirty(project); + } + }); + } } /*@internal*/ diff --git a/src/server/project.ts b/src/server/project.ts index 6ec0b6197e5c0..9a03f579475ca 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1986,6 +1986,10 @@ namespace ts.server { isOrphan(): boolean { return true; } + + scheduleInvalidateResolutionsOfFailedLookupLocations(): void { + + } } export class AutoImportProviderProject extends Project { From 8f4e622918eccb04873e597b17ee5d14e945f605 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 12:09:35 -0700 Subject: [PATCH 34/45] =?UTF-8?q?Don=E2=80=99t=20create=20auxiliary=20proj?= =?UTF-8?q?ect=20outside=20of=20semantic=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/project.ts | 1 + src/server/session.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/project.ts b/src/server/project.ts index 9a03f579475ca..31a3504ae4a28 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1791,6 +1791,7 @@ namespace ts.server { /*@internal*/ getNoDtsResolutionProject(rootFileNames: readonly string[]): Project { + Debug.assert(this.projectService.serverMode === LanguageServiceMode.Semantic); if (!this.noDtsResolutionProject) { const options: CompilerOptions = { ...this.getCompilerOptions(), diff --git a/src/server/session.ts b/src/server/session.ts index 1d0cda407fa0d..38e34b135d408 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1244,7 +1244,9 @@ namespace ts.server { } let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution); + const needsJsResolution = this.projectService.serverMode === LanguageServiceMode.Semantic && ( + !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || + some(definitions, d => !!d.failedAliasResolution)); if (needsJsResolution) { const definitionSet = createSet(d => d.textSpan.start, documentSpansEqual); From d63335683a40cba7d8454aa5722ccd71b435146b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 15:16:46 -0700 Subject: [PATCH 35/45] No-op on scheduled invalidation --- src/server/project.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/project.ts b/src/server/project.ts index 31a3504ae4a28..c91a542a65040 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1989,7 +1989,8 @@ namespace ts.server { } scheduleInvalidateResolutionsOfFailedLookupLocations(): void { - + // Invalidation will happen on-demand as part of updateGraph + return; } } From b89b7cb44eae7243c1a545954c6e36ed6839773d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 15:16:52 -0700 Subject: [PATCH 36/45] Add comments to unit test --- src/testRunner/unittests/tsserver/auxiliaryProject.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/testRunner/unittests/tsserver/auxiliaryProject.ts b/src/testRunner/unittests/tsserver/auxiliaryProject.ts index dc4cd36d99131..4b5f865c19aeb 100644 --- a/src/testRunner/unittests/tsserver/auxiliaryProject.ts +++ b/src/testRunner/unittests/tsserver/auxiliaryProject.ts @@ -18,17 +18,26 @@ namespace ts.projectSystem { const projectService = session.getProjectService(); openFilesForSession([aTs], session); + // Open file is in inferred project checkNumberOfInferredProjects(projectService, 1); const inferredProject = projectService.inferredProjects[0]; + + // getNoDtsResolutionProject will create an AuxiliaryProject with a.ts and b.js const auxProject = inferredProject.getNoDtsResolutionProject([aTs.path]); auxProject.updateGraph(); + // b.js ScriptInfo is now available because it's contained by the AuxiliaryProject. + // The AuxiliaryProject should never be the default project for anything, so + // the ScriptInfo should still report being an orphan, and getting its default + // project should throw. const bJsScriptInfo = Debug.checkDefined(projectService.getScriptInfo(bJs.path)); assert(bJsScriptInfo.isOrphan()); assert(bJsScriptInfo.isContainedByBackgroundProject()); assert.deepEqual(bJsScriptInfo.containingProjects, [auxProject]); assert.throws(() => bJsScriptInfo.getDefaultProject()); + // When b.js is opened in the editor, it should be put into an InferredProject + // even though it's still contained by the AuxiliaryProject. openFilesForSession([bJs], session); assert(!bJsScriptInfo.isOrphan()); assert(bJsScriptInfo.isContainedByBackgroundProject()); From a59024cd4519b039f680e04b85a6e68ed00e25b5 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 15:22:52 -0700 Subject: [PATCH 37/45] Sync compiler options to auxiliary project --- src/server/project.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index c91a542a65040..d10cb4cbe8526 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1414,6 +1414,7 @@ namespace ts.server { const oldOptions = this.compilerOptions; this.compilerOptions = compilerOptions; this.setInternalCompilerOptionsForEmittingJsFiles(); + this.noDtsResolutionProject?.setCompilerOptions(this.getCompilerOptionsForNoDtsResolutionProject()); if (changesAffectModuleResolution(oldOptions, compilerOptions)) { // reset cached unresolved imports if changes in compiler options affected module resolution this.cachedUnresolvedImportsPerFile.clear(); @@ -1793,19 +1794,7 @@ namespace ts.server { getNoDtsResolutionProject(rootFileNames: readonly string[]): Project { Debug.assert(this.projectService.serverMode === LanguageServiceMode.Semantic); if (!this.noDtsResolutionProject) { - 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, - }; - this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, options); + this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, this.getCompilerOptionsForNoDtsResolutionProject()); } enumerateInsertsAndDeletes( @@ -1829,6 +1818,22 @@ namespace ts.server { return this.noDtsResolutionProject; } + + /*@internal*/ + private getCompilerOptionsForNoDtsResolutionProject() { + return { + ...this.getCompilerOptions(), + noDtsResolution: true, + allowJs: true, + maxNodeModuleJsDepth: 3, + diagnostics: false, + skipLibCheck: true, + sourceMap: false, + types: ts.emptyArray, + lib: ts.emptyArray, + noLib: true, + }; + } } function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: ESMap): SortedReadonlyArray { From 7f290bb0fc428c9ad86fdd6d7b7021708878e1f7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 15:25:03 -0700 Subject: [PATCH 38/45] Fix case sensitivity --- src/server/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/project.ts b/src/server/project.ts index d10cb4cbe8526..b6f53ac0ebb42 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1800,7 +1800,7 @@ namespace ts.server { enumerateInsertsAndDeletes( rootFileNames.map(toNormalizedPath), this.noDtsResolutionProject.getRootFiles(), - compareStringsCaseInsensitive, + getStringComparer(!this.useCaseSensitiveFileNames), pathToAdd => { const info = this.noDtsResolutionProject!.getScriptInfo(pathToAdd); if (info) { From c5bddbe22379fab0cc8fdb7241bb1ce4834c5a3b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 7 Apr 2022 15:37:08 -0700 Subject: [PATCH 39/45] Update extensionIsOk with new file extensions --- src/compiler/moduleNameResolver.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 81d36839bb5a8..5a54ceaaa0635 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1914,16 +1914,16 @@ namespace ts { function extensionIsOk(extensions: Extensions, extension: Extension): boolean { switch (extensions) { case Extensions.JavaScript: - return extension === Extension.Js || extension === Extension.Jsx; + return extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs; case Extensions.TSConfig: case Extensions.Json: return extension === Extension.Json; case Extensions.TypeScript: - return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts || extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; case Extensions.TsOnly: - return extension === Extension.Ts || extension === Extension.Tsx; + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts; case Extensions.DtsOnly: - return extension === Extension.Dts; + return extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; } } From 24c0d8ae386757339ff06b14d87cececaf0d1364 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 13 Apr 2022 09:42:28 -0700 Subject: [PATCH 40/45] PR feedback --- src/server/project.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index b6f53ac0ebb42..36bf8685efdb3 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1800,9 +1800,12 @@ namespace ts.server { enumerateInsertsAndDeletes( rootFileNames.map(toNormalizedPath), this.noDtsResolutionProject.getRootFiles(), - getStringComparer(!this.useCaseSensitiveFileNames), + getStringComparer(!this.useCaseSensitiveFileNames()), pathToAdd => { - const info = this.noDtsResolutionProject!.getScriptInfo(pathToAdd); + const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient( + pathToAdd, + this.currentDirectory, + this.noDtsResolutionProject!.directoryStructureHost); if (info) { this.noDtsResolutionProject!.addRoot(info, pathToAdd); } @@ -2208,6 +2211,11 @@ namespace ts.server { return hasSameSetOfFiles; } + scheduleInvalidateResolutionsOfFailedLookupLocations(): void { + // Invalidation will happen on-demand as part of updateGraph + return; + } + hasRoots() { return !!this.rootFileNames?.length; } From f6bd9d885c2ece30e8bfd32b57b50883a2c55e6e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 13 Apr 2022 10:06:14 -0700 Subject: [PATCH 41/45] Update API baseline --- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b5b52794381be..7bee422dbe7fb 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10101,6 +10101,7 @@ declare namespace ts.server { private rootFileNames; isOrphan(): boolean; updateGraph(): boolean; + scheduleInvalidateResolutionsOfFailedLookupLocations(): void; hasRoots(): boolean; markAsDirty(): void; getScriptFileNames(): string[]; From 2cb2048648d811016850ae38dc2d1a5631e6a76c Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 13 Apr 2022 13:42:45 -0700 Subject: [PATCH 42/45] Mark scheduleInvalidateResolutionsOfFailedLookupLocations internal --- src/server/project.ts | 2 ++ tests/baselines/reference/api/tsserverlibrary.d.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/project.ts b/src/server/project.ts index 36bf8685efdb3..4f997968ddfd2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1996,6 +1996,7 @@ namespace ts.server { return true; } + /*@internal*/ scheduleInvalidateResolutionsOfFailedLookupLocations(): void { // Invalidation will happen on-demand as part of updateGraph return; @@ -2211,6 +2212,7 @@ namespace ts.server { return hasSameSetOfFiles; } + /*@internal*/ scheduleInvalidateResolutionsOfFailedLookupLocations(): void { // Invalidation will happen on-demand as part of updateGraph return; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7bee422dbe7fb..b5b52794381be 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10101,7 +10101,6 @@ declare namespace ts.server { private rootFileNames; isOrphan(): boolean; updateGraph(): boolean; - scheduleInvalidateResolutionsOfFailedLookupLocations(): void; hasRoots(): boolean; markAsDirty(): void; getScriptFileNames(): string[]; From f9f459268dfb8362c4f8ddd530ea162e26b40a75 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 14 Apr 2022 13:50:55 -0700 Subject: [PATCH 43/45] Use same heuristics on property accesses of loosely-resolvable aliases as unresolvable named imports --- src/compiler/utilities.ts | 44 +++++++++++++++++ src/server/session.ts | 49 +++++++++++++------ src/services/goToDefinition.ts | 2 +- src/services/services.ts | 8 +-- src/services/types.ts | 5 +- .../server/goToSource1_localJsBesideDts.ts | 7 +-- .../server/goToSource9_mapFromAtTypes2.ts | 6 ++- 7 files changed, 96 insertions(+), 25 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2998b2ba01934..b218adf7260c7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5159,6 +5159,11 @@ namespace ts { (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).name === node); } + export function isRightSideOfAccessExpression(node: Node) { + return isPropertyAccessExpression(node.parent) && node.parent.name === node + || isElementAccessExpression(node.parent) && node.parent.argumentExpression === node; + } + export function isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(node: Node) { return isQualifiedName(node.parent) && node.parent.right === node || isPropertyAccessExpression(node.parent) && node.parent.name === node @@ -5846,6 +5851,45 @@ namespace ts { return expr; } + export function forEachNameInAccessChainWalkingLeft(name: MemberName | StringLiteralLike, action: (name: MemberName | StringLiteralLike) => T | undefined): T | undefined { + if (isAccessExpression(name.parent) && isRightSideOfAccessExpression(name)) { + return walkAccessExpression(name.parent); + } + + function walkAccessExpression(access: AccessExpression): T | undefined { + if (access.kind === SyntaxKind.PropertyAccessExpression) { + const res = action(access.name); + if (res !== undefined) { + return res; + } + } + else if (access.kind === SyntaxKind.ElementAccessExpression) { + if (isIdentifier(access.argumentExpression) || isStringLiteralLike(access.argumentExpression)) { + const res = action(access.argumentExpression); + if (res !== undefined) { + return res; + } + } + else { + // Chain interrupted by non-static-name access 'x[expr()].y.z' + return undefined; + } + } + + if (isAccessExpression(access.expression)) { + return walkAccessExpression(access.expression); + } + if (isIdentifier(access.expression)) { + // End of chain at Identifier 'x.y.z' + return action(access.expression); + } + // End of chain at non-Identifier 'x().y.z' + return undefined; + } + } + + + export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) { while (true) { switch (node.kind) { diff --git a/src/server/session.ts b/src/server/session.ts index 38e34b135d408..c310d1d94bb5f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1235,26 +1235,20 @@ namespace ts.server { 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 - }; - } - - let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); + let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan?.definitions || emptyArray, project).slice(); + let textSpan = unmappedDefinitionAndBoundSpan?.textSpan; const needsJsResolution = this.projectService.serverMode === LanguageServiceMode.Semantic && ( !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution)); if (needsJsResolution) { const definitionSet = createSet(d => d.textSpan.start, documentSpansEqual); - definitionSet.forEach(d => definitionSet.add(d)); + definitions?.forEach(d => definitionSet.add(d)); const noDtsProject = project.getNoDtsResolutionProject([file]); const ls = noDtsProject.getLanguageService(); - const jsDefinitions = ls - .getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true) + const definitionAndBoundSpan = ls.getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true); + textSpan ||= definitionAndBoundSpan?.textSpan; + const jsDefinitions = definitionAndBoundSpan ?.definitions ?.filter(d => toNormalizedPath(d.fileName) !== file); if (some(jsDefinitions)) { @@ -1273,7 +1267,7 @@ namespace ts.server { } else { const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient); - for (const candidate of ambientCandidates) { + for (const candidate of some(ambientCandidates) ? ambientCandidates : getAmbientCandidatesByClimbingAccessChain()) { const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject); if (!fileNameToSearch || !ensureRoot(noDtsProject, fileNameToSearch)) { continue; @@ -1288,8 +1282,11 @@ namespace ts.server { definitions = arrayFrom(definitionSet.values()); } + if (!textSpan) { + return { definitions: emptyArray, textSpan: undefined! }; + } + definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); - const { textSpan } = unmappedDefinitionAndBoundSpan; if (simplifiedResult) { return { @@ -1345,6 +1342,30 @@ namespace ts.server { return undefined; } + // In 'foo.bar./**/baz', if we got not results on 'baz', see if we can get an ambient definition + // for 'bar' or 'foo' (in that order) so we can search for declarations of 'baz' later. + function getAmbientCandidatesByClimbingAccessChain(): readonly { name: string, fileName: string }[] { + const ls = project.getLanguageService(); + const program = ls.getProgram()!; + const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position); + textSpan = createTextSpanFromNode(initialNode); // TODO remove me + if ((isStringLiteralLike(initialNode) || isIdentifier(initialNode)) && isAccessExpression(initialNode.parent)) { + return forEachNameInAccessChainWalkingLeft(initialNode, nameInChain => { + if (nameInChain === initialNode) return undefined; + const candidates = ls.getDefinitionAtPosition(file, nameInChain.getStart(), /*searchOtherFilesOnly*/ true) + ?.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient) + .map(d => ({ + fileName: d.fileName, + name: getTextOfIdentifierOrLiteral(initialNode) + })); + if (some(candidates)) { + return candidates; + } + }) || emptyArray; + } + return emptyArray; + } + function tryRefineDefinition(definition: DefinitionInfo, program: Program, noDtsProgram: Program) { const fileToSearch = noDtsProgram.getSourceFile(definition.fileName); if (!fileToSearch) { diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 266e126780a9a..53f48b81a45e2 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -45,7 +45,7 @@ namespace ts.GoToDefinition { if (searchOtherFilesOnly && failedAliasResolution) { // We couldn't resolve the specific import, try on the module specifier. - const importDeclaration = findAncestor(node, isAnyImportOrBareOrAccessedRequire); + const importDeclaration = forEach([node, ...symbol?.declarations || emptyArray], n => findAncestor(n, isAnyImportOrBareOrAccessedRequire)); const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); if (moduleSpecifier) { ({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker)); diff --git a/src/services/services.ts b/src/services/services.ts index 85bcc92ebfc32..afe5451a2fa6c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1768,14 +1768,14 @@ namespace ts { } /// Goto definition - function getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { + function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean): readonly DefinitionInfo[] | undefined { synchronizeHostData(); - return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position); + return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly); } - function getDefinitionAndBoundSpan(fileName: string, position: number, aliasesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { + function getDefinitionAndBoundSpan(fileName: string, position: number, searchOtherFilesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { synchronizeHostData(); - return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position, aliasesOnly); + return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position, searchOtherFilesOnly); } function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { diff --git a/src/services/types.ts b/src/services/types.ts index 86fcacbf62611..5b15832f7d0db 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -472,10 +472,13 @@ namespace ts { getSmartSelectionRange(fileName: string, position: number): SelectionRange; + /*@internal*/ + // eslint-disable-next-line @typescript-eslint/unified-signatures + getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean): readonly DefinitionInfo[] | undefined; 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, searchOtherFilesOnly: 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; diff --git a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts index c9fbfb98fae3e..5685db4076977 100644 --- a/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts +++ b/tests/cases/fourslash/server/goToSource1_localJsBesideDts.ts @@ -7,7 +7,8 @@ //// export declare const a: string; // @Filename: /index.ts -//// import { a } from "./a"; -//// [|a/*start*/|] +//// import { a } from [|"./a"/*moduleSpecifier*/|]; +//// [|a/*identifier*/|] -verify.goToSourceDefinition("start", "end"); +verify.goToSourceDefinition("identifier", "end"); +verify.goToSourceDefinition("moduleSpecifier", { file: "/a.js" }) \ No newline at end of file diff --git a/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts index 0c68d4922f2a0..024fe099b4d8e 100644 --- a/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts +++ b/tests/cases/fourslash/server/goToSource9_mapFromAtTypes2.ts @@ -22,12 +22,12 @@ //// * _.add(6, 4); //// * // => 10 //// */ -//// var add = createMathOperation(function(augend, addend) { +//// var [|/*variable*/add|] = createMathOperation(function(augend, addend) { //// return augend + addend; //// }, 0); //// //// function lodash(value) {} -//// lodash.add = add; +//// lodash.[|/*property*/add|] = add; //// //// /** Detect free variable `global` from Node.js. */ //// var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; @@ -73,7 +73,9 @@ // @Filename: /index.ts //// import [|/*defaultImport*/_|], { [|/*unresolvableNamedImport*/foo|] } from [|/*moduleSpecifier*/'lodash'|]; +//// _.[|/*propertyAccess*/add|] verify.goToSourceDefinition("defaultImport", { file: "/node_modules/lodash/lodash.js", unverified: true }); verify.goToSourceDefinition("unresolvableNamedImport", { file: "/node_modules/lodash/lodash.js", unverified: true }); verify.goToSourceDefinition("moduleSpecifier", { file: "/node_modules/lodash/lodash.js", unverified: true }); +verify.goToSourceDefinition("propertyAccess", [{ marker: "variable", unverified: true }, { marker: "property", unverified: true }]) From e302f56b62b097ca3ba340f6479c3199772e7b2d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 14 Apr 2022 14:31:48 -0700 Subject: [PATCH 44/45] Rename command, and no need to return the bound span --- src/harness/client.ts | 27 +++++++++++------------ src/server/protocol.ts | 8 +++---- src/server/session.ts | 39 ++++++---------------------------- src/services/goToDefinition.ts | 4 ++-- src/services/services.ts | 4 ++-- src/services/types.ts | 3 --- 6 files changed, 26 insertions(+), 59 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index 1cc02af1f790d..388f6473fa8da 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -332,24 +332,21 @@ namespace ts.server { })); } - getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfo[] { const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.SourceDefinitionAndBoundSpan, args); - const response = this.processResponse(request); + const request = this.processRequest(CommandNames.FindSourceDefinition, args); + const response = this.processResponse(request); const body = Debug.checkDefined(response.body); // TODO: GH#18217 - return { - definitions: body.definitions.map(entry => ({ - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "", - unverified: entry.unverified, - })), - textSpan: this.decodeSpan(body.textSpan, request.arguments.file) - }; + return body.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "", + unverified: entry.unverified, + })); } getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b8eec8cc380ff..fc53ad151ed67 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -83,9 +83,7 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", - SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", - /* @internal */ - SourceDefinitionAndBoundSpanFull = "sourceDefinitionAndBoundSpan-full", + FindSourceDefinition = "findSourceDefinition", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -907,8 +905,8 @@ namespace ts.server.protocol { readonly command: CommandTypes.DefinitionAndBoundSpan; } - export interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { - readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + export interface FindSourceDefinitionRequest extends FileLocationRequest { + readonly command: CommandTypes.FindSourceDefinition; } export interface DefinitionAndBoundSpanResponse extends Response { diff --git a/src/server/session.ts b/src/server/session.ts index c310d1d94bb5f..0c57ea613391d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1229,14 +1229,11 @@ namespace ts.server { }; } - private getSourceDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { + private findSourceDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.DefinitionInfo[] { 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); - let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan?.definitions || emptyArray, project).slice(); - let textSpan = unmappedDefinitionAndBoundSpan?.textSpan; + const unmappedDefinitions = project.getLanguageService().getDefinitionAtPosition(file, position); + let definitions: readonly DefinitionInfo[] = this.mapDefinitionInfoLocations(unmappedDefinitions || emptyArray, project).slice(); const needsJsResolution = this.projectService.serverMode === LanguageServiceMode.Semantic && ( !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) || some(definitions, d => !!d.failedAliasResolution)); @@ -1246,10 +1243,7 @@ namespace ts.server { definitions?.forEach(d => definitionSet.add(d)); const noDtsProject = project.getNoDtsResolutionProject([file]); const ls = noDtsProject.getLanguageService(); - const definitionAndBoundSpan = ls.getDefinitionAndBoundSpan(file, position, /*searchOtherFilesOnly*/ true); - textSpan ||= definitionAndBoundSpan?.textSpan; - const jsDefinitions = definitionAndBoundSpan - ?.definitions + const jsDefinitions = ls.getDefinitionAtPosition(file, position, /*searchOtherFilesOnly*/ true) ?.filter(d => toNormalizedPath(d.fileName) !== file); if (some(jsDefinitions)) { for (const jsDefinition of jsDefinitions) { @@ -1282,23 +1276,8 @@ namespace ts.server { definitions = arrayFrom(definitionSet.values()); } - if (!textSpan) { - return { definitions: emptyArray, textSpan: undefined! }; - } - definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution); - - if (simplifiedResult) { - return { - definitions: this.mapDefinitionInfo(definitions, project), - textSpan: toProtocolTextSpan(textSpan, scriptInfo) - }; - } - - return { - definitions: definitions.map(Session.mapToOriginalLocation), - textSpan, - }; + return this.mapDefinitionInfo(definitions, project); function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) { const nodeModulesPathParts = getNodeModulePathParts(fileName); @@ -1348,7 +1327,6 @@ namespace ts.server { const ls = project.getLanguageService(); const program = ls.getProgram()!; const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position); - textSpan = createTextSpanFromNode(initialNode); // TODO remove me if ((isStringLiteralLike(initialNode) || isIdentifier(initialNode)) && isAccessExpression(initialNode.parent)) { return forEachNameInAccessChainWalkingLeft(initialNode, nameInChain => { if (nameInChain === initialNode) return undefined; @@ -2933,11 +2911,8 @@ namespace ts.server { [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { - return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + [CommandNames.FindSourceDefinition]: (request: protocol.FindSourceDefinitionRequest) => { + return this.requiredResponse(this.findSourceDefinition(request.arguments)); }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 53f48b81a45e2..b1c8ec28c1b8d 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -265,8 +265,8 @@ namespace ts.GoToDefinition { return undefined; } - export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { - const definitions = getDefinitionAtPosition(program, sourceFile, position, searchOtherFilesOnly); + export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined { + const definitions = getDefinitionAtPosition(program, sourceFile, position); if (!definitions || definitions.length === 0) { return undefined; diff --git a/src/services/services.ts b/src/services/services.ts index afe5451a2fa6c..20373fa773e91 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1773,9 +1773,9 @@ namespace ts { return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly); } - function getDefinitionAndBoundSpan(fileName: string, position: number, searchOtherFilesOnly?: boolean): DefinitionInfoAndBoundSpan | undefined { + function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { synchronizeHostData(); - return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position, searchOtherFilesOnly); + return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); } function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { diff --git a/src/services/types.ts b/src/services/types.ts index 5b15832f7d0db..f84ee7d3ca16b 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -476,9 +476,6 @@ namespace ts { // eslint-disable-next-line @typescript-eslint/unified-signatures getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean): readonly DefinitionInfo[] | undefined; getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; - /*@internal*/ - // eslint-disable-next-line @typescript-eslint/unified-signatures - getDefinitionAndBoundSpan(fileName: string, position: number, searchOtherFilesOnly: 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; From 3f15ed32e5a6115aa214d0fddca342b4681bcc9d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 14 Apr 2022 14:59:52 -0700 Subject: [PATCH 45/45] Update API baseline --- tests/baselines/reference/api/tsserverlibrary.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b5b52794381be..3005c96c17dfa 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7039,7 +7039,7 @@ declare namespace ts.server.protocol { Rename = "rename", Saveto = "saveto", SignatureHelp = "signatureHelp", - SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", + FindSourceDefinition = "findSourceDefinition", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -7659,8 +7659,8 @@ declare namespace ts.server.protocol { interface DefinitionAndBoundSpanRequest extends FileLocationRequest { readonly command: CommandTypes.DefinitionAndBoundSpan; } - interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { - readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + interface FindSourceDefinitionRequest extends FileLocationRequest { + readonly command: CommandTypes.FindSourceDefinition; } interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; @@ -10646,7 +10646,7 @@ declare namespace ts.server { private getDefinition; private mapDefinitionInfoLocations; private getDefinitionAndBoundSpan; - private getSourceDefinitionAndBoundSpan; + private findSourceDefinition; private getEmitOutput; private mapJSDocTagInfo; private mapDisplayParts;