Skip to content
Merged
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
20 changes: 20 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -8706,6 +8706,10 @@ func (node *TemplateHead) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewTemplateHead(node.Text, node.RawText, node.TemplateFlags), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsTemplateHead(node *Node) bool {
return node.Kind == KindTemplateHead
}

// TemplateMiddle

type TemplateMiddle struct {
Expand All @@ -8726,6 +8730,10 @@ func (node *TemplateMiddle) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewTemplateMiddle(node.Text, node.RawText, node.TemplateFlags), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsTemplateMiddle(node *Node) bool {
return node.Kind == KindTemplateMiddle
}

// TemplateTail

type TemplateTail struct {
Expand All @@ -8746,6 +8754,10 @@ func (node *TemplateTail) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewTemplateTail(node.Text, node.RawText, node.TemplateFlags), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsTemplateTail(node *Node) bool {
return node.Kind == KindTemplateTail
}

// TemplateLiteralTypeNode

type TemplateLiteralTypeNode struct {
Expand Down Expand Up @@ -9635,6 +9647,10 @@ func (node *JSDocTypeExpression) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewJSDocTypeExpression(node.Type), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsJSDocTypeExpression(node *Node) bool {
return node.Kind == KindJSDocTypeExpression
}

// JSDocNonNullableType

type JSDocNonNullableType struct {
Expand Down Expand Up @@ -10565,6 +10581,10 @@ func (node *JSDocTypeLiteral) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewJSDocTypeLiteral(node.JSDocPropertyTags, node.IsArrayType), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsJSDocTypeLiteral(node *Node) bool {
return node.Kind == KindJSDocTypeLiteral
}

// JSDocSignature
type JSDocSignature struct {
TypeNodeBase
Expand Down
19 changes: 19 additions & 0 deletions internal/fourslash/_scripts/convertFourslash.mts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
return parseBaselineQuickInfo(callExpression.arguments);
case "baselineSignatureHelp":
return [parseBaselineSignatureHelp(callExpression.arguments)];
case "baselineSmartSelection":
return [parseBaselineSmartSelection(callExpression.arguments)];
case "baselineGoToDefinition":
case "baselineGetDefinitionAtPosition":
case "baselineGoToType":
Expand Down Expand Up @@ -1422,6 +1424,16 @@ function parseBaselineSignatureHelp(args: ts.NodeArray<ts.Expression>): Cmd {
};
}

function parseBaselineSmartSelection(args: ts.NodeArray<ts.Expression>): Cmd {
if (args.length !== 0) {
// All calls are currently empty!
throw new Error("Expected no arguments in verify.baselineSmartSelection");
}
return {
kind: "verifyBaselineSmartSelection",
};
}

function parseKind(expr: ts.Expression): string | undefined {
if (!ts.isStringLiteral(expr)) {
console.error(`Expected string literal for kind, got ${expr.getText()}`);
Expand Down Expand Up @@ -1591,6 +1603,10 @@ interface VerifyBaselineSignatureHelpCmd {
kind: "verifyBaselineSignatureHelp";
}

interface VerifyBaselineSmartSelection {
kind: "verifyBaselineSmartSelection";
}

interface VerifyBaselineRenameCmd {
kind: "verifyBaselineRename" | "verifyBaselineRenameAtRangesWithText";
args: string[];
Expand Down Expand Up @@ -1635,6 +1651,7 @@ type Cmd =
| VerifyBaselineGoToDefinitionCmd
| VerifyBaselineQuickInfoCmd
| VerifyBaselineSignatureHelpCmd
| VerifyBaselineSmartSelection
| GoToCmd
| EditCmd
| VerifyQuickInfoCmd
Expand Down Expand Up @@ -1754,6 +1771,8 @@ function generateCmd(cmd: Cmd): string {
return `f.VerifyBaselineHover(t)`;
case "verifyBaselineSignatureHelp":
return `f.VerifyBaselineSignatureHelp(t)`;
case "verifyBaselineSmartSelection":
return `f.VerifyBaselineSelectionRanges(t)`;
case "goTo":
return generateGoToCommand(cmd);
case "edit":
Expand Down
7 changes: 6 additions & 1 deletion internal/fourslash/baselineutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func getBaselineFileName(t *testing.T, command string) string {

func getBaselineExtension(command string) string {
switch command {
case "QuickInfo", "SignatureHelp":
case "QuickInfo", "SignatureHelp", "Smart Selection":
return "baseline"
case "Auto Imports":
return "baseline.md"
Expand All @@ -61,6 +61,11 @@ func getBaselineExtension(command string) string {
func getBaselineOptions(command string) baseline.Options {
subfolder := "fourslash/" + normalizeCommandName(command)
switch command {
case "Smart Selection":
return baseline.Options{
Subfolder: subfolder,
IsSubmodule: true,
}
case "findRenameLocations":
return baseline.Options{
Subfolder: subfolder,
Expand Down
143 changes: 143 additions & 0 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,149 @@ func (f *FourslashTest) VerifyBaselineSignatureHelp(t *testing.T) {
}
}

func (f *FourslashTest) VerifyBaselineSelectionRanges(t *testing.T) {
markers := f.Markers()
var result strings.Builder
newLine := "\n"

for i, marker := range markers {
if i > 0 {
result.WriteString(newLine + strings.Repeat("=", 80) + newLine + newLine)
}

script := f.getScriptInfo(marker.FileName())
fileContent := script.content

// Add the marker position indicator
markerPos := marker.Position
baselineContent := fileContent[:markerPos] + "/**/" + fileContent[markerPos:] + newLine
result.WriteString(baselineContent)

// Get selection ranges at this marker
params := &lsproto.SelectionRangeParams{
TextDocument: lsproto.TextDocumentIdentifier{
Uri: ls.FileNameToDocumentURI(marker.FileName()),
},
Positions: []lsproto.Position{marker.LSPosition},
}

resMsg, selectionRangeResult, resultOk := sendRequest(t, f, lsproto.TextDocumentSelectionRangeInfo, params)
markerNameStr := *core.OrElse(marker.Name, ptrTo("(unnamed)"))
if resMsg == nil {
t.Fatalf("Nil response received for selection range request at marker '%s'", markerNameStr)
}
if !resultOk {
if resMsg.AsResponse().Error != nil {
t.Fatalf("Error response for selection range request at marker '%s': %v", markerNameStr, resMsg.AsResponse().Error)
}
t.Fatalf("Unexpected selection range response type at marker '%s': %T", markerNameStr, resMsg.AsResponse().Result)
}

if selectionRangeResult.SelectionRanges == nil || len(*selectionRangeResult.SelectionRanges) == 0 {
result.WriteString("No selection ranges available\n")
continue
}

selectionRange := (*selectionRangeResult.SelectionRanges)[0]

// Add blank line after source code section
result.WriteString(newLine)

// Walk through the selection range chain
for selectionRange != nil {
start := int(f.converters.LineAndCharacterToPosition(script, selectionRange.Range.Start))
end := int(f.converters.LineAndCharacterToPosition(script, selectionRange.Range.End))

// Create a masked version of the file showing only this range
runes := []rune(fileContent)
masked := make([]rune, len(runes))
for i, ch := range runes {
if i >= start && i < end {
// Keep characters in the selection range
if ch == ' ' {
masked[i] = '•'
} else if ch == '\n' || ch == '\r' {
masked[i] = ch // Keep line breaks as-is, will add arrow later
} else {
masked[i] = ch
}
} else {
// Replace characters outside the range
if ch == '\n' || ch == '\r' {
masked[i] = ch
} else {
masked[i] = ' '
}
}
}

maskedStr := string(masked)

// Add line break arrows
maskedStr = strings.ReplaceAll(maskedStr, "\n", "↲\n")
maskedStr = strings.ReplaceAll(maskedStr, "\r", "↲\r")

// Remove blank lines
lines := strings.Split(maskedStr, "\n")
var nonBlankLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" && trimmed != "↲" {
nonBlankLines = append(nonBlankLines, line)
}
}
maskedStr = strings.Join(nonBlankLines, "\n")

// Find leading and trailing width of non-whitespace characters
maskedRunes := []rune(maskedStr)
isRealCharacter := func(ch rune) bool {
return ch != '•' && ch != '↲' && !stringutil.IsWhiteSpaceLike(ch)
}

leadingWidth := -1
for i, ch := range maskedRunes {
if isRealCharacter(ch) {
leadingWidth = i
break
}
}

trailingWidth := -1
for j := len(maskedRunes) - 1; j >= 0; j-- {
if isRealCharacter(maskedRunes[j]) {
trailingWidth = j
break
}
}

if leadingWidth != -1 && trailingWidth != -1 && leadingWidth <= trailingWidth {
// Clean up middle section
prefix := string(maskedRunes[:leadingWidth])
middle := string(maskedRunes[leadingWidth : trailingWidth+1])
suffix := string(maskedRunes[trailingWidth+1:])

middle = strings.ReplaceAll(middle, "•", " ")
middle = strings.ReplaceAll(middle, "↲", "")

maskedStr = prefix + middle + suffix
}

// Add blank line before multi-line ranges
if strings.Contains(maskedStr, "\n") {
result.WriteString(newLine)
}

result.WriteString(maskedStr)
if !strings.HasSuffix(maskedStr, "\n") {
result.WriteString(newLine)
}

selectionRange = selectionRange.Parent
}
}
f.addResultToBaseline(t, "Smart Selection", strings.TrimSuffix(result.String(), "\n"))
}

func (f *FourslashTest) VerifyBaselineDocumentHighlights(
t *testing.T,
preferences *ls.UserPreferences,
Expand Down
22 changes: 22 additions & 0 deletions internal/fourslash/tests/gen/smartSelection_JSDocTags10_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `/**
* @template T
* @extends {/**/Set<T>}
*/
class A extends B {
}`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineSelectionRanges(t)
}
21 changes: 21 additions & 0 deletions internal/fourslash/tests/gen/smartSelection_JSDocTags11_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `const x = 1;
type Foo = {
/** comment */
/*2*/readonly /*1*/status: number;
};`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineSelectionRanges(t)
}
20 changes: 20 additions & 0 deletions internal/fourslash/tests/gen/smartSelection_JSDocTags12_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `type B = {};
type A = {
a(/** Comment */ /*1*/p0: number, /** Comment */ /*2*/p1: number, /** Comment */ /*3*/p2: number): string;
};`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineSelectionRanges(t)
}
23 changes: 23 additions & 0 deletions internal/fourslash/tests/gen/smartSelection_JSDocTags13_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `let a;
let b: {
/** Comment */ /*1*/p0: number
/** Comment */ /*2*/p1: number
/** Comment */ /*3*/p2: number
};
let c;`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineSelectionRanges(t)
}
20 changes: 20 additions & 0 deletions internal/fourslash/tests/gen/smartSelection_JSDocTags1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `/**
* @returns {Array<{ value: /**/string }>}
*/
function foo() { return [] }`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineSelectionRanges(t)
}
Loading