vendor tsgo
This commit is contained in:
402
tools/tsgo/internal/format/comment_test.go
Normal file
402
tools/tsgo/internal/format/comment_test.go
Normal file
@@ -0,0 +1,402 @@
|
||||
package format_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"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/ls/lsutil"
|
||||
"github.com/microsoft/typescript-go/internal/parser"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCommentFormatting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("format comment issue reproduction", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 4,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSTrue,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// Original code that causes the bug
|
||||
originalText := `class C {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async x() {}
|
||||
}`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Apply formatting once
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
firstFormatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// Check that the asterisk is not corrupted
|
||||
assert.Check(t, !strings.Contains(firstFormatted, "*/\n /"), "should not corrupt */ to /")
|
||||
assert.Check(t, strings.Contains(firstFormatted, "*/"), "should preserve */ token")
|
||||
assert.Check(t, strings.Contains(firstFormatted, "async"), "should preserve async keyword")
|
||||
|
||||
// Apply formatting a second time to test stability
|
||||
sourceFile2 := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, firstFormatted, core.ScriptKindTS)
|
||||
|
||||
edits2 := format.FormatDocument(ctx, sourceFile2)
|
||||
secondFormatted := applyBulkEdits(firstFormatted, edits2)
|
||||
|
||||
// Check that second formatting doesn't introduce corruption
|
||||
assert.Check(t, !strings.Contains(secondFormatted, " sync x()"), "should not corrupt async to sync")
|
||||
assert.Check(t, strings.Contains(secondFormatted, "async"), "should preserve async keyword on second pass")
|
||||
})
|
||||
|
||||
t.Run("format JSDoc with tab indentation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse, // Use tabs
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// Original code with tab indentation (tabs represented as \t)
|
||||
originalText := "class Foo {\n\t/**\n\t * @param {string} argument - This is a param description.\n\t */\n\texample(argument) {\nconsole.log(argument);\n\t}\n}"
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Apply formatting
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// Check that tabs come before spaces (not spaces before tabs)
|
||||
// The comment lines should have format: tab followed by space and asterisk
|
||||
// NOT: space followed by tab and asterisk
|
||||
assert.Check(t, !strings.Contains(formatted, " \t*"), "should not have space before tab before asterisk")
|
||||
assert.Check(t, strings.Contains(formatted, "\t *"), "should have tab before space before asterisk")
|
||||
|
||||
// Verify console.log is properly indented with tabs
|
||||
assert.Check(t, strings.Contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs")
|
||||
})
|
||||
|
||||
t.Run("format comment inside multi-line argument list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse, // Use tabs
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// Original code with proper indentation
|
||||
originalText := "console.log(\n\t\"a\",\n\t// the second arg\n\t\"b\"\n);"
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Apply formatting
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// The comment should remain indented with a tab
|
||||
assert.Check(t, strings.Contains(formatted, "\t// the second arg"), "comment should be indented with tab")
|
||||
// The comment should not lose its indentation
|
||||
assert.Check(t, !strings.Contains(formatted, "\n// the second arg"), "comment should not lose indentation")
|
||||
})
|
||||
|
||||
t.Run("format comment in chained method calls", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse, // Use tabs
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// Original code with proper indentation
|
||||
originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();"
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Apply formatting
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// The comment should remain indented
|
||||
assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented")
|
||||
// The comment should not lose its indentation
|
||||
assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not lose indentation")
|
||||
})
|
||||
|
||||
// Regression test for issue #1928 - panic when formatting chained method call with comment
|
||||
t.Run("format chained method call with comment (issue #1928)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse, // Use tabs
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// This code previously caused a panic with "strings: negative Repeat count"
|
||||
// because tokenIndentation was -1 and was being used directly for indentation
|
||||
originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();"
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Apply formatting - should not panic
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// Verify the comment maintains proper indentation and doesn't lose it
|
||||
assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented")
|
||||
assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not be at column 0")
|
||||
})
|
||||
|
||||
t.Run("multiline comment inside block that opens on first line (issue #2649)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
}, "\n")
|
||||
|
||||
originalText := `document.addEventListener('DOMContentLoaded', () => {
|
||||
/** @type {NodeListOf<HTMLSpanElement>} */
|
||||
const elements = document.querySelectorAll('.test')
|
||||
});`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.js",
|
||||
Path: "/test.js",
|
||||
}, originalText, core.ScriptKindJS)
|
||||
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
assert.Check(t, len(formatted) > 0, "formatted text should not be empty")
|
||||
})
|
||||
|
||||
t.Run("single-line comment inside block that opens on first line (issue #2649)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSFalse,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
}, "\n")
|
||||
|
||||
originalText := `document.addEventListener('DOMContentLoaded', () => {
|
||||
// a comment
|
||||
const x = 1
|
||||
});`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
assert.Check(t, len(formatted) > 0, "formatted text should not be empty")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFormatSelectionPreservesComments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("format selection should not delete block comment when selection ends inside comment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSTrue,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
}, "\n")
|
||||
|
||||
// Reproduce: const test/* comment */=5;
|
||||
// When selecting a range that ends inside the comment (before */), format selection should not delete the comment.
|
||||
originalText := `const test/* comment */=5;`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Select a range that starts at the beginning of the line and ends inside the block comment.
|
||||
// This covers `const test/* comment`, stopping before the closing `*/`.
|
||||
commentStart := strings.Index(originalText, "/*")
|
||||
selectionEnd := commentStart + len("/* comment") // ends inside the comment, before the closing `*/`
|
||||
|
||||
edits := format.FormatSelection(ctx, sourceFile, 0, selectionEnd)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// The entire statement should be preserved unchanged
|
||||
assert.Equal(t, formatted, originalText, "format selection should not delete the block comment or alter the statement")
|
||||
})
|
||||
|
||||
t.Run("format selection should not delete block comment when selection starts inside comment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSTrue,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
}, "\n")
|
||||
|
||||
originalText := `const test/* comment */=5;`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// Select from inside the comment to the end
|
||||
commentStart := strings.Index(originalText, "/*")
|
||||
selectionStart := commentStart + 3 // inside the comment
|
||||
|
||||
edits := format.FormatSelection(ctx, sourceFile, selectionStart, len(originalText))
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// The entire statement should be preserved unchanged
|
||||
assert.Equal(t, formatted, originalText, "format selection should not delete the block comment or alter the statement")
|
||||
})
|
||||
|
||||
t.Run("full document format should preserve block comment and add spaces", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 0,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSTrue,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
originalText := `const test/* comment */=5;`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// Full document format should preserve the comment and add spaces around `=`
|
||||
assert.Equal(t, "const test/* comment */ = 5;", formatted, "full format should preserve the block comment and add spaces")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSliceBoundsPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("format code with trailing semicolon should not panic", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := format.WithFormatCodeSettings(t.Context(), lsutil.FormatCodeSettings{
|
||||
EditorSettings: lsutil.EditorSettings{
|
||||
TabSize: 4,
|
||||
IndentSize: 4,
|
||||
BaseIndentSize: 4,
|
||||
NewLineCharacter: "\n",
|
||||
ConvertTabsToSpaces: core.TSTrue,
|
||||
IndentStyle: lsutil.IndentStyleSmart,
|
||||
TrimTrailingWhitespace: core.TSTrue,
|
||||
},
|
||||
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
|
||||
}, "\n")
|
||||
|
||||
// Code from the issue that causes slice bounds panic
|
||||
originalText := `const _enableDisposeWithListenerWarning = false
|
||||
// || Boolean("TRUE") // causes a linter warning so that it cannot be pushed
|
||||
;
|
||||
`
|
||||
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, originalText, core.ScriptKindTS)
|
||||
|
||||
// This should not panic
|
||||
edits := format.FormatDocument(ctx, sourceFile)
|
||||
formatted := applyBulkEdits(originalText, edits)
|
||||
|
||||
// Basic sanity checks
|
||||
assert.Check(t, len(formatted) > 0, "formatted text should not be empty")
|
||||
assert.Check(t, strings.Contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user