// Copyright 2012 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package buildimport (_// for linkname)type importReader struct { b *bufio.Reader buf []byte peek byte err error eof bool nerr int pos token.Position}var bom = []byte{0xef, 0xbb, 0xbf}func newImportReader( string, io.Reader) *importReader { := bufio.NewReader()// Remove leading UTF-8 BOM. // Per https://golang.org/ref/spec#Source_code_representation: // a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) // if it is the first Unicode code point in the source text.if , := .Peek(3); == nil && bytes.Equal(, bom) { .Discard(3) }return &importReader{b: ,pos: token.Position{Filename: ,Line: 1,Column: 1, }, }}func isIdent( byte) bool {return'A' <= && <= 'Z' || 'a' <= && <= 'z' || '0' <= && <= '9' || == '_' || >= utf8.RuneSelf}var ( errSyntax = errors.New("syntax error") errNUL = errors.New("unexpected NUL in input"))// syntaxError records a syntax error, but only if an I/O error has not already been recorded.func ( *importReader) () {if .err == nil { .err = errSyntax }}// readByte reads the next byte from the input, saves it in buf, and returns it.// If an error occurs, readByte records the error in r.err and returns 0.func ( *importReader) () byte { , := .b.ReadByte()if == nil { .buf = append(.buf, )if == 0 { = errNUL } }if != nil {if == io.EOF { .eof = true } elseif .err == nil { .err = } = 0 }return}// readByteNoBuf is like readByte but doesn't buffer the byte.// It exhausts r.buf before reading from r.b.func ( *importReader) () byte {varbytevarerroriflen(.buf) > 0 { = .buf[0] .buf = .buf[1:] } else { , = .b.ReadByte()if == nil && == 0 { = errNUL } }if != nil {if == io.EOF { .eof = true } elseif .err == nil { .err = }return0 } .pos.Offset++if == '\n' { .pos.Line++ .pos.Column = 1 } else { .pos.Column++ }return}// peekByte returns the next byte from the input reader but does not advance beyond it.// If skipSpace is set, peekByte skips leading spaces and comments.func ( *importReader) ( bool) byte {if .err != nil {if .nerr++; .nerr > 10000 {panic("go/build: import reader looping") }return0 }// Use r.peek as first input byte. // Don't just return r.peek here: it might have been left by peekByte(false) // and this might be peekByte(true). := .peekif == 0 { = .readByte() }for .err == nil && !.eof {if {// For the purposes of this reader, semicolons are never necessary to // understand the input and are treated as spaces.switch {case' ', '\f', '\t', '\r', '\n', ';': = .readByte()continuecase'/': = .readByte()if == '/' {for != '\n' && .err == nil && !.eof { = .readByte() } } elseif == '*' {varbytefor ( != '*' || != '/') && .err == nil {if .eof { .syntaxError() } , = , .readByte() } } else { .syntaxError() } = .readByte()continue } }break } .peek = return .peek}// nextByte is like peekByte but advances beyond the returned byte.func ( *importReader) ( bool) byte { := .peekByte() .peek = 0return}var goEmbed = []byte("go:embed")// findEmbed advances the input reader to the next //go:embed comment.// It reports whether it found a comment.// (Otherwise it found an error or EOF.)func ( *importReader) ( bool) bool {// The import block scan stopped after a non-space character, // so the reader is not at the start of a line on the first call. // After that, each //go:embed extraction leaves the reader // at the end of a line. := !varbytefor .err == nil && !.eof { = .readByteNoBuf() :switch {default: = falsecase'\n': = truecase' ', '\t':// leave startLine alonecase'"': = falsefor .err == nil {if .eof { .syntaxError() } = .readByteNoBuf()if == '\\' { .readByteNoBuf()if .err != nil { .syntaxError()returnfalse }continue }if == '"' { = .readByteNoBuf()goto } }gotocase'`': = falsefor .err == nil {if .eof { .syntaxError() } = .readByteNoBuf()if == '`' { = .readByteNoBuf()goto } }case'\'': = falsefor .err == nil {if .eof { .syntaxError() } = .readByteNoBuf()if == '\\' { .readByteNoBuf()if .err != nil { .syntaxError()returnfalse }continue }if == '\'' { = .readByteNoBuf()goto } }case'/': = .readByteNoBuf()switch {default: = falsegotocase'*':varbytefor ( != '*' || != '/') && .err == nil {if .eof { .syntaxError() } , = , .readByteNoBuf() } = falsecase'/':if {// Try to read this as a //go:embed comment.for := rangegoEmbed { = .readByteNoBuf()if != goEmbed[] {goto } } = .readByteNoBuf()if == ' ' || == '\t' {// Found one!returntrue } } :for != '\n' && .err == nil && !.eof { = .readByteNoBuf() } = true } } }returnfalse}// readKeyword reads the given keyword from the input.// If the keyword is not present, readKeyword records a syntax error.func ( *importReader) ( string) { .peekByte(true)for := 0; < len(); ++ {if .nextByte(false) != [] { .syntaxError()return } }ifisIdent(.peekByte(false)) { .syntaxError() }}// readIdent reads an identifier from the input.// If an identifier is not present, readIdent records a syntax error.func ( *importReader) () { := .peekByte(true)if !isIdent() { .syntaxError()return }forisIdent(.peekByte(false)) { .peek = 0 }}// readString reads a quoted string literal from the input.// If an identifier is not present, readString records a syntax error.func ( *importReader) () {switch .nextByte(true) {case'`':for .err == nil {if .nextByte(false) == '`' {break }if .eof { .syntaxError() } }case'"':for .err == nil { := .nextByte(false)if == '"' {break }if .eof || == '\n' { .syntaxError() }if == '\\' { .nextByte(false) } }default: .syntaxError() }}// readImport reads an import clause - optional identifier followed by quoted string -// from the input.func ( *importReader) () { := .peekByte(true)if == '.' { .peek = 0 } elseifisIdent() { .readIdent() } .readString()}// readComments is like io.ReadAll, except that it only reads the leading// block of comments in the file.//// readComments should be an internal detail,// but widely used packages access it using linkname.// Notable members of the hall of shame include:// - github.com/bazelbuild/bazel-gazelle//// Do not remove or change the type signature.// See go.dev/issue/67401.////go:linkname readCommentsfunc readComments( io.Reader) ([]byte, error) { := newImportReader("", ) .peekByte(true)if .err == nil && !.eof {// Didn't reach EOF, so must have found a non-space byte. Remove it. .buf = .buf[:len(.buf)-1] }return .buf, .err}// readGoInfo expects a Go file as input and reads the file up to and including the import section.// It records what it learned in *info.// If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr,// info.imports and info.embeds.//// It only returns an error if there are problems reading the file,// not for syntax errors in the file itself.func readGoInfo( io.Reader, *fileInfo) error { := newImportReader(.name, ) .readKeyword("package") .readIdent()for .peekByte(true) == 'i' { .readKeyword("import")if .peekByte(true) == '(' { .nextByte(false)for .peekByte(true) != ')' && .err == nil { .readImport() } .nextByte(false) } else { .readImport() } } .header = .buf// If we stopped successfully before EOF, we read a byte that told us we were done. // Return all but that last byte, which would cause a syntax error if we let it through.if .err == nil && !.eof { .header = .buf[:len(.buf)-1] }// If we stopped for a syntax error, consume the whole file so that // we are sure we don't change the errors that go/parser returns.if .err == errSyntax { .err = nilfor .err == nil && !.eof { .readByte() } .header = .buf }if .err != nil {return .err }if .fset == nil {returnnil }// Parse file header & record imports. .parsed, .parseErr = parser.ParseFile(.fset, .name, .header, parser.ImportsOnly|parser.ParseComments)if .parseErr != nil {returnnil } := falsefor , := range .parsed.Decls { , := .(*ast.GenDecl)if ! {continue }for , := range .Specs { , := .(*ast.ImportSpec)if ! {continue } := .Path.Value , := strconv.Unquote()if != nil {returnfmt.Errorf("parser returned invalid quoted string: <%s>", ) }if !isValidImport() {// The parser used to return a parse error for invalid import paths, but // no longer does, so check for and create the error here instead. .parseErr = scanner.Error{Pos: .fset.Position(.Pos()), Msg: "invalid import path: " + } .imports = nilreturnnil }if == "embed" { = true } := .Docif == nil && len(.Specs) == 1 { = .Doc } .imports = append(.imports, fileImport{, .Pos(), }) } }// Extract directives.for , := range .parsed.Comments {if .Pos() >= .parsed.Package {break }for , := range .List {ifstrings.HasPrefix(.Text, "//go:") { .directives = append(.directives, Directive{.Text, .fset.Position(.Slash)}) } } }// If the file imports "embed", // we have to look for //go:embed comments // in the remainder of the file. // The compiler will enforce the mapping of comments to // declared variables. We just need to know the patterns. // If there were //go:embed comments earlier in the file // (near the package statement or imports), the compiler // will reject them. They can be (and have already been) ignored.if {var []bytefor := true; .findEmbed(); = false { = [:0] := .posfor { := .readByteNoBuf()if == '\n' || .err != nil || .eof {break } = append(, ) }// 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. , := parseGoEmbed(string(), )if == nil { .embeds = append(.embeds, ...) } } }returnnil}// isValidImport checks if the import is a valid import using the more strict// checks allowed by the implementation restriction in https://go.dev/ref/spec#Import_declarations.// It was ported from the function of the same name that was removed from the// parser in CL 424855, when the parser stopped doing these checks.func isValidImport( string) bool {const = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"for , := range {if !unicode.IsGraphic() || unicode.IsSpace() || strings.ContainsRune(, ) {returnfalse } }return != ""}// 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.// This is based on a similar function in cmd/compile/internal/gc/noder.go;// this version calculates position information as well.func parseGoEmbed( string, token.Position) ([]fileEmbed, error) { := func( int) { .Offset += .Column += utf8.RuneCountInString([:]) = [:] } := func() { := strings.TrimLeftFunc(, unicode.IsSpace) (len() - len()) }var []fileEmbedfor (); != ""; () {varstring := :switch [0] {default: := len()for , := range {ifunicode.IsSpace() { = break } } = [:] ()case'`':varbool , _, = strings.Cut([1:], "`")if ! {returnnil, fmt.Errorf("invalid quoted string in //go:embed: %s", ) } (1 + len() + 1)case'"': := 1for ; < len(); ++ {if [] == '\\' { ++continue }if [] == '"' { , := strconv.Unquote([:+1])if != nil {returnnil, fmt.Errorf("invalid quoted string in //go:embed: %s", [:+1]) } = ( + 1)break } }if >= len() {returnnil, fmt.Errorf("invalid quoted string in //go:embed: %s", ) } }if != "" { , := utf8.DecodeRuneInString()if !unicode.IsSpace() {returnnil, fmt.Errorf("invalid quoted string in //go:embed: %s", ) } } = append(, fileEmbed{, }) }return , nil}
The pages are generated with Goldsv0.6.9-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.