1
2
3
4
5
6
7
8 package modindex
9
10 import (
11 "bufio"
12 "bytes"
13 "errors"
14 "fmt"
15 "go/ast"
16 "go/build"
17 "go/parser"
18 "go/scanner"
19 "go/token"
20 "io"
21 "strconv"
22 "strings"
23 "unicode"
24 "unicode/utf8"
25 )
26
27 type importReader struct {
28 b *bufio.Reader
29 buf []byte
30 peek byte
31 err error
32 eof bool
33 nerr int
34 pos token.Position
35 }
36
37 var bom = []byte{0xef, 0xbb, 0xbf}
38
39 func newImportReader(name string, r io.Reader) *importReader {
40 b := bufio.NewReader(r)
41
42
43
44
45 if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
46 b.Discard(3)
47 }
48 return &importReader{
49 b: b,
50 pos: token.Position{
51 Filename: name,
52 Line: 1,
53 Column: 1,
54 },
55 }
56 }
57
58 func isIdent(c byte) bool {
59 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
60 }
61
62 var (
63 errSyntax = errors.New("syntax error")
64 errNUL = errors.New("unexpected NUL in input")
65 )
66
67
68 func (r *importReader) syntaxError() {
69 if r.err == nil {
70 r.err = errSyntax
71 }
72 }
73
74
75
76 func (r *importReader) readByte() byte {
77 c, err := r.b.ReadByte()
78 if err == nil {
79 r.buf = append(r.buf, c)
80 if c == 0 {
81 err = errNUL
82 }
83 }
84 if err != nil {
85 if err == io.EOF {
86 r.eof = true
87 } else if r.err == nil {
88 r.err = err
89 }
90 c = 0
91 }
92 return c
93 }
94
95
96 func (r *importReader) readRest() {
97 for {
98 if len(r.buf) == cap(r.buf) {
99
100 r.buf = append(r.buf, 0)[:len(r.buf)]
101 }
102 n, err := r.b.Read(r.buf[len(r.buf):cap(r.buf)])
103 r.buf = r.buf[:len(r.buf)+n]
104 if err != nil {
105 if err == io.EOF {
106 r.eof = true
107 } else if r.err == nil {
108 r.err = err
109 }
110 break
111 }
112 }
113 }
114
115
116
117 func (r *importReader) peekByte(skipSpace bool) byte {
118 if r.err != nil {
119 if r.nerr++; r.nerr > 10000 {
120 panic("go/build: import reader looping")
121 }
122 return 0
123 }
124
125
126
127
128 c := r.peek
129 if c == 0 {
130 c = r.readByte()
131 }
132 for r.err == nil && !r.eof {
133 if skipSpace {
134
135
136 switch c {
137 case ' ', '\f', '\t', '\r', '\n', ';':
138 c = r.readByte()
139 continue
140
141 case '/':
142 c = r.readByte()
143 if c == '/' {
144 for c != '\n' && r.err == nil && !r.eof {
145 c = r.readByte()
146 }
147 } else if c == '*' {
148 var c1 byte
149 for (c != '*' || c1 != '/') && r.err == nil {
150 if r.eof {
151 r.syntaxError()
152 }
153 c, c1 = c1, r.readByte()
154 }
155 } else {
156 r.syntaxError()
157 }
158 c = r.readByte()
159 continue
160 }
161 }
162 break
163 }
164 r.peek = c
165 return r.peek
166 }
167
168
169 func (r *importReader) nextByte(skipSpace bool) byte {
170 c := r.peekByte(skipSpace)
171 r.peek = 0
172 return c
173 }
174
175
176
177 func (r *importReader) readKeyword(kw string) {
178 r.peekByte(true)
179 for i := 0; i < len(kw); i++ {
180 if r.nextByte(false) != kw[i] {
181 r.syntaxError()
182 return
183 }
184 }
185 if isIdent(r.peekByte(false)) {
186 r.syntaxError()
187 }
188 }
189
190
191
192 func (r *importReader) readIdent() {
193 c := r.peekByte(true)
194 if !isIdent(c) {
195 r.syntaxError()
196 return
197 }
198 for isIdent(r.peekByte(false)) {
199 r.peek = 0
200 }
201 }
202
203
204
205 func (r *importReader) readString() {
206 switch r.nextByte(true) {
207 case '`':
208 for r.err == nil {
209 if r.nextByte(false) == '`' {
210 break
211 }
212 if r.eof {
213 r.syntaxError()
214 }
215 }
216 case '"':
217 for r.err == nil {
218 c := r.nextByte(false)
219 if c == '"' {
220 break
221 }
222 if r.eof || c == '\n' {
223 r.syntaxError()
224 }
225 if c == '\\' {
226 r.nextByte(false)
227 }
228 }
229 default:
230 r.syntaxError()
231 }
232 }
233
234
235
236 func (r *importReader) readImport() {
237 c := r.peekByte(true)
238 if c == '.' {
239 r.peek = 0
240 } else if isIdent(c) {
241 r.readIdent()
242 }
243 r.readString()
244 }
245
246
247
248 func readComments(f io.Reader) ([]byte, error) {
249 r := newImportReader("", f)
250 r.peekByte(true)
251 if r.err == nil && !r.eof {
252
253 r.buf = r.buf[:len(r.buf)-1]
254 }
255 return r.buf, r.err
256 }
257
258
259
260
261
262
263
264
265 func readGoInfo(f io.Reader, info *fileInfo) error {
266 r := newImportReader(info.name, f)
267
268 r.readKeyword("package")
269 r.readIdent()
270 for r.peekByte(true) == 'i' {
271 r.readKeyword("import")
272 if r.peekByte(true) == '(' {
273 r.nextByte(false)
274 for r.peekByte(true) != ')' && r.err == nil {
275 r.readImport()
276 }
277 r.nextByte(false)
278 } else {
279 r.readImport()
280 }
281 }
282
283 info.header = r.buf
284
285
286
287 if r.err == nil && !r.eof {
288 info.header = r.buf[:len(r.buf)-1]
289 }
290
291
292
293 if r.err == errSyntax {
294 r.err = nil
295 r.readRest()
296 info.header = r.buf
297 }
298 if r.err != nil {
299 return r.err
300 }
301
302 if info.fset == nil {
303 return nil
304 }
305
306
307 info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments)
308 if info.parseErr != nil {
309 return nil
310 }
311
312 hasEmbed := false
313 for _, decl := range info.parsed.Decls {
314 d, ok := decl.(*ast.GenDecl)
315 if !ok {
316 continue
317 }
318 for _, dspec := range d.Specs {
319 spec, ok := dspec.(*ast.ImportSpec)
320 if !ok {
321 continue
322 }
323 quoted := spec.Path.Value
324 path, err := strconv.Unquote(quoted)
325 if err != nil {
326 return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
327 }
328 if !isValidImport(path) {
329
330
331 info.parseErr = scanner.Error{Pos: info.fset.Position(spec.Pos()), Msg: "invalid import path: " + path}
332 info.imports = nil
333 return nil
334 }
335 if path == "embed" {
336 hasEmbed = true
337 }
338
339 doc := spec.Doc
340 if doc == nil && len(d.Specs) == 1 {
341 doc = d.Doc
342 }
343 info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
344 }
345 }
346
347
348 for _, group := range info.parsed.Comments {
349 if group.Pos() >= info.parsed.Package {
350 break
351 }
352 for _, c := range group.List {
353 if strings.HasPrefix(c.Text, "//go:") {
354 info.directives = append(info.directives, build.Directive{Text: c.Text, Pos: info.fset.Position(c.Slash)})
355 }
356 }
357 }
358
359
360
361
362
363
364
365
366
367 if hasEmbed {
368 r.readRest()
369 fset := token.NewFileSet()
370 file := fset.AddFile(r.pos.Filename, -1, len(r.buf))
371 var sc scanner.Scanner
372 sc.Init(file, r.buf, nil, scanner.ScanComments)
373 for {
374 pos, tok, lit := sc.Scan()
375 if tok == token.EOF {
376 break
377 }
378 if tok == token.COMMENT && strings.HasPrefix(lit, "//go:embed") {
379
380
381 embs, err := parseGoEmbed(fset, pos, lit)
382 if err == nil {
383 info.embeds = append(info.embeds, embs...)
384 }
385 }
386 }
387 }
388
389 return nil
390 }
391
392
393
394
395
396 func isValidImport(s string) bool {
397 const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
398 for _, r := range s {
399 if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
400 return false
401 }
402 }
403 return s != ""
404 }
405
406
407
408
409 func parseGoEmbed(fset *token.FileSet, pos token.Pos, comment string) ([]fileEmbed, error) {
410 dir, ok := ast.ParseDirective(pos, comment)
411 if !ok || dir.Tool != "go" || dir.Name != "embed" {
412 return nil, nil
413 }
414 args, err := dir.ParseArgs()
415 if err != nil {
416 return nil, err
417 }
418 var list []fileEmbed
419 for _, arg := range args {
420 list = append(list, fileEmbed{arg.Arg, fset.Position(arg.Pos)})
421 }
422 return list, nil
423 }
424
View as plain text