Skip to content

Commit c920059

Browse files
committed
source maps + go to def
1 parent 865ec14 commit c920059

File tree

11 files changed

+526
-16
lines changed

11 files changed

+526
-16
lines changed

internal/api/api.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (api *API) GetSymbolAtPosition(ctx context.Context, projectId Handle[projec
160160
return nil, errors.New("project not found")
161161
}
162162

163-
languageService := ls.NewLanguageService(project, snapshot.Converters())
163+
languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.ReadFile, snapshot.UseCaseSensitiveFileNames())
164164
symbol, err := languageService.GetSymbolAtPosition(ctx, fileName, position)
165165
if err != nil || symbol == nil {
166166
return nil, err
@@ -202,7 +202,7 @@ func (api *API) GetSymbolAtLocation(ctx context.Context, projectId Handle[projec
202202
if node == nil {
203203
return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName())
204204
}
205-
languageService := ls.NewLanguageService(project, snapshot.Converters())
205+
languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.ReadFile, snapshot.UseCaseSensitiveFileNames())
206206
symbol := languageService.GetSymbolAtLocation(ctx, node)
207207
if symbol == nil {
208208
return nil, nil
@@ -232,7 +232,7 @@ func (api *API) GetTypeOfSymbol(ctx context.Context, projectId Handle[project.Pr
232232
if !ok {
233233
return nil, fmt.Errorf("symbol %q not found", symbolHandle)
234234
}
235-
languageService := ls.NewLanguageService(project, snapshot.Converters())
235+
languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.ReadFile, snapshot.UseCaseSensitiveFileNames())
236236
t := languageService.GetTypeOfSymbol(ctx, symbol)
237237
if t == nil {
238238
return nil, nil

internal/core/core.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,3 +648,22 @@ func Deduplicate[T comparable](slice []T) []T {
648648
}
649649
return slice
650650
}
651+
652+
func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
653+
if len(slice) == 0 {
654+
return slice
655+
}
656+
last := slice[0]
657+
deduplicated := slice[:1]
658+
for i := 1; i < len(slice); i++ {
659+
next := slice[i]
660+
if isEqual(last, next) {
661+
continue
662+
}
663+
664+
deduplicated = append(deduplicated, next)
665+
last = next
666+
}
667+
668+
return deduplicated
669+
}

internal/ls/definition.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/microsoft/typescript-go/internal/astnav"
99
"github.com/microsoft/typescript-go/internal/checker"
1010
"github.com/microsoft/typescript-go/internal/core"
11+
"github.com/microsoft/typescript-go/internal/debug"
1112
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
1213
"github.com/microsoft/typescript-go/internal/scanner"
1314
)
@@ -22,28 +23,54 @@ func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsp
2223
c, done := program.GetTypeCheckerForFile(ctx, file)
2324
defer done()
2425

26+
return l.getMappedDefinition(l.provideDefinitions(c, node)), nil
27+
}
28+
29+
func (l *LanguageService) getMappedDefinition(definitions lsproto.DefinitionResponse) lsproto.DefinitionResponse {
30+
if definitions.Location != nil {
31+
definitions.Location = l.getMappedLocation(definitions.Location)
32+
}
33+
if definitions.Locations != nil {
34+
for i, loc := range *definitions.Locations {
35+
(*definitions.Locations)[i] = *l.getMappedLocation(&loc)
36+
}
37+
}
38+
if definitions.DefinitionLinks != nil {
39+
for i, link := range *definitions.DefinitionLinks {
40+
mappedTarget := l.getMappedLocation(&lsproto.Location{Uri: link.TargetUri, Range: link.TargetRange})
41+
mappedSelection := l.getMappedLocation(&lsproto.Location{Uri: link.TargetUri, Range: link.TargetSelectionRange})
42+
debug.Assert(mappedTarget.Uri == mappedSelection.Uri, "target and selection should be in same file")
43+
(*definitions.DefinitionLinks)[i].TargetUri = mappedTarget.Uri
44+
(*definitions.DefinitionLinks)[i].TargetRange = mappedTarget.Range
45+
(*definitions.DefinitionLinks)[i].TargetSelectionRange = mappedSelection.Range
46+
}
47+
}
48+
return definitions
49+
}
50+
51+
func (l *LanguageService) provideDefinitions(c *checker.Checker, node *ast.Node) lsproto.DefinitionResponse {
2552
if node.Kind == ast.KindOverrideKeyword {
2653
if sym := getSymbolForOverriddenMember(c, node); sym != nil {
27-
return l.createLocationsFromDeclarations(sym.Declarations), nil
54+
return l.createLocationsFromDeclarations(sym.Declarations)
2855
}
2956
}
3057

3158
if ast.IsJumpStatementTarget(node) {
3259
if label := getTargetLabel(node.Parent, node.Text()); label != nil {
33-
return l.createLocationsFromDeclarations([]*ast.Node{label}), nil
60+
return l.createLocationsFromDeclarations([]*ast.Node{label})
3461
}
3562
}
3663

3764
if node.Kind == ast.KindCaseKeyword || node.Kind == ast.KindDefaultKeyword && ast.IsDefaultClause(node.Parent) {
3865
if stmt := ast.FindAncestor(node.Parent, ast.IsSwitchStatement); stmt != nil {
3966
file := ast.GetSourceFileOfNode(stmt)
40-
return l.createLocationFromFileAndRange(file, scanner.GetRangeOfTokenAtPosition(file, stmt.Pos())), nil
67+
return l.createLocationFromFileAndRange(file, scanner.GetRangeOfTokenAtPosition(file, stmt.Pos()))
4168
}
4269
}
4370

4471
if node.Kind == ast.KindReturnKeyword || node.Kind == ast.KindYieldKeyword || node.Kind == ast.KindAwaitKeyword {
4572
if fn := ast.FindAncestor(node, ast.IsFunctionLikeDeclaration); fn != nil {
46-
return l.createLocationsFromDeclarations([]*ast.Node{fn}), nil
73+
return l.createLocationsFromDeclarations([]*ast.Node{fn})
4774
}
4875
}
4976

@@ -54,7 +81,7 @@ func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsp
5481
nonFunctionDeclarations := core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return !ast.IsFunctionLike(node) })
5582
declarations = append(nonFunctionDeclarations, calledDeclaration)
5683
}
57-
return l.createLocationsFromDeclarations(declarations), nil
84+
return l.createLocationsFromDeclarations(declarations)
5885
}
5986

6087
func (l *LanguageService) ProvideTypeDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.DefinitionResponse, error) {

internal/ls/languageservice.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,29 @@ import (
44
"github.com/microsoft/typescript-go/internal/ast"
55
"github.com/microsoft/typescript-go/internal/compiler"
66
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
7+
"github.com/microsoft/typescript-go/internal/sourcemap"
78
)
89

910
type LanguageService struct {
10-
host Host
11-
converters *Converters
11+
host Host
12+
converters *Converters
13+
documentPositionMappers map[string]sourcemap.DocumentPositionMapper // !!! TODO: needs sync?
14+
useCaseSensitiveFileNames bool
15+
readFile func(path string) (contents string, ok bool)
1216
}
1317

14-
func NewLanguageService(host Host, converters *Converters) *LanguageService {
18+
func NewLanguageService(
19+
host Host,
20+
converters *Converters,
21+
readFile func(path string) (contents string, ok bool),
22+
useCaseSensitiveFileNames bool,
23+
) *LanguageService {
1524
return &LanguageService{
16-
host: host,
17-
converters: converters,
25+
host: host,
26+
converters: converters,
27+
readFile: readFile,
28+
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
29+
documentPositionMappers: map[string]sourcemap.DocumentPositionMapper{},
1830
}
1931
}
2032

@@ -36,3 +48,32 @@ func (l *LanguageService) getProgramAndFile(documentURI lsproto.DocumentUri) (*c
3648
}
3749
return program, file
3850
}
51+
52+
func (l *LanguageService) GetDocumentPositionMapper(fileName string) sourcemap.DocumentPositionMapper {
53+
d, ok := l.documentPositionMappers[fileName]
54+
if !ok {
55+
d = sourcemap.GetDocumentPositionMapper(l, fileName)
56+
l.documentPositionMappers[fileName] = d
57+
}
58+
return d
59+
}
60+
61+
func (l *LanguageService) ReadFile(fileName string) (string, bool) {
62+
return l.readFile(fileName)
63+
}
64+
65+
func (l *LanguageService) UseCaseSensitiveFileNames() bool {
66+
return l.useCaseSensitiveFileNames
67+
}
68+
69+
func (l *LanguageService) GetLineInfo(fileName string) *sourcemap.LineInfo {
70+
text, ok := l.ReadFile(fileName)
71+
if !ok {
72+
return nil
73+
}
74+
lineMap := l.converters.getLineMap(fileName)
75+
if lineMap == nil {
76+
return nil
77+
}
78+
return sourcemap.CreateLineInfo(text, lineMap.LineStarts)
79+
}

internal/ls/source_map.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package ls
2+
3+
import (
4+
"github.com/microsoft/typescript-go/internal/core"
5+
"github.com/microsoft/typescript-go/internal/debug"
6+
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
7+
"github.com/microsoft/typescript-go/internal/sourcemap"
8+
"github.com/microsoft/typescript-go/internal/tspath"
9+
)
10+
11+
func (l *LanguageService) getMappedLocation(location *lsproto.Location) *lsproto.Location {
12+
uriStart, start := l.tryGetSourceLSPPosition(location.Uri.FileName(), &location.Range.Start)
13+
if uriStart == nil {
14+
return location
15+
}
16+
uriEnd, end := l.tryGetSourceLSPPosition(location.Uri.FileName(), &location.Range.End)
17+
debug.Assert(uriEnd == uriStart, "start and end should be in same file")
18+
debug.Assert(end != nil, "end position should be valid")
19+
return &lsproto.Location{
20+
Uri: *uriStart,
21+
Range: lsproto.Range{Start: *start, End: *end},
22+
}
23+
}
24+
25+
func (l *LanguageService) getMappedPosition() {
26+
// !!! HERE
27+
}
28+
29+
type script struct {
30+
fileName string
31+
text string
32+
}
33+
34+
func (s *script) FileName() string {
35+
return s.fileName
36+
}
37+
38+
func (s *script) Text() string {
39+
return s.text
40+
}
41+
42+
func (l *LanguageService) tryGetSourceLSPPosition(
43+
genFileName string,
44+
position *lsproto.Position,
45+
) (*lsproto.DocumentUri, *lsproto.Position) {
46+
genText, ok := l.ReadFile(genFileName)
47+
if !ok {
48+
return nil, nil // That shouldn't happen
49+
}
50+
genPos := l.converters.LineAndCharacterToPosition(&script{fileName: genFileName, text: genText}, *position)
51+
documentPos := l.tryGetSourcePosition(genFileName, genPos)
52+
if documentPos == nil {
53+
return nil, nil
54+
}
55+
documentURI := FileNameToDocumentURI(documentPos.FileName)
56+
sourceText, ok := l.ReadFile(documentPos.FileName)
57+
if !ok {
58+
return nil, nil
59+
}
60+
sourcePos := l.converters.PositionToLineAndCharacter(
61+
&script{fileName: documentPos.FileName, text: sourceText},
62+
core.TextPos(documentPos.Pos),
63+
)
64+
return &documentURI, &sourcePos
65+
}
66+
67+
func (l *LanguageService) tryGetSourcePosition(
68+
fileName string,
69+
genPosition core.TextPos,
70+
) *sourcemap.DocumentPosition {
71+
if !tspath.IsDeclarationFileName(fileName) {
72+
return nil
73+
}
74+
75+
positionMapper := l.GetDocumentPositionMapper(fileName)
76+
documentPos := positionMapper.GetSourcePosition(&sourcemap.DocumentPosition{FileName: fileName, Pos: int(genPosition)})
77+
if documentPos == nil {
78+
return nil
79+
}
80+
if newPos := l.tryGetSourcePosition(documentPos.FileName, core.TextPos(documentPos.Pos)); newPos != nil {
81+
return newPos
82+
}
83+
return documentPos
84+
}

internal/project/session.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUr
367367
if project == nil {
368368
return nil, fmt.Errorf("no project found for URI %s", uri)
369369
}
370-
return ls.NewLanguageService(project, snapshot.Converters()), nil
370+
return ls.NewLanguageService(project, snapshot.Converters(), snapshot.ReadFile, snapshot.UseCaseSensitiveFileNames()), nil
371371
}
372372

373373
func (s *Session) UpdateSnapshot(ctx context.Context, overlays map[tspath.Path]*overlay, change SnapshotChange) *Snapshot {

internal/project/snapshot.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ func (s *Snapshot) ID() uint64 {
8989
return s.id
9090
}
9191

92+
func (s *Snapshot) UseCaseSensitiveFileNames() bool {
93+
return s.fs.fs.UseCaseSensitiveFileNames()
94+
}
95+
96+
func (s *Snapshot) ReadFile(fileName string) (string, bool) {
97+
handle := s.GetFile(fileName)
98+
if handle == nil {
99+
return "", false
100+
}
101+
return handle.Content(), true
102+
}
103+
92104
type APISnapshotRequest struct {
93105
OpenProjects *collections.Set[string]
94106
CloseProjects *collections.Set[tspath.Path]

internal/project/snapshotfs.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package project
22

33
import (
4+
"sync"
5+
6+
"github.com/microsoft/typescript-go/internal/collections"
47
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
58
"github.com/microsoft/typescript-go/internal/project/dirty"
69
"github.com/microsoft/typescript-go/internal/tspath"
@@ -24,6 +27,12 @@ type snapshotFS struct {
2427
fs vfs.FS
2528
overlays map[tspath.Path]*overlay
2629
diskFiles map[tspath.Path]*diskFile
30+
readFiles collections.SyncMap[tspath.Path, memoizedFileEntry]
31+
}
32+
33+
// !!! newtype?
34+
type memoizedFileEntry struct {
35+
read func() *diskFile
2736
}
2837

2938
func (s *snapshotFS) FS() vfs.FS {
@@ -37,6 +46,17 @@ func (s *snapshotFS) GetFile(fileName string) FileHandle {
3746
if file, ok := s.diskFiles[s.toPath(fileName)]; ok {
3847
return file
3948
}
49+
newEntry := memoizedFileEntry{
50+
read: sync.OnceValue(func() *diskFile {
51+
if contents, ok := s.fs.ReadFile(fileName); ok {
52+
return newDiskFile(fileName, contents)
53+
}
54+
return nil
55+
}),
56+
}
57+
if entry, ok := s.readFiles.LoadOrStore(s.toPath(fileName), newEntry); ok {
58+
return entry.read()
59+
}
4060
return nil
4161
}
4262

internal/sourcemap/lineinfo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type LineInfo struct {
77
lineStarts []core.TextPos
88
}
99

10-
func GetLineInfo(text string, lineStarts []core.TextPos) *LineInfo {
10+
func CreateLineInfo(text string, lineStarts []core.TextPos) *LineInfo {
1111
return &LineInfo{
1212
text: text,
1313
lineStarts: lineStarts,

0 commit comments

Comments
 (0)