Skip to content
Draft
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
6 changes: 6 additions & 0 deletions tsc/internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten

type FourslashOptions struct {
Capabilities *lsproto.ClientCapabilities
InitializeParams *lsproto.InitializeParams
ContentMapperSpawner contentmapper.Spawner
RunExternalCode bool
// Makes every textDocument/diagnostic request also emit the program and compare
Expand Down Expand Up @@ -386,6 +387,11 @@ func (f *FourslashTest) initialize(t *testing.T, options *FourslashOptions) {
InitializationOptions: initializationOptions,
},
}
if options.InitializeParams != nil {
params.RootUri = options.InitializeParams.RootUri
params.RootPath = options.InitializeParams.RootPath
params.WorkspaceFolders = options.InitializeParams.WorkspaceFolders
}
if options.RunExternalCode {
params.RootUri = lsproto.DocumentUriOrNull{DocumentUri: new(lsproto.DocumentUri("file:///"))}
}
Expand Down
67 changes: 66 additions & 1 deletion tsc/internal/fourslash/tests/callHierarchyFile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,82 @@ import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestCallHierarchyFile(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `foo();
const content = `// @Filename: /src/callers.ts
foo();
function /**/foo() {
}`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.GoToMarker(t, "")
f.VerifyBaselineCallHierarchy(t)
}

func TestCallHierarchyFileWorkspace(t *testing.T) {
t.Parallel()
const content = `// @Filename: /workspace/project/tsconfig.json
{}
// @Filename: /workspace/project/src/callers.ts
foo();
function /**/foo() {
}`
for _, test := range []struct {
name string
params *lsproto.InitializeParams
}{
{
name: "callHierarchyFileWorkspaceRootUri",
params: &lsproto.InitializeParams{
RootUri: lsproto.DocumentUriOrNull{DocumentUri: new(lsproto.DocumentUri("file:///workspace"))},
},
},
{
name: "callHierarchyFileWorkspaceFolderRoot",
params: &lsproto.InitializeParams{
WorkspaceFolders: &lsproto.WorkspaceFoldersOrNull{WorkspaceFolders: new([]*lsproto.WorkspaceFolder{
{Uri: "file:///workspace/project/src", Name: "source"},
})},
},
},
{
name: "callHierarchyFileWorkspaceFolders",
params: &lsproto.InitializeParams{
RootUri: lsproto.DocumentUriOrNull{DocumentUri: new(lsproto.DocumentUri("file:///wrong"))},
WorkspaceFolders: &lsproto.WorkspaceFoldersOrNull{WorkspaceFolders: new([]*lsproto.WorkspaceFolder{
{Uri: "file:///workspace", Name: "outer"},
{Uri: "file:///workspace/project", Name: "inner"},
})},
},
},
{
name: "callHierarchyFileNestedWorkspaceRoot",
params: &lsproto.InitializeParams{
WorkspaceFolders: &lsproto.WorkspaceFoldersOrNull{WorkspaceFolders: new([]*lsproto.WorkspaceFolder{
{Uri: "file:///workspace", Name: "outer"},
{Uri: "file:///workspace/project/src", Name: "inner"},
})},
},
},
{
name: "callHierarchyFileOutsideWorkspace",
params: &lsproto.InitializeParams{
RootUri: lsproto.DocumentUriOrNull{DocumentUri: new(lsproto.DocumentUri("file:///work"))},
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{InitializeParams: test.params})
defer done()
f.GoToMarker(t, "")
f.VerifyBaselineCallHierarchy(t)
})
}
}
39 changes: 38 additions & 1 deletion tsc/internal/ls/callhierarchy.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func getSymbolOfCallHierarchyDeclaration(c *checker.Checker, node *ast.Node) *as
func getCallHierarchyItemName(program *compiler.Program, node *ast.Node) (text string, pos int, end int) {
if ast.IsSourceFile(node) {
sourceFile := node.AsSourceFile()
return sourceFile.FileName().AsString(), 0, 0
return sourceFile.OriginalFileName().BaseName(), 0, 0
}

if (ast.IsFunctionDeclaration(node) || ast.IsClassDeclaration(node)) && node.Name() == nil {
Expand Down Expand Up @@ -504,6 +504,20 @@ func (l *LanguageService) createCallHierarchyItem(program *compiler.Program, nod
sourceFile := ast.GetSourceFileOfNode(node)
nameText, namePos, nameEnd := getCallHierarchyItemName(program, node)
containerName := getCallHierarchyItemContainerName(program, node)
if ast.IsSourceFile(node) {
directory := sourceFile.OriginalFileName().Directory()
directoryPath := directory.AsPath()
containerName = directory.AsString()
workspaceFolders := l.host.GetWorkspaceFolders()
containingFolder := getContainingWorkspaceFolder(directory, workspaceFolders, l.CaseSensitivity())
if containingFolder != nil {
relativePath, _ := l.CaseSensitivity().RelativePathWithinDirectory(containingFolder.Path, directoryPath)
containerName = relativePath.AsString()
if len(workspaceFolders) > 1 && containingFolder.Name != "" {
containerName = containingFolder.Name + "/" + containerName
}
}
}

kind := getSymbolKindFromNode(node)

Expand Down Expand Up @@ -532,6 +546,29 @@ func (l *LanguageService) createCallHierarchyItem(program *compiler.Program, nod
return item
}

func getContainingWorkspaceFolder(directory tspath.RootedDirectoryPath, workspaceFolders []WorkspaceFolder, caseSensitivity tspath.CaseSensitivity) *WorkspaceFolder {
directoryPath := directory.AsPath()
var containingFolder *WorkspaceFolder
for i := range workspaceFolders {
folder := &workspaceFolders[i]
searchDirectory := directoryPath
// Like workspace.asRelativePath, resolve a workspace directory through its parent.
if caseSensitivity.ComparePaths(folder.Path.AsPath(), directoryPath) == 0 {
searchDirectory = directoryPath.Directory().AsPath()
}
if !caseSensitivity.ContainsPath(folder.Path, searchDirectory) {
continue
}
if containingFolder != nil &&
(caseSensitivity.ComparePaths(containingFolder.Path.AsPath(), folder.Path.AsPath()) == 0 ||
!caseSensitivity.ContainsPath(containingFolder.Path, folder.Path.AsPath())) {
continue
}
containingFolder = folder
}
return containingFolder
}

type callSite struct {
declaration *ast.Node
textRange core.TextRange
Expand Down
22 changes: 22 additions & 0 deletions tsc/internal/ls/file_rename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (caseInsensitiveHost) GetPreferences(activeFile string) lsutil.UserPreferen
panic("not implemented")
}

func (caseInsensitiveHost) GetWorkspaceFolders() []WorkspaceFolder {
panic("not implemented")
}

func (caseInsensitiveHost) GetECMALineInfo(fileName tspath.RootedFilePath) *sourcemap.ECMALineInfo {
panic("not implemented")
}
Expand Down Expand Up @@ -69,3 +73,21 @@ func TestCreatePathUpdaterCaseFoldingShrinksOldPath(t *testing.T) {
assert.Assert(t, ok)
assert.Equal(t, updated, tspath.RootedFilePath("/a/new/x.ts"))
}

func TestGetContainingWorkspaceFolderCaseFolding(t *testing.T) {
t.Parallel()

workspaceFolders := []WorkspaceFolder{
{Path: tspath.RootedDirectoryPath("/repo/KKKK"), Name: "outer"},
{Path: tspath.RootedDirectoryPath("/repo/kkkk/sub"), Name: "inner"},
}

folder := getContainingWorkspaceFolder(
tspath.RootedDirectoryPath("/repo/kkkk/sub/src"),
workspaceFolders,
tspath.CaseInsensitive,
)

assert.Assert(t, folder != nil)
assert.Equal(t, folder.Name, "inner")
}
6 changes: 6 additions & 0 deletions tsc/internal/ls/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import (
"github.com/microsoft/TypeScript/tsc/internal/tspath"
)

type WorkspaceFolder struct {
Path tspath.RootedDirectoryPath
Name string
}

type Host interface {
CaseSensitivity() tspath.CaseSensitivity
ReadFile(path tspath.RootedFilePath) (contents string, ok bool)
Converters() *lsconv.Converters
GetPreferences(activeFile string) lsutil.UserPreferences
GetWorkspaceFolders() []WorkspaceFolder
GetECMALineInfo(fileName tspath.RootedFilePath) *sourcemap.ECMALineInfo
AutoImportRegistry() *autoimport.Registry

Expand Down
39 changes: 28 additions & 11 deletions tsc/internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,22 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
}

workspaceRoots := getInitialWorkspaceRoots(s.initializeParams)
var workspaceFolders []ls.WorkspaceFolder
if s.initializeParams.WorkspaceFolders != nil && s.initializeParams.WorkspaceFolders.WorkspaceFolders != nil {
for _, folder := range *s.initializeParams.WorkspaceFolders.WorkspaceFolders {
workspaceFolders = append(workspaceFolders, ls.WorkspaceFolder{
Path: tspath.RootedDirectoryPathFromPath(lsproto.DocumentUri(folder.Uri).Path()),
Name: folder.Name,
})
}
} else {
for _, root := range workspaceRoots {
workspaceFolders = append(workspaceFolders, ls.WorkspaceFolder{
Path: root,
Name: root.BaseName(),
})
}
}

cwd := s.cwd
if s.clientCapabilities.Workspace.WorkspaceFolders &&
Expand All @@ -1770,17 +1786,18 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
s.session = project.NewSession(&project.SessionInit{
BackgroundCtx: lsproto.WithClientCapabilities(s.backgroundCtx, &s.clientCapabilities),
Options: &project.SessionOptions{
CurrentDirectory: cwd,
DefaultLibraryPath: s.defaultLibraryPath,
TypingsLocation: s.typingsLocation,
PositionEncoding: s.positionEncoding,
WatchEnabled: s.watchEnabled,
LoggingEnabled: true,
TelemetryEnabled: enableTelemetry,
DebounceDelay: 500 * time.Millisecond,
PushDiagnosticsEnabled: !disablePushDiagnostics,
RunExternalCode: runExternalCode,
InitialWorkspaceRoots: workspaceRoots,
CurrentDirectory: cwd,
DefaultLibraryPath: s.defaultLibraryPath,
TypingsLocation: s.typingsLocation,
PositionEncoding: s.positionEncoding,
WatchEnabled: s.watchEnabled,
LoggingEnabled: true,
TelemetryEnabled: enableTelemetry,
DebounceDelay: 500 * time.Millisecond,
PushDiagnosticsEnabled: !disablePushDiagnostics,
RunExternalCode: runExternalCode,
InitialWorkspaceRoots: workspaceRoots,
InitialWorkspaceFolders: workspaceFolders,
},
FS: s.fs,
Logger: s.logger,
Expand Down
9 changes: 5 additions & 4 deletions tsc/internal/project/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ type SessionOptions struct {
PushDiagnosticsEnabled bool
// RunExternalCode allows configured content mappers to run their (external) processes,
// gated on workspace trust by the client. It corresponds to the --runExternalCode CLI flag.
RunExternalCode bool
InitialWorkspaceRoots []tspath.RootedDirectoryPath
DebounceDelay time.Duration
CheckerPoolOptions CheckerPoolOptions
RunExternalCode bool
InitialWorkspaceRoots []tspath.RootedDirectoryPath
InitialWorkspaceFolders []ls.WorkspaceFolder
DebounceDelay time.Duration
CheckerPoolOptions CheckerPoolOptions
}

type SessionInit struct {
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/project/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ func (s *Snapshot) GetPreferences(activeFile string) lsutil.UserPreferences {
return s.userPreferences
}

func (s *Snapshot) GetWorkspaceFolders() []ls.WorkspaceFolder {
return s.host.options.InitialWorkspaceFolders
}

func (s *Snapshot) UserPreferences() lsutil.UserPreferences {
return s.userPreferences
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// === Call Hierarchy ===
╭ name: /main.ts
╭ name: main.ts
├ kind: file
├ containerName: /
├ file: /main.ts
├ span:
│ ╭ /main.ts:1:1-3:3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// === Call Hierarchy ===
╭ name: /main.ts
╭ name: main.ts
├ kind: file
├ containerName: /
├ file: /main.ts
├ span:
│ ╭ /main.ts:1:1-1:16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
│ │ │ ^^^
│ ╰ ╰
│ ╭ from:
│ │ ╭ name: /callHierarchyCallExpressionByConstNamedFunctionExpression.ts
│ │ ╭ name: callHierarchyCallExpressionByConstNamedFunctionExpression.ts
│ │ ├ kind: file
│ │ ├ containerName: /
│ │ ├ file: /callHierarchyCallExpressionByConstNamedFunctionExpression.ts
│ │ ├ span:
│ │ │ ╭ /callHierarchyCallExpressionByConstNamedFunctionExpression.ts:1:1-12:6
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// === Call Hierarchy ===
╭ name: /other.ts
╭ name: other.ts
├ kind: module
├ containerName: /
├ file: /other.ts
├ span:
│ ╭ /other.ts:1:1-6:2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
// === Call Hierarchy ===
╭ name: foo
├ kind: function
├ file: /callHierarchyFile.ts
├ file: /src/callers.ts
├ span:
│ ╭ /callHierarchyFile.ts:2:1-3:2
│ ╭ /src/callers.ts:2:1-3:2
│ │ 2: function foo() {
│ │ ^^^^^^^^^^^^^^^^
│ │ 3: }
│ │ ^
│ ╰
├ selectionSpan:
│ ╭ /callHierarchyFile.ts:2:10-2:13
│ ╭ /src/callers.ts:2:10-2:13
│ │ 2: function foo() {
│ │ ^^^
│ ╰
├ incoming:
│ ╭ from:
│ │ ╭ name: /callHierarchyFile.ts
│ │ ╭ name: callers.ts
│ │ ├ kind: file
│ │ ├ file: /callHierarchyFile.ts
│ │ ├ containerName: /src
│ │ ├ file: /src/callers.ts
│ │ ├ span:
│ │ │ ╭ /callHierarchyFile.ts:1:1-3:2
│ │ │ ╭ /src/callers.ts:1:1-3:2
│ │ │ │ 1: foo();
│ │ │ │ ^^^^^^
│ │ │ │ 2: function foo() {
Expand All @@ -29,13 +30,13 @@
│ │ │ │ ^
│ │ │ ╰
│ │ ├ selectionSpan:
│ │ │ ╭ /callHierarchyFile.ts:1:1-1:1
│ │ │ ╭ /src/callers.ts:1:1-1:1
│ │ │ │ 1: foo();
│ │ │ │ <
│ │ │ ╰
│ │ ╰ incoming: none
│ ├ fromSpans:
│ │ ╭ /callHierarchyFile.ts:1:1-1:4
│ │ ╭ /src/callers.ts:1:1-1:4
│ │ │ 1: foo();
│ │ │ ^^^
│ ╰ ╰
Expand Down
Loading
Loading