Build new functionality into go-to-implementation

This commit is contained in:
Andrew Branch
2022-03-10 16:13:52 -08:00
parent 309c4bbf59
commit 381799d0f1
18 changed files with 177 additions and 237 deletions
-20
View File
@@ -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<protocol.SourceDefinitionAndBoundSpanRequest>(CommandNames.SourceDefinitionAndBoundSpan, args);
const response = this.processResponse<protocol.DefinitionInfoAndBoundSpanResponse>(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);
+6 -8
View File
@@ -697,13 +697,6 @@ namespace FourSlash {
this.verifyGoToX(arg0, endMarkerNames, () => this.getGoToDefinitionAndBoundSpan());
}
public verifyGoToSourceDefinition(startMarkerNames: ArrayOrSingle<string>, end?: ArrayOrSingle<string | { marker: string, unverified?: boolean }> | { 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");
}
}
}
-4
View File
@@ -324,10 +324,6 @@ namespace FourSlashInterface {
this.state.verifyGoToType(arg0, endMarkerName);
}
public goToSourceDefinition(startMarkerNames: ArrayOrSingle<string>, end: { file: string } | ArrayOrSingle<string>) {
this.state.verifyGoToSourceDefinition(startMarkerNames, end);
}
public goToDefinitionForMarkers(...markerNames: string[]) {
this.state.verifyGoToDefinitionForMarkers(markerNames);
}
-7
View File
@@ -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;
}
+110 -141
View File
@@ -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));
},
+1 -1
View File
@@ -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)) : [];
}
+1 -1
View File
@@ -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());
+29 -21
View File
@@ -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<SymbolAndEntries, ReferencedSymbol>(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<T>(
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>,
): 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<T> = (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<string> = 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<string> = 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<string> = 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<string>) {
function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], sourceMapper: SourceMapper | undefined, cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet<string>) {
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<string>, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] {
function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>, 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<SymbolAndEntries>) {
@@ -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)));
+3 -3
View File
@@ -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[] {
-2
View File
@@ -317,8 +317,6 @@ declare namespace FourSlashInterface {
goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle<string> }): void;
/** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */
goToDefinitionForMarkers(...markerNames: string[]): void;
goToSourceDefinition(startMarkerNames: ArrayOrSingle<string>, fileResult: { file: string }): void;
goToSourceDefinition(startMarkerNames: ArrayOrSingle<string>, endMarkerNames: ArrayOrSingle<string>): void;
goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle<string> }): void;
goToType(startMarkerNames: ArrayOrSingle<string>, endMarkerNames: ArrayOrSingle<string>): void;
verifyGetEmitOutputForCurrentFile(expected: string): void;
@@ -1,13 +1,13 @@
/// <reference path="../fourslash.ts" />
// @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");
@@ -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");
@@ -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");
@@ -1,8 +1,8 @@
/// <reference path="../fourslash.ts" />
// @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();
@@ -1,7 +1,7 @@
/// <reference path="../fourslash.ts" />
// @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");
@@ -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");
@@ -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");
@@ -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");