Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/fourslash/test_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const (

func parseFileContent(fileName string, content string, fileOptions map[string]string) (*testFileWithMarkers, error) {
fileName = tspath.GetNormalizedAbsolutePath(fileName, "/")
content = chompLeadingSpace(content)

// The file content (minus metacharacters) so far
var output strings.Builder
Expand Down Expand Up @@ -466,6 +467,23 @@ func reportError(fileName string, line int, col int, message string) error {
return &fourslashError{fmt.Sprintf("%v (%v,%v): %v", fileName, line, col, message)}
}

func chompLeadingSpace(content string) string {
lines := strings.Split(content, "\n")
for _, line := range lines {
if len(line) > 0 && line[0] != ' ' {
return content
}
}

result := make([]string, len(lines))
for i, line := range lines {
if len(line) > 0 {
result[i] = line[1:]
}
}
return strings.Join(result, "\n")
}

type fourslashError struct {
err string
}
Expand Down
54 changes: 42 additions & 12 deletions internal/ls/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/ls/lsconv"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/scanner"
)
Expand All @@ -22,24 +23,31 @@ func (l *LanguageService) ProvideDefinition(
clientSupportsLink := caps.TextDocument.Definition.LinkSupport

program, file := l.getProgramAndFile(documentURI)
node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position)))
pos := int(l.converters.LineAndCharacterToPosition(file, position))
node := astnav.GetTouchingPropertyName(file, pos)
reference := getReferenceAtPosition(file, pos, program)

if node.Kind == ast.KindSourceFile {
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil
}

originSelectionRange := l.createLspRangeFromNode(node, file)
if reference != nil && reference.file != nil {
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{}, reference), nil
}

c, done := program.GetTypeCheckerForFile(ctx, file)
defer done()

if node.Kind == ast.KindOverrideKeyword {
if sym := getSymbolForOverriddenMember(c, node); sym != nil {
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, sym.Declarations), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, sym.Declarations, nil /*reference*/), nil
}
}

if ast.IsJumpStatementTarget(node) {
if label := getTargetLabel(node.Parent, node.Text()); label != nil {
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, []*ast.Node{label}), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{label}, nil /*reference*/), nil
}
}

Expand All @@ -52,7 +60,7 @@ func (l *LanguageService) ProvideDefinition(

if node.Kind == ast.KindReturnKeyword || node.Kind == ast.KindYieldKeyword || node.Kind == ast.KindAwaitKeyword {
if fn := ast.FindAncestor(node, ast.IsFunctionLikeDeclaration); fn != nil {
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, []*ast.Node{fn}), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{fn}, nil /*reference*/), nil
}
}

Expand All @@ -63,7 +71,7 @@ func (l *LanguageService) ProvideDefinition(
nonFunctionDeclarations := core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return !ast.IsFunctionLike(node) })
declarations = append(nonFunctionDeclarations, calledDeclaration)
}
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, declarations), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, reference), nil
}

func (l *LanguageService) ProvideTypeDefinition(
Expand Down Expand Up @@ -93,10 +101,10 @@ func (l *LanguageService) ProvideTypeDefinition(
declarations = core.Concatenate(getDeclarationsFromType(typeArgument), declarations)
}
if len(declarations) != 0 {
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, declarations), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, nil /*reference*/), nil
}
if symbol.Flags&ast.SymbolFlagsValue == 0 && symbol.Flags&ast.SymbolFlagsType != 0 {
return l.createLocationsFromDeclarations(originSelectionRange, clientSupportsLink, symbol.Declarations), nil
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, symbol.Declarations, nil /*reference*/), nil
}
}

Expand All @@ -121,13 +129,34 @@ type fileRange struct {
fileRange core.TextRange
}

func (l *LanguageService) createLocationsFromDeclarations(
func (l *LanguageService) createDefinitionLocations(
originSelectionRange *lsproto.Range,
clientSupportsLink bool,
declarations []*ast.Node,
reference *refInfo,
) lsproto.DefinitionResponse {
locations := make([]*lsproto.LocationLink, 0, len(declarations))
locations := make([]*lsproto.LocationLink, 0)
locationRanges := collections.Set[fileRange]{}

if reference != nil {
targetRange := lsproto.Range{
Start: lsproto.Position{
Line: 0,
Character: 0,
},
End: lsproto.Position{
Line: 0,
Character: 0,
},
}
Comment on lines +142 to +151
Copy link
Member

@DanielRosenwasser DanielRosenwasser Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To minimize some of the diffs, can you do something like this?

l.converters.ToLSPRange(reference.file, reference.file.Loc)

(not sure if file might be nil though)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several differences in this PR are due to handling whitespace at the start of the file, for example

In that case, createLspRangeFromNode or createLspRangeFromRange should take care of those differences.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These diffs are related to missing chompLeadingSpace from the test parser. I’m not sure whether this behavior should be preserved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of failing formatting tests, but they’re all in gen folder, which shouldn’t be modified directly. We probably need to regenerate the tests for these cases. For example, Strada behaviour for some of them,

this test has 4 spaces after ///
https://github.com/microsoft/TypeScript/blob/f3770c9b89ba7969b163c5cea7805d5df23b2086/tests/cases/fourslash/semicolonFormattingInsideAStringLiteral.ts#L3

but the behavior is validated with 3 spaces
https://github.com/microsoft/TypeScript/blob/f3770c9b89ba7969b163c5cea7805d5df23b2086/tests/cases/fourslash/semicolonFormattingInsideAStringLiteral.ts#L9

Anyway, the remaining question is whether we should proceed with or without the chompLeadingSpace helper.

locations = append(locations, &lsproto.LocationLink{
OriginSelectionRange: originSelectionRange,
TargetUri: lsconv.FileNameToDocumentURI(reference.fileName),
TargetRange: targetRange,
TargetSelectionRange: targetRange,
})
}

for _, decl := range declarations {
file := ast.GetSourceFileOfNode(decl)
fileName := file.FileName()
Expand All @@ -146,10 +175,11 @@ func (l *LanguageService) createLocationsFromDeclarations(
})
}
}
if !clientSupportsLink {
return createLocationsFromLinks(locations)

if clientSupportsLink {
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &locations}
}
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &locations}
return createLocationsFromLinks(locations)
}

func createLocationsFromLinks(links []*lsproto.LocationLink) lsproto.DefinitionResponse {
Expand Down
49 changes: 0 additions & 49 deletions internal/ls/findallreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -1450,55 +1450,6 @@ func (l *LanguageService) getReferencedSymbolsForModule(ctx context.Context, pro
return []*SymbolAndEntries{}
}

func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo {
if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil {
if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil {
return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false}
}
return nil
}

if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil {
if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil {
if file := program.GetSourceFile(reference.ResolvedFileName); file != nil {
return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
}
}
return nil
}

if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil {
if file := program.GetLibFileFromReference(libReferenceDirective); file != nil {
return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
}
return nil
}

if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 {
return nil
}

node := astnav.GetTouchingToken(sourceFile, position)
if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) {
return nil
}
if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil {
verifiedFileName := resolution.ResolvedFileName
fileName := resolution.ResolvedFileName
if fileName == "" {
fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text())
}
return &refInfo{
file: program.GetSourceFile(fileName),
fileName: fileName,
reference: nil,
unverified: verifiedFileName != "",
}
}

return nil
}

// -- Core algorithm for find all references --
func getSpecialSearchKind(node *ast.Node) string {
if node == nil {
Expand Down
51 changes: 51 additions & 0 deletions internal/ls/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/microsoft/typescript-go/internal/astnav"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/jsnum"
Expand Down Expand Up @@ -1590,3 +1591,53 @@ func toContextRange(textRange *core.TextRange, contextFile *ast.SourceFile, cont
}
return nil
}

func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo {
if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil {
if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil {
return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false}
}
return nil
}

if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil {
if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil {
if file := program.GetSourceFile(reference.ResolvedFileName); file != nil {
return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
}
}
return nil
}

if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil {
if file := program.GetLibFileFromReference(libReferenceDirective); file != nil {
return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
}
return nil
}

if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 {
return nil
}

node := astnav.GetTouchingToken(sourceFile, position)
if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) {
return nil
}

if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil {
verifiedFileName := resolution.ResolvedFileName
fileName := resolution.ResolvedFileName
if fileName == "" {
fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text())
}
return &refInfo{
file: program.GetSourceFile(fileName),
fileName: fileName,
reference: nil,
unverified: verifiedFileName != "",
}
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@

// === documentHighlights ===
// === /d.ts ===
// /// <reference path="/*HIGHLIGHTS*/[|./a.ts|]" />
// /// <reference path="/*HIGHLIGHTS*/[|./a.ts|]" />
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// const a = require("[|../a|]");

// === /d.ts ===
// /// <reference path="[|./a.ts|]" />
// /// <reference path="[|./a.ts|]" />



Expand All @@ -18,7 +18,7 @@
// const a = require("/*FIND ALL REFS*/[|../a|]");

// === /d.ts ===
// /// <reference path="[|./a.ts|]" />
// /// <reference path="[|./a.ts|]" />



Expand All @@ -30,4 +30,4 @@
// const a = require("[|../a|]");

// === /d.ts ===
// /// <reference path="/*FIND ALL REFS*/[|./a.ts|]" />
// /// <reference path="/*FIND ALL REFS*/[|./a.ts|]" />
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// === findAllReferences ===
// === /importing.ts ===
// import { Leaf } from './exporting';
// Leaf.[|hello|]()
// import { Leaf } from './exporting';
// Leaf.[|hello|]()

// === /leafModule.ts ===
// export const /*FIND ALL REFS*/[|hello|] = () => 'Hello';
Expand All @@ -10,8 +10,8 @@

// === findAllReferences ===
// === /importing.ts ===
// import { Leaf } from './exporting';
// Leaf./*FIND ALL REFS*/[|hello|]()
// import { Leaf } from './exporting';
// Leaf./*FIND ALL REFS*/[|hello|]()

// === /leafModule.ts ===
// export const [|hello|] = () => 'Hello';
Expand All @@ -23,8 +23,8 @@
// export * as /*FIND ALL REFS*/[|Leaf|] from './leafModule';

// === /importing.ts ===
// import { [|Leaf|] } from './exporting';
// [|Leaf|].hello()
// import { [|Leaf|] } from './exporting';
// [|Leaf|].hello()



Expand All @@ -33,8 +33,8 @@
// export * as [|Leaf|] from './leafModule';

// === /importing.ts ===
// import { /*FIND ALL REFS*/[|Leaf|] } from './exporting';
// [|Leaf|].hello()
// import { /*FIND ALL REFS*/[|Leaf|] } from './exporting';
// [|Leaf|].hello()



Expand All @@ -43,5 +43,5 @@
// export * as [|Leaf|] from './leafModule';

// === /importing.ts ===
// import { [|Leaf|] } from './exporting';
// /*FIND ALL REFS*/[|Leaf|].hello()
// import { [|Leaf|] } from './exporting';
// /*FIND ALL REFS*/[|Leaf|].hello()
Loading
Loading