Skip to content

Commit 389bd25

Browse files
committed
Introduce LanguageService.updateIsDefinitionOfReferencedSymbols
...to allow use of the checker when computing isDefinition across projects.
1 parent 10283d4 commit 389bd25

File tree

7 files changed

+176
-43
lines changed

7 files changed

+176
-43
lines changed

src/harness/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,10 @@ namespace ts.server {
849849
throw new Error("Program objects are not serializable through the server protocol.");
850850
}
851851

852+
updateIsDefinitionOfReferencedSymbols(_referencedSymbols: readonly ReferencedSymbol[], _knownSymbolSpans: Set<DocumentSpan>): boolean {
853+
return notImplemented();
854+
}
855+
852856
getNonBoundSourceFile(_fileName: string): SourceFile {
853857
throw new Error("SourceFile objects are not serializable through the server protocol.");
854858
}

src/harness/harnessLanguageService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,9 @@ namespace Harness.LanguageService {
630630
getAutoImportProvider(): ts.Program | undefined {
631631
throw new Error("Program can not be marshaled across the shim layer.");
632632
}
633+
updateIsDefinitionOfReferencedSymbols(_referencedSymbols: readonly ts.ReferencedSymbol[], _knownSymbolSpans: ts.Set<ts.DocumentSpan>): boolean {
634+
return ts.notImplemented();
635+
}
633636
getNonBoundSourceFile(): ts.SourceFile {
634637
throw new Error("SourceFile can not be marshaled across the shim layer.");
635638
}

src/server/session.ts

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ namespace ts.server {
330330
perProjectResults.forEach((projectResults, project) => {
331331
for (const result of projectResults) {
332332
// If there's a mapped location, it'll appear in the results for another project
333-
if (!seen.has(result) && !getMappedLocation(documentSpanLocation(result), project)) {
333+
if (!seen.has(result) && !getMappedLocationForProject(documentSpanLocation(result), project)) {
334334
results.push(result);
335335
seen.add(result);
336336
}
@@ -368,7 +368,7 @@ namespace ts.server {
368368
},
369369
);
370370

371-
// No re-mapping is required if there's exactly one project
371+
// No re-mapping or isDefinition updatses are required if there's exactly one project
372372
if (isArray(perProjectResults)) {
373373
return perProjectResults;
374374
}
@@ -378,18 +378,66 @@ namespace ts.server {
378378

379379
const results: ReferencedSymbol[] = [];
380380

381-
const clearIsDefinition = perProjectResults.get(defaultProject)![0].references[0].isDefinition === undefined;
381+
const defaultProjectResults = perProjectResults.get(defaultProject)!;
382+
if (defaultProjectResults[0].references[0].isDefinition === undefined) {
383+
// Clear all isDefinition properties
384+
perProjectResults.forEach(projectResults => {
385+
for (const referencedSymbol of projectResults) {
386+
for (const ref of referencedSymbol.references) {
387+
delete ref.isDefinition;
388+
}
389+
}
390+
});
391+
}
392+
else {
393+
// Correct isDefinition properties from projects other than defaultProject
394+
const knownSymbolSpans = createDocumentSpanSet();
395+
for (const referencedSymbol of defaultProjectResults) {
396+
for (const ref of referencedSymbol.references) {
397+
if (ref.isDefinition) {
398+
knownSymbolSpans.add(ref);
399+
// One is enough - updateIsDefinitionOfReferencedSymbols will fill out the set based on symbols
400+
break;
401+
}
402+
}
403+
}
404+
405+
const updatedProjects = new Set<Project>();
406+
while (true) {
407+
let progress = false;
408+
perProjectResults.forEach((referencedSymbols, project) => {
409+
if (updatedProjects.has(project)) return;
410+
const updated = project.getLanguageService().updateIsDefinitionOfReferencedSymbols(referencedSymbols, knownSymbolSpans);
411+
if (updated) {
412+
updatedProjects.add(project);
413+
progress = true;
414+
}
415+
});
416+
if (!progress) break;
417+
}
418+
419+
perProjectResults.forEach((referencedSymbols, project) => {
420+
if (updatedProjects.has(project)) return;
421+
for (const referencedSymbol of referencedSymbols) {
422+
for (const ref of referencedSymbol.references) {
423+
ref.isDefinition = false;
424+
}
425+
}
426+
});
427+
}
428+
429+
// TODO (acasey): skip this aggregation when simplifying results?
382430

383431
perProjectResults.forEach((projectResults, project) => {
384432
for (const referencedSymbol of projectResults) {
385-
const mappedDefinitionFile = getMappedLocation(documentSpanLocation(referencedSymbol.definition), project);
433+
const mappedDefinitionFile = getMappedLocationForProject(documentSpanLocation(referencedSymbol.definition), project);
386434
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ?
387435
referencedSymbol.definition :
388436
{
389437
...referencedSymbol.definition,
390438
textSpan: createTextSpan(mappedDefinitionFile.pos, referencedSymbol.definition.textSpan.length), // Why would the length be the same in the original?
391439
fileName: mappedDefinitionFile.fileName,
392-
contextSpan: getMappedContextSpan(referencedSymbol.definition, project)
440+
contextSpan: getMappedContextSpanForProject(referencedSymbol.definition, project)
393441
};
394442

395443
let symbolToAddTo = find(results, o => documentSpansEqual(o.definition, definition));
@@ -399,10 +447,7 @@ namespace ts.server {
399447
}
400448

401449
for (const ref of referencedSymbol.references) {
402-
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(documentSpanLocation(ref), project)) {
403-
if (clearIsDefinition) {
404-
delete ref.isDefinition;
405-
}
450+
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocationForProject(documentSpanLocation(ref), project)) {
406451
symbolToAddTo.references.push(ref);
407452
}
408453
}
@@ -603,39 +648,16 @@ namespace ts.server {
603648
return { fileName, pos: textSpan.start };
604649
}
605650

606-
function getMappedLocation(location: DocumentPosition, project: Project): DocumentPosition | undefined {
607-
const mapsTo = project.getSourceMapper().tryGetSourcePosition(location);
608-
return mapsTo && project.projectService.fileExists(toNormalizedPath(mapsTo.fileName)) ? mapsTo : undefined;
651+
function getMappedLocationForProject(location: DocumentPosition, project: Project): DocumentPosition | undefined {
652+
return getMappedLocation(location, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
609653
}
610654

611-
function getMappedDocumentSpan(documentSpan: DocumentSpan, project: Project): DocumentSpan | undefined {
612-
const newPosition = getMappedLocation(documentSpanLocation(documentSpan), project);
613-
if (!newPosition) return undefined;
614-
return {
615-
fileName: newPosition.fileName,
616-
textSpan: {
617-
start: newPosition.pos,
618-
length: documentSpan.textSpan.length
619-
},
620-
originalFileName: documentSpan.fileName,
621-
originalTextSpan: documentSpan.textSpan,
622-
contextSpan: getMappedContextSpan(documentSpan, project),
623-
originalContextSpan: documentSpan.contextSpan
624-
};
655+
function getMappedDocumentSpanForProject(documentSpan: DocumentSpan, project: Project): DocumentSpan | undefined {
656+
return getMappedDocumentSpan(documentSpan, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
625657
}
626658

627-
function getMappedContextSpan(documentSpan: DocumentSpan, project: Project): TextSpan | undefined {
628-
const contextSpanStart = documentSpan.contextSpan && getMappedLocation(
629-
{ fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start },
630-
project
631-
);
632-
const contextSpanEnd = documentSpan.contextSpan && getMappedLocation(
633-
{ fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length },
634-
project
635-
);
636-
return contextSpanStart && contextSpanEnd ?
637-
{ start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } :
638-
undefined;
659+
function getMappedContextSpanForProject(documentSpan: DocumentSpan, project: Project): TextSpan | undefined {
660+
return getMappedContextSpan(documentSpan, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
639661
}
640662

641663
const invalidPartialSemanticModeCommands: readonly CommandNames[] = [
@@ -1249,7 +1271,7 @@ namespace ts.server {
12491271

12501272
private mapDefinitionInfoLocations(definitions: readonly DefinitionInfo[], project: Project): readonly DefinitionInfo[] {
12511273
return definitions.map((info): DefinitionInfo => {
1252-
const newDocumentSpan = getMappedDocumentSpan(info, project);
1274+
const newDocumentSpan = getMappedDocumentSpanForProject(info, project);
12531275
return !newDocumentSpan ? info : {
12541276
...newDocumentSpan,
12551277
containerKind: info.containerKind,
@@ -1390,7 +1412,7 @@ namespace ts.server {
13901412

13911413
private mapImplementationLocations(implementations: readonly ImplementationLocation[], project: Project): readonly ImplementationLocation[] {
13921414
return implementations.map((info): ImplementationLocation => {
1393-
const newDocumentSpan = getMappedDocumentSpan(info, project);
1415+
const newDocumentSpan = getMappedDocumentSpanForProject(info, project);
13941416
return !newDocumentSpan ? info : {
13951417
...newDocumentSpan,
13961418
kind: info.kind,
@@ -1626,7 +1648,7 @@ namespace ts.server {
16261648
const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0;
16271649
const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : "";
16281650
const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => {
1629-
return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry));
1651+
return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry)); // TODO (acasey): de-dup
16301652
});
16311653
return { refs, symbolName, symbolStartOffset, symbolDisplayString };
16321654
}
@@ -2230,7 +2252,7 @@ namespace ts.server {
22302252
// Mutates `outputs`
22312253
function addItemsForProject(project: Project) {
22322254
const projectItems = project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*filename*/ undefined, /*excludeDts*/ project.isNonTsProject());
2233-
const unseenItems = filter(projectItems, item => tryAddSeenItem(item) && !getMappedLocation(documentSpanLocation(item), project));
2255+
const unseenItems = filter(projectItems, item => tryAddSeenItem(item) && !getMappedLocationForProject(documentSpanLocation(item), project));
22342256
if (unseenItems.length) {
22352257
outputs.push({ project, navigateToItems: unseenItems });
22362258
}

src/services/findAllReferences.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ namespace ts.FindAllReferences {
564564
}
565565

566566
/** Whether a reference, `node`, is a definition of the `target` symbol */
567-
function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean {
567+
export function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean {
568568
if (!target) return false;
569569
const source = getDeclarationFromName(node) ||
570570
(node.kind === SyntaxKind.DefaultKeyword ? node.parent

src/services/services.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,62 @@ namespace ts {
16001600
return host.getPackageJsonAutoImportProvider?.();
16011601
}
16021602

1603+
function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set<DocumentSpan>): boolean {
1604+
const checker = program.getTypeChecker();
1605+
const symbol = getSymbolForProgram();
1606+
1607+
if (!symbol) return false;
1608+
1609+
for (const referencedSymbol of referencedSymbols) {
1610+
for (const ref of referencedSymbol.references) {
1611+
const refNode = getNodeForSpan(ref);
1612+
Debug.assertIsDefined(refNode);
1613+
if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) {
1614+
knownSymbolSpans.add(ref);
1615+
ref.isDefinition = true;
1616+
const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists));
1617+
if (mappedSpan) {
1618+
knownSymbolSpans.add(mappedSpan);
1619+
}
1620+
}
1621+
else {
1622+
ref.isDefinition = false;
1623+
}
1624+
}
1625+
}
1626+
1627+
return true;
1628+
1629+
function getSymbolForProgram(): Symbol | undefined {
1630+
for (const referencedSymbol of referencedSymbols) {
1631+
for (const ref of referencedSymbol.references) {
1632+
if (knownSymbolSpans.has(ref)) {
1633+
const refNode = getNodeForSpan(ref);
1634+
Debug.assertIsDefined(refNode);
1635+
return checker.getSymbolAtLocation(refNode);
1636+
}
1637+
const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists));
1638+
if (mappedSpan && knownSymbolSpans.has(mappedSpan)) {
1639+
const refNode = getNodeForSpan(mappedSpan);
1640+
if (refNode) {
1641+
return checker.getSymbolAtLocation(refNode);
1642+
}
1643+
}
1644+
}
1645+
}
1646+
1647+
return undefined;
1648+
}
1649+
1650+
function getNodeForSpan(docSpan: DocumentSpan): Node | undefined {
1651+
const sourceFile = program.getSourceFile(docSpan.fileName);
1652+
if (!sourceFile) return undefined;
1653+
const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start);
1654+
const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References });
1655+
return adjustedNode;
1656+
}
1657+
}
1658+
16031659
function cleanupSemanticCache(): void {
16041660
program = undefined!; // TODO: GH#18217
16051661
}
@@ -2715,6 +2771,7 @@ namespace ts {
27152771
getNonBoundSourceFile,
27162772
getProgram,
27172773
getAutoImportProvider,
2774+
updateIsDefinitionOfReferencedSymbols,
27182775
getApplicableRefactors,
27192776
getEditsForRefactor,
27202777
toLineColumnOffset,

src/services/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ namespace ts {
546546
/* @internal */ getNonBoundSourceFile(fileName: string): SourceFile;
547547
/* @internal */ getAutoImportProvider(): Program | undefined;
548548

549+
/// Returns true if a suitable symbol was found in the project.
550+
/// May set isDefinition properties in `referencedSymbols` to false.
551+
/// May add elements to `knownSymbolSpans`.
552+
/* @internal */ updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set<DocumentSpan>): boolean;
553+
549554
toggleLineComment(fileName: string, textRange: TextRange): TextChange[];
550555
toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[];
551556
commentSelection(fileName: string, textRange: TextRange): TextChange[];

src/services/utilities.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,48 @@ namespace ts {
21072107
return true;
21082108
}
21092109

2110+
export function getMappedLocation(location: DocumentPosition, sourceMapper: SourceMapper, fileExists: ((path: string) => boolean) | undefined): DocumentPosition | undefined {
2111+
const mapsTo = sourceMapper.tryGetSourcePosition(location);
2112+
return mapsTo && (!fileExists || fileExists(normalizePath(mapsTo.fileName)) ? mapsTo : undefined);
2113+
}
2114+
2115+
export function getMappedDocumentSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): DocumentSpan | undefined {
2116+
const { fileName, textSpan } = documentSpan;
2117+
const newPosition = getMappedLocation({ fileName, pos: textSpan.start }, sourceMapper, fileExists);
2118+
if (!newPosition) return undefined;
2119+
const newEndPosition = getMappedLocation({ fileName, pos: textSpan.start + textSpan.length }, sourceMapper, fileExists);
2120+
const newLength = newEndPosition
2121+
? newEndPosition.pos - newPosition.pos
2122+
: textSpan.length; // This shouldn't happen
2123+
return {
2124+
fileName: newPosition.fileName,
2125+
textSpan: {
2126+
start: newPosition.pos,
2127+
length: newLength,
2128+
},
2129+
originalFileName: documentSpan.fileName,
2130+
originalTextSpan: documentSpan.textSpan,
2131+
contextSpan: getMappedContextSpan(documentSpan, sourceMapper, fileExists),
2132+
originalContextSpan: documentSpan.contextSpan
2133+
};
2134+
}
2135+
2136+
export function getMappedContextSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): TextSpan | undefined {
2137+
const contextSpanStart = documentSpan.contextSpan && getMappedLocation(
2138+
{ fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start },
2139+
sourceMapper,
2140+
fileExists
2141+
);
2142+
const contextSpanEnd = documentSpan.contextSpan && getMappedLocation(
2143+
{ fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length },
2144+
sourceMapper,
2145+
fileExists
2146+
);
2147+
return contextSpanStart && contextSpanEnd ?
2148+
{ start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } :
2149+
undefined;
2150+
}
2151+
21102152
// #endregion
21112153

21122154
// Display-part writer helpers

0 commit comments

Comments
 (0)