Skip to content

Commit 080a4f0

Browse files
authored
(feat) find references (#569)
#566
1 parent 96254df commit 080a4f0

File tree

10 files changed

+209
-3
lines changed

10 files changed

+209
-3
lines changed

packages/language-server/src/ls-config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const defaultLSConfig: LSConfig = {
1010
hover: { enable: true },
1111
completions: { enable: true },
1212
definitions: { enable: true },
13+
findReferences: { enable: true },
1314
documentSymbols: { enable: true },
1415
codeActions: { enable: true },
1516
rename: { enable: true },
@@ -67,6 +68,9 @@ export interface LSTypescriptConfig {
6768
completions: {
6869
enable: boolean;
6970
};
71+
findReferences: {
72+
enable: boolean;
73+
};
7074
definitions: {
7175
enable: boolean;
7276
};

packages/language-server/src/plugins/PluginHost.ts

+19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
CompletionContext,
2020
WorkspaceEdit,
2121
FormattingOptions,
22+
ReferenceContext,
23+
Location,
2224
} from 'vscode-languageserver';
2325
import { LSConfig, LSConfigManager } from '../ls-config';
2426
import { DocumentManager } from '../lib/documents';
@@ -315,6 +317,23 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
315317
);
316318
}
317319

320+
async findReferences(
321+
textDocument: TextDocumentIdentifier,
322+
position: Position,
323+
context: ReferenceContext,
324+
): Promise<Location[] | null> {
325+
const document = this.getDocument(textDocument.uri);
326+
if (!document) {
327+
throw new Error('Cannot call methods on an unopened document');
328+
}
329+
330+
return await this.execute<any>(
331+
'findReferences',
332+
[document, position, context],
333+
ExecuteMode.FirstNonNull,
334+
);
335+
}
336+
318337
onWatchFileChanges(fileName: string, changeType: FileChangeType): void {
319338
for (const support of this.plugins) {
320339
support.onWatchFileChanges?.(fileName, changeType);

packages/language-server/src/plugins/interfaces.ts

+11
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import {
1111
Diagnostic,
1212
FormattingOptions,
1313
Hover,
14+
Location,
1415
Position,
1516
Range,
17+
ReferenceContext,
1618
SymbolInformation,
1719
TextDocumentIdentifier,
1820
TextEdit,
@@ -110,6 +112,14 @@ export interface RenameProvider {
110112
prepareRename(document: Document, position: Position): Resolvable<Range | null>;
111113
}
112114

115+
export interface FindReferencesProvider {
116+
findReferences(
117+
document: Document,
118+
position: Position,
119+
context: ReferenceContext,
120+
): Promise<Location[] | null>;
121+
}
122+
113123
export interface OnWatchFileChanges {
114124
onWatchFileChanges(fileName: string, changeType: FileChangeType): void;
115125
}
@@ -125,6 +135,7 @@ export type LSProvider = DiagnosticsProvider &
125135
DefinitionsProvider &
126136
UpdateImportsProvider &
127137
CodeActionsProvider &
138+
FindReferencesProvider &
128139
RenameProvider;
129140

130141
export type Plugin = Partial<LSProvider & OnWatchFileChanges>;

packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
Diagnostic,
88
FileChangeType,
99
Hover,
10+
Location,
1011
LocationLink,
1112
Position,
1213
Range,
14+
ReferenceContext,
1315
SymbolInformation,
1416
WorkspaceEdit,
1517
CompletionList,
@@ -31,6 +33,7 @@ import {
3133
DiagnosticsProvider,
3234
DocumentSymbolsProvider,
3335
FileRename,
36+
FindReferencesProvider,
3437
HoverProvider,
3538
OnWatchFileChanges,
3639
RenameProvider,
@@ -49,6 +52,7 @@ import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
4952
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
5053
import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils';
5154
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
55+
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
5256

5357
export class TypeScriptPlugin
5458
implements
@@ -59,6 +63,7 @@ export class TypeScriptPlugin
5963
CodeActionsProvider,
6064
UpdateImportsProvider,
6165
RenameProvider,
66+
FindReferencesProvider,
6267
OnWatchFileChanges,
6368
CompletionsProvider<CompletionEntryWithIdentifer> {
6469
private readonly configManager: LSConfigManager;
@@ -69,6 +74,7 @@ export class TypeScriptPlugin
6974
private readonly diagnosticsProvider: DiagnosticsProviderImpl;
7075
private readonly renameProvider: RenameProviderImpl;
7176
private readonly hoverProvider: HoverProviderImpl;
77+
private readonly findReferencesProvider: FindReferencesProviderImpl;
7278

7379
constructor(
7480
docManager: DocumentManager,
@@ -86,6 +92,7 @@ export class TypeScriptPlugin
8692
this.diagnosticsProvider = new DiagnosticsProviderImpl(this.lsAndTsDocResolver);
8793
this.renameProvider = new RenameProviderImpl(this.lsAndTsDocResolver);
8894
this.hoverProvider = new HoverProviderImpl(this.lsAndTsDocResolver);
95+
this.findReferencesProvider = new FindReferencesProviderImpl(this.lsAndTsDocResolver);
8996
}
9097

9198
async getDiagnostics(document: Document): Promise<Diagnostic[]> {
@@ -194,19 +201,19 @@ export class TypeScriptPlugin
194201
const tsDirectiveCommentCompletions = getDirectiveCommentCompletions(
195202
position,
196203
document,
197-
completionContext
204+
completionContext,
198205
);
199206

200207
const completions = await this.completionProvider.getCompletions(
201208
document,
202209
position,
203-
completionContext
210+
completionContext,
204211
);
205212

206213
if (completions && tsDirectiveCommentCompletions) {
207214
return CompletionList.create(
208215
completions.items.concat(tsDirectiveCommentCompletions.items),
209-
completions.isIncomplete
216+
completions.isIncomplete,
210217
);
211218
}
212219

@@ -309,6 +316,18 @@ export class TypeScriptPlugin
309316
return this.updateImportsProvider.updateImports(fileRename);
310317
}
311318

319+
async findReferences(
320+
document: Document,
321+
position: Position,
322+
context: ReferenceContext,
323+
): Promise<Location[] | null> {
324+
if (!this.featureEnabled('findReferences')) {
325+
return null;
326+
}
327+
328+
return this.findReferencesProvider.findReferences(document, position, context);
329+
}
330+
312331
onWatchFileChanges(fileName: string, changeType: FileChangeType) {
313332
const scriptKind = getScriptKindFromFileName(fileName);
314333

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Location, Position, ReferenceContext } from 'vscode-languageserver';
2+
import { Document } from '../../../lib/documents';
3+
import { pathToUrl } from '../../../utils';
4+
import { FindReferencesProvider } from '../../interfaces';
5+
import { SnapshotFragment } from '../DocumentSnapshot';
6+
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
7+
import { convertToLocationRange } from '../utils';
8+
9+
export class FindReferencesProviderImpl implements FindReferencesProvider {
10+
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
11+
12+
async findReferences(
13+
document: Document,
14+
position: Position,
15+
context: ReferenceContext,
16+
): Promise<Location[] | null> {
17+
const { lang, tsDoc } = this.getLSAndTSDoc(document);
18+
const fragment = await tsDoc.getFragment();
19+
20+
const references = lang.getReferencesAtPosition(
21+
tsDoc.filePath,
22+
fragment.offsetAt(fragment.getGeneratedPosition(position)),
23+
);
24+
if (!references) {
25+
return null;
26+
}
27+
28+
const docs = new Map<string, SnapshotFragment>([[tsDoc.filePath, fragment]]);
29+
30+
return await Promise.all(
31+
references
32+
.filter((ref) => context.includeDeclaration || !ref.isDefinition)
33+
.map(async (ref) => {
34+
let defDoc = docs.get(ref.fileName);
35+
if (!defDoc) {
36+
defDoc = await this.getSnapshot(ref.fileName).getFragment();
37+
docs.set(ref.fileName, defDoc);
38+
}
39+
40+
return Location.create(
41+
pathToUrl(ref.fileName),
42+
convertToLocationRange(defDoc, ref.textSpan),
43+
);
44+
}),
45+
);
46+
}
47+
48+
private getLSAndTSDoc(document: Document) {
49+
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
50+
}
51+
52+
private getSnapshot(filePath: string, document?: Document) {
53+
return this.lsAndTsDocResolver.getSnapshot(filePath, document);
54+
}
55+
}

packages/language-server/src/server.ts

+4
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export function startServer(options?: LSOptions) {
180180
renameProvider: evt.capabilities.textDocument?.rename?.prepareSupport
181181
? { prepareProvider: true }
182182
: true,
183+
referencesProvider: true,
183184
},
184185
};
185186
});
@@ -218,6 +219,9 @@ export function startServer(options?: LSOptions) {
218219
);
219220
connection.onDocumentSymbol((evt) => pluginHost.getDocumentSymbols(evt.textDocument));
220221
connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position));
222+
connection.onReferences((evt) =>
223+
pluginHost.findReferences(evt.textDocument, evt.position, evt.context),
224+
);
221225

222226
connection.onCodeAction((evt) =>
223227
pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import * as assert from 'assert';
2+
import * as path from 'path';
3+
import ts from 'typescript';
4+
import { Location, Position, Range } from 'vscode-languageserver';
5+
import { Document, DocumentManager } from '../../../../src/lib/documents';
6+
import { FindReferencesProviderImpl } from '../../../../src/plugins/typescript/features/FindReferencesProvider';
7+
import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver';
8+
import { pathToUrl } from '../../../../src/utils';
9+
10+
const testDir = path.join(__dirname, '..');
11+
12+
describe('FindReferencesProvider', () => {
13+
function getFullPath(filename: string) {
14+
return path.join(testDir, 'testfiles', filename);
15+
}
16+
function getUri(filename: string) {
17+
const filePath = path.join(testDir, 'testfiles', filename);
18+
return pathToUrl(filePath);
19+
}
20+
21+
function setup(filename: string) {
22+
const docManager = new DocumentManager(
23+
(textDocument) => new Document(textDocument.uri, textDocument.text),
24+
);
25+
const lsAndTsDocResolver = new LSAndTSDocResolver(docManager, [testDir]);
26+
const provider = new FindReferencesProviderImpl(lsAndTsDocResolver);
27+
const document = openDoc(filename);
28+
return { provider, document };
29+
30+
function openDoc(filename: string) {
31+
const filePath = getFullPath(filename);
32+
const doc = docManager.openDocument(<any>{
33+
uri: pathToUrl(filePath),
34+
text: ts.sys.readFile(filePath) || '',
35+
});
36+
return doc;
37+
}
38+
}
39+
40+
async function test(position: Position, includeDeclaration: boolean) {
41+
const { provider, document } = setup('find-references.svelte');
42+
43+
const results = await provider.findReferences(document, position, { includeDeclaration });
44+
45+
let expectedResults = [
46+
Location.create(
47+
getUri('find-references.svelte'),
48+
Range.create(Position.create(2, 8), Position.create(2, 14)),
49+
),
50+
Location.create(
51+
getUri('find-references.svelte'),
52+
Range.create(Position.create(3, 8), Position.create(3, 14)),
53+
),
54+
];
55+
if (includeDeclaration) {
56+
expectedResults = [
57+
Location.create(
58+
getUri('find-references.svelte'),
59+
Range.create(Position.create(1, 10), Position.create(1, 16)),
60+
),
61+
].concat(expectedResults);
62+
}
63+
64+
assert.deepStrictEqual(results, expectedResults);
65+
}
66+
67+
it('finds references', async () => {
68+
await test(Position.create(1, 11), true);
69+
});
70+
71+
it('finds references, exluding definition', async () => {
72+
await test(Position.create(1, 11), false);
73+
});
74+
75+
it('finds references (not searching from declaration)', async () => {
76+
await test(Position.create(2, 8), true);
77+
});
78+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
const findMe = true;
3+
if (findMe) {
4+
findMe;
5+
}
6+
</script>

packages/svelte-vscode/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Enable document symbols for TypeScript. _Default_: `true`
8686

8787
Enable completions for TypeScript. _Default_: `true`
8888

89+
##### `svelte.plugin.typescript.findReferences`
90+
91+
Enable find-references for TypeScript. _Default_: `true`
92+
8993
##### `svelte.plugin.typescript.definitions`
9094

9195
Enable go to definition for TypeScript. _Default_: `true`

packages/svelte-vscode/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@
9292
"title": "TypeScript: Completions",
9393
"description": "Enable completions for TypeScript"
9494
},
95+
"svelte.plugin.typescript.findReferences.enable": {
96+
"type": "boolean",
97+
"default": true,
98+
"title": "TypeScript: Find References",
99+
"description": "Enable find-references for TypeScript"
100+
},
95101
"svelte.plugin.typescript.definitions.enable": {
96102
"type": "boolean",
97103
"default": true,

0 commit comments

Comments
 (0)