diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 7939f87efc..0b69261644 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -10983,7 +10983,7 @@ func (node *SourceFile) computeDeclarationMap() map[string][]*Node { result := make(map[string][]*Node) addDeclaration := func(declaration *Node) { - name := getDeclarationName(declaration) + name := GetDeclarationName(declaration) if name != "" { result[name] = append(result[name], declaration) } @@ -10993,7 +10993,7 @@ func (node *SourceFile) computeDeclarationMap() map[string][]*Node { visit = func(node *Node) bool { switch node.Kind { case KindFunctionDeclaration, KindFunctionExpression, KindMethodDeclaration, KindMethodSignature: - declarationName := getDeclarationName(node) + declarationName := GetDeclarationName(node) if declarationName != "" { declarations := result[declarationName] var lastDeclaration *Node @@ -11025,7 +11025,7 @@ func (node *SourceFile) computeDeclarationMap() map[string][]*Node { break } fallthrough - case KindVariableDeclaration, KindBindingElement: + case KindVariableDeclaration, KindBindingElement, KindCommonJSExport: name := node.Name() if name != nil { if IsBindingPattern(name) { @@ -11074,6 +11074,12 @@ func (node *SourceFile) computeDeclarationMap() map[string][]*Node { } } } + case KindBinaryExpression: + switch GetAssignmentDeclarationKind(node.AsBinaryExpression()) { + case JSDeclarationKindThisProperty, JSDeclarationKindProperty: + addDeclaration(node) + } + node.ForEachChild(visit) default: node.ForEachChild(visit) } @@ -11083,7 +11089,7 @@ func (node *SourceFile) computeDeclarationMap() map[string][]*Node { return result } -func getDeclarationName(declaration *Node) string { +func GetDeclarationName(declaration *Node) string { name := GetNonAssignedNameOfDeclaration(declaration) if name != nil { if IsComputedPropertyName(name) { diff --git a/internal/compiler/program.go b/internal/compiler/program.go index f773a45e59..f25efc404d 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -68,6 +68,10 @@ type Program struct { // Cached unresolved imports for ATA unresolvedImportsOnce sync.Once unresolvedImports *collections.Set[string] + + // Used by workspace/symbol + hasTSFileOnce sync.Once + hasTSFile bool } // FileExists implements checker.Program. @@ -1634,6 +1638,23 @@ func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmi return sourceFileMayBeEmitted(sourceFile, p, forceDtsEmit) } +func (p *Program) IsLibFile(sourceFile *ast.SourceFile) bool { + _, ok := p.libFiles[sourceFile.Path()] + return ok +} + +func (p *Program) HasTSFile() bool { + p.hasTSFileOnce.Do(func() { + for _, file := range p.files { + if tspath.HasImplementationTSFileExtension(file.FileName()) { + p.hasTSFile = true + break + } + } + }) + return p.hasTSFile +} + var plainJSErrors = collections.NewSetFromItems( // binder errors diagnostics.Cannot_redeclare_block_scoped_variable_0.Code(), diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index 3573bec579..d4dc96c952 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -137,7 +137,7 @@ function getTestInput(content: string): string { /** * Parses a Strada fourslash statement and returns the corresponding Corsa commands. - * @returns an array of commands if the statement is a valid fourslash command, or `false` if the statement could not be parsed. + * @returns an array of commands if the statement is a valid fourslash command, or `undefined` if the statement could not be parsed. */ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { if (ts.isVariableStatement(statement)) { @@ -222,6 +222,8 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { case "baselineSyntacticDiagnostics": case "baselineSyntacticAndSemanticDiagnostics": return [{ kind: "verifyBaselineDiagnostics" }]; + case "navigateTo": + return parseVerifyNavigateTo(callExpression.arguments); } } // `goTo....` @@ -1469,29 +1471,9 @@ function parseBaselineMarkerOrRangeArg(arg: ts.Expression): string | undefined { return getGoStringLiteral(arg.text); } else if (ts.isIdentifier(arg) || (ts.isElementAccessExpression(arg) && ts.isIdentifier(arg.expression))) { - const argName = ts.isIdentifier(arg) ? arg.text : (arg.expression as ts.Identifier).text; - const file = arg.getSourceFile(); - const varStmts = file.statements.filter(ts.isVariableStatement); - for (const varStmt of varStmts) { - for (const decl of varStmt.declarationList.declarations) { - if (ts.isArrayBindingPattern(decl.name) && decl.initializer?.getText().includes("ranges")) { - for (let i = 0; i < decl.name.elements.length; i++) { - const elem = decl.name.elements[i]; - if (ts.isBindingElement(elem) && ts.isIdentifier(elem.name) && elem.name.text === argName) { - // `const [range_0, ..., range_n, ...] = test.ranges();` and arg is `range_n` - if (elem.dotDotDotToken === undefined) { - return `f.Ranges()[${i}]`; - } - // `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest[n]` - if (ts.isElementAccessExpression(arg)) { - return `f.Ranges()[${i + parseInt(arg.argumentExpression!.getText())}]`; - } - // `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest` - return `ToAny(f.Ranges()[${i}:])...`; - } - } - } - } + const result = parseRangeVariable(arg); + if (result) { + return result; } const init = getNodeOfKind(arg, ts.isCallExpression); if (init) { @@ -1507,7 +1489,35 @@ function parseBaselineMarkerOrRangeArg(arg: ts.Expression): string | undefined { return result; } } - console.error(`Unrecognized range argument: ${arg.getText()}`); + console.error(`Unrecognized argument in verify.baselineRename: ${arg.getText()}`); + return undefined; +} + +function parseRangeVariable(arg: ts.Identifier | ts.ElementAccessExpression): string | undefined { + const argName = ts.isIdentifier(arg) ? arg.text : (arg.expression as ts.Identifier).text; + const file = arg.getSourceFile(); + const varStmts = file.statements.filter(ts.isVariableStatement); + for (const varStmt of varStmts) { + for (const decl of varStmt.declarationList.declarations) { + if (ts.isArrayBindingPattern(decl.name) && decl.initializer?.getText().includes("ranges")) { + for (let i = 0; i < decl.name.elements.length; i++) { + const elem = decl.name.elements[i]; + if (ts.isBindingElement(elem) && ts.isIdentifier(elem.name) && elem.name.text === argName) { + // `const [range_0, ..., range_n, ...] = test.ranges();` and arg is `range_n` + if (elem.dotDotDotToken === undefined) { + return `f.Ranges()[${i}]`; + } + // `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest[n]` + if (ts.isElementAccessExpression(arg)) { + return `f.Ranges()[${i + parseInt(arg.argumentExpression!.getText())}]`; + } + // `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest` + return `ToAny(f.Ranges()[${i}:])...`; + } + } + } + } + } return undefined; } @@ -1794,6 +1804,212 @@ function parseSortText(expr: ts.Expression): string | undefined { } } +function parseVerifyNavigateTo(args: ts.NodeArray): [VerifyNavToCmd] | undefined { + const goArgs = []; + for (const arg of args) { + const result = parseVerifyNavigateToArg(arg); + if (!result) { + return undefined; + } + goArgs.push(result); + } + return [{ + kind: "verifyNavigateTo", + args: goArgs, + }]; +} + +function parseVerifyNavigateToArg(arg: ts.Expression): string | undefined { + if (!ts.isObjectLiteralExpression(arg)) { + console.error(`Expected object literal expression for verify.navigateTo argument, got ${arg.getText()}`); + return undefined; + } + let prefs; + const items = []; + let pattern: string | undefined; + for (const prop of arg.properties) { + if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) { + console.error(`Expected property assignment with identifier name for verify.navigateTo argument, got ${prop.getText()}`); + return undefined; + } + const propName = prop.name.text; + switch (propName) { + case "pattern": { + let patternInit = getStringLiteralLike(prop.initializer); + if (!patternInit) { + console.error(`Expected string literal for pattern in verify.navigateTo argument, got ${prop.initializer.getText()}`); + return undefined; + } + pattern = getGoStringLiteral(patternInit.text); + break; + } + case "fileName": + // no longer supported + continue; + case "expected": { + const init = prop.initializer; + if (!ts.isArrayLiteralExpression(init)) { + console.error(`Expected array literal expression for expected property in verify.navigateTo argument, got ${init.getText()}`); + return undefined; + } + for (const elem of init.elements) { + const result = parseNavToItem(elem); + if (!result) { + return undefined; + } + items.push(result); + } + break; + } + case "excludeLibFiles": { + if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) { + prefs = `&lsutil.UserPreferences{ExcludeLibrarySymbolsInNavTo: false}`; + } + } + } + } + if (!prefs) { + prefs = "nil"; + } + return `{ + Pattern: ${pattern ? pattern : '""'}, + Preferences: ${prefs}, + Exact: PtrTo([]*lsproto.SymbolInformation{${items.length ? items.join(",\n") + ",\n" : ""}}), + }`; +} + +function parseNavToItem(arg: ts.Expression): string | undefined { + let item = getNodeOfKind(arg, ts.isObjectLiteralExpression); + if (!item) { + console.error(`Expected object literal expression for navigateTo item, got ${arg.getText()}`); + return undefined; + } + const itemProps: string[] = []; + for (const prop of item.properties) { + if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) { + console.error(`Expected property assignment with identifier name for navigateTo item, got ${prop.getText()}`); + return undefined; + } + const propName = prop.name.text; + const init = prop.initializer; + switch (propName) { + case "name": { + let nameInit; + if (!(nameInit = getStringLiteralLike(init))) { + console.error(`Expected string literal for name in navigateTo item, got ${init.getText()}`); + return undefined; + } + itemProps.push(`Name: ${getGoStringLiteral(nameInit.text)}`); + break; + } + case "kind": { + const goKind = getSymbolKind(init); + if (!goKind) { + return undefined; + } + itemProps.push(`Kind: lsproto.${goKind}`); + break; + } + case "kindModifiers": { + if (init.getText().includes("deprecated")) { + itemProps.push(`Tags: &[]lsproto.SymbolTag{lsproto.SymbolTagDeprecated}`); + } + break; + } + case "range": { + if (ts.isIdentifier(init) || (ts.isElementAccessExpression(init) && ts.isIdentifier(init.expression))) { + let parsedRange = parseRangeVariable(init); + if (parsedRange) { + itemProps.push(`Location: ${parsedRange}.LSLocation()`); + continue; + } + } + if (ts.isElementAccessExpression(init) && init.expression.getText() === "test.ranges()") { + itemProps.push(`Location: f.Ranges()[${parseInt(init.argumentExpression.getText())}].LSLocation()`); + continue; + } + console.error(`Expected range variable for range in navigateTo item, got ${init.getText()}`); + return undefined; + } + case "containerName": { + let nameInit; + if (!(nameInit = getStringLiteralLike(init))) { + console.error(`Expected string literal for container name in navigateTo item, got ${init.getText()}`); + return undefined; + } + itemProps.push(`ContainerName: PtrTo(${getGoStringLiteral(nameInit.text)})`); + break; + } + default: + // ignore other properties + } + } + return `{\n${itemProps.join(",\n")},\n}`; +} + +function getSymbolKind(kind: ts.Expression): string | undefined { + let result; + if (!(result = getStringLiteralLike(kind))) { + console.error(`Expected string literal for symbol kind, got ${kind.getText()}`); + return undefined; + } + switch (result.text) { + case "script": + return "SymbolKindFile"; + case "module": + return "SymbolKindModule"; + case "class": + case "local class": + return "SymbolKindClass"; + case "interface": + return "SymbolKindInterface"; + case "type": + return "SymbolKindClass"; + case "enum": + return "SymbolKindEnum"; + case "enum member": + return "SymbolKindEnumMember"; + case "var": + case "local var": + case "using": + case "await using": + return "SymbolKindVariable"; + case "function": + case "local function": + return "SymbolKindFunction"; + case "method": + return "SymbolKindMethod"; + case "getter": + case "setter": + case "property": + case "accessor": + return "SymbolKindProperty"; + case "constructor": + case "construct": + return "SymbolKindConstructor"; + case "call": + case "index": + return "SymbolKindFunction"; + case "parameter": + return "SymbolKindVariable"; + case "type parameter": + return "SymbolKindTypeParameter"; + case "primitive type": + return "SymbolKindObject"; + case "const": + case "let": + return "SymbolKindVariable"; + case "directory": + return "SymbolKindPackage"; + case "external module name": + return "SymbolKindModule"; + case "string": + return "SymbolKindString"; + default: + return "SymbolKindVariable"; + } +} + interface VerifyCompletionsCmd { kind: "verifyCompletions"; marker: string; @@ -1905,6 +2121,11 @@ interface VerifyBaselineDiagnosticsCmd { kind: "verifyBaselineDiagnostics"; } +interface VerifyNavToCmd { + kind: "verifyNavigateTo"; + args: string[]; +} + type Cmd = | VerifyCompletionsCmd | VerifyApplyCodeActionFromCompletionCmd @@ -1919,6 +2140,7 @@ type Cmd = | VerifyQuickInfoCmd | VerifyBaselineRenameCmd | VerifyRenameInfoCmd + | VerifyNavToCmd | VerifyBaselineInlayHintsCmd | VerifyImportFixAtPositionCmd | VerifyDiagnosticsCmd @@ -2036,6 +2258,10 @@ function generateImportFixAtPosition({ expectedTexts, preferences }: VerifyImpor return `f.VerifyImportFixAtPosition(t, []string{\n${expectedTexts.join(",\n")},\n}, ${preferences})`; } +function generateNavigateTo({ args }: VerifyNavToCmd): string { + return `f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{\n${args.join(", ")}})`; +} + function generateCmd(cmd: Cmd): string { switch (cmd.kind) { case "verifyCompletions": @@ -2082,6 +2308,8 @@ function generateCmd(cmd: Cmd): string { return `f.${funcName}(t, ${cmd.arg})`; case "verifyBaselineDiagnostics": return `f.VerifyBaselineNonSuggestionDiagnostics(t)`; + case "verifyNavigateTo": + return generateNavigateTo(cmd); default: let neverCommand: never = cmd; throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`); diff --git a/internal/fourslash/_scripts/manualTests.txt b/internal/fourslash/_scripts/manualTests.txt index bc1045b9eb..1dfd6557eb 100644 --- a/internal/fourslash/_scripts/manualTests.txt +++ b/internal/fourslash/_scripts/manualTests.txt @@ -2,6 +2,12 @@ completionListInClosedFunction05 completionsAtIncompleteObjectLiteralProperty completionsSelfDeclaring1 completionsWithDeprecatedTag4 +navigationItemsExactMatch2 +navigationItemsSpecialPropertyAssignment +navto_excludeLib1 +navto_excludeLib2 +navto_excludeLib4 +navto_serverExcludeLib parserCorruptionAfterMapInClass quickInfoForOverloadOnConst1 renameDefaultKeyword diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 2eb95dd18b..90a701b36c 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -2422,7 +2422,7 @@ func (f *FourslashTest) verifyBaselines(t *testing.T, testPath string) { func (f *FourslashTest) VerifyBaselineInlayHints( t *testing.T, span *lsproto.Range, - userPreferences *lsutil.UserPreferences, + testPreferences *lsutil.UserPreferences, ) { fileName := f.activeFilename var lspRange lsproto.Range @@ -2437,10 +2437,12 @@ func (f *FourslashTest) VerifyBaselineInlayHints( Range: lspRange, } - if userPreferences != nil { - reset := f.ConfigureWithReset(t, userPreferences) - defer reset() + preferences := testPreferences + if preferences == nil { + preferences = lsutil.NewDefaultUserPreferences() } + reset := f.ConfigureWithReset(t, preferences) + defer reset() prefix := fmt.Sprintf("At position (Ln %d, Col %d): ", lspRange.Start.Line, lspRange.Start.Character) result := sendRequest(t, f, lsproto.TextDocumentInlayHintInfo, params) @@ -2747,3 +2749,76 @@ func (f *FourslashTest) VerifyBaselineGoToImplementation(t *testing.T, markerNam markerNames..., ) } + +type VerifyWorkspaceSymbolCase struct { + Pattern string + Includes *[]*lsproto.SymbolInformation + Exact *[]*lsproto.SymbolInformation + Preferences *lsutil.UserPreferences +} + +// `verify.navigateTo` in Strada. +func (f *FourslashTest) VerifyWorkspaceSymbol(t *testing.T, cases []*VerifyWorkspaceSymbolCase) { + originalPreferences := f.userPreferences.Copy() + for _, testCase := range cases { + preferences := testCase.Preferences + if preferences == nil { + preferences = lsutil.NewDefaultUserPreferences() + } + f.Configure(t, preferences) + result := sendRequest(t, f, lsproto.WorkspaceSymbolInfo, &lsproto.WorkspaceSymbolParams{Query: testCase.Pattern}) + if result.SymbolInformations == nil { + t.Fatalf("Expected non-nil symbol information array from workspace symbol request") + } + if testCase.Includes != nil { + if testCase.Exact != nil { + t.Fatalf("Test case cannot have both 'Includes' and 'Exact' fields set") + } + verifyIncludesSymbols(t, *result.SymbolInformations, *testCase.Includes, "Workspace symbols mismatch with pattern '"+testCase.Pattern+"'") + } else { + if testCase.Exact == nil { + t.Fatalf("Test case must have either 'Includes' or 'Exact' field set") + } + verifyExactSymbols(t, *result.SymbolInformations, *testCase.Exact, "Workspace symbols mismatch with pattern '"+testCase.Pattern+"'") + } + } + f.Configure(t, originalPreferences) +} + +func verifyExactSymbols( + t *testing.T, + actual []*lsproto.SymbolInformation, + expected []*lsproto.SymbolInformation, + prefix string, +) { + if len(actual) != len(expected) { + t.Fatalf("%s: Expected %d symbols, but got %d:\n%s", prefix, len(expected), len(actual), cmp.Diff(actual, expected)) + } + for i := range actual { + assertDeepEqual(t, actual[i], expected[i], prefix) + } +} + +func verifyIncludesSymbols( + t *testing.T, + actual []*lsproto.SymbolInformation, + includes []*lsproto.SymbolInformation, + prefix string, +) { + type key struct { + name string + loc lsproto.Location + } + nameAndLocToActualSymbol := make(map[key]*lsproto.SymbolInformation, len(actual)) + for _, sym := range actual { + nameAndLocToActualSymbol[key{name: sym.Name, loc: sym.Location}] = sym + } + + for _, sym := range includes { + actualSym, ok := nameAndLocToActualSymbol[key{name: sym.Name, loc: sym.Location}] + if !ok { + t.Fatalf("%s: Expected symbol '%s' at location '%v' not found", prefix, sym.Name, sym.Location) + } + assertDeepEqual(t, actualSym, sym, fmt.Sprintf("%s: Symbol '%s' at location '%v' mismatch", prefix, sym.Name, sym.Location)) + } +} diff --git a/internal/fourslash/test_parser.go b/internal/fourslash/test_parser.go index a0710ea1ec..4187d1c8ce 100644 --- a/internal/fourslash/test_parser.go +++ b/internal/fourslash/test_parser.go @@ -44,6 +44,13 @@ func (r *RangeMarker) GetName() *string { return r.Marker.Name } +func (r *RangeMarker) LSLocation() lsproto.Location { + return lsproto.Location{ + Uri: lsconv.FileNameToDocumentURI(r.fileName), + Range: r.LSRange, + } +} + type Marker struct { fileName string Position int diff --git a/internal/fourslash/tests/gen/declareFunction_test.go b/internal/fourslash/tests/gen/declareFunction_test.go new file mode 100644 index 0000000000..84d8b17e64 --- /dev/null +++ b/internal/fourslash/tests/gen/declareFunction_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDeclareFunction(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @filename: index.ts +declare function` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{}), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navigateItemsLet_test.go b/internal/fourslash/tests/gen/navigateItemsLet_test.go new file mode 100644 index 0000000000..b9ee83d2e0 --- /dev/null +++ b/internal/fourslash/tests/gen/navigateItemsLet_test.go @@ -0,0 +1,46 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigateItemsLet(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @noLib: true +let [|c = 10|]; +function foo() { + let [|d = 10|]; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "c", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "c", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, { + Pattern: "d", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "d", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[1].LSLocation(), + ContainerName: PtrTo("foo"), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navigateToImport_test.go b/internal/fourslash/tests/gen/navigateToImport_test.go new file mode 100644 index 0000000000..bd96c10a74 --- /dev/null +++ b/internal/fourslash/tests/gen/navigateToImport_test.go @@ -0,0 +1,55 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigateToImport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: library.ts +[|export function foo() {}|] +[|export function bar() {}|] +// @Filename: user.ts +import {foo, [|bar as baz|]} from './library';` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "foo", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "foo", + Kind: lsproto.SymbolKindFunction, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, { + Pattern: "bar", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "bar", + Kind: lsproto.SymbolKindFunction, + Location: f.Ranges()[1].LSLocation(), + }, + }), + }, { + Pattern: "baz", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "baz", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[2].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navigateToSymbolIterator_test.go b/internal/fourslash/tests/gen/navigateToSymbolIterator_test.go new file mode 100644 index 0000000000..0ec211a53a --- /dev/null +++ b/internal/fourslash/tests/gen/navigateToSymbolIterator_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigateToSymbolIterator(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class C { + [|[Symbol.iterator]() {}|] +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "iterator", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "iterator", + Kind: lsproto.SymbolKindMethod, + Location: f.Ranges()[0].LSLocation(), + ContainerName: PtrTo("C"), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navigationItemsInConstructorsExactMatch_test.go b/internal/fourslash/tests/gen/navigationItemsInConstructorsExactMatch_test.go new file mode 100644 index 0000000000..bf5a3d8940 --- /dev/null +++ b/internal/fourslash/tests/gen/navigationItemsInConstructorsExactMatch_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigationItemsInConstructorsExactMatch(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @noLib: true +class Test { + [|private search1: number;|] + constructor([|public search2: boolean|], [|readonly search3: string|], search4: string) { + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "search", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "search1", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[0].LSLocation(), + ContainerName: PtrTo("Test"), + }, + { + Name: "search2", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[1].LSLocation(), + ContainerName: PtrTo("Test"), + }, + { + Name: "search3", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[2].LSLocation(), + ContainerName: PtrTo("Test"), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navigationItemsPrefixMatch2_test.go b/internal/fourslash/tests/gen/navigationItemsPrefixMatch2_test.go new file mode 100644 index 0000000000..aed3cc0848 --- /dev/null +++ b/internal/fourslash/tests/gen/navigationItemsPrefixMatch2_test.go @@ -0,0 +1,94 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigationItemsPrefixMatch2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module Shapes { + export class Point { + [|private originality = 0.0;|] + [|private distanceFromOrig = 0.0;|] + [|get distanceFarFarAway(distanceFarFarAwayParam: number): number { + var [|distanceFarFarAwayLocal|]; + return 0; + }|] + } +} +var pointsSquareBox = new Shapes.Point(); +function PointsFunc(): void { + var pointFuncLocal; +} +[|interface OriginI { + 123; + [|origin1;|] + [|public _distance(distanceParam): void;|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "origin", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "origin1", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[5].LSLocation(), + ContainerName: PtrTo("OriginI"), + }, + { + Name: "originality", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[0].LSLocation(), + ContainerName: PtrTo("Point"), + }, + { + Name: "OriginI", + Kind: lsproto.SymbolKindInterface, + Location: f.Ranges()[4].LSLocation(), + }, + }), + }, { + Pattern: "distance", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "distanceFarFarAway", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[2].LSLocation(), + ContainerName: PtrTo("Point"), + }, + { + Name: "distanceFarFarAwayLocal", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[3].LSLocation(), + ContainerName: PtrTo("distanceFarFarAway"), + }, + { + Name: "distanceFromOrig", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[1].LSLocation(), + ContainerName: PtrTo("Point"), + }, + { + Name: "_distance", + Kind: lsproto.SymbolKindMethod, + Location: f.Ranges()[6].LSLocation(), + ContainerName: PtrTo("OriginI"), + }, + }), + }, { + Pattern: "mPointThatIJustInitiated wrongKeyWord", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{}), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navto_emptyPattern_test.go b/internal/fourslash/tests/gen/navto_emptyPattern_test.go new file mode 100644 index 0000000000..5c3702e9e2 --- /dev/null +++ b/internal/fourslash/tests/gen/navto_emptyPattern_test.go @@ -0,0 +1,38 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavto_emptyPattern(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @filename: foo.ts +const [|x: number = 1|]; +[|function y(x: string): string { return x; }|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "x", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + { + Name: "y", + Kind: lsproto.SymbolKindFunction, + Location: f.Ranges()[1].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/gen/navto_excludeLib3_test.go b/internal/fourslash/tests/gen/navto_excludeLib3_test.go new file mode 100644 index 0000000000..7fbae17d69 --- /dev/null +++ b/internal/fourslash/tests/gen/navto_excludeLib3_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavto_excludeLib3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @filename: /index.ts +[|function parseInt(s: string): number {}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "parseInt", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "parseInt", + Kind: lsproto.SymbolKindFunction, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/manual/navigationItemsExactMatch2_test.go b/internal/fourslash/tests/manual/navigationItemsExactMatch2_test.go new file mode 100644 index 0000000000..fc19b34f6c --- /dev/null +++ b/internal/fourslash/tests/manual/navigationItemsExactMatch2_test.go @@ -0,0 +1,101 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigationItemsExactMatch2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module Shapes { + [|class Point { + [|private _origin = 0.0;|] + [|private distanceFromA = 0.0;|] + + [|get distance1(distanceParam): number { + var [|distanceLocal|]; + return 0; + }|] + }|] +} + +var [|point = new Shapes.Point()|]; +[|function distance2(distanceParam1): void { + var [|distanceLocal1|]; +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "point", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "Point", + Kind: lsproto.SymbolKindClass, + Location: f.Ranges()[0].LSLocation(), + ContainerName: PtrTo("Shapes"), + }, + { + Name: "point", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[5].LSLocation(), + }, + }), + }, { + Pattern: "distance", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "distance1", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[3].LSLocation(), + ContainerName: PtrTo("Point"), + }, + { + Name: "distance2", + Kind: lsproto.SymbolKindFunction, + Location: f.Ranges()[6].LSLocation(), + }, + { + Name: "distanceFromA", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[2].LSLocation(), + ContainerName: PtrTo("Point"), + }, + { + Name: "distanceLocal", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[4].LSLocation(), + ContainerName: PtrTo("distance1"), + }, + { + Name: "distanceLocal1", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[7].LSLocation(), + ContainerName: PtrTo("distance2"), + }, + }), + }, { + Pattern: "origin", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "_origin", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[1].LSLocation(), + ContainerName: PtrTo("Point"), + }, + }), + }, { + Pattern: "square", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{}), + }, + }) +} diff --git a/internal/fourslash/tests/manual/navigationItemsSpecialPropertyAssignment_test.go b/internal/fourslash/tests/manual/navigationItemsSpecialPropertyAssignment_test.go new file mode 100644 index 0000000000..61608dec68 --- /dev/null +++ b/internal/fourslash/tests/manual/navigationItemsSpecialPropertyAssignment_test.go @@ -0,0 +1,92 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavigationItemsSpecialPropertyAssignment(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @noLib: true +// @allowJs: true +// @Filename: /a.js +[|exports.x = 0|]; +[|exports.z = function() {}|]; +function Cls() { + [|this.instanceProp = 0|]; +} +[|Cls.staticMethod = function() {}|]; +[|Cls.staticProperty = 0|]; +[|Cls.prototype.instanceMethod = function() {}|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "x", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "x", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, { + Pattern: "z", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "z", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[1].LSLocation(), + }, + }), + }, { + Pattern: "instanceProp", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "instanceProp", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[2].LSLocation(), + ContainerName: PtrTo("Cls"), + }, + }), + }, { + Pattern: "staticMethod", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "staticMethod", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[3].LSLocation(), + }, + }), + }, { + Pattern: "staticProperty", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "staticProperty", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[4].LSLocation(), + }, + }), + }, { + Pattern: "instanceMethod", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "instanceMethod", + Kind: lsproto.SymbolKindProperty, + Location: f.Ranges()[5].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/manual/navto_excludeLib1_test.go b/internal/fourslash/tests/manual/navto_excludeLib1_test.go new file mode 100644 index 0000000000..dfb5a9861e --- /dev/null +++ b/internal/fourslash/tests/manual/navto_excludeLib1_test.go @@ -0,0 +1,58 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/ls/lsutil" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavto_excludeLib1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @filename: /index.ts +import { weirdName as otherName } from "bar"; +const [|weirdName: number = 1|]; +// @filename: /tsconfig.json +{} +// @filename: /node_modules/bar/index.d.ts +export const [|weirdName: number|]; +// @filename: /node_modules/bar/package.json +{}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "weirdName", + Preferences: &lsutil.UserPreferences{ExcludeLibrarySymbolsInNavTo: false}, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "weirdName", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + { + Name: "weirdName", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[1].LSLocation(), + }, + }), + }, + }) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "weirdName", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "weirdName", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/fourslash/tests/manual/navto_excludeLib2_test.go b/internal/fourslash/tests/manual/navto_excludeLib2_test.go new file mode 100644 index 0000000000..6d13284b2b --- /dev/null +++ b/internal/fourslash/tests/manual/navto_excludeLib2_test.go @@ -0,0 +1,52 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/ls/lsutil" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestNavto_excludeLib2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @filename: /index.ts +import { [|someName as weirdName|] } from "bar"; +// @filename: /tsconfig.json +{} +// @filename: /node_modules/bar/index.d.ts +export const someName: number; +// @filename: /node_modules/bar/package.json +{}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "weirdName", + Preferences: &lsutil.UserPreferences{ExcludeLibrarySymbolsInNavTo: false}, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "weirdName", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, + }) + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + { + Pattern: "weirdName", + Preferences: nil, + Exact: PtrTo([]*lsproto.SymbolInformation{ + { + Name: "weirdName", + Kind: lsproto.SymbolKindVariable, + Location: f.Ranges()[0].LSLocation(), + }, + }), + }, + }) +} diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index b6ce0c9537..43573d4f8f 100644 --- a/internal/ls/lsutil/userpreferences.go +++ b/internal/ls/lsutil/userpreferences.go @@ -20,6 +20,8 @@ func NewDefaultUserPreferences() *UserPreferences { DisplayPartsForJSDoc: true, DisableLineTextInReferences: true, ReportStyleChecksAsWarnings: true, + + ExcludeLibrarySymbolsInNavTo: true, } } @@ -143,13 +145,16 @@ type UserPreferences struct { IncludeInlayFunctionLikeReturnTypeHints bool IncludeInlayEnumMemberValueHints bool + // ------- Symbols ------- + + ExcludeLibrarySymbolsInNavTo bool + // ------- Misc ------- - ExcludeLibrarySymbolsInNavTo bool // !!! - DisableSuggestions bool // !!! - DisableLineTextInReferences bool // !!! - DisplayPartsForJSDoc bool // !!! - ReportStyleChecksAsWarnings bool // !!! If this changes, we need to ask the client to recompute diagnostics + DisableSuggestions bool // !!! + DisableLineTextInReferences bool // !!! + DisplayPartsForJSDoc bool // !!! + ReportStyleChecksAsWarnings bool // !!! If this changes, we need to ask the client to recompute diagnostics } type JsxAttributeCompletionStyle string @@ -380,6 +385,8 @@ func (p *UserPreferences) parseWorker(config map[string]any) { p.parseSuggest(values) case "preferences": p.parsePreferences(values) + case "workspaceSymbols": + p.parseWorkspaceSymbols(values) case "format": // !!! case "tsserver": @@ -516,6 +523,22 @@ func (p *UserPreferences) parseOrganizeImportsPreferences(prefs any) { } } +func (p *UserPreferences) parseWorkspaceSymbols(prefs any) { + symbolPreferences, ok := prefs.(map[string]any) + if !ok { + return + } + for name, value := range symbolPreferences { + switch name { + // !!! scope + case "excludeLibrarySymbols": + p.ExcludeLibrarySymbolsInNavTo = parseBoolWithDefault(value, true) + default: + p.set(name, value) + } + } +} + func parseEnabledBool(v map[string]any) bool { // vscode nested option if enabled, ok := v["enabled"]; ok { @@ -623,7 +646,7 @@ func (p *UserPreferences) set(name string, value any) { case "includeinlayenummembervaluehints": p.IncludeInlayEnumMemberValueHints = parseBoolWithDefault(value, false) case "excludelibrarysymbolsinnavto": - p.ExcludeLibrarySymbolsInNavTo = parseBoolWithDefault(value, false) + p.ExcludeLibrarySymbolsInNavTo = parseBoolWithDefault(value, true) case "disablesuggestions": p.DisableSuggestions = parseBoolWithDefault(value, false) case "disablelinetextinreferences": diff --git a/internal/ls/symbols.go b/internal/ls/symbols.go index e2614556ad..b45695e703 100644 --- a/internal/ls/symbols.go +++ b/internal/ls/symbols.go @@ -8,9 +8,11 @@ import ( "unicode/utf8" "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls/lsconv" + "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" @@ -238,12 +240,20 @@ type DeclarationInfo struct { matchScore int } -func ProvideWorkspaceSymbols(ctx context.Context, programs []*compiler.Program, converters *lsconv.Converters, query string) (lsproto.WorkspaceSymbolResponse, error) { +func ProvideWorkspaceSymbols( + ctx context.Context, + programs []*compiler.Program, + converters *lsconv.Converters, + preferences *lsutil.UserPreferences, + query string, +) (lsproto.WorkspaceSymbolResponse, error) { + excludeLibrarySymbols := preferences.ExcludeLibrarySymbolsInNavTo // Obtain set of non-declaration source files from all active programs. sourceFiles := map[tspath.Path]*ast.SourceFile{} for _, program := range programs { for _, sourceFile := range program.SourceFiles() { - if !sourceFile.IsDeclarationFile { + if (program.HasTSFile() || !sourceFile.IsDeclarationFile) && + !shouldExcludeFile(sourceFile, program, excludeLibrarySymbols) { sourceFiles[sourceFile.Path()] = sourceFile } } @@ -269,19 +279,33 @@ func ProvideWorkspaceSymbols(ctx context.Context, programs []*compiler.Program, count := min(len(infos), 256) symbols := make([]*lsproto.SymbolInformation, count) for i, info := range infos[0:count] { - node := core.OrElse(ast.GetNameOfDeclaration(info.declaration), info.declaration) + node := info.declaration sourceFile := ast.GetSourceFileOfNode(node) - pos := scanner.SkipTrivia(sourceFile.Text(), node.Pos()) + pos := astnav.GetStartOfNode(node, sourceFile, false /*includeJsDoc*/) + container := getContainerNode(info.declaration) + var containerName *string + if container != nil { + containerName = strPtrTo(ast.GetDeclarationName(container)) + } var symbol lsproto.SymbolInformation symbol.Name = info.name symbol.Kind = getSymbolKindFromNode(info.declaration) symbol.Location = converters.ToLSPLocation(sourceFile, core.NewTextRange(pos, node.End())) + symbol.ContainerName = containerName symbols[i] = &symbol } return lsproto.SymbolInformationsOrWorkspaceSymbolsOrNull{SymbolInformations: &symbols}, nil } +func shouldExcludeFile(file *ast.SourceFile, program *compiler.Program, excludeLibrarySymbols bool) bool { + return excludeLibrarySymbols && (isInsideNodeModules(file.FileName()) || program.IsLibFile(file)) +} + +func isInsideNodeModules(fileName string) bool { + return strings.Contains(fileName, "/node_modules/") +} + // Return a score for matching `s` against `pattern`. In order to match, `s` must contain each of the characters in // `pattern` in the same order. Upper case characters in `pattern` must match exactly, whereas lower case characters // in `pattern` match either case in `s`. If `s` doesn't match, -1 is returned. Otherwise, the returned score is the @@ -348,6 +372,16 @@ func getSymbolKindFromNode(node *ast.Node) lsproto.SymbolKind { return lsproto.SymbolKindEnumMember case ast.KindTypeParameter: return lsproto.SymbolKindTypeParameter + case ast.KindParameter: + if ast.HasSyntacticModifier(node, ast.ModifierFlagsParameterPropertyModifier) { + return lsproto.SymbolKindProperty + } + return lsproto.SymbolKindVariable + case ast.KindBinaryExpression: + switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) { + case ast.JSDeclarationKindThisProperty, ast.JSDeclarationKindProperty: + return lsproto.SymbolKindProperty + } } return lsproto.SymbolKindVariable } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index e076bb27d4..55fe97d68d 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -1210,7 +1210,12 @@ func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.Work defer s.recover(reqMsg) programs := core.Map(snapshot.ProjectCollection.Projects(), (*project.Project).GetProgram) - return ls.ProvideWorkspaceSymbols(ctx, programs, snapshot.Converters(), params.Query) + return ls.ProvideWorkspaceSymbols( + ctx, + programs, + snapshot.Converters(), + snapshot.UserPreferences(), + params.Query) } func (s *Server) handleDocumentSymbol(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentSymbolParams) (lsproto.DocumentSymbolResponse, error) { diff --git a/testdata/baselines/reference/fourslash/state/declarationMapsWorkspaceSymbols.baseline b/testdata/baselines/reference/fourslash/state/declarationMapsWorkspaceSymbols.baseline index 27ef54e300..27108ed47a 100644 --- a/testdata/baselines/reference/fourslash/state/declarationMapsWorkspaceSymbols.baseline +++ b/testdata/baselines/reference/fourslash/state/declarationMapsWorkspaceSymbols.baseline @@ -144,24 +144,24 @@ Config:: // === workspaceSymbol === // === /a/a.ts === -// export function [|{| name: fnA, kind: Function |}fnA|]() {} +// [|{| name: fnA, kind: Function |}export function fnA() {}|] // export interface IfaceA {} // export const instanceA: IfaceA = {}; // === /b/b.ts === -// export function [|{| name: fnB, kind: Function |}fnB|]() {} +// [|{| name: fnB, kind: Function |}export function fnB() {}|] // === /b/c.ts === -// export function [|{| name: fnC, kind: Function |}fnC|]() {} +// [|{| name: fnC, kind: Function |}export function fnC() {}|] // === /user/user.ts === // import * as a from "../a/a"; // import * as b from "../b/b"; -// export function [|{| name: fnUser, kind: Function |}fnUser|]() { +// [|{| name: fnUser, kind: Function |}export function fnUser() { // a.fnA(); // b.fnB(); // a.instanceA; -// } +// }|] { "method": "textDocument/didClose", "params": {