diff --git a/internal/format/indent.go b/internal/format/indent.go index d5ccb949a3..8defe56d29 100644 --- a/internal/format/indent.go +++ b/internal/format/indent.go @@ -341,12 +341,13 @@ func getVisualListRange(node *ast.Node, list core.TextRange, sourceFile *ast.Sou } else { priorEnd = prior.End() } - next := astnav.FindNextToken(prior, node, sourceFile) + // Find the token that starts at or after list.End() using the scanner + scan := scanner.GetScannerForSourceFile(sourceFile, list.End()) var nextStart int - if next == nil { + if scan.Token() == ast.KindEndOfFile { nextStart = list.End() } else { - nextStart = next.Pos() + nextStart = scan.TokenStart() } return core.NewTextRange(priorEnd, nextStart) } diff --git a/internal/format/indent_test.go b/internal/format/indent_test.go new file mode 100644 index 0000000000..ce08ecb3f9 --- /dev/null +++ b/internal/format/indent_test.go @@ -0,0 +1,50 @@ +package format_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/format" + "github.com/microsoft/typescript-go/internal/parser" + "gotest.tools/v3/assert" +) + +func TestGetContainingList_NamedImports(t *testing.T) { + t.Parallel() + + text := `import type { + AAA, + BBB, +} from "./bar";` + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, text, core.ScriptKindTS) + + // Find ImportSpecifier nodes (AAA and BBB) + var importSpecifiers []*ast.Node + forEachDescendantOfKind(sourceFile.AsNode(), ast.KindImportSpecifier, func(node *ast.Node) { + importSpecifiers = append(importSpecifiers, node) + }) + + assert.Assert(t, len(importSpecifiers) == 2, "Expected 2 import specifiers, got %d", len(importSpecifiers)) + + // Test GetContainingList for each import specifier + for _, specifier := range importSpecifiers { + list := format.GetContainingList(specifier, sourceFile) + assert.Assert(t, list != nil, "GetContainingList should return non-nil for import specifier") + assert.Assert(t, len(list.Nodes) == 2, "Expected list with 2 elements, got %d", len(list.Nodes)) + } +} + +func forEachDescendantOfKind(node *ast.Node, kind ast.Kind, action func(*ast.Node)) { + node.ForEachChild(func(child *ast.Node) bool { + if child.Kind == kind { + action(child) + } + forEachDescendantOfKind(child, kind, action) + return false + }) +} diff --git a/internal/fourslash/tests/codeFixPromoteTypeOnlyOrderingCrash_test.go b/internal/fourslash/tests/codeFixPromoteTypeOnlyOrderingCrash_test.go new file mode 100644 index 0000000000..0edde66790 --- /dev/null +++ b/internal/fourslash/tests/codeFixPromoteTypeOnlyOrderingCrash_test.go @@ -0,0 +1,40 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +// Test case for crash when promoting type-only import to value import +// when existing type imports precede the new value import +// https://github.com/microsoft/typescript-go/issues/XXX +func TestCodeFixPromoteTypeOnlyOrderingCrash(t *testing.T) { + fourslash.SkipIfFailing(t) + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: node18 +// @verbatimModuleSyntax: true +// @Filename: /bar.ts +export interface AAA {} +export class BBB {} +// @Filename: /foo.ts +import type { + AAA, + BBB, +} from "./bar"; + +let x: AAA = new BBB()` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToFile(t, "/foo.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { + BBB, + type AAA, +} from "./bar"; + +let x: AAA = new BBB()`, + }, nil /*preferences*/) +}