Skip to content

Commit d5215bc

Browse files
authored
Port source map position mapping + go to definition source mapping (#1767)
1 parent 688c31d commit d5215bc

22 files changed

+586
-79
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.GetProgram(), snapshot)
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.GetProgram(), snapshot)
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.GetProgram(), snapshot)
236236
t := languageService.GetTypeOfSymbol(ctx, symbol)
237237
if t == nil {
238238
return nil, nil

internal/core/core.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ func Coalesce[T *U, U any](a T, b T) T {
362362
}
363363
}
364364

365-
func ComputeECMALineStarts(text string) []TextPos {
365+
type ECMALineStarts []TextPos
366+
367+
func ComputeECMALineStarts(text string) ECMALineStarts {
366368
result := make([]TextPos, 0, strings.Count(text, "\n")+1)
367369
return slices.AppendSeq(result, ComputeECMALineStartsSeq(text))
368370
}
@@ -648,3 +650,22 @@ func Deduplicate[T comparable](slice []T) []T {
648650
}
649651
return slice
650652
}
653+
654+
func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
655+
if len(slice) == 0 {
656+
return slice
657+
}
658+
last := slice[0]
659+
deduplicated := slice[:1]
660+
for i := 1; i < len(slice); i++ {
661+
next := slice[i]
662+
if isEqual(last, next) {
663+
continue
664+
}
665+
666+
deduplicated = append(deduplicated, next)
667+
last = next
668+
}
669+
670+
return deduplicated
671+
}

internal/ls/autoimports.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ func (l *LanguageService) createPackageJsonImportFilter(fromFile *ast.SourceFile
702702
return nil
703703
}
704704
specifier := modulespecifiers.GetNodeModulesPackageName(
705-
l.host.GetProgram().Options(),
705+
l.program.Options(),
706706
fromFile,
707707
importedFileName,
708708
moduleSpecifierResolutionHost,

internal/ls/definition.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,17 @@ func (l *LanguageService) createLocationsFromDeclarations(declarations []*ast.No
104104
for _, decl := range declarations {
105105
file := ast.GetSourceFileOfNode(decl)
106106
name := core.OrElse(ast.GetNameOfDeclaration(decl), decl)
107-
locations = core.AppendIfUnique(locations, lsproto.Location{
108-
Uri: FileNameToDocumentURI(file.FileName()),
109-
Range: *l.createLspRangeFromNode(name, file),
110-
})
107+
nodeRange := createRangeFromNode(name, file)
108+
mappedLocation := l.getMappedLocation(file.FileName(), nodeRange)
109+
locations = core.AppendIfUnique(locations, mappedLocation)
111110
}
112111
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations}
113112
}
114113

115114
func (l *LanguageService) createLocationFromFileAndRange(file *ast.SourceFile, textRange core.TextRange) lsproto.DefinitionResponse {
115+
mappedLocation := l.getMappedLocation(file.FileName(), textRange)
116116
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{
117-
Location: &lsproto.Location{
118-
Uri: FileNameToDocumentURI(file.FileName()),
119-
Range: *l.createLspRangeFromBounds(textRange.Pos(), textRange.End(), file),
120-
},
117+
Location: &mappedLocation,
121118
}
122119
}
123120

internal/ls/host.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package ls
22

3-
import (
4-
"github.com/microsoft/typescript-go/internal/compiler"
5-
)
3+
import "github.com/microsoft/typescript-go/internal/sourcemap"
64

75
type Host interface {
8-
GetProgram() *compiler.Program
6+
UseCaseSensitiveFileNames() bool
7+
ReadFile(path string) (contents string, ok bool)
8+
Converters() *Converters
9+
GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo
910
}

internal/ls/languageservice.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@ 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+
program *compiler.Program
13+
converters *Converters
14+
documentPositionMappers map[string]*sourcemap.DocumentPositionMapper
1215
}
1316

14-
func NewLanguageService(host Host, converters *Converters) *LanguageService {
17+
func NewLanguageService(
18+
program *compiler.Program,
19+
host Host,
20+
) *LanguageService {
1521
return &LanguageService{
16-
host: host,
17-
converters: converters,
22+
host: host,
23+
program: program,
24+
converters: host.Converters(),
25+
documentPositionMappers: map[string]*sourcemap.DocumentPositionMapper{},
1826
}
1927
}
2028

2129
func (l *LanguageService) GetProgram() *compiler.Program {
22-
return l.host.GetProgram()
30+
return l.program
2331
}
2432

2533
func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
@@ -36,3 +44,24 @@ func (l *LanguageService) getProgramAndFile(documentURI lsproto.DocumentUri) (*c
3644
}
3745
return program, file
3846
}
47+
48+
func (l *LanguageService) GetDocumentPositionMapper(fileName string) *sourcemap.DocumentPositionMapper {
49+
d, ok := l.documentPositionMappers[fileName]
50+
if !ok {
51+
d = sourcemap.GetDocumentPositionMapper(l, fileName)
52+
l.documentPositionMappers[fileName] = d
53+
}
54+
return d
55+
}
56+
57+
func (l *LanguageService) ReadFile(fileName string) (string, bool) {
58+
return l.host.ReadFile(fileName)
59+
}
60+
61+
func (l *LanguageService) UseCaseSensitiveFileNames() bool {
62+
return l.host.UseCaseSensitiveFileNames()
63+
}
64+
65+
func (l *LanguageService) GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo {
66+
return l.host.GetECMALineInfo(fileName)
67+
}

internal/ls/linemap.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"github.com/microsoft/typescript-go/internal/core"
1010
)
1111

12+
type LSPLineStarts []core.TextPos
13+
1214
type LSPLineMap struct {
13-
LineStarts []core.TextPos
15+
LineStarts LSPLineStarts
1416
AsciiOnly bool // TODO(jakebailey): collect ascii-only info per line
1517
}
1618

internal/ls/source_map.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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(fileName string, fileRange core.TextRange) lsproto.Location {
12+
startPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.Pos()))
13+
if startPos == nil {
14+
lspRange := l.createLspRangeFromRange(fileRange, l.getScript(fileName))
15+
return lsproto.Location{
16+
Uri: FileNameToDocumentURI(fileName),
17+
Range: *lspRange,
18+
}
19+
}
20+
endPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.End()))
21+
debug.Assert(endPos.FileName == startPos.FileName, "start and end should be in same file")
22+
newRange := core.NewTextRange(startPos.Pos, endPos.Pos)
23+
lspRange := l.createLspRangeFromRange(newRange, l.getScript(startPos.FileName))
24+
return lsproto.Location{
25+
Uri: FileNameToDocumentURI(startPos.FileName),
26+
Range: *lspRange,
27+
}
28+
}
29+
30+
type script struct {
31+
fileName string
32+
text string
33+
}
34+
35+
func (s *script) FileName() string {
36+
return s.fileName
37+
}
38+
39+
func (s *script) Text() string {
40+
return s.text
41+
}
42+
43+
func (l *LanguageService) getScript(fileName string) *script {
44+
text, ok := l.host.ReadFile(fileName)
45+
if !ok {
46+
return nil
47+
}
48+
return &script{fileName: fileName, text: text}
49+
}
50+
51+
func (l *LanguageService) tryGetSourcePosition(
52+
fileName string,
53+
position core.TextPos,
54+
) *sourcemap.DocumentPosition {
55+
newPos := l.tryGetSourcePositionWorker(fileName, position)
56+
if newPos != nil {
57+
if _, ok := l.ReadFile(newPos.FileName); !ok { // File doesn't exist
58+
return nil
59+
}
60+
}
61+
return newPos
62+
}
63+
64+
func (l *LanguageService) tryGetSourcePositionWorker(
65+
fileName string,
66+
position core.TextPos,
67+
) *sourcemap.DocumentPosition {
68+
if !tspath.IsDeclarationFileName(fileName) {
69+
return nil
70+
}
71+
72+
positionMapper := l.GetDocumentPositionMapper(fileName)
73+
documentPos := positionMapper.GetSourcePosition(&sourcemap.DocumentPosition{FileName: fileName, Pos: int(position)})
74+
if documentPos == nil {
75+
return nil
76+
}
77+
if newPos := l.tryGetSourcePositionWorker(documentPos.FileName, core.TextPos(documentPos.Pos)); newPos != nil {
78+
return newPos
79+
}
80+
return documentPos
81+
}

internal/ls/utilities.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,20 @@ func (l *LanguageService) createLspRangeFromNode(node *ast.Node, file *ast.Sourc
433433
return l.createLspRangeFromBounds(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End(), file)
434434
}
435435

436+
func createRangeFromNode(node *ast.Node, file *ast.SourceFile) core.TextRange {
437+
return core.NewTextRange(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End())
438+
}
439+
436440
func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.SourceFile) *lsproto.Range {
437441
lspRange := l.converters.ToLSPRange(file, core.NewTextRange(start, end))
438442
return &lspRange
439443
}
440444

445+
func (l *LanguageService) createLspRangeFromRange(textRange core.TextRange, script Script) *lsproto.Range {
446+
lspRange := l.converters.ToLSPRange(script, textRange)
447+
return &lspRange
448+
}
449+
441450
func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position {
442451
return l.converters.PositionToLineAndCharacter(file, core.TextPos(position))
443452
}

internal/project/overlayfs.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/microsoft/typescript-go/internal/core"
88
"github.com/microsoft/typescript-go/internal/ls"
99
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
10+
"github.com/microsoft/typescript-go/internal/sourcemap"
1011
"github.com/microsoft/typescript-go/internal/tspath"
1112
"github.com/microsoft/typescript-go/internal/vfs"
1213
"github.com/zeebo/xxh3"
@@ -24,6 +25,7 @@ type FileHandle interface {
2425
MatchesDiskText() bool
2526
IsOverlay() bool
2627
LSPLineMap() *ls.LSPLineMap
28+
ECMALineInfo() *sourcemap.ECMALineInfo
2729
Kind() core.ScriptKind
2830
}
2931

@@ -32,8 +34,10 @@ type fileBase struct {
3234
content string
3335
hash xxh3.Uint128
3436

35-
lineMapOnce sync.Once
36-
lineMap *ls.LSPLineMap
37+
lineMapOnce sync.Once
38+
lineMap *ls.LSPLineMap
39+
lineInfoOnce sync.Once
40+
lineInfo *sourcemap.ECMALineInfo
3741
}
3842

3943
func (f *fileBase) FileName() string {
@@ -55,6 +59,14 @@ func (f *fileBase) LSPLineMap() *ls.LSPLineMap {
5559
return f.lineMap
5660
}
5761

62+
func (f *fileBase) ECMALineInfo() *sourcemap.ECMALineInfo {
63+
f.lineInfoOnce.Do(func() {
64+
lineStarts := core.ComputeECMALineStarts(f.content)
65+
f.lineInfo = sourcemap.CreateECMALineInfo(f.content, lineStarts)
66+
})
67+
return f.lineInfo
68+
}
69+
5870
type diskFile struct {
5971
fileBase
6072
needsReload bool

0 commit comments

Comments
 (0)