Skip to content

Commit 546838b

Browse files
authored
fix(tools/spxls): improve declaration end position handling in formatting (#1341)
- Fixed declaration end position handling to account for trailing comments. - Ensured correct trailing newlines after declarations and comments. - Fixed the exclusion of non-floating comments. Signed-off-by: Aofei Sheng <[email protected]>
1 parent 8ca78b1 commit 546838b

File tree

2 files changed

+98
-42
lines changed

2 files changed

+98
-42
lines changed

tools/spxls/internal/server/format.go

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"go/types"
77
"io/fs"
88
"path"
9+
"slices"
910
"time"
1011

1112
"github.com/goplus/builder/tools/spxls/internal/vfs"
@@ -175,21 +176,10 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
175176
)
176177
for _, decl := range astFile.Decls {
177178
// Skip the declaration if it appears after the error position.
178-
if errorPos.IsValid() && errorPos <= decl.Pos() {
179+
if errorPos.IsValid() && decl.Pos() >= errorPos {
179180
continue
180181
}
181182

182-
// Pre-process all comments within the declaration to exclude
183-
// them from floating comments.
184-
gopast.Inspect(decl, func(node gopast.Node) bool {
185-
cg, ok := node.(*gopast.CommentGroup)
186-
if !ok {
187-
return true
188-
}
189-
processedComments[cg] = struct{}{}
190-
return true
191-
})
192-
193183
switch decl := decl.(type) {
194184
case *gopast.GenDecl:
195185
switch decl.Tok {
@@ -214,6 +204,23 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
214204
default:
215205
otherDecls = append(otherDecls, decl)
216206
}
207+
208+
// Pre-process all comments within the declaration to exclude
209+
// them from floating comments.
210+
if doc := getDeclDoc(decl); doc != nil {
211+
processedComments[doc] = struct{}{}
212+
}
213+
startLine := compileResult.fset.Position(decl.Pos()).Line
214+
endLine := compileResult.fset.Position(decl.End()).Line
215+
for _, cg := range astFile.Comments {
216+
if _, ok := processedComments[cg]; ok {
217+
continue
218+
}
219+
cgStartLine := compileResult.fset.Position(cg.Pos()).Line
220+
if cgStartLine >= startLine && cgStartLine <= endLine {
221+
processedComments[cg] = struct{}{}
222+
}
223+
}
217224
}
218225

219226
// Reorder declarations: imports -> types -> consts -> vars -> funcs -> others.
@@ -259,30 +266,51 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
259266

260267
// Format the sorted declarations.
261268
formattedBuf := bytes.NewBuffer(make([]byte, 0, len(astFile.Code)))
269+
ensureTrailingNewlines := func(count int) {
270+
if formattedBuf.Len() == 0 {
271+
return
272+
}
273+
for _, b := range slices.Backward(formattedBuf.Bytes()) {
274+
if b != '\n' {
275+
break
276+
}
277+
count--
278+
}
279+
for range count {
280+
formattedBuf.WriteByte('\n')
281+
}
282+
}
262283

263284
// Handle declarations and floating comments in order of their position.
264-
var lastDeclEnd goptoken.Pos
265285
processDecl := func(decl gopast.Decl) error {
266286
startPos := decl.Pos()
267287
if doc := getDeclDoc(decl); doc != nil {
268288
startPos = doc.Pos()
269289
}
270290

271-
if formattedBuf.Len() > 0 {
272-
formattedBuf.WriteByte('\n')
291+
endPos := decl.End()
292+
endLine := compileResult.fset.Position(endPos).Line
293+
for _, cg := range astFile.Comments {
294+
if compileResult.fset.Position(cg.Pos()).Line != endLine {
295+
continue
296+
}
297+
if cg.Pos() > endPos {
298+
endPos = cg.End()
299+
break
300+
}
273301
}
302+
303+
ensureTrailingNewlines(2)
274304
if genDecl, ok := decl.(*gopast.GenDecl); ok && genDecl.Tok == goptoken.VAR {
275305
if err := gopfmt.Node(formattedBuf, compileResult.fset, decl); err != nil {
276306
return err
277307
}
278308
} else {
279309
start := compileResult.fset.Position(startPos).Offset
280-
end := compileResult.fset.Position(decl.End()).Offset
310+
end := compileResult.fset.Position(endPos).Offset
281311
formattedBuf.Write(astFile.Code[start:end])
282312
}
283-
formattedBuf.WriteByte('\n')
284-
285-
lastDeclEnd = decl.End()
313+
ensureTrailingNewlines(1)
286314
return nil
287315
}
288316

@@ -294,10 +322,9 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
294322
}
295323
processedComments[cg] = struct{}{}
296324

297-
if errorPos.IsValid() && errorPos <= cg.Pos() {
298-
continue
299-
}
300-
if lastDeclEnd.IsValid() && cg.Pos() < lastDeclEnd {
325+
// Skip the comment if it appears after the error position or
326+
// shadow entry position.
327+
if errorPos.IsValid() && cg.Pos() >= errorPos {
301328
continue
302329
}
303330
if shadowEntryPos.IsValid() && cg.Pos() >= shadowEntryPos {
@@ -307,9 +334,6 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
307334
// Process declarations that should come before this comment.
308335
for ; declIndex < len(sortedDecls); declIndex++ {
309336
decl := sortedDecls[declIndex]
310-
if errorPos.IsValid() && errorPos <= decl.Pos() {
311-
continue
312-
}
313337

314338
startPos := decl.Pos()
315339
if doc := getDeclDoc(decl); doc != nil {
@@ -325,35 +349,27 @@ func (s *Server) formatSpxDecls(snapshot *vfs.MapFS, spxFile string) ([]byte, er
325349
}
326350

327351
// Add the floating comment.
328-
if formattedBuf.Len() > 0 {
329-
formattedBuf.WriteByte('\n')
330-
}
352+
ensureTrailingNewlines(2)
331353
start := compileResult.fset.Position(cg.Pos()).Offset
332354
end := compileResult.fset.Position(cg.End()).Offset
333355
formattedBuf.Write(astFile.Code[start:end])
334-
formattedBuf.WriteByte('\n')
356+
ensureTrailingNewlines(1)
335357
}
336358

337359
// Process remaining declarations before shadow entry.
338360
for ; declIndex < len(sortedDecls); declIndex++ {
339-
decl := sortedDecls[declIndex]
340-
if errorPos.IsValid() && errorPos <= decl.Pos() {
341-
continue
342-
}
343-
if err := processDecl(decl); err != nil {
361+
if err := processDecl(sortedDecls[declIndex]); err != nil {
344362
return nil, err
345363
}
346364
}
347365

348366
// Add the shadow entry if it exists and not empty.
349367
if shadowEntryPos.IsValid() {
350-
if formattedBuf.Len() > 0 {
351-
formattedBuf.WriteByte('\n')
352-
}
368+
ensureTrailingNewlines(2)
353369
start := compileResult.fset.Position(shadowEntryPos).Offset
354370
end := compileResult.fset.Position(astFile.ShadowEntry.End()).Offset
355371
formattedBuf.Write(astFile.Code[start:end])
356-
formattedBuf.WriteByte('\n')
372+
ensureTrailingNewlines(1)
357373
}
358374

359375
formatted := formattedBuf.Bytes()

tools/spxls/internal/server/format_test.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,9 @@ var a int
363363
// floating comment2
364364
365365
// comment for func test
366-
func test() {}
366+
func test() {
367+
// comment inside func test
368+
}
367369
368370
// floating comment3
369371
@@ -383,7 +385,7 @@ const b = "123"
383385
assert.Contains(t, edits, TextEdit{
384386
Range: Range{
385387
Start: Position{Line: 0, Character: 0},
386-
End: Position{Line: 18, Character: 0},
388+
End: Position{Line: 20, Character: 0},
387389
},
388390
NewText: `import "fmt"
389391
@@ -402,9 +404,47 @@ var (
402404
)
403405
404406
// comment for func test
405-
func test() {}
407+
func test() {
408+
// comment inside func test
409+
}
406410
407411
// floating comment4
412+
`,
413+
})
414+
})
415+
416+
t.Run("WithTrailingComments", func(t *testing.T) {
417+
s := New(newMapFSWithoutModTime(map[string][]byte{
418+
"main.spx": []byte(`import "fmt" // trailing comment for import "fmt"
419+
420+
const foo = "bar" // trailing comment for const foo
421+
422+
var a int // trailing comment for var a
423+
424+
func test() {} // trailing comment for func test
425+
`),
426+
}), nil)
427+
params := &DocumentFormattingParams{
428+
TextDocument: TextDocumentIdentifier{URI: "file:///main.spx"},
429+
}
430+
431+
edits, err := s.textDocumentFormatting(params)
432+
require.NoError(t, err)
433+
require.Len(t, edits, 1)
434+
assert.Contains(t, edits, TextEdit{
435+
Range: Range{
436+
Start: Position{Line: 0, Character: 0},
437+
End: Position{Line: 7, Character: 0},
438+
},
439+
NewText: `import "fmt" // trailing comment for import "fmt"
440+
441+
const foo = "bar" // trailing comment for const foo
442+
443+
var (
444+
a int
445+
) // trailing comment for var a
446+
447+
func test() {} // trailing comment for func test
408448
`,
409449
})
410450
})

0 commit comments

Comments
 (0)