Skip to content

Commit

Permalink
internal/lsp: fix end positions for multi-line comments with CRLF lines
Browse files Browse the repository at this point in the history
The previous work-around assumed that the end of the comment would be
off by one line, when in reality, it is off by the offset of the number
of \r in the comment, which may result in the comment end position being
off by multiple lines. Work around this by scanning for the end of the
comment position using text/scanner.

Fixes golang/go#42646

Change-Id: Icc33e889546f324c6b65b55a98dea587f84c8f01
Reviewed-on: https://go-review.googlesource.com/c/tools/+/270879
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
  • Loading branch information
stamblerre committed Nov 18, 2020
1 parent bd56c0a commit 598b068
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 5 deletions.
39 changes: 39 additions & 0 deletions gopls/internal/regtest/formatting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,42 @@ func Hi() {
}
})
}

func TestCRLF_42646(t *testing.T) {
runner.Run(t, "-- main.go --", func(t *testing.T, env *Env) {
want := `package main
import (
"fmt"
)
/*
func upload(c echo.Context) error {
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
fmt.Fprintf(w, "POST request successful")
path_ver := r.FormValue("path_ver")
ukclin_ver := r.FormValue("ukclin_ver")
fmt.Fprintf(w, "Name = %s\n", path_ver)
fmt.Fprintf(w, "Address = %s\n", ukclin_ver)
}
*/
func main() {
const server_port = 8080
fmt.Printf("port: %d\n", server_port)
}
`
crlf := strings.ReplaceAll(want, "\n", "\r\n")
env.CreateBuffer("main.go", crlf)
env.OrganizeImports("main.go")
got := env.Editor.BufferText("main.go")
got = strings.ReplaceAll(got, "\r\n", "\n") // convert everything to LF for simplicity
if want != got {
t.Errorf("unexpected content after save:\n%s", tests.Diff(want, got))
}
})
}
30 changes: 25 additions & 5 deletions internal/lsp/source/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"go/parser"
"go/token"
"strings"
"text/scanner"

"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
Expand Down Expand Up @@ -228,12 +229,17 @@ func importPrefix(src []byte) string {
}
for _, c := range f.Comments {
if end := tok.Offset(c.End()); end > importEnd {
// Work-around golang/go#41197: For multi-line comments add +2 to
// the offset. The end position does not account for the */ at the
// end.
startLine := tok.Position(c.Pos()).Line
endLine := tok.Position(c.End()).Line
if end+2 <= tok.Size() && tok.Position(tok.Pos(end+2)).Line == endLine {
end += 2

// Work around golang/go#41197 by checking if the comment might
// contain "\r", and if so, find the actual end position of the
// comment by scanning the content of the file.
startOffset := tok.Offset(c.Pos())
if startLine != endLine && bytes.Contains(src[startOffset:], []byte("\r")) {
if commentEnd := scanForCommentEnd(tok, src[startOffset:]); commentEnd > 0 {
end = startOffset + commentEnd
}
}
importEnd = maybeAdjustToLineEnd(tok.Pos(end), true)
}
Expand All @@ -244,6 +250,20 @@ func importPrefix(src []byte) string {
return string(src[:importEnd])
}

// scanForCommentEnd returns the offset of the end of the multi-line comment
// at the start of the given byte slice.
func scanForCommentEnd(tok *token.File, src []byte) int {
var s scanner.Scanner
s.Init(bytes.NewReader(src))
s.Mode ^= scanner.SkipComments

t := s.Scan()
if t == scanner.Comment {
return s.Pos().Offset
}
return 0
}

func computeTextEdits(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile, formatted string) ([]protocol.TextEdit, error) {
_, done := event.Start(ctx, "source.computeTextEdits")
defer done()
Expand Down

0 comments on commit 598b068

Please sign in to comment.