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