Skip to content

Commit

Permalink
go/build: report positions for go:embed directives
Browse files Browse the repository at this point in the history
For golang#43469
For golang#43632

Change-Id: I9ac2da690344935da0e1dbe00b134dfcee65ec8a
Reviewed-on: https://go-review.googlesource.com/c/go/+/283636
Run-TryBot: Jay Conrod <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Trust: Jay Conrod <[email protected]>
Reviewed-by: Bryan C. Mills <[email protected]>
  • Loading branch information
Jay Conrod committed Jan 14, 2021
1 parent 7eb31d9 commit 6aa28d3
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 66 deletions.
3 changes: 3 additions & 0 deletions api/go1.16.txt
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,12 @@ pkg embed, type FS struct
pkg flag, func Func(string, string, func(string) error)
pkg flag, method (*FlagSet) Func(string, string, func(string) error)
pkg go/build, type Package struct, EmbedPatterns []string
pkg go/build, type Package struct, EmbedPatternPos map[string][]token.Position
pkg go/build, type Package struct, IgnoredOtherFiles []string
pkg go/build, type Package struct, TestEmbedPatterns []string
pkg go/build, type Package struct, TestEmbedPatternPos map[string][]token.Position
pkg go/build, type Package struct, XTestEmbedPatterns []string
pkg go/build, type Package struct, XTestEmbedPatternPos map[string][]token.Position
pkg html/template, func ParseFS(fs.FS, ...string) (*Template, error)
pkg html/template, method (*Template) ParseFS(fs.FS, ...string) (*Template, error)
pkg io, func NopCloser(Reader) ReadCloser
Expand Down
68 changes: 40 additions & 28 deletions src/go/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,12 @@ type Package struct {
// //go:embed a* b.c
// then the list will contain those two strings as separate entries.
// (See package embed for more details about //go:embed.)
EmbedPatterns []string // patterns from GoFiles, CgoFiles
TestEmbedPatterns []string // patterns from TestGoFiles
XTestEmbedPatterns []string // patterns from XTestGoFiles
EmbedPatterns []string // patterns from GoFiles, CgoFiles
EmbedPatternPos map[string][]token.Position // line information for EmbedPatterns
TestEmbedPatterns []string // patterns from TestGoFiles
TestEmbedPatternPos map[string][]token.Position // line information for TestEmbedPatterns
XTestEmbedPatterns []string // patterns from XTestGoFiles
XTestEmbedPatternPos map[string][]token.Position // line information for XTestEmbedPatternPos
}

// IsCommand reports whether the package is considered a
Expand Down Expand Up @@ -794,10 +797,12 @@ Found:
var badGoError error
var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems)
var firstFile, firstCommentFile string
var embeds, testEmbeds, xTestEmbeds []string
imported := make(map[string][]token.Position)
testImported := make(map[string][]token.Position)
xTestImported := make(map[string][]token.Position)
embedPos := make(map[string][]token.Position)
testEmbedPos := make(map[string][]token.Position)
xTestEmbedPos := make(map[string][]token.Position)
importPos := make(map[string][]token.Position)
testImportPos := make(map[string][]token.Position)
xTestImportPos := make(map[string][]token.Position)
allTags := make(map[string]bool)
fset := token.NewFileSet()
for _, d := range dirs {
Expand Down Expand Up @@ -920,40 +925,42 @@ Found:
}
}

var fileList, embedList *[]string
var importMap map[string][]token.Position
var fileList *[]string
var importMap, embedMap map[string][]token.Position
switch {
case isCgo:
allTags["cgo"] = true
if ctxt.CgoEnabled {
fileList = &p.CgoFiles
importMap = imported
embedList = &embeds
importMap = importPos
embedMap = embedPos
} else {
// Ignore imports from cgo files if cgo is disabled.
// Ignore imports and embeds from cgo files if cgo is disabled.
fileList = &p.IgnoredGoFiles
}
case isXTest:
fileList = &p.XTestGoFiles
importMap = xTestImported
embedList = &xTestEmbeds
importMap = xTestImportPos
embedMap = xTestEmbedPos
case isTest:
fileList = &p.TestGoFiles
importMap = testImported
embedList = &testEmbeds
importMap = testImportPos
embedMap = testEmbedPos
default:
fileList = &p.GoFiles
importMap = imported
embedList = &embeds
importMap = importPos
embedMap = embedPos
}
*fileList = append(*fileList, name)
if importMap != nil {
for _, imp := range info.imports {
importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos))
}
}
if embedList != nil {
*embedList = append(*embedList, info.embeds...)
if embedMap != nil {
for _, emb := range info.embeds {
embedMap[emb.pattern] = append(embedMap[emb.pattern], emb.pos)
}
}
}

Expand All @@ -962,13 +969,13 @@ Found:
}
sort.Strings(p.AllTags)

p.EmbedPatterns = uniq(embeds)
p.TestEmbedPatterns = uniq(testEmbeds)
p.XTestEmbedPatterns = uniq(xTestEmbeds)
p.EmbedPatterns, p.EmbedPatternPos = cleanDecls(embedPos)
p.TestEmbedPatterns, p.TestEmbedPatternPos = cleanDecls(testEmbedPos)
p.XTestEmbedPatterns, p.XTestEmbedPatternPos = cleanDecls(xTestEmbedPos)

p.Imports, p.ImportPos = cleanImports(imported)
p.TestImports, p.TestImportPos = cleanImports(testImported)
p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
p.Imports, p.ImportPos = cleanDecls(importPos)
p.TestImports, p.TestImportPos = cleanDecls(testImportPos)
p.XTestImports, p.XTestImportPos = cleanDecls(xTestImportPos)

// add the .S/.sx files only if we are using cgo
// (which means gcc will compile them).
Expand Down Expand Up @@ -1340,7 +1347,7 @@ type fileInfo struct {
parsed *ast.File
parseErr error
imports []fileImport
embeds []string
embeds []fileEmbed
embedErr error
}

Expand All @@ -1350,6 +1357,11 @@ type fileImport struct {
doc *ast.CommentGroup
}

type fileEmbed struct {
pattern string
pos token.Position
}

// matchFile determines whether the file with the given name in the given directory
// should be included in the package being constructed.
// If the file should be included, matchFile returns a non-nil *fileInfo (and a nil error).
Expand Down Expand Up @@ -1424,7 +1436,7 @@ func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binary
return info, nil
}

func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) {
all := make([]string, 0, len(m))
for path := range m {
all = append(all, path)
Expand Down
74 changes: 55 additions & 19 deletions src/go/build/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"strconv"
"strings"
Expand All @@ -24,6 +25,18 @@ type importReader struct {
err error
eof bool
nerr int
pos token.Position
}

func newImportReader(name string, r io.Reader) *importReader {
return &importReader{
b: bufio.NewReader(r),
pos: token.Position{
Filename: name,
Line: 1,
Column: 1,
},
}
}

func isIdent(c byte) bool {
Expand Down Expand Up @@ -66,22 +79,32 @@ func (r *importReader) readByte() byte {
// readByteNoBuf is like readByte but doesn't buffer the byte.
// It exhausts r.buf before reading from r.b.
func (r *importReader) readByteNoBuf() byte {
var c byte
var err error
if len(r.buf) > 0 {
c := r.buf[0]
c = r.buf[0]
r.buf = r.buf[1:]
return c
}
c, err := r.b.ReadByte()
if err == nil && c == 0 {
err = errNUL
} else {
c, err = r.b.ReadByte()
if err == nil && c == 0 {
err = errNUL
}
}

if err != nil {
if err == io.EOF {
r.eof = true
} else if r.err == nil {
r.err = err
}
c = 0
return 0
}
r.pos.Offset++
if c == '\n' {
r.pos.Line++
r.pos.Column = 1
} else {
r.pos.Column++
}
return c
}
Expand Down Expand Up @@ -323,7 +346,7 @@ func (r *importReader) readImport() {
// readComments is like io.ReadAll, except that it only reads the leading
// block of comments in the file.
func readComments(f io.Reader) ([]byte, error) {
r := &importReader{b: bufio.NewReader(f)}
r := newImportReader("", f)
r.peekByte(true)
if r.err == nil && !r.eof {
// Didn't reach EOF, so must have found a non-space byte. Remove it.
Expand All @@ -340,7 +363,7 @@ func readComments(f io.Reader) ([]byte, error) {
// It only returns an error if there are problems reading the file,
// not for syntax errors in the file itself.
func readGoInfo(f io.Reader, info *fileInfo) error {
r := &importReader{b: bufio.NewReader(f)}
r := newImportReader(info.name, f)

r.readKeyword("package")
r.readIdent()
Expand Down Expand Up @@ -428,6 +451,7 @@ func readGoInfo(f io.Reader, info *fileInfo) error {
var line []byte
for first := true; r.findEmbed(first); first = false {
line = line[:0]
pos := r.pos
for {
c := r.readByteNoBuf()
if c == '\n' || r.err != nil || r.eof {
Expand All @@ -438,9 +462,9 @@ func readGoInfo(f io.Reader, info *fileInfo) error {
// Add args if line is well-formed.
// Ignore badly-formed lines - the compiler will report them when it finds them,
// and we can pretend they are not there to help go list succeed with what it knows.
args, err := parseGoEmbed(string(line))
embs, err := parseGoEmbed(string(line), pos)
if err == nil {
info.embeds = append(info.embeds, args...)
info.embeds = append(info.embeds, embs...)
}
}
}
Expand All @@ -450,11 +474,23 @@ func readGoInfo(f io.Reader, info *fileInfo) error {

// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
// There is a copy of this code in cmd/compile/internal/gc/noder.go as well.
func parseGoEmbed(args string) ([]string, error) {
var list []string
for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
// This is based on a similar function in cmd/compile/internal/gc/noder.go;
// this version calculates position information as well.
func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) {
trimBytes := func(n int) {
pos.Offset += n
pos.Column += utf8.RuneCountInString(args[:n])
args = args[n:]
}
trimSpace := func() {
trim := strings.TrimLeftFunc(args, unicode.IsSpace)
trimBytes(len(args) - len(trim))
}

var list []fileEmbed
for trimSpace(); args != ""; trimSpace() {
var path string
pathPos := pos
Switch:
switch args[0] {
default:
Expand All @@ -466,15 +502,15 @@ func parseGoEmbed(args string) ([]string, error) {
}
}
path = args[:i]
args = args[i:]
trimBytes(i)

case '`':
i := strings.Index(args[1:], "`")
if i < 0 {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
}
path = args[1 : 1+i]
args = args[1+i+1:]
trimBytes(1 + i + 1)

case '"':
i := 1
Expand All @@ -489,7 +525,7 @@ func parseGoEmbed(args string) ([]string, error) {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
}
path = q
args = args[i+1:]
trimBytes(i + 1)
break Switch
}
}
Expand All @@ -504,7 +540,7 @@ func parseGoEmbed(args string) ([]string, error) {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
}
}
list = append(list, path)
list = append(list, fileEmbed{path, pathPos})
}
return list, nil
}
Loading

0 comments on commit 6aa28d3

Please sign in to comment.