1
0
mirror of https://github.com/golang/go synced 2024-11-15 00:20:30 -07:00

go/parser: set File{Start,End} correctly in all cases

...even when the file is empty or lacks a valid package decl.

+ test

Fixes #70162

Change-Id: Idf33998911475fe8cdfaa4786ac3ba1745f54963
Reviewed-on: https://go-review.googlesource.com/c/go/+/624655
Reviewed-by: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2024-11-02 13:38:44 -04:00
parent 9c93d99c61
commit 9c5f5bd6d3
4 changed files with 43 additions and 12 deletions

View File

@ -1065,11 +1065,15 @@ type File struct {
}
// Pos returns the position of the package declaration.
// (Use FileStart for the start of the entire file.)
// It may be invalid, for example in an empty file.
//
// (Use FileStart for the start of the entire file. It is always valid.)
func (f *File) Pos() token.Pos { return f.Package }
// End returns the end of the last declaration in the file.
// (Use FileEnd for the end of the entire file.)
// It may be invalid, for example in an empty file.
//
// (Use FileEnd for the end of the entire file. It is always valid.)
func (f *File) End() token.Pos {
if n := len(f.Decls); n > 0 {
return f.Decls[n-1].End()

View File

@ -92,6 +92,8 @@ func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast
return nil, err
}
file := fset.AddFile(filename, -1, len(text))
var p parser
defer func() {
if e := recover(); e != nil {
@ -115,12 +117,17 @@ func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast
}
}
// Ensure the start/end are consistent,
// whether parsing succeeded or not.
f.FileStart = token.Pos(file.Base())
f.FileEnd = token.Pos(file.Base() + file.Size())
p.errors.Sort()
err = p.errors.Err()
}()
// parse source
p.init(fset, filename, text, mode)
p.init(file, text, mode)
f = p.parseFile()
return
@ -215,7 +222,8 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
}()
// parse expr
p.init(fset, filename, text, mode)
file := fset.AddFile(filename, -1, len(text))
p.init(file, text, mode)
expr = p.parseRhs()
// If a semicolon was inserted, consume it;

View File

@ -65,8 +65,8 @@ type parser struct {
nestLev int
}
func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
p.file = fset.AddFile(filename, -1, len(src))
func (p *parser) init(file *token.File, src []byte, mode Mode) {
p.file = file
eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) }
p.scanner.Init(p.file, src, eh, scanner.ScanComments)
@ -2904,8 +2904,7 @@ func (p *parser) parseFile() *ast.File {
Package: pos,
Name: ident,
Decls: decls,
FileStart: token.Pos(p.file.Base()),
FileEnd: token.Pos(p.file.Base() + p.file.Size()),
// File{Start,End} are set by the defer in the caller.
Imports: p.imports,
Comments: p.comments,
GoVersion: p.goVersion,

View File

@ -838,3 +838,23 @@ func TestParseTypeParamsAsParenExpr(t *testing.T) {
t.Fatalf("typeParam is a %T; want: *ast.ParenExpr", typeParam)
}
}
// TestEmptyFileHasValidStartEnd is a regression test for #70162.
func TestEmptyFileHasValidStartEnd(t *testing.T) {
for _, test := range []struct {
src string
want string // "Pos() FileStart FileEnd"
}{
{src: "", want: "0 1 1"},
{src: "package ", want: "0 1 9"},
{src: "package p", want: "1 1 10"},
{src: "type T int", want: "0 1 11"},
} {
fset := token.NewFileSet()
f, _ := ParseFile(fset, "a.go", test.src, 0)
got := fmt.Sprintf("%d %d %d", f.Pos(), f.FileStart, f.FileEnd)
if got != test.want {
t.Fatalf("src = %q: got %s, want %s", test.src, got, test.want)
}
}
}