xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/read.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2012 The Go Authors. All rights reserved.
2*9bb1b549SSpandan Das// Use of this source code is governed by a BSD-style
3*9bb1b549SSpandan Das// license that can be found in the LICENSE file.
4*9bb1b549SSpandan Das
5*9bb1b549SSpandan Das// This file was adapted from Go src/go/build/read.go at commit 8634a234df2a
6*9bb1b549SSpandan Das// on 2021-01-26. It's used to extract metadata from .go files without requiring
7*9bb1b549SSpandan Das// them to be in the same directory.
8*9bb1b549SSpandan Das
9*9bb1b549SSpandan Daspackage main
10*9bb1b549SSpandan Das
11*9bb1b549SSpandan Dasimport (
12*9bb1b549SSpandan Das	"bufio"
13*9bb1b549SSpandan Das	"errors"
14*9bb1b549SSpandan Das	"fmt"
15*9bb1b549SSpandan Das	"go/ast"
16*9bb1b549SSpandan Das	"go/parser"
17*9bb1b549SSpandan Das	"go/token"
18*9bb1b549SSpandan Das	"io"
19*9bb1b549SSpandan Das	"strconv"
20*9bb1b549SSpandan Das	"strings"
21*9bb1b549SSpandan Das	"unicode"
22*9bb1b549SSpandan Das	"unicode/utf8"
23*9bb1b549SSpandan Das)
24*9bb1b549SSpandan Das
25*9bb1b549SSpandan Dastype importReader struct {
26*9bb1b549SSpandan Das	b    *bufio.Reader
27*9bb1b549SSpandan Das	buf  []byte
28*9bb1b549SSpandan Das	peek byte
29*9bb1b549SSpandan Das	err  error
30*9bb1b549SSpandan Das	eof  bool
31*9bb1b549SSpandan Das	nerr int
32*9bb1b549SSpandan Das	pos  token.Position
33*9bb1b549SSpandan Das}
34*9bb1b549SSpandan Das
35*9bb1b549SSpandan Dasfunc newImportReader(name string, r io.Reader) *importReader {
36*9bb1b549SSpandan Das	return &importReader{
37*9bb1b549SSpandan Das		b: bufio.NewReader(r),
38*9bb1b549SSpandan Das		pos: token.Position{
39*9bb1b549SSpandan Das			Filename: name,
40*9bb1b549SSpandan Das			Line:     1,
41*9bb1b549SSpandan Das			Column:   1,
42*9bb1b549SSpandan Das		},
43*9bb1b549SSpandan Das	}
44*9bb1b549SSpandan Das}
45*9bb1b549SSpandan Das
46*9bb1b549SSpandan Dasfunc isIdent(c byte) bool {
47*9bb1b549SSpandan Das	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
48*9bb1b549SSpandan Das}
49*9bb1b549SSpandan Das
50*9bb1b549SSpandan Dasvar (
51*9bb1b549SSpandan Das	errSyntax = errors.New("syntax error")
52*9bb1b549SSpandan Das	errNUL    = errors.New("unexpected NUL in input")
53*9bb1b549SSpandan Das)
54*9bb1b549SSpandan Das
55*9bb1b549SSpandan Das// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
56*9bb1b549SSpandan Dasfunc (r *importReader) syntaxError() {
57*9bb1b549SSpandan Das	if r.err == nil {
58*9bb1b549SSpandan Das		r.err = errSyntax
59*9bb1b549SSpandan Das	}
60*9bb1b549SSpandan Das}
61*9bb1b549SSpandan Das
62*9bb1b549SSpandan Das// readByte reads the next byte from the input, saves it in buf, and returns it.
63*9bb1b549SSpandan Das// If an error occurs, readByte records the error in r.err and returns 0.
64*9bb1b549SSpandan Dasfunc (r *importReader) readByte() byte {
65*9bb1b549SSpandan Das	c, err := r.b.ReadByte()
66*9bb1b549SSpandan Das	if err == nil {
67*9bb1b549SSpandan Das		r.buf = append(r.buf, c)
68*9bb1b549SSpandan Das		if c == 0 {
69*9bb1b549SSpandan Das			err = errNUL
70*9bb1b549SSpandan Das		}
71*9bb1b549SSpandan Das	}
72*9bb1b549SSpandan Das	if err != nil {
73*9bb1b549SSpandan Das		if err == io.EOF {
74*9bb1b549SSpandan Das			r.eof = true
75*9bb1b549SSpandan Das		} else if r.err == nil {
76*9bb1b549SSpandan Das			r.err = err
77*9bb1b549SSpandan Das		}
78*9bb1b549SSpandan Das		c = 0
79*9bb1b549SSpandan Das	}
80*9bb1b549SSpandan Das	return c
81*9bb1b549SSpandan Das}
82*9bb1b549SSpandan Das
83*9bb1b549SSpandan Das// readByteNoBuf is like readByte but doesn't buffer the byte.
84*9bb1b549SSpandan Das// It exhausts r.buf before reading from r.b.
85*9bb1b549SSpandan Dasfunc (r *importReader) readByteNoBuf() byte {
86*9bb1b549SSpandan Das	var c byte
87*9bb1b549SSpandan Das	var err error
88*9bb1b549SSpandan Das	if len(r.buf) > 0 {
89*9bb1b549SSpandan Das		c = r.buf[0]
90*9bb1b549SSpandan Das		r.buf = r.buf[1:]
91*9bb1b549SSpandan Das	} else {
92*9bb1b549SSpandan Das		c, err = r.b.ReadByte()
93*9bb1b549SSpandan Das		if err == nil && c == 0 {
94*9bb1b549SSpandan Das			err = errNUL
95*9bb1b549SSpandan Das		}
96*9bb1b549SSpandan Das	}
97*9bb1b549SSpandan Das
98*9bb1b549SSpandan Das	if err != nil {
99*9bb1b549SSpandan Das		if err == io.EOF {
100*9bb1b549SSpandan Das			r.eof = true
101*9bb1b549SSpandan Das		} else if r.err == nil {
102*9bb1b549SSpandan Das			r.err = err
103*9bb1b549SSpandan Das		}
104*9bb1b549SSpandan Das		return 0
105*9bb1b549SSpandan Das	}
106*9bb1b549SSpandan Das	r.pos.Offset++
107*9bb1b549SSpandan Das	if c == '\n' {
108*9bb1b549SSpandan Das		r.pos.Line++
109*9bb1b549SSpandan Das		r.pos.Column = 1
110*9bb1b549SSpandan Das	} else {
111*9bb1b549SSpandan Das		r.pos.Column++
112*9bb1b549SSpandan Das	}
113*9bb1b549SSpandan Das	return c
114*9bb1b549SSpandan Das}
115*9bb1b549SSpandan Das
116*9bb1b549SSpandan Das// peekByte returns the next byte from the input reader but does not advance beyond it.
117*9bb1b549SSpandan Das// If skipSpace is set, peekByte skips leading spaces and comments.
118*9bb1b549SSpandan Dasfunc (r *importReader) peekByte(skipSpace bool) byte {
119*9bb1b549SSpandan Das	if r.err != nil {
120*9bb1b549SSpandan Das		if r.nerr++; r.nerr > 10000 {
121*9bb1b549SSpandan Das			panic("go/build: import reader looping")
122*9bb1b549SSpandan Das		}
123*9bb1b549SSpandan Das		return 0
124*9bb1b549SSpandan Das	}
125*9bb1b549SSpandan Das
126*9bb1b549SSpandan Das	// Use r.peek as first input byte.
127*9bb1b549SSpandan Das	// Don't just return r.peek here: it might have been left by peekByte(false)
128*9bb1b549SSpandan Das	// and this might be peekByte(true).
129*9bb1b549SSpandan Das	c := r.peek
130*9bb1b549SSpandan Das	if c == 0 {
131*9bb1b549SSpandan Das		c = r.readByte()
132*9bb1b549SSpandan Das	}
133*9bb1b549SSpandan Das	for r.err == nil && !r.eof {
134*9bb1b549SSpandan Das		if skipSpace {
135*9bb1b549SSpandan Das			// For the purposes of this reader, semicolons are never necessary to
136*9bb1b549SSpandan Das			// understand the input and are treated as spaces.
137*9bb1b549SSpandan Das			switch c {
138*9bb1b549SSpandan Das			case ' ', '\f', '\t', '\r', '\n', ';':
139*9bb1b549SSpandan Das				c = r.readByte()
140*9bb1b549SSpandan Das				continue
141*9bb1b549SSpandan Das
142*9bb1b549SSpandan Das			case '/':
143*9bb1b549SSpandan Das				c = r.readByte()
144*9bb1b549SSpandan Das				if c == '/' {
145*9bb1b549SSpandan Das					for c != '\n' && r.err == nil && !r.eof {
146*9bb1b549SSpandan Das						c = r.readByte()
147*9bb1b549SSpandan Das					}
148*9bb1b549SSpandan Das				} else if c == '*' {
149*9bb1b549SSpandan Das					var c1 byte
150*9bb1b549SSpandan Das					for (c != '*' || c1 != '/') && r.err == nil {
151*9bb1b549SSpandan Das						if r.eof {
152*9bb1b549SSpandan Das							r.syntaxError()
153*9bb1b549SSpandan Das						}
154*9bb1b549SSpandan Das						c, c1 = c1, r.readByte()
155*9bb1b549SSpandan Das					}
156*9bb1b549SSpandan Das				} else {
157*9bb1b549SSpandan Das					r.syntaxError()
158*9bb1b549SSpandan Das				}
159*9bb1b549SSpandan Das				c = r.readByte()
160*9bb1b549SSpandan Das				continue
161*9bb1b549SSpandan Das			}
162*9bb1b549SSpandan Das		}
163*9bb1b549SSpandan Das		break
164*9bb1b549SSpandan Das	}
165*9bb1b549SSpandan Das	r.peek = c
166*9bb1b549SSpandan Das	return r.peek
167*9bb1b549SSpandan Das}
168*9bb1b549SSpandan Das
169*9bb1b549SSpandan Das// nextByte is like peekByte but advances beyond the returned byte.
170*9bb1b549SSpandan Dasfunc (r *importReader) nextByte(skipSpace bool) byte {
171*9bb1b549SSpandan Das	c := r.peekByte(skipSpace)
172*9bb1b549SSpandan Das	r.peek = 0
173*9bb1b549SSpandan Das	return c
174*9bb1b549SSpandan Das}
175*9bb1b549SSpandan Das
176*9bb1b549SSpandan Dasvar goEmbed = []byte("go:embed")
177*9bb1b549SSpandan Das
178*9bb1b549SSpandan Das// findEmbed advances the input reader to the next //go:embed comment.
179*9bb1b549SSpandan Das// It reports whether it found a comment.
180*9bb1b549SSpandan Das// (Otherwise it found an error or EOF.)
181*9bb1b549SSpandan Dasfunc (r *importReader) findEmbed(first bool) bool {
182*9bb1b549SSpandan Das	// The import block scan stopped after a non-space character,
183*9bb1b549SSpandan Das	// so the reader is not at the start of a line on the first call.
184*9bb1b549SSpandan Das	// After that, each //go:embed extraction leaves the reader
185*9bb1b549SSpandan Das	// at the end of a line.
186*9bb1b549SSpandan Das	startLine := !first
187*9bb1b549SSpandan Das	var c byte
188*9bb1b549SSpandan Das	for r.err == nil && !r.eof {
189*9bb1b549SSpandan Das		c = r.readByteNoBuf()
190*9bb1b549SSpandan Das	Reswitch:
191*9bb1b549SSpandan Das		switch c {
192*9bb1b549SSpandan Das		default:
193*9bb1b549SSpandan Das			startLine = false
194*9bb1b549SSpandan Das
195*9bb1b549SSpandan Das		case '\n':
196*9bb1b549SSpandan Das			startLine = true
197*9bb1b549SSpandan Das
198*9bb1b549SSpandan Das		case ' ', '\t':
199*9bb1b549SSpandan Das			// leave startLine alone
200*9bb1b549SSpandan Das
201*9bb1b549SSpandan Das		case '"':
202*9bb1b549SSpandan Das			startLine = false
203*9bb1b549SSpandan Das			for r.err == nil {
204*9bb1b549SSpandan Das				if r.eof {
205*9bb1b549SSpandan Das					r.syntaxError()
206*9bb1b549SSpandan Das				}
207*9bb1b549SSpandan Das				c = r.readByteNoBuf()
208*9bb1b549SSpandan Das				if c == '\\' {
209*9bb1b549SSpandan Das					r.readByteNoBuf()
210*9bb1b549SSpandan Das					if r.err != nil {
211*9bb1b549SSpandan Das						r.syntaxError()
212*9bb1b549SSpandan Das						return false
213*9bb1b549SSpandan Das					}
214*9bb1b549SSpandan Das					continue
215*9bb1b549SSpandan Das				}
216*9bb1b549SSpandan Das				if c == '"' {
217*9bb1b549SSpandan Das					c = r.readByteNoBuf()
218*9bb1b549SSpandan Das					goto Reswitch
219*9bb1b549SSpandan Das				}
220*9bb1b549SSpandan Das			}
221*9bb1b549SSpandan Das			goto Reswitch
222*9bb1b549SSpandan Das
223*9bb1b549SSpandan Das		case '`':
224*9bb1b549SSpandan Das			startLine = false
225*9bb1b549SSpandan Das			for r.err == nil {
226*9bb1b549SSpandan Das				if r.eof {
227*9bb1b549SSpandan Das					r.syntaxError()
228*9bb1b549SSpandan Das				}
229*9bb1b549SSpandan Das				c = r.readByteNoBuf()
230*9bb1b549SSpandan Das				if c == '`' {
231*9bb1b549SSpandan Das					c = r.readByteNoBuf()
232*9bb1b549SSpandan Das					goto Reswitch
233*9bb1b549SSpandan Das				}
234*9bb1b549SSpandan Das			}
235*9bb1b549SSpandan Das
236*9bb1b549SSpandan Das		case '/':
237*9bb1b549SSpandan Das			c = r.readByteNoBuf()
238*9bb1b549SSpandan Das			switch c {
239*9bb1b549SSpandan Das			default:
240*9bb1b549SSpandan Das				startLine = false
241*9bb1b549SSpandan Das				goto Reswitch
242*9bb1b549SSpandan Das
243*9bb1b549SSpandan Das			case '*':
244*9bb1b549SSpandan Das				var c1 byte
245*9bb1b549SSpandan Das				for (c != '*' || c1 != '/') && r.err == nil {
246*9bb1b549SSpandan Das					if r.eof {
247*9bb1b549SSpandan Das						r.syntaxError()
248*9bb1b549SSpandan Das					}
249*9bb1b549SSpandan Das					c, c1 = c1, r.readByteNoBuf()
250*9bb1b549SSpandan Das				}
251*9bb1b549SSpandan Das				startLine = false
252*9bb1b549SSpandan Das
253*9bb1b549SSpandan Das			case '/':
254*9bb1b549SSpandan Das				if startLine {
255*9bb1b549SSpandan Das					// Try to read this as a //go:embed comment.
256*9bb1b549SSpandan Das					for i := range goEmbed {
257*9bb1b549SSpandan Das						c = r.readByteNoBuf()
258*9bb1b549SSpandan Das						if c != goEmbed[i] {
259*9bb1b549SSpandan Das							goto SkipSlashSlash
260*9bb1b549SSpandan Das						}
261*9bb1b549SSpandan Das					}
262*9bb1b549SSpandan Das					c = r.readByteNoBuf()
263*9bb1b549SSpandan Das					if c == ' ' || c == '\t' {
264*9bb1b549SSpandan Das						// Found one!
265*9bb1b549SSpandan Das						return true
266*9bb1b549SSpandan Das					}
267*9bb1b549SSpandan Das				}
268*9bb1b549SSpandan Das			SkipSlashSlash:
269*9bb1b549SSpandan Das				for c != '\n' && r.err == nil && !r.eof {
270*9bb1b549SSpandan Das					c = r.readByteNoBuf()
271*9bb1b549SSpandan Das				}
272*9bb1b549SSpandan Das				startLine = true
273*9bb1b549SSpandan Das			}
274*9bb1b549SSpandan Das		}
275*9bb1b549SSpandan Das	}
276*9bb1b549SSpandan Das	return false
277*9bb1b549SSpandan Das}
278*9bb1b549SSpandan Das
279*9bb1b549SSpandan Das// readKeyword reads the given keyword from the input.
280*9bb1b549SSpandan Das// If the keyword is not present, readKeyword records a syntax error.
281*9bb1b549SSpandan Dasfunc (r *importReader) readKeyword(kw string) {
282*9bb1b549SSpandan Das	r.peekByte(true)
283*9bb1b549SSpandan Das	for i := 0; i < len(kw); i++ {
284*9bb1b549SSpandan Das		if r.nextByte(false) != kw[i] {
285*9bb1b549SSpandan Das			r.syntaxError()
286*9bb1b549SSpandan Das			return
287*9bb1b549SSpandan Das		}
288*9bb1b549SSpandan Das	}
289*9bb1b549SSpandan Das	if isIdent(r.peekByte(false)) {
290*9bb1b549SSpandan Das		r.syntaxError()
291*9bb1b549SSpandan Das	}
292*9bb1b549SSpandan Das}
293*9bb1b549SSpandan Das
294*9bb1b549SSpandan Das// readIdent reads an identifier from the input.
295*9bb1b549SSpandan Das// If an identifier is not present, readIdent records a syntax error.
296*9bb1b549SSpandan Dasfunc (r *importReader) readIdent() {
297*9bb1b549SSpandan Das	c := r.peekByte(true)
298*9bb1b549SSpandan Das	if !isIdent(c) {
299*9bb1b549SSpandan Das		r.syntaxError()
300*9bb1b549SSpandan Das		return
301*9bb1b549SSpandan Das	}
302*9bb1b549SSpandan Das	for isIdent(r.peekByte(false)) {
303*9bb1b549SSpandan Das		r.peek = 0
304*9bb1b549SSpandan Das	}
305*9bb1b549SSpandan Das}
306*9bb1b549SSpandan Das
307*9bb1b549SSpandan Das// readString reads a quoted string literal from the input.
308*9bb1b549SSpandan Das// If an identifier is not present, readString records a syntax error.
309*9bb1b549SSpandan Dasfunc (r *importReader) readString() {
310*9bb1b549SSpandan Das	switch r.nextByte(true) {
311*9bb1b549SSpandan Das	case '`':
312*9bb1b549SSpandan Das		for r.err == nil {
313*9bb1b549SSpandan Das			if r.nextByte(false) == '`' {
314*9bb1b549SSpandan Das				break
315*9bb1b549SSpandan Das			}
316*9bb1b549SSpandan Das			if r.eof {
317*9bb1b549SSpandan Das				r.syntaxError()
318*9bb1b549SSpandan Das			}
319*9bb1b549SSpandan Das		}
320*9bb1b549SSpandan Das	case '"':
321*9bb1b549SSpandan Das		for r.err == nil {
322*9bb1b549SSpandan Das			c := r.nextByte(false)
323*9bb1b549SSpandan Das			if c == '"' {
324*9bb1b549SSpandan Das				break
325*9bb1b549SSpandan Das			}
326*9bb1b549SSpandan Das			if r.eof || c == '\n' {
327*9bb1b549SSpandan Das				r.syntaxError()
328*9bb1b549SSpandan Das			}
329*9bb1b549SSpandan Das			if c == '\\' {
330*9bb1b549SSpandan Das				r.nextByte(false)
331*9bb1b549SSpandan Das			}
332*9bb1b549SSpandan Das		}
333*9bb1b549SSpandan Das	default:
334*9bb1b549SSpandan Das		r.syntaxError()
335*9bb1b549SSpandan Das	}
336*9bb1b549SSpandan Das}
337*9bb1b549SSpandan Das
338*9bb1b549SSpandan Das// readImport reads an import clause - optional identifier followed by quoted string -
339*9bb1b549SSpandan Das// from the input.
340*9bb1b549SSpandan Dasfunc (r *importReader) readImport() {
341*9bb1b549SSpandan Das	c := r.peekByte(true)
342*9bb1b549SSpandan Das	if c == '.' {
343*9bb1b549SSpandan Das		r.peek = 0
344*9bb1b549SSpandan Das	} else if isIdent(c) {
345*9bb1b549SSpandan Das		r.readIdent()
346*9bb1b549SSpandan Das	}
347*9bb1b549SSpandan Das	r.readString()
348*9bb1b549SSpandan Das}
349*9bb1b549SSpandan Das
350*9bb1b549SSpandan Das// readComments is like io.ReadAll, except that it only reads the leading
351*9bb1b549SSpandan Das// block of comments in the file.
352*9bb1b549SSpandan Dasfunc readComments(f io.Reader) ([]byte, error) {
353*9bb1b549SSpandan Das	r := newImportReader("", f)
354*9bb1b549SSpandan Das	r.peekByte(true)
355*9bb1b549SSpandan Das	if r.err == nil && !r.eof {
356*9bb1b549SSpandan Das		// Didn't reach EOF, so must have found a non-space byte. Remove it.
357*9bb1b549SSpandan Das		r.buf = r.buf[:len(r.buf)-1]
358*9bb1b549SSpandan Das	}
359*9bb1b549SSpandan Das	return r.buf, r.err
360*9bb1b549SSpandan Das}
361*9bb1b549SSpandan Das
362*9bb1b549SSpandan Das// readGoInfo expects a Go file as input and reads the file up to and including the import section.
363*9bb1b549SSpandan Das// It records what it learned in *info.
364*9bb1b549SSpandan Das// If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr,
365*9bb1b549SSpandan Das// info.imports, info.embeds, and info.embedErr.
366*9bb1b549SSpandan Das//
367*9bb1b549SSpandan Das// It only returns an error if there are problems reading the file,
368*9bb1b549SSpandan Das// not for syntax errors in the file itself.
369*9bb1b549SSpandan Dasfunc readGoInfo(f io.Reader, info *fileInfo) error {
370*9bb1b549SSpandan Das	r := newImportReader(info.filename, f)
371*9bb1b549SSpandan Das
372*9bb1b549SSpandan Das	r.readKeyword("package")
373*9bb1b549SSpandan Das	r.readIdent()
374*9bb1b549SSpandan Das	for r.peekByte(true) == 'i' {
375*9bb1b549SSpandan Das		r.readKeyword("import")
376*9bb1b549SSpandan Das		if r.peekByte(true) == '(' {
377*9bb1b549SSpandan Das			r.nextByte(false)
378*9bb1b549SSpandan Das			for r.peekByte(true) != ')' && r.err == nil {
379*9bb1b549SSpandan Das				r.readImport()
380*9bb1b549SSpandan Das			}
381*9bb1b549SSpandan Das			r.nextByte(false)
382*9bb1b549SSpandan Das		} else {
383*9bb1b549SSpandan Das			r.readImport()
384*9bb1b549SSpandan Das		}
385*9bb1b549SSpandan Das	}
386*9bb1b549SSpandan Das
387*9bb1b549SSpandan Das	info.header = r.buf
388*9bb1b549SSpandan Das
389*9bb1b549SSpandan Das	// If we stopped successfully before EOF, we read a byte that told us we were done.
390*9bb1b549SSpandan Das	// Return all but that last byte, which would cause a syntax error if we let it through.
391*9bb1b549SSpandan Das	if r.err == nil && !r.eof {
392*9bb1b549SSpandan Das		info.header = r.buf[:len(r.buf)-1]
393*9bb1b549SSpandan Das	}
394*9bb1b549SSpandan Das
395*9bb1b549SSpandan Das	// If we stopped for a syntax error, consume the whole file so that
396*9bb1b549SSpandan Das	// we are sure we don't change the errors that go/parser returns.
397*9bb1b549SSpandan Das	if r.err == errSyntax {
398*9bb1b549SSpandan Das		r.err = nil
399*9bb1b549SSpandan Das		for r.err == nil && !r.eof {
400*9bb1b549SSpandan Das			r.readByte()
401*9bb1b549SSpandan Das		}
402*9bb1b549SSpandan Das		info.header = r.buf
403*9bb1b549SSpandan Das	}
404*9bb1b549SSpandan Das	if r.err != nil {
405*9bb1b549SSpandan Das		return r.err
406*9bb1b549SSpandan Das	}
407*9bb1b549SSpandan Das
408*9bb1b549SSpandan Das	if info.fset == nil {
409*9bb1b549SSpandan Das		return nil
410*9bb1b549SSpandan Das	}
411*9bb1b549SSpandan Das
412*9bb1b549SSpandan Das	// Parse file header & record imports.
413*9bb1b549SSpandan Das	info.parsed, info.parseErr = parser.ParseFile(info.fset, info.filename, info.header, parser.ImportsOnly|parser.ParseComments)
414*9bb1b549SSpandan Das	if info.parseErr != nil {
415*9bb1b549SSpandan Das		return nil
416*9bb1b549SSpandan Das	}
417*9bb1b549SSpandan Das	info.pkg = info.parsed.Name.Name
418*9bb1b549SSpandan Das
419*9bb1b549SSpandan Das	hasEmbed := false
420*9bb1b549SSpandan Das	for _, decl := range info.parsed.Decls {
421*9bb1b549SSpandan Das		d, ok := decl.(*ast.GenDecl)
422*9bb1b549SSpandan Das		if !ok {
423*9bb1b549SSpandan Das			continue
424*9bb1b549SSpandan Das		}
425*9bb1b549SSpandan Das		for _, dspec := range d.Specs {
426*9bb1b549SSpandan Das			spec, ok := dspec.(*ast.ImportSpec)
427*9bb1b549SSpandan Das			if !ok {
428*9bb1b549SSpandan Das				continue
429*9bb1b549SSpandan Das			}
430*9bb1b549SSpandan Das			quoted := spec.Path.Value
431*9bb1b549SSpandan Das			path, err := strconv.Unquote(quoted)
432*9bb1b549SSpandan Das			if err != nil {
433*9bb1b549SSpandan Das				return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
434*9bb1b549SSpandan Das			}
435*9bb1b549SSpandan Das			if path == "embed" {
436*9bb1b549SSpandan Das				hasEmbed = true
437*9bb1b549SSpandan Das			}
438*9bb1b549SSpandan Das
439*9bb1b549SSpandan Das			doc := spec.Doc
440*9bb1b549SSpandan Das			if doc == nil && len(d.Specs) == 1 {
441*9bb1b549SSpandan Das				doc = d.Doc
442*9bb1b549SSpandan Das			}
443*9bb1b549SSpandan Das			info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
444*9bb1b549SSpandan Das		}
445*9bb1b549SSpandan Das	}
446*9bb1b549SSpandan Das
447*9bb1b549SSpandan Das	// If the file imports "embed",
448*9bb1b549SSpandan Das	// we have to look for //go:embed comments
449*9bb1b549SSpandan Das	// in the remainder of the file.
450*9bb1b549SSpandan Das	// The compiler will enforce the mapping of comments to
451*9bb1b549SSpandan Das	// declared variables. We just need to know the patterns.
452*9bb1b549SSpandan Das	// If there were //go:embed comments earlier in the file
453*9bb1b549SSpandan Das	// (near the package statement or imports), the compiler
454*9bb1b549SSpandan Das	// will reject them. They can be (and have already been) ignored.
455*9bb1b549SSpandan Das	if hasEmbed {
456*9bb1b549SSpandan Das		var line []byte
457*9bb1b549SSpandan Das		for first := true; r.findEmbed(first); first = false {
458*9bb1b549SSpandan Das			line = line[:0]
459*9bb1b549SSpandan Das			pos := r.pos
460*9bb1b549SSpandan Das			for {
461*9bb1b549SSpandan Das				c := r.readByteNoBuf()
462*9bb1b549SSpandan Das				if c == '\n' || r.err != nil || r.eof {
463*9bb1b549SSpandan Das					break
464*9bb1b549SSpandan Das				}
465*9bb1b549SSpandan Das				line = append(line, c)
466*9bb1b549SSpandan Das			}
467*9bb1b549SSpandan Das			// Add args if line is well-formed.
468*9bb1b549SSpandan Das			// Ignore badly-formed lines - the compiler will report them when it finds them,
469*9bb1b549SSpandan Das			// and we can pretend they are not there to help go list succeed with what it knows.
470*9bb1b549SSpandan Das			embs, err := parseGoEmbed(string(line), pos)
471*9bb1b549SSpandan Das			if err == nil {
472*9bb1b549SSpandan Das				info.embeds = append(info.embeds, embs...)
473*9bb1b549SSpandan Das			}
474*9bb1b549SSpandan Das		}
475*9bb1b549SSpandan Das	}
476*9bb1b549SSpandan Das
477*9bb1b549SSpandan Das	return nil
478*9bb1b549SSpandan Das}
479*9bb1b549SSpandan Das
480*9bb1b549SSpandan Das// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
481*9bb1b549SSpandan Das// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
482*9bb1b549SSpandan Das// This is based on a similar function in cmd/compile/internal/gc/noder.go;
483*9bb1b549SSpandan Das// this version calculates position information as well.
484*9bb1b549SSpandan Dasfunc parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) {
485*9bb1b549SSpandan Das	trimBytes := func(n int) {
486*9bb1b549SSpandan Das		pos.Offset += n
487*9bb1b549SSpandan Das		pos.Column += utf8.RuneCountInString(args[:n])
488*9bb1b549SSpandan Das		args = args[n:]
489*9bb1b549SSpandan Das	}
490*9bb1b549SSpandan Das	trimSpace := func() {
491*9bb1b549SSpandan Das		trim := strings.TrimLeftFunc(args, unicode.IsSpace)
492*9bb1b549SSpandan Das		trimBytes(len(args) - len(trim))
493*9bb1b549SSpandan Das	}
494*9bb1b549SSpandan Das
495*9bb1b549SSpandan Das	var list []fileEmbed
496*9bb1b549SSpandan Das	for trimSpace(); args != ""; trimSpace() {
497*9bb1b549SSpandan Das		var path string
498*9bb1b549SSpandan Das		pathPos := pos
499*9bb1b549SSpandan Das	Switch:
500*9bb1b549SSpandan Das		switch args[0] {
501*9bb1b549SSpandan Das		default:
502*9bb1b549SSpandan Das			i := len(args)
503*9bb1b549SSpandan Das			for j, c := range args {
504*9bb1b549SSpandan Das				if unicode.IsSpace(c) {
505*9bb1b549SSpandan Das					i = j
506*9bb1b549SSpandan Das					break
507*9bb1b549SSpandan Das				}
508*9bb1b549SSpandan Das			}
509*9bb1b549SSpandan Das			path = args[:i]
510*9bb1b549SSpandan Das			trimBytes(i)
511*9bb1b549SSpandan Das
512*9bb1b549SSpandan Das		case '`':
513*9bb1b549SSpandan Das			i := strings.Index(args[1:], "`")
514*9bb1b549SSpandan Das			if i < 0 {
515*9bb1b549SSpandan Das				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
516*9bb1b549SSpandan Das			}
517*9bb1b549SSpandan Das			path = args[1 : 1+i]
518*9bb1b549SSpandan Das			trimBytes(1 + i + 1)
519*9bb1b549SSpandan Das
520*9bb1b549SSpandan Das		case '"':
521*9bb1b549SSpandan Das			i := 1
522*9bb1b549SSpandan Das			for ; i < len(args); i++ {
523*9bb1b549SSpandan Das				if args[i] == '\\' {
524*9bb1b549SSpandan Das					i++
525*9bb1b549SSpandan Das					continue
526*9bb1b549SSpandan Das				}
527*9bb1b549SSpandan Das				if args[i] == '"' {
528*9bb1b549SSpandan Das					q, err := strconv.Unquote(args[:i+1])
529*9bb1b549SSpandan Das					if err != nil {
530*9bb1b549SSpandan Das						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
531*9bb1b549SSpandan Das					}
532*9bb1b549SSpandan Das					path = q
533*9bb1b549SSpandan Das					trimBytes(i + 1)
534*9bb1b549SSpandan Das					break Switch
535*9bb1b549SSpandan Das				}
536*9bb1b549SSpandan Das			}
537*9bb1b549SSpandan Das			if i >= len(args) {
538*9bb1b549SSpandan Das				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
539*9bb1b549SSpandan Das			}
540*9bb1b549SSpandan Das		}
541*9bb1b549SSpandan Das
542*9bb1b549SSpandan Das		if args != "" {
543*9bb1b549SSpandan Das			r, _ := utf8.DecodeRuneInString(args)
544*9bb1b549SSpandan Das			if !unicode.IsSpace(r) {
545*9bb1b549SSpandan Das				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
546*9bb1b549SSpandan Das			}
547*9bb1b549SSpandan Das		}
548*9bb1b549SSpandan Das		list = append(list, fileEmbed{path, pathPos})
549*9bb1b549SSpandan Das	}
550*9bb1b549SSpandan Das	return list, nil
551*9bb1b549SSpandan Das}
552