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
17 changes: 17 additions & 0 deletions internal/fourslash/baselineutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
smartSelectionCmd baselineCommand = "Smart Selection"
codeLensesCmd baselineCommand = "Code Lenses"
documentSymbolsCmd baselineCommand = "Document Symbols"
typeHierarchyCmd baselineCommand = "Type Hierarchy"
)

type baselineCommand string
Expand Down Expand Up @@ -77,6 +78,8 @@ func getBaselineExtension(command baselineCommand) string {
return "baseline"
case callHierarchyCmd:
return "callHierarchy.txt"
case typeHierarchyCmd:
return "typeHierarchy.txt"
case autoImportsCmd:
return "baseline.md"
default:
Expand Down Expand Up @@ -112,6 +115,20 @@ func (f *FourslashTest) getBaselineOptions(command baselineCommand, testPath str
return s
},
}
case typeHierarchyCmd:
return baseline.Options{
Subfolder: subfolder,
IsSubmodule: true,
DiffFixupOld: func(s string) string {
// TypeScript baselines have "/tests/cases/fourslash/" prefix in file paths
s = strings.ReplaceAll(s, "/tests/cases/fourslash/server/", "/")
s = strings.ReplaceAll(s, "/tests/cases/fourslash/", "/")
// SymbolKind enum differences
s = strings.ReplaceAll(s, "kind: getter", "kind: property")
s = strings.ReplaceAll(s, "kind: script", "kind: file")
return s
},
}
case renameCmd:
return baseline.Options{
Subfolder: subfolder,
Expand Down
171 changes: 171 additions & 0 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2422,6 +2422,11 @@ func formatCallHierarchyItemSpan(
prefix string,
closingPrefix string,
) {
if file == nil {
result.WriteString(fmt.Sprintf("%s╭ <external>:%d:%d-%d:%d\n", prefix, span.Start.Line+1, span.Start.Character+1, span.End.Line+1, span.End.Character+1))
result.WriteString(closingPrefix + "╰\n")
return
}
startLc := span.Start
endLc := span.End
startPos := f.converters.LineAndCharacterToPosition(file, span.Start)
Expand Down Expand Up @@ -2540,6 +2545,172 @@ func formatCallHierarchyItemSpans(
}
}

// VerifyBaselineTypeHierarchy generates a baseline for the type hierarchy at the current position.
func (f *FourslashTest) VerifyBaselineTypeHierarchy(t *testing.T) {
fileName := f.activeFilename
position := f.currentCaretPosition

params := &lsproto.TypeHierarchyPrepareParams{
TextDocument: lsproto.TextDocumentIdentifier{
Uri: lsconv.FileNameToDocumentURI(fileName),
},
Position: position,
}

prepareResult := sendRequest(t, f, lsproto.TextDocumentPrepareTypeHierarchyInfo, params)
if prepareResult.TypeHierarchyItems == nil || len(*prepareResult.TypeHierarchyItems) == 0 {
f.addResultToBaseline(t, typeHierarchyCmd, "No type hierarchy items available")
return
}

var result strings.Builder

for _, typeHierarchyItem := range *prepareResult.TypeHierarchyItems {
seen := make(map[typeHierarchyItemKey]bool)
itemFileName := typeHierarchyItem.Uri.FileName()
script := f.getScriptInfo(itemFileName)
formatTypeHierarchyItem(t, f, script, &result, *typeHierarchyItem, typeHierarchyItemDirectionRoot, seen, "")
}

f.addResultToBaseline(t, typeHierarchyCmd, strings.TrimSuffix(result.String(), "\n"))
}

type typeHierarchyItemDirection int

const (
typeHierarchyItemDirectionRoot typeHierarchyItemDirection = iota
typeHierarchyItemDirectionSupertypes
typeHierarchyItemDirectionSubtypes
)

type typeHierarchyItemKey struct {
uri lsproto.DocumentUri
range_ lsproto.Range
direction typeHierarchyItemDirection
}

func formatTypeHierarchyItem(
t *testing.T,
f *FourslashTest,
file *scriptInfo,
result *strings.Builder,
typeHierarchyItem lsproto.TypeHierarchyItem,
direction typeHierarchyItemDirection,
seen map[typeHierarchyItemKey]bool,
prefix string,
) {
key := typeHierarchyItemKey{
uri: typeHierarchyItem.Uri,
range_: typeHierarchyItem.Range,
direction: direction,
}
alreadySeen := seen[key]
seen[key] = true

type supertypesResult struct {
skip bool
seen bool
values []*lsproto.TypeHierarchyItem
}
type subtypesResult struct {
skip bool
seen bool
values []*lsproto.TypeHierarchyItem
}

var supertypes supertypesResult
var subtypes subtypesResult

if direction == typeHierarchyItemDirectionSubtypes {
supertypes.skip = true
} else if alreadySeen {
supertypes.seen = true
} else {
supertypesParams := &lsproto.TypeHierarchySupertypesParams{
Item: &typeHierarchyItem,
}
supertypesResponse := sendRequest(t, f, lsproto.TypeHierarchySupertypesInfo, supertypesParams)
if supertypesResponse.TypeHierarchyItems != nil {
supertypes.values = *supertypesResponse.TypeHierarchyItems
}
}

if direction == typeHierarchyItemDirectionSupertypes {
subtypes.skip = true
} else if alreadySeen {
subtypes.seen = true
} else {
subtypesParams := &lsproto.TypeHierarchySubtypesParams{
Item: &typeHierarchyItem,
}
subtypesResponse := sendRequest(t, f, lsproto.TypeHierarchySubtypesInfo, subtypesParams)
if subtypesResponse.TypeHierarchyItems != nil {
subtypes.values = *subtypesResponse.TypeHierarchyItems
}
}

trailingPrefix := prefix
result.WriteString(fmt.Sprintf("%s╭ name: %s\n", prefix, typeHierarchyItem.Name))
result.WriteString(fmt.Sprintf("%s├ kind: %s\n", prefix, symbolKindToLowercase(typeHierarchyItem.Kind)))
if typeHierarchyItem.Detail != nil && *typeHierarchyItem.Detail != "" {
result.WriteString(fmt.Sprintf("%s├ detail: %s\n", prefix, *typeHierarchyItem.Detail))
}
result.WriteString(fmt.Sprintf("%s├ file: %s\n", prefix, typeHierarchyItem.Uri.FileName()))
result.WriteString(prefix + "├ span:\n")
formatCallHierarchyItemSpan(f, file, result, typeHierarchyItem.Range, prefix+"│ ", prefix+"│ ")
result.WriteString(prefix + "├ selectionSpan:\n")
formatCallHierarchyItemSpan(f, file, result, typeHierarchyItem.SelectionRange, prefix+"│ ", prefix+"│ ")

// Handle supertypes
if supertypes.seen {
if subtypes.skip {
result.WriteString(trailingPrefix + "╰ supertypes: ...\n")
} else {
result.WriteString(prefix + "├ supertypes: ...\n")
}
} else if !supertypes.skip {
if len(supertypes.values) == 0 {
if subtypes.skip {
result.WriteString(trailingPrefix + "╰ supertypes: none\n")
} else {
result.WriteString(prefix + "├ supertypes: none\n")
}
} else {
result.WriteString(prefix + "├ supertypes:\n")
for i, supertype := range supertypes.values {
supertypeFileName := supertype.Uri.FileName()
supertypeFile := f.getScriptInfo(supertypeFileName)
itemTrailingPrefix := prefix + "│ ╰ "
if i < len(supertypes.values)-1 {
itemTrailingPrefix = prefix + "│ │ "
} else if !subtypes.skip && (!subtypes.seen || len(subtypes.values) > 0) {
itemTrailingPrefix = prefix + "│ │ "
}
result.WriteString(prefix + "│ ╭ supertype:\n")
formatTypeHierarchyItem(t, f, supertypeFile, result, *supertype, typeHierarchyItemDirectionSupertypes, seen, prefix+"│ │ ")
_ = itemTrailingPrefix // Used for formatting context
}
}
}

// Handle subtypes
if subtypes.seen {
result.WriteString(trailingPrefix + "╰ subtypes: ...\n")
} else if !subtypes.skip {
if len(subtypes.values) == 0 {
result.WriteString(trailingPrefix + "╰ subtypes: none\n")
} else {
result.WriteString(prefix + "├ subtypes:\n")
for _, subtype := range subtypes.values {
subtypeFileName := subtype.Uri.FileName()
subtypeFile := f.getScriptInfo(subtypeFileName)
result.WriteString(prefix + "│ ╭ subtype:\n")
formatTypeHierarchyItem(t, f, subtypeFile, result, *subtype, typeHierarchyItemDirectionSubtypes, seen, prefix+"│ │ ")
}
}
}
}

func (f *FourslashTest) VerifyBaselineDocumentHighlights(
t *testing.T,
preferences *lsutil.UserPreferences,
Expand Down
Loading