diff --git a/src/harness/client.ts b/src/harness/client.ts index 06b954b2c83..2040ed2018a 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 7f0d385b796..520666383a2 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 48cae7705f0..98b61cf2fb5 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 56aaf000044..ca8dbd1cac6 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 05d514c6672..c1fed85410d 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 bfb533b0160..19f59d0127c 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 ffcec349cc6..021dcabda43 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 139eac1423e..709af091e72 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 5a3a7c21dff..b2e2cf465a4 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 8990c6615a2..955d87c6f63 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 c9fbfb98fae..8cf54c9ea4c 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 a2166786c54..a6384efd75a 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 152d411040a..b79f870fc2b 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 dc8f9a47abc..57396683f2d 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 f2b7e2b7345..8637439ee68 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 87b7065a17b..316fde37f4c 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 6a0e13962ba..203b0e6ed92 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 e596fd97d9e..3712fb9303c 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");