Skip to content
Merged
6 changes: 3 additions & 3 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (api *API) GetSymbolAtPosition(ctx context.Context, projectId Handle[projec
return nil, errors.New("project not found")
}

languageService := ls.NewLanguageService(project, snapshot.Converters())
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
symbol, err := languageService.GetSymbolAtPosition(ctx, fileName, position)
if err != nil || symbol == nil {
return nil, err
Expand Down Expand Up @@ -202,7 +202,7 @@ func (api *API) GetSymbolAtLocation(ctx context.Context, projectId Handle[projec
if node == nil {
return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName())
}
languageService := ls.NewLanguageService(project, snapshot.Converters())
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
symbol := languageService.GetSymbolAtLocation(ctx, node)
if symbol == nil {
return nil, nil
Expand Down Expand Up @@ -232,7 +232,7 @@ func (api *API) GetTypeOfSymbol(ctx context.Context, projectId Handle[project.Pr
if !ok {
return nil, fmt.Errorf("symbol %q not found", symbolHandle)
}
languageService := ls.NewLanguageService(project, snapshot.Converters())
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
t := languageService.GetTypeOfSymbol(ctx, symbol)
if t == nil {
return nil, nil
Expand Down
23 changes: 22 additions & 1 deletion internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ func Coalesce[T *U, U any](a T, b T) T {
}
}

func ComputeECMALineStarts(text string) []TextPos {
type ECMALineStarts []TextPos

func ComputeECMALineStarts(text string) ECMALineStarts {
result := make([]TextPos, 0, strings.Count(text, "\n")+1)
return slices.AppendSeq(result, ComputeECMALineStartsSeq(text))
}
Expand Down Expand Up @@ -648,3 +650,22 @@ func Deduplicate[T comparable](slice []T) []T {
}
return slice
}

func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
if len(slice) == 0 {
return slice
}
last := slice[0]
deduplicated := slice[:1]
for i := 1; i < len(slice); i++ {
next := slice[i]
if isEqual(last, next) {
continue
}

deduplicated = append(deduplicated, next)
last = next
}

return deduplicated
}
2 changes: 1 addition & 1 deletion internal/ls/autoimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ func (l *LanguageService) createPackageJsonImportFilter(fromFile *ast.SourceFile
return nil
}
specifier := modulespecifiers.GetNodeModulesPackageName(
l.host.GetProgram().Options(),
l.program.Options(),
fromFile,
importedFileName,
moduleSpecifierResolutionHost,
Expand Down
13 changes: 5 additions & 8 deletions internal/ls/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,17 @@ func (l *LanguageService) createLocationsFromDeclarations(declarations []*ast.No
for _, decl := range declarations {
file := ast.GetSourceFileOfNode(decl)
name := core.OrElse(ast.GetNameOfDeclaration(decl), decl)
locations = core.AppendIfUnique(locations, lsproto.Location{
Uri: FileNameToDocumentURI(file.FileName()),
Range: *l.createLspRangeFromNode(name, file),
})
nodeRange := createRangeFromNode(name, file)
mappedLocation := l.getMappedLocation(file.FileName(), nodeRange)
locations = core.AppendIfUnique(locations, mappedLocation)
}
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations}
}

func (l *LanguageService) createLocationFromFileAndRange(file *ast.SourceFile, textRange core.TextRange) lsproto.DefinitionResponse {
mappedLocation := l.getMappedLocation(file.FileName(), textRange)
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{
Location: &lsproto.Location{
Uri: FileNameToDocumentURI(file.FileName()),
Range: *l.createLspRangeFromBounds(textRange.Pos(), textRange.End(), file),
},
Location: &mappedLocation,
}
}

Expand Down
9 changes: 5 additions & 4 deletions internal/ls/host.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package ls

import (
"github.com/microsoft/typescript-go/internal/compiler"
)
import "github.com/microsoft/typescript-go/internal/sourcemap"

type Host interface {
GetProgram() *compiler.Program
UseCaseSensitiveFileNames() bool
ReadFile(path string) (contents string, ok bool)
Converters() *Converters
GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo
}
41 changes: 35 additions & 6 deletions internal/ls/languageservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/sourcemap"
)

type LanguageService struct {
host Host
converters *Converters
host Host
program *compiler.Program
converters *Converters
documentPositionMappers map[string]*sourcemap.DocumentPositionMapper
}

func NewLanguageService(host Host, converters *Converters) *LanguageService {
func NewLanguageService(
program *compiler.Program,
host Host,
) *LanguageService {
return &LanguageService{
host: host,
converters: converters,
host: host,
program: program,
converters: host.Converters(),
documentPositionMappers: map[string]*sourcemap.DocumentPositionMapper{},
}
}

func (l *LanguageService) GetProgram() *compiler.Program {
return l.host.GetProgram()
return l.program
}

func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
Expand All @@ -36,3 +44,24 @@ func (l *LanguageService) getProgramAndFile(documentURI lsproto.DocumentUri) (*c
}
return program, file
}

func (l *LanguageService) GetDocumentPositionMapper(fileName string) *sourcemap.DocumentPositionMapper {
d, ok := l.documentPositionMappers[fileName]
if !ok {
d = sourcemap.GetDocumentPositionMapper(l, fileName)
l.documentPositionMappers[fileName] = d
}
return d
}

func (l *LanguageService) ReadFile(fileName string) (string, bool) {
return l.host.ReadFile(fileName)
}

func (l *LanguageService) UseCaseSensitiveFileNames() bool {
return l.host.UseCaseSensitiveFileNames()
}

func (l *LanguageService) GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo {
return l.host.GetECMALineInfo(fileName)
}
4 changes: 3 additions & 1 deletion internal/ls/linemap.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"github.com/microsoft/typescript-go/internal/core"
)

type LSPLineStarts []core.TextPos

type LSPLineMap struct {
LineStarts []core.TextPos
LineStarts LSPLineStarts
AsciiOnly bool // TODO(jakebailey): collect ascii-only info per line
}

Expand Down
81 changes: 81 additions & 0 deletions internal/ls/source_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ls

import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/sourcemap"
"github.com/microsoft/typescript-go/internal/tspath"
)

func (l *LanguageService) getMappedLocation(fileName string, fileRange core.TextRange) lsproto.Location {
startPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.Pos()))
if startPos == nil {
lspRange := l.createLspRangeFromRange(fileRange, l.getScript(fileName))
return lsproto.Location{
Uri: FileNameToDocumentURI(fileName),
Range: *lspRange,
}
}
endPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.End()))
debug.Assert(endPos.FileName == startPos.FileName, "start and end should be in same file")
newRange := core.NewTextRange(startPos.Pos, endPos.Pos)
lspRange := l.createLspRangeFromRange(newRange, l.getScript(startPos.FileName))
return lsproto.Location{
Uri: FileNameToDocumentURI(startPos.FileName),
Range: *lspRange,
}
}

type script struct {
fileName string
text string
}

func (s *script) FileName() string {
return s.fileName
}

func (s *script) Text() string {
return s.text
}

func (l *LanguageService) getScript(fileName string) *script {
text, ok := l.host.ReadFile(fileName)
if !ok {
return nil
}
return &script{fileName: fileName, text: text}
}

func (l *LanguageService) tryGetSourcePosition(
fileName string,
position core.TextPos,
) *sourcemap.DocumentPosition {
newPos := l.tryGetSourcePositionWorker(fileName, position)
if newPos != nil {
if _, ok := l.ReadFile(newPos.FileName); !ok { // File doesn't exist
return nil
}
}
return newPos
}

func (l *LanguageService) tryGetSourcePositionWorker(
fileName string,
position core.TextPos,
) *sourcemap.DocumentPosition {
if !tspath.IsDeclarationFileName(fileName) {
return nil
}

positionMapper := l.GetDocumentPositionMapper(fileName)
documentPos := positionMapper.GetSourcePosition(&sourcemap.DocumentPosition{FileName: fileName, Pos: int(position)})
if documentPos == nil {
return nil
}
if newPos := l.tryGetSourcePositionWorker(documentPos.FileName, core.TextPos(documentPos.Pos)); newPos != nil {
return newPos
}
return documentPos
}
9 changes: 9 additions & 0 deletions internal/ls/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,20 @@ func (l *LanguageService) createLspRangeFromNode(node *ast.Node, file *ast.Sourc
return l.createLspRangeFromBounds(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End(), file)
}

func createRangeFromNode(node *ast.Node, file *ast.SourceFile) core.TextRange {
return core.NewTextRange(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End())
}

func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.SourceFile) *lsproto.Range {
lspRange := l.converters.ToLSPRange(file, core.NewTextRange(start, end))
return &lspRange
}

func (l *LanguageService) createLspRangeFromRange(textRange core.TextRange, script Script) *lsproto.Range {
lspRange := l.converters.ToLSPRange(script, textRange)
return &lspRange
}

func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position {
return l.converters.PositionToLineAndCharacter(file, core.TextPos(position))
}
Expand Down
16 changes: 14 additions & 2 deletions internal/project/overlayfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/sourcemap"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/zeebo/xxh3"
Expand All @@ -24,6 +25,7 @@ type FileHandle interface {
MatchesDiskText() bool
IsOverlay() bool
LSPLineMap() *ls.LSPLineMap
ECMALineInfo() *sourcemap.ECMALineInfo
Kind() core.ScriptKind
}

Expand All @@ -32,8 +34,10 @@ type fileBase struct {
content string
hash xxh3.Uint128

lineMapOnce sync.Once
lineMap *ls.LSPLineMap
lineMapOnce sync.Once
lineMap *ls.LSPLineMap
lineInfoOnce sync.Once
lineInfo *sourcemap.ECMALineInfo
}

func (f *fileBase) FileName() string {
Expand All @@ -55,6 +59,14 @@ func (f *fileBase) LSPLineMap() *ls.LSPLineMap {
return f.lineMap
}

func (f *fileBase) ECMALineInfo() *sourcemap.ECMALineInfo {
f.lineInfoOnce.Do(func() {
lineStarts := core.ComputeECMALineStarts(f.content)
f.lineInfo = sourcemap.CreateECMALineInfo(f.content, lineStarts)
})
return f.lineInfo
}

type diskFile struct {
fileBase
needsReload bool
Expand Down
4 changes: 0 additions & 4 deletions internal/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"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/ls"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/project/ata"
"github.com/microsoft/typescript-go/internal/project/logging"
Expand Down Expand Up @@ -49,8 +48,6 @@ const (
PendingReloadFull
)

var _ ls.Host = (*Project)(nil)

// Project represents a TypeScript project.
// If changing struct fields, also update the Clone method.
type Project struct {
Expand Down Expand Up @@ -195,7 +192,6 @@ func (p *Project) ConfigFilePath() tspath.Path {
return p.configFilePath
}

// GetProgram implements ls.Host.
func (p *Project) GetProgram() *compiler.Program {
return p.Program
}
Expand Down
2 changes: 1 addition & 1 deletion internal/project/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUr
if project == nil {
return nil, fmt.Errorf("no project found for URI %s", uri)
}
return ls.NewLanguageService(project, snapshot.Converters()), nil
return ls.NewLanguageService(project.GetProgram(), snapshot), nil
}

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