Skip to content
Closed
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
39 changes: 39 additions & 0 deletions internal/format/comment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,42 @@ func TestCommentFormatting(t *testing.T) {
func contains(s, substr string) bool {
return len(substr) > 0 && strings.Contains(s, substr)
}

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(), &format.FormatCodeSettings{
EditorSettings: format.EditorSettings{
TabSize: 4,
IndentSize: 4,
BaseIndentSize: 4,
NewLineCharacter: "\n",
ConvertTabsToSpaces: true,
IndentStyle: format.IndentStyleSmart,
TrimTrailingWhitespace: true,
},
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, contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name")
})
}
13 changes: 12 additions & 1 deletion internal/format/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,12 @@ func (w *formatSpanWorker) characterToColumn(startLinePosition int, characterInL
}

func (w *formatSpanWorker) indentationIsDifferent(indentationString string, startLinePosition int) bool {
return indentationString != w.sourceFile.Text()[startLinePosition:startLinePosition+len(indentationString)]
// Check bounds to prevent slice panic
endPosition := startLinePosition + len(indentationString)
if endPosition > len(w.sourceFile.Text()) {
return true
}
return indentationString != w.sourceFile.Text()[startLinePosition:endPosition]
}

func (w *formatSpanWorker) indentTriviaItems(trivia []TextRangeWithKind, commentIndentation int, indentNextTokenOrTrivia bool, indentSingleLine func(item TextRangeWithKind)) bool {
Expand Down Expand Up @@ -969,6 +974,12 @@ func (w *formatSpanWorker) indentMultilineComment(commentRange core.TextRange, i
}

func getIndentationString(indentation int, options *FormatCodeSettings) string {
// Handle negative indentation (e.g., Constants.Unknown = -1)
// Return empty string like TypeScript's repeatString does when count is negative
if indentation < 0 {
return ""
}

// go's `strings.Repeat` already has static, global caching for repeated tabs and spaces, so there's no need to cache here like in strada
if !options.ConvertTabsToSpaces {
tabs := int(math.Floor(float64(indentation) / float64(options.TabSize)))
Expand Down