Skip to content

Commit dea1bee

Browse files
authored
Add Go to Source Definition (#3349)
1 parent 9c19dee commit dea1bee

151 files changed

Lines changed: 4793 additions & 46 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

_extension/package.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@
113113
"enablement": "typescript.native-preview.serverRunning",
114114
"category": "TypeScript Native Preview"
115115
},
116+
{
117+
"command": "typescript.native-preview.goToSourceDefinition",
118+
"title": "Go to Source Definition",
119+
"enablement": "typescript.native-preview.serverRunning",
120+
"category": "TypeScript Native Preview"
121+
},
116122
{
117123
"title": "Show References of CodeLens",
118124
"command": "typescript.native-preview.codeLens.showLocations",
@@ -154,7 +160,22 @@
154160
"enablement": "typescript.native-preview.serverRunning",
155161
"category": "Developer: TypeScript Native Preview"
156162
}
157-
]
163+
],
164+
"menus": {
165+
"commandPalette": [
166+
{
167+
"command": "typescript.native-preview.goToSourceDefinition",
168+
"when": "typescript.native-preview.serverRunning && tsSupportsSourceDefinition"
169+
}
170+
],
171+
"editor/context": [
172+
{
173+
"command": "typescript.native-preview.goToSourceDefinition",
174+
"when": "typescript.native-preview.serverRunning && tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)",
175+
"group": "navigation@1.41"
176+
}
177+
]
178+
}
158179
},
159180
"main": "./dist/extension.bundle.js",
160181
"files": [

_extension/src/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
configurationMiddleware,
2121
sendNotificationMiddleware,
2222
} from "./configurationMiddleware";
23+
import { registerSourceDefinitionFeature } from "./languageFeatures/sourceDefinition";
2324
import { registerTagClosingFeature } from "./languageFeatures/tagClosing";
2425
import * as tr from "./telemetryReporting";
2526
import {
@@ -205,6 +206,7 @@ export class Client implements vscode.Disposable {
205206

206207
this.disposables.push(
207208
serverTelemetryListener,
209+
registerSourceDefinitionFeature(this.client),
208210
registerTagClosingFeature("typescript", this.documentSelector, this.client),
209211
registerTagClosingFeature("javascript", this.documentSelector, this.client),
210212
);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import * as vscode from "vscode";
2+
import {
3+
LanguageClient,
4+
Location,
5+
LocationLink,
6+
} from "vscode-languageclient/node";
7+
8+
const sourceDefinitionMethod = "custom/textDocument/sourceDefinition";
9+
const sourceDefinitionCommand = "typescript.native-preview.goToSourceDefinition";
10+
const sourceDefinitionContext = "tsSupportsSourceDefinition";
11+
12+
type SourceDefinitionResponse = Location | Location[] | LocationLink[] | null;
13+
14+
export function registerSourceDefinitionFeature(client: LanguageClient): vscode.Disposable {
15+
const capabilities = client.initializeResult?.capabilities as { customSourceDefinitionProvider?: boolean; } | undefined;
16+
const enabled = !!capabilities?.customSourceDefinitionProvider;
17+
void vscode.commands.executeCommand("setContext", sourceDefinitionContext, enabled);
18+
19+
if (!enabled) {
20+
return new vscode.Disposable(() => {
21+
void vscode.commands.executeCommand("setContext", sourceDefinitionContext, false);
22+
});
23+
}
24+
25+
const disposable = vscode.commands.registerCommand(sourceDefinitionCommand, async () => {
26+
const activeEditor = vscode.window.activeTextEditor;
27+
if (!activeEditor) {
28+
vscode.window.showErrorMessage("Go to Source Definition failed. No editor is active.");
29+
return;
30+
}
31+
32+
const { document } = activeEditor;
33+
if (!["javascript", "javascriptreact", "typescript", "typescriptreact"].includes(document.languageId)) {
34+
vscode.window.showErrorMessage("Go to Source Definition failed. Unsupported file type.");
35+
return;
36+
}
37+
38+
const position = activeEditor.selection.active;
39+
await vscode.window.withProgress({
40+
location: vscode.ProgressLocation.Window,
41+
title: "Finding source definitions",
42+
}, async (_, token) => {
43+
let response: SourceDefinitionResponse;
44+
try {
45+
response = await client.sendRequest<SourceDefinitionResponse>(
46+
sourceDefinitionMethod,
47+
client.code2ProtocolConverter.asTextDocumentPositionParams(document, position),
48+
token,
49+
);
50+
}
51+
catch {
52+
return;
53+
}
54+
55+
if (token.isCancellationRequested) {
56+
return;
57+
}
58+
59+
const p2c = client.protocol2CodeConverter;
60+
const items = !response ? [] : Array.isArray(response) ? response : [response];
61+
const locations = items.map(item =>
62+
LocationLink.is(item)
63+
? p2c.asLocation({ uri: item.targetUri, range: item.targetSelectionRange })
64+
: p2c.asLocation(item)
65+
);
66+
67+
await vscode.commands.executeCommand(
68+
"editor.action.goToLocations",
69+
document.uri,
70+
position,
71+
locations,
72+
"goto",
73+
"No source definitions found.",
74+
);
75+
});
76+
});
77+
78+
return vscode.Disposable.from(
79+
disposable,
80+
new vscode.Disposable(() => {
81+
void vscode.commands.executeCommand("setContext", sourceDefinitionContext, false);
82+
}),
83+
);
84+
}

internal/fourslash/_scripts/convertFourslash.mts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] {
253253
case "baselineGetDefinitionAtPosition":
254254
case "baselineGoToType":
255255
case "baselineGoToImplementation":
256+
case "baselineGoToSourceDefinition":
256257
// Both `baselineGoToDefinition` and `baselineGetDefinitionAtPosition` take the same
257258
// arguments, but differ in that...
258259
// - `verify.baselineGoToDefinition(...)` called getDefinitionAndBoundSpan
@@ -1289,14 +1290,14 @@ function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [V
12891290
}
12901291

12911292
function parseBaselineGoToDefinitionArgs(
1292-
funcName: "baselineGoToDefinition" | "baselineGoToType" | "baselineGetDefinitionAtPosition" | "baselineGoToImplementation",
1293+
funcName: "baselineGoToDefinition" | "baselineGoToType" | "baselineGetDefinitionAtPosition" | "baselineGoToImplementation" | "baselineGoToSourceDefinition",
12931294
args: readonly ts.Expression[],
12941295
): [VerifyBaselineGoToDefinitionCmd] {
12951296
let boundSpan: true | undefined;
12961297
if (funcName === "baselineGoToDefinition") {
12971298
boundSpan = true;
12981299
}
1299-
let kind: "verifyBaselineGoToDefinition" | "verifyBaselineGoToType" | "verifyBaselineGoToImplementation";
1300+
let kind: "verifyBaselineGoToDefinition" | "verifyBaselineGoToType" | "verifyBaselineGoToImplementation" | "verifyBaselineGoToSourceDefinition";
13001301
switch (funcName) {
13011302
case "baselineGoToDefinition":
13021303
case "baselineGetDefinitionAtPosition":
@@ -1308,6 +1309,9 @@ function parseBaselineGoToDefinitionArgs(
13081309
case "baselineGoToImplementation":
13091310
kind = "verifyBaselineGoToImplementation";
13101311
break;
1312+
case "baselineGoToSourceDefinition":
1313+
kind = "verifyBaselineGoToSourceDefinition";
1314+
break;
13111315
}
13121316
const newArgs = [];
13131317
for (const arg of args) {
@@ -2986,7 +2990,7 @@ interface VerifyBaselineFindAllReferencesCmd {
29862990
}
29872991

29882992
interface VerifyBaselineGoToDefinitionCmd {
2989-
kind: "verifyBaselineGoToDefinition" | "verifyBaselineGoToType" | "verifyBaselineGoToImplementation";
2993+
kind: "verifyBaselineGoToDefinition" | "verifyBaselineGoToType" | "verifyBaselineGoToImplementation" | "verifyBaselineGoToSourceDefinition";
29902994
markers: string[];
29912995
boundSpan?: true;
29922996
ranges?: boolean;
@@ -3310,6 +3314,11 @@ function generateBaselineGoToDefinition({ markers, ranges, kind, boundSpan }: Ve
33103314
return `f.VerifyBaselineGoToImplementation(t)`;
33113315
}
33123316
return `f.VerifyBaselineGoToImplementation(t, ${markers.join(", ")})`;
3317+
case "verifyBaselineGoToSourceDefinition":
3318+
if (ranges || markers.length === 0) {
3319+
return `f.VerifyBaselineGoToSourceDefinition(t)`;
3320+
}
3321+
return `f.VerifyBaselineGoToSourceDefinition(t, ${markers.join(", ")})`;
33133322
}
33143323
}
33153324
@@ -3502,6 +3511,7 @@ function generateCmd(cmd: Cmd): string {
35023511
case "verifyBaselineGoToDefinition":
35033512
case "verifyBaselineGoToType":
35043513
case "verifyBaselineGoToImplementation":
3514+
case "verifyBaselineGoToSourceDefinition":
35053515
return generateBaselineGoToDefinition(cmd);
35063516
case "verifyBaselineQuickInfo":
35073517
// Quick Info -> Hover

internal/fourslash/_scripts/unparsedTests.txt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,23 +1737,6 @@ getMatchingBracesAdjacentBraces.ts parse error: "Unrecognized fourslash statemen
17371737
getMatchingBracesNegativeCases.ts parse error: "Unrecognized fourslash statement: test.ranges().forEach(...)"
17381738
getNameOrDottedNameSpan.ts parse error: "Unrecognized fourslash statement: verify.nameOrDottedNameSpanTextIs(...)"
17391739
globalCompletionListInsideObjectLiterals.ts parse error: "Expected string literal or object literal for expected completion item, got exact.filter(name => name !== 'p1')"
1740-
goToSource1_localJsBesideDts.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1741-
goToSource10_mapFromAtTypes3.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1742-
goToSource11_propertyOfAlias.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1743-
goToSource12_callbackParam.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1744-
goToSource13_nodenext.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1745-
goToSource14_unresolvedRequireDestructuring.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1746-
goToSource15_bundler.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1747-
goToSource16_callbackParamDifferentFile.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1748-
goToSource17_AddsFileToProject.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1749-
goToSource18_reusedFromDifferentFolder.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1750-
goToSource2_nodeModulesWithTypes.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1751-
goToSource3_nodeModulesAtTypes.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1752-
goToSource5_sameAsGoToDef1.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1753-
goToSource6_sameAsGoToDef2.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1754-
goToSource7_conditionallyMinified.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1755-
goToSource8_mapFromAtTypes.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
1756-
goToSource9_mapFromAtTypes2.ts parse error: "Unrecognized fourslash statement: verify.baselineGoToSourceDefinition(...)"
17571740
identationAfterInterfaceCall.ts parse error: "Unrecognized verify content function: indentationIs"
17581741
impliedNodeFormat.ts parse error: "Expected a single string literal argument in edit.paste, got `\\n\"${\"a\".repeat(256)}\";`"
17591742
importDeclPaste0.ts parse error: "Unrecognized edit function: disableFormatting"

internal/fourslash/baselineutil.go

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
findAllReferencesCmd baselineCommand = "findAllReferences"
2929
goToDefinitionCmd baselineCommand = "goToDefinition"
3030
goToImplementationCmd baselineCommand = "goToImplementation"
31+
goToSourceDefinitionCmd baselineCommand = "goToSourceDefinition"
3132
goToTypeDefinitionCmd baselineCommand = "goToType"
3233
inlayHintsCmd baselineCommand = "Inlay Hints"
3334
nonSuggestionDiagnosticsCmd baselineCommand = "Syntax and Semantic Diagnostics"
@@ -274,7 +275,7 @@ func (f *FourslashTest) getBaselineOptions(command baselineCommand, testPath str
274275
return strings.Join(fixedLines, "\n")
275276
},
276277
}
277-
case goToDefinitionCmd, goToTypeDefinitionCmd, goToImplementationCmd:
278+
case goToDefinitionCmd, goToTypeDefinitionCmd, goToImplementationCmd, goToSourceDefinitionCmd:
278279
return baseline.Options{
279280
Subfolder: subfolder,
280281
IsSubmodule: true,
@@ -515,7 +516,9 @@ type baselineFourslashLocationsOptions struct {
515516
endMarkerSuffix func(span documentSpan) *string
516517
getLocationData func(span documentSpan) string
517518

518-
additionalSpan *documentSpan
519+
additionalSpan *documentSpan
520+
preserveResultOrder bool
521+
orderedFiles []lsproto.DocumentUri
519522
}
520523

521524
func locationToSpan(loc lsproto.Location) documentSpan {
@@ -534,6 +537,9 @@ func (f *FourslashTest) getBaselineForLocationsWithFileContents(locations []lspr
534537

535538
func (f *FourslashTest) getBaselineForSpansWithFileContents(spans []documentSpan, options baselineFourslashLocationsOptions) string {
536539
spansByFile := collections.GroupBy(spans, func(span documentSpan) lsproto.DocumentUri { return span.uri })
540+
if options.preserveResultOrder {
541+
options.orderedFiles = uniqueFilesInSpanOrder(spans)
542+
}
537543
return f.getBaselineForGroupedSpansWithFileContents(
538544
spansByFile,
539545
options,
@@ -549,25 +555,16 @@ func (f *FourslashTest) getBaselineForGroupedSpansWithFileContents(groupedRanges
549555
spanToContextId := map[documentSpan]int{}
550556

551557
baselineEntries := []string{}
552-
walkDirFn := func(path string, d vfs.DirEntry, e error) error {
553-
if e != nil {
554-
return e
555-
}
556-
557-
if !d.Type().IsRegular() {
558-
return nil
559-
}
560-
558+
addFileEntry := func(path string) {
561559
fileName := lsconv.FileNameToDocumentURI(path)
562560
ranges := groupedRanges.Get(fileName)
563561
if len(ranges) == 0 {
564-
return nil
562+
return
565563
}
566564

567565
content, ok := f.textOfFile(path)
568566
if !ok {
569-
// !!! error?
570-
return nil
567+
return
571568
}
572569

573570
if options.marker != nil && options.marker.FileName() == path {
@@ -579,17 +576,34 @@ func (f *FourslashTest) getBaselineForGroupedSpansWithFileContents(groupedRanges
579576
}
580577

581578
baselineEntries = append(baselineEntries, f.getBaselineContentForFile(path, content, ranges, spanToContextId, options))
582-
return nil
583579
}
580+
walkDirFn := func(path string, d vfs.DirEntry, e error) error {
581+
if e != nil {
582+
return e
583+
}
584+
585+
if !d.Type().IsRegular() {
586+
return nil
587+
}
584588

585-
err := f.vfs.WalkDir("/", walkDirFn)
586-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
587-
panic("walkdir error during fourslash baseline: " + err.Error())
589+
addFileEntry(path)
590+
return nil
588591
}
589592

590-
err = f.vfs.WalkDir("bundled:///", walkDirFn)
591-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
592-
panic("walkdir error during fourslash baseline: " + err.Error())
593+
if options.preserveResultOrder {
594+
for _, uri := range options.orderedFiles {
595+
addFileEntry(uri.FileName())
596+
}
597+
} else {
598+
err := f.vfs.WalkDir("/", walkDirFn)
599+
if err != nil && !errors.Is(err, fs.ErrNotExist) {
600+
panic("walkdir error during fourslash baseline: " + err.Error())
601+
}
602+
603+
err = f.vfs.WalkDir("bundled:///", walkDirFn)
604+
if err != nil && !errors.Is(err, fs.ErrNotExist) {
605+
panic("walkdir error during fourslash baseline: " + err.Error())
606+
}
593607
}
594608

595609
// In Strada, there is a bug where we only ever add additional spans to baselines if we haven't
@@ -620,6 +634,22 @@ func (f *FourslashTest) getBaselineForGroupedSpansWithFileContents(groupedRanges
620634
return strings.Join(baselineEntries, "\n\n")
621635
}
622636

637+
func uniqueFilesInSpanOrder(spans []documentSpan) []lsproto.DocumentUri {
638+
if len(spans) == 0 {
639+
return nil
640+
}
641+
seen := map[lsproto.DocumentUri]struct{}{}
642+
result := make([]lsproto.DocumentUri, 0, len(spans))
643+
for _, span := range spans {
644+
if _, ok := seen[span.uri]; ok {
645+
continue
646+
}
647+
seen[span.uri] = struct{}{}
648+
result = append(result, span.uri)
649+
}
650+
return result
651+
}
652+
623653
func (f *FourslashTest) textOfFile(fileName string) (string, bool) {
624654
if _, ok := f.openFiles[fileName]; ok {
625655
return f.getScriptInfo(fileName).content, true

0 commit comments

Comments
 (0)