Skip to content

Commit 8f56f6b

Browse files
authored
Don't go past import in cross-project renaming (#48758)
* WIP * fix cross-project renaming logic * only use configure if prefix opt is defined * refactor skipAlias into stopAtAlias * fix stopAtAlias * update another stopAtAlias location
1 parent 3b8b207 commit 8f56f6b

File tree

9 files changed

+90
-18
lines changed

9 files changed

+90
-18
lines changed

src/harness/client.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace ts.server {
3838
private lineMaps = new Map<string, number[]>();
3939
private messages: string[] = [];
4040
private lastRenameEntry: RenameEntry | undefined;
41+
private preferences: UserPreferences | undefined;
4142

4243
constructor(private host: SessionClientHost) {
4344
}
@@ -124,6 +125,7 @@ namespace ts.server {
124125

125126
/*@internal*/
126127
configure(preferences: UserPreferences) {
128+
this.preferences = preferences;
127129
const args: protocol.ConfigureRequestArguments = { preferences };
128130
const request = this.processRequest(CommandNames.Configure, args);
129131
this.processResponse(request, /*expectEmptyBody*/ true);
@@ -488,13 +490,25 @@ namespace ts.server {
488490
return notImplemented();
489491
}
490492

491-
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
493+
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] {
492494
if (!this.lastRenameEntry ||
493495
this.lastRenameEntry.inputs.fileName !== fileName ||
494496
this.lastRenameEntry.inputs.position !== position ||
495497
this.lastRenameEntry.inputs.findInStrings !== findInStrings ||
496498
this.lastRenameEntry.inputs.findInComments !== findInComments) {
497-
this.getRenameInfo(fileName, position, { allowRenameOfImportPath: true }, findInStrings, findInComments);
499+
if (providePrefixAndSuffixTextForRename !== undefined) {
500+
// User preferences have to be set through the `Configure` command
501+
this.configure({ providePrefixAndSuffixTextForRename });
502+
// Options argument is not used, so don't pass in options
503+
this.getRenameInfo(fileName, position, /*options*/{}, findInStrings, findInComments);
504+
// Restore previous user preferences
505+
if (this.preferences) {
506+
this.configure(this.preferences);
507+
}
508+
}
509+
else {
510+
this.getRenameInfo(fileName, position, /*options*/{}, findInStrings, findInComments);
511+
}
498512
}
499513

500514
return this.lastRenameEntry!.locations;

src/server/session.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ namespace ts.server {
317317
projects,
318318
defaultProject,
319319
initialLocation,
320+
/*isForRename*/ true,
320321
(project, location, tryAddToTodo) => {
321322
const projectOutputs = project.getLanguageService().findRenameLocations(location.fileName, location.pos, findInStrings, findInComments, providePrefixAndSuffixTextForRename);
322323
if (projectOutputs) {
@@ -332,8 +333,8 @@ namespace ts.server {
332333
return outputs;
333334
}
334335

335-
function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition): DocumentPosition | undefined {
336-
const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos);
336+
function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition, isForRename: boolean): DocumentPosition | undefined {
337+
const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos, /*searchOtherFilesOnly*/ false, isForRename);
337338
const info = infos && firstOrUndefined(infos);
338339
return info && !info.isLocal ? { fileName: info.fileName, pos: info.textSpan.start } : undefined;
339340
}
@@ -350,6 +351,7 @@ namespace ts.server {
350351
projects,
351352
defaultProject,
352353
initialLocation,
354+
/*isForRename*/ false,
353355
(project, location, getMappedLocation) => {
354356
logger.info(`Finding references to ${location.fileName} position ${location.pos} in project ${project.getProjectName()}`);
355357
const projectOutputs = project.getLanguageService().findReferences(location.fileName, location.pos);
@@ -417,7 +419,8 @@ namespace ts.server {
417419
projects: Projects,
418420
defaultProject: Project,
419421
initialLocation: TLocation,
420-
cb: CombineProjectOutputCallback<TLocation>
422+
isForRename: boolean,
423+
cb: CombineProjectOutputCallback<TLocation>,
421424
): void {
422425
const projectService = defaultProject.projectService;
423426
let toDo: ProjectAndLocation<TLocation>[] | undefined;
@@ -430,7 +433,7 @@ namespace ts.server {
430433

431434
// After initial references are collected, go over every other project and see if it has a reference for the symbol definition.
432435
if (initialLocation) {
433-
const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation);
436+
const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation, isForRename);
434437
if (defaultDefinition) {
435438
const getGeneratedDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition.fileName) ?
436439
defaultDefinition :
@@ -1243,7 +1246,7 @@ namespace ts.server {
12431246
definitions?.forEach(d => definitionSet.add(d));
12441247
const noDtsProject = project.getNoDtsResolutionProject([file]);
12451248
const ls = noDtsProject.getLanguageService();
1246-
const jsDefinitions = ls.getDefinitionAtPosition(file, position, /*searchOtherFilesOnly*/ true)
1249+
const jsDefinitions = ls.getDefinitionAtPosition(file, position, /*searchOtherFilesOnly*/ true, /*stopAtAlias*/ false)
12471250
?.filter(d => toNormalizedPath(d.fileName) !== file);
12481251
if (some(jsDefinitions)) {
12491252
for (const jsDefinition of jsDefinitions) {
@@ -1330,7 +1333,7 @@ namespace ts.server {
13301333
if ((isStringLiteralLike(initialNode) || isIdentifier(initialNode)) && isAccessExpression(initialNode.parent)) {
13311334
return forEachNameInAccessChainWalkingLeft(initialNode, nameInChain => {
13321335
if (nameInChain === initialNode) return undefined;
1333-
const candidates = ls.getDefinitionAtPosition(file, nameInChain.getStart(), /*searchOtherFilesOnly*/ true)
1336+
const candidates = ls.getDefinitionAtPosition(file, nameInChain.getStart(), /*searchOtherFilesOnly*/ true, /*stopAtAlias*/ false)
13341337
?.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient)
13351338
.map(d => ({
13361339
fileName: d.fileName,

src/services/goToDefinition.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @internal */
22
namespace ts.GoToDefinition {
3-
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean): readonly DefinitionInfo[] | undefined {
3+
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined {
44
const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
55
const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray;
66
if (resolvedRef?.file) {
@@ -28,7 +28,7 @@ namespace ts.GoToDefinition {
2828

2929
if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) {
3030
const classDecl = node.parent.parent;
31-
const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker);
31+
const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker, stopAtAlias);
3232

3333
const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration);
3434
const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : "";
@@ -40,15 +40,15 @@ namespace ts.GoToDefinition {
4040
});
4141
}
4242

43-
let { symbol, failedAliasResolution } = getSymbol(node, typeChecker);
43+
let { symbol, failedAliasResolution } = getSymbol(node, typeChecker, stopAtAlias);
4444
let fallbackNode = node;
4545

4646
if (searchOtherFilesOnly && failedAliasResolution) {
4747
// We couldn't resolve the specific import, try on the module specifier.
4848
const importDeclaration = forEach([node, ...symbol?.declarations || emptyArray], n => findAncestor(n, isAnyImportOrBareOrAccessedRequire));
4949
const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration);
5050
if (moduleSpecifier) {
51-
({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker));
51+
({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker, stopAtAlias));
5252
fallbackNode = moduleSpecifier;
5353
}
5454
}
@@ -235,7 +235,7 @@ namespace ts.GoToDefinition {
235235
return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*failedAliasResolution*/ false);
236236
}
237237

238-
const { symbol, failedAliasResolution } = getSymbol(node, typeChecker);
238+
const { symbol, failedAliasResolution } = getSymbol(node, typeChecker, /*stopAtAlias*/ false);
239239
if (!symbol) return undefined;
240240

241241
const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
@@ -292,14 +292,14 @@ namespace ts.GoToDefinition {
292292
return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration));
293293
}
294294

295-
function getSymbol(node: Node, checker: TypeChecker) {
295+
function getSymbol(node: Node, checker: TypeChecker, stopAtAlias: boolean | undefined) {
296296
const symbol = checker.getSymbolAtLocation(node);
297297
// If this is an alias, and the request came at the declaration location
298298
// get the aliased symbol instead. This allows for goto def on an import e.g.
299299
// import {A, B} from "mod";
300300
// to jump to the implementation directly.
301301
let failedAliasResolution = false;
302-
if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
302+
if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && !stopAtAlias && shouldSkipAlias(node, symbol.declarations[0])) {
303303
const aliased = checker.getAliasedSymbol(symbol);
304304
if (aliased.declarations) {
305305
return { symbol: aliased };

src/services/services.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,9 +1768,9 @@ namespace ts {
17681768
}
17691769

17701770
/// Goto definition
1771-
function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean): readonly DefinitionInfo[] | undefined {
1771+
function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined {
17721772
synchronizeHostData();
1773-
return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly);
1773+
return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias);
17741774
}
17751775

17761776
function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined {

src/services/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,10 @@ namespace ts {
474474

475475
/*@internal*/
476476
// eslint-disable-next-line @typescript-eslint/unified-signatures
477-
getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean): readonly DefinitionInfo[] | undefined;
477+
getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: false, stopAtAlias: boolean): readonly DefinitionInfo[] | undefined;
478+
/*@internal*/
479+
// eslint-disable-next-line @typescript-eslint/unified-signatures
480+
getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean, stopAtAlias: false): readonly DefinitionInfo[] | undefined;
478481
getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined;
479482
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
480483
getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*====== /src/index.ts ======*/
2+
3+
import { [|RENAME|] } from '../lib/index';
4+
RENAME;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*====== /src/index.ts ======*/
2+
3+
import * as [|RENAME|] from '../lib/index';
4+
RENAME.someExportedVariable;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='../fourslash.ts' />
2+
3+
// @Filename: /lib/tsconfig.json
4+
//// {}
5+
6+
// @Filename: /lib/index.ts
7+
//// const unrelatedLocalVariable = 123;
8+
//// export const someExportedVariable = unrelatedLocalVariable;
9+
10+
// @Filename: /src/tsconfig.json
11+
//// {}
12+
13+
// @Filename: /src/index.ts
14+
//// import { /*i*/someExportedVariable } from '../lib/index';
15+
//// someExportedVariable;
16+
17+
// @Filename: /tsconfig.json
18+
//// {}
19+
20+
goTo.file("/lib/index.ts");
21+
goTo.file("/src/index.ts");
22+
verify.baselineRename("i", { providePrefixAndSuffixTextForRename: true });
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='../fourslash.ts' />
2+
3+
// @Filename: /lib/tsconfig.json
4+
//// {}
5+
6+
// @Filename: /lib/index.ts
7+
//// const unrelatedLocalVariable = 123;
8+
//// export const someExportedVariable = unrelatedLocalVariable;
9+
10+
// @Filename: /src/tsconfig.json
11+
//// {}
12+
13+
// @Filename: /src/index.ts
14+
//// import * as /*i*/lib from '../lib/index';
15+
//// lib.someExportedVariable;
16+
17+
// @Filename: /tsconfig.json
18+
//// {}
19+
20+
goTo.file("/lib/index.ts");
21+
goTo.file("/src/index.ts");
22+
verify.baselineRename("i", {});

0 commit comments

Comments
 (0)