// Copyright 2022 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 commentimport ()// A Doc is a parsed Go doc comment.typeDocstruct {// Content is the sequence of content blocks in the comment. Content []Block// Links is the link definitions in the comment. Links []*LinkDef}// A LinkDef is a single link definition.typeLinkDefstruct { Text string// the link text URL string// the link URL Used bool// whether the comment uses the definition}// A Block is block-level content in a doc comment,// one of [*Code], [*Heading], [*List], or [*Paragraph].typeBlockinterface { block()}// A Heading is a doc comment heading.typeHeadingstruct { Text []Text// the heading text}func (*Heading) () {}// A List is a numbered or bullet list.// Lists are always non-empty: len(Items) > 0.// In a numbered list, every Items[i].Number is a non-empty string.// In a bullet list, every Items[i].Number is an empty string.typeListstruct {// Items is the list items. Items []*ListItem// ForceBlankBefore indicates that the list must be // preceded by a blank line when reformatting the comment, // overriding the usual conditions. See the BlankBefore method. // // The comment parser sets ForceBlankBefore for any list // that is preceded by a blank line, to make sure // the blank line is preserved when printing. ForceBlankBefore bool// ForceBlankBetween indicates that list items must be // separated by blank lines when reformatting the comment, // overriding the usual conditions. See the BlankBetween method. // // The comment parser sets ForceBlankBetween for any list // that has a blank line between any two of its items, to make sure // the blank lines are preserved when printing. ForceBlankBetween bool}func (*List) () {}// BlankBefore reports whether a reformatting of the comment// should include a blank line before the list.// The default rule is the same as for [BlankBetween]:// if the list item content contains any blank lines// (meaning at least one item has multiple paragraphs)// then the list itself must be preceded by a blank line.// A preceding blank line can be forced by setting [List].ForceBlankBefore.func ( *List) () bool {return .ForceBlankBefore || .BlankBetween()}// BlankBetween reports whether a reformatting of the comment// should include a blank line between each pair of list items.// The default rule is that if the list item content contains any blank lines// (meaning at least one item has multiple paragraphs)// then list items must themselves be separated by blank lines.// Blank line separators can be forced by setting [List].ForceBlankBetween.func ( *List) () bool {if .ForceBlankBetween {returntrue }for , := range .Items {iflen(.Content) != 1 {// Unreachable for parsed comments today, // since the only way to get multiple item.Content // is multiple paragraphs, which must have been // separated by a blank line.returntrue } }returnfalse}// A ListItem is a single item in a numbered or bullet list.typeListItemstruct {// Number is a decimal string in a numbered list // or an empty string in a bullet list. Number string// "1", "2", ...; "" for bullet list// Content is the list content. // Currently, restrictions in the parser and printer // require every element of Content to be a *Paragraph. Content []Block// Content of this item.}// A Paragraph is a paragraph of text.typeParagraphstruct { Text []Text}func (*Paragraph) () {}// A Code is a preformatted code block.typeCodestruct {// Text is the preformatted text, ending with a newline character. // It may be multiple lines, each of which ends with a newline character. // It is never empty, nor does it start or end with a blank line. Text string}func (*Code) () {}// A Text is text-level content in a doc comment,// one of [Plain], [Italic], [*Link], or [*DocLink].typeTextinterface { text()}// A Plain is a string rendered as plain text (not italicized).typePlainstringfunc (Plain) () {}// An Italic is a string rendered as italicized text.typeItalicstringfunc (Italic) () {}// A Link is a link to a specific URL.typeLinkstruct { Auto bool// is this an automatic (implicit) link of a literal URL? Text []Text// text of link URL string// target URL of link}func (*Link) () {}// A DocLink is a link to documentation for a Go package or symbol.typeDocLinkstruct { Text []Text// text of link// ImportPath, Recv, and Name identify the Go package or symbol // that is the link target. The potential combinations of // non-empty fields are: // - ImportPath: a link to another package // - ImportPath, Name: a link to a const, func, type, or var in another package // - ImportPath, Recv, Name: a link to a method in another package // - Name: a link to a const, func, type, or var in this package // - Recv, Name: a link to a method in this package ImportPath string// import path Recv string// receiver type, without any pointer star, for methods Name string// const, func, type, var, or method name}func (*DocLink) () {}// A Parser is a doc comment parser.// The fields in the struct can be filled in before calling [Parser.Parse]// in order to customize the details of the parsing process.typeParserstruct {// Words is a map of Go identifier words that // should be italicized and potentially linked. // If Words[w] is the empty string, then the word w // is only italicized. Otherwise it is linked, using // Words[w] as the link target. // Words corresponds to the [go/doc.ToHTML] words parameter. Words map[string]string// LookupPackage resolves a package name to an import path. // // If LookupPackage(name) returns ok == true, then [name] // (or [name.Sym] or [name.Sym.Method]) // is considered a documentation link to importPath's package docs. // It is valid to return "", true, in which case name is considered // to refer to the current package. // // If LookupPackage(name) returns ok == false, // then [name] (or [name.Sym] or [name.Sym.Method]) // will not be considered a documentation link, // except in the case where name is the full (but single-element) import path // of a package in the standard library, such as in [math] or [io.Reader]. // LookupPackage is still called for such names, // in order to permit references to imports of other packages // with the same package names. // // Setting LookupPackage to nil is equivalent to setting it to // a function that always returns "", false. LookupPackage func(name string) (importPath string, ok bool)// LookupSym reports whether a symbol name or method name // exists in the current package. // // If LookupSym("", "Name") returns true, then [Name] // is considered a documentation link for a const, func, type, or var. // // Similarly, if LookupSym("Recv", "Name") returns true, // then [Recv.Name] is considered a documentation link for // type Recv's method Name. // // Setting LookupSym to nil is equivalent to setting it to a function // that always returns false. LookupSym func(recv, name string) (ok bool)}// parseDoc is parsing state for a single doc comment.type parseDoc struct { *Parser *Doc links map[string]*LinkDef lines []string lookupSym func(recv, name string) bool}// lookupPkg is called to look up the pkg in [pkg], [pkg.Name], and [pkg.Name.Recv].// If pkg has a slash, it is assumed to be the full import path and is returned with ok = true.//// Otherwise, pkg is probably a simple package name like "rand" (not "crypto/rand" or "math/rand").// d.LookupPackage provides a way for the caller to allow resolving such names with reference// to the imports in the surrounding package.//// There is one collision between these two cases: single-element standard library names// like "math" are full import paths but don't contain slashes. We let d.LookupPackage have// the first chance to resolve it, in case there's a different package imported as math,// and otherwise we refer to a built-in list of single-element standard library package names.func ( *parseDoc) ( string) ( string, bool) {ifstrings.Contains(, "/") { // assume a full import pathifvalidImportPath() {return , true }return"", false }if .LookupPackage != nil {// Give LookupPackage a chance.if , := .LookupPackage(); {return , true } }returnDefaultLookupPackage()}func isStdPkg( string) bool { , := slices.BinarySearch(stdPkgs, )return}// DefaultLookupPackage is the default package lookup// function, used when [Parser.LookupPackage] is nil.// It recognizes names of the packages from the standard// library with single-element import paths, such as math,// which would otherwise be impossible to name.//// Note that the go/doc package provides a more sophisticated// lookup based on the imports used in the current package.func ( string) ( string, bool) {ifisStdPkg() {return , true }return"", false}// Parse parses the doc comment text and returns the *[Doc] form.// Comment markers (/* // and */) in the text must have already been removed.func ( *Parser) ( string) *Doc { := unindent(strings.Split(, "\n")) := &parseDoc{Parser: ,Doc: new(Doc),links: make(map[string]*LinkDef),lines: ,lookupSym: func(, string) bool { returnfalse }, }if .LookupSym != nil { .lookupSym = .LookupSym }// First pass: break into block structure and collect known links. // The text is all recorded as Plain for now.varspanfor , := rangeparseSpans() {varBlockswitch .kind {default:panic("go/doc/comment: internal error: unknown span kind")casespanList: = .list([.start:.end], .end < .start)casespanCode: = .code([.start:.end])casespanOldHeading: = .oldHeading([.start])casespanHeading: = .heading([.start])casespanPara: = .paragraph([.start:.end]) }if != nil { .Content = append(.Content, ) } = }// Second pass: interpret all the Plain text now that we know the links.for , := range .Content {switch b := .(type) {case *Paragraph: .Text = .parseLinkedText(string(.Text[0].(Plain)))case *List:for , := range .Items {for , := range .Content { := .(*Paragraph) .Text = .parseLinkedText(string(.Text[0].(Plain))) } } } }return .Doc}// A span represents a single span of comment lines (lines[start:end])// of an identified kind (code, heading, paragraph, and so on).type span struct { start int end int kind spanKind}// A spanKind describes the kind of span.type spanKind intconst ( _ spanKind = iota spanCode spanHeading spanList spanOldHeading spanPara)func parseSpans( []string) []span {var []span// The loop may process a line twice: once as unindented // and again forced indented. So the maximum expected // number of iterations is 2*len(lines). The repeating logic // can be subtle, though, and to protect against introduction // of infinite loops in future changes, we watch to see that // we are not looping too much. A panic is better than a // quiet infinite loop. := 2 * len() := 0 := 0:for {// Skip blank lines.for < len() && [] == "" { ++ }if >= len() {break }if --; < 0 {panic("go/doc/comment: internal error: not making progress") }varspanKind := := if < || indented([]) {// Indented (or force indented). // Ends before next unindented. (Blank lines are OK.) // If this is an unindented list that we are heuristically treating as indented, // then accept unindented list item lines up to the first blank lines. // The heuristic is disabled at blank lines to contain its effect // to non-gofmt'ed sections of the comment. := isList([]) && < ++for < len() && ([] == "" || < || indented([]) || ( && isList([]))) {if [] == "" { = false } ++ }// Drop trailing blank lines. = for > && [-1] == "" { -- }// If indented lines are followed (without a blank line) // by an unindented line ending in a brace, // take that one line too. This fixes the common mistake // of pasting in something like // // func main() { // fmt.Println("hello, world") // } // // and forgetting to indent it. // The heuristic will never trigger on a gofmt'ed comment, // because any gofmt'ed code block or list would be // followed by a blank line or end of comment.if < len() && strings.HasPrefix([], "}") { ++ }ifisList([]) { = spanList } else { = spanCode } } else {// Unindented. Ends at next blank or indented line. ++for < len() && [] != "" && !indented([]) { ++ } = // If unindented lines are followed (without a blank line) // by an indented line that would start a code block, // check whether the final unindented lines // should be left for the indented section. // This can happen for the common mistakes of // unindented code or unindented lists. // The heuristic will never trigger on a gofmt'ed comment, // because any gofmt'ed code block would have a blank line // preceding it after the unindented lines.if < len() && [] != "" && !isList([]) {switch {caseisList([-1]):// If the final unindented line looks like a list item, // this may be the first indented line wrap of // a mistakenly unindented list. // Leave all the unindented list items. = --for > && isList([-1]) { -- }casestrings.HasSuffix([-1], "{") || strings.HasSuffix([-1], `\`):// If the final unindented line ended in { or \ // it is probably the start of a misindented code block. // Give the user a single line fix. // Often that's enough; if not, the user can fix the others themselves. = -- }if == && > { = continue } }// Span is either paragraph or heading.if - == 1 && isHeading([]) { = spanHeading } elseif - == 1 && isOldHeading([], , ) { = spanOldHeading } else { = spanPara } } = append(, span{, , }) = }return}// indented reports whether line is indented// (starts with a leading space or tab).func indented( string) bool {return != "" && ([0] == ' ' || [0] == '\t')}// unindent removes any common space/tab prefix// from each line in lines, returning a copy of lines in which// those prefixes have been trimmed from each line.// It also replaces any lines containing only spaces with blank lines (empty strings).func unindent( []string) []string {// Trim leading and trailing blank lines.forlen() > 0 && isBlank([0]) { = [1:] }forlen() > 0 && isBlank([len()-1]) { = [:len()-1] }iflen() == 0 {returnnil }// Compute and remove common indentation. := leadingSpace([0])for , := range [1:] {if !isBlank() { = commonPrefix(, leadingSpace()) } } := make([]string, len())for , := range { = strings.TrimPrefix(, )ifstrings.TrimSpace() == "" { = "" } [] = }forlen() > 0 && [0] == "" { = [1:] }forlen() > 0 && [len()-1] == "" { = [:len()-1] }return}// isBlank reports whether s is a blank line.func isBlank( string) bool {returnlen() == 0 || (len() == 1 && [0] == '\n')}// commonPrefix returns the longest common prefix of a and b.func commonPrefix(, string) string { := 0for < len() && < len() && [] == [] { ++ }return [0:]}// leadingSpace returns the longest prefix of s consisting of spaces and tabs.func leadingSpace( string) string { := 0for < len() && ([] == ' ' || [] == '\t') { ++ }return [:]}// isOldHeading reports whether line is an old-style section heading.// line is all[off].func isOldHeading( string, []string, int) bool {if <= 0 || [-1] != "" || +2 >= len() || [+1] != "" || leadingSpace([+2]) != "" {returnfalse } = strings.TrimSpace()// a heading must start with an uppercase letter , := utf8.DecodeRuneInString()if !unicode.IsLetter() || !unicode.IsUpper() {returnfalse }// it must end in a letter or digit: , _ = utf8.DecodeLastRuneInString()if !unicode.IsLetter() && !unicode.IsDigit() {returnfalse }// exclude lines with illegal characters. we allow "(),"ifstrings.ContainsAny(, ";:!?+*/=[]{}_^°&§~%#@<\">\\") {returnfalse }// allow "'" for possessive "'s" onlyfor := ; ; {varboolif _, , = strings.Cut(, "'"); ! {break }if != "s" && !strings.HasPrefix(, "s ") {returnfalse// ' not followed by s and then end-of-word } }// allow "." when followed by non-spacefor := ; ; {varboolif _, , = strings.Cut(, "."); ! {break }if == "" || strings.HasPrefix(, " ") {returnfalse// not followed by non-space } }returntrue}// oldHeading returns the *Heading for the given old-style section heading line.func ( *parseDoc) ( string) Block {return &Heading{Text: []Text{Plain(strings.TrimSpace())}}}// isHeading reports whether line is a new-style section heading.func isHeading( string) bool {returnlen() >= 2 && [0] == '#' && ([1] == ' ' || [1] == '\t') &&strings.TrimSpace() != "#"}// heading returns the *Heading for the given new-style section heading line.func ( *parseDoc) ( string) Block {return &Heading{Text: []Text{Plain(strings.TrimSpace([1:]))}}}// code returns a code block built from the lines.func ( *parseDoc) ( []string) *Code { := unindent() = append(, "") // to get final \n from Joinreturn &Code{Text: strings.Join(, "\n")}}// paragraph returns a paragraph block built from the lines.// If the lines are link definitions, paragraph adds them to d and returns nil.func ( *parseDoc) ( []string) Block {// Is this a block of known links? Handle.var []*LinkDeffor , := range { , := parseLink()if ! {goto } = append(, ) }for , := range { .Links = append(.Links, )if .links[.Text] == nil { .links[.Text] = } }returnnil:return &Paragraph{Text: []Text{Plain(strings.Join(, "\n"))}}}// parseLink parses a single link definition line://// [text]: url//// It returns the link definition and whether the line was well formed.func parseLink( string) (*LinkDef, bool) {if == "" || [0] != '[' {returnnil, false } := strings.Index(, "]:")if < 0 || +3 >= len() || ([+2] != ' ' && [+2] != '\t') {returnnil, false } := [1:] := strings.TrimSpace([+3:]) := strings.Index(, "://")if < 0 || !isScheme([:]) {returnnil, false }// Line has right form and has valid scheme://. // That's good enough for us - we are not as picky // about the characters beyond the :// as we are // when extracting inline URLs from text.return &LinkDef{Text: , URL: }, true}// list returns a list built from the indented lines,// using forceBlankBefore as the value of the List's ForceBlankBefore field.func ( *parseDoc) ( []string, bool) *List { , , := listMarker([0])var ( *List = &List{ForceBlankBefore: } *ListItem []string ) := func() {if != nil {if := .paragraph(); != nil { .Content = append(.Content, ) } } = nil }for , := range {if , , := listMarker(); && ( != "") == ( != "") {// start new list item () = &ListItem{Number: } .Items = append(.Items, ) = } = strings.TrimSpace()if == "" { .ForceBlankBetween = true ()continue } = append(, strings.TrimSpace()) } ()return}// listMarker parses the line as beginning with a list marker.// If it can do that, it returns the numeric marker ("" for a bullet list),// the rest of the line, and ok == true.// Otherwise, it returns "", "", false.func listMarker( string) (, string, bool) { = strings.TrimSpace()if == "" {return"", "", false }// Can we find a marker?if , := utf8.DecodeRuneInString(); == '•' || == '*' || == '+' || == '-' { , = "", [:] } elseif'0' <= [0] && [0] <= '9' { := 1for < len() && '0' <= [] && [] <= '9' { ++ }if >= len() || ([] != '.' && [] != ')') {return"", "", false } , = [:], [+1:] } else {return"", "", false }if !indented() || strings.TrimSpace() == "" {return"", "", false }return , , true}// isList reports whether the line is the first line of a list,// meaning starts with a list marker after any indentation.// (The caller is responsible for checking the line is indented, as appropriate.)func isList( string) bool { , , := listMarker()return}// parseLinkedText parses text that is allowed to contain explicit links,// such as [math.Sin] or [Go home page], into a slice of Text items.//// A “pkg” is only assumed to be a full import path if it starts with// a domain name (a path element with a dot) or is one of the packages// from the standard library (“[os]”, “[encoding/json]”, and so on).// To avoid problems with maps, generics, and array types, doc links// must be both preceded and followed by punctuation, spaces, tabs,// or the start or end of a line. An example problem would be treating// map[ast.Expr]TypeAndValue as containing a link.func ( *parseDoc) ( string) []Text {var []Text := 0 := func( int) {if < { = .parseText(, [:], true) = } } := -1var []bytefor := 0; < len(); ++ { := []if == '\n' || == '\t' { = ' ' }switch {case'[': = case']':if >= 0 {if , := .links[string()]; { .Used = true () = append(, &Link{Text: .parseText(nil, [+1:], false),URL: .URL, }) = + 1 } elseif , := .docLink([+1:], [:], [+1:]); { () .Text = .parseText(nil, [+1:], false) = append(, ) = + 1 } } = -1 = [:0] }if >= 0 && != { = append(, ) } } (len())return}// docLink parses text, which was found inside [ ] brackets,// as a doc link if possible, returning the DocLink and ok == true// or else nil, false.// The before and after strings are the text before the [ and after the ]// on the same line. Doc links must be preceded and followed by// punctuation, spaces, tabs, or the start or end of a line.func ( *parseDoc) (, , string) ( *DocLink, bool) {if != "" { , := utf8.DecodeLastRuneInString()if !unicode.IsPunct() && != ' ' && != '\t' && != '\n' {returnnil, false } }if != "" { , := utf8.DecodeRuneInString()if !unicode.IsPunct() && != ' ' && != '\t' && != '\n' {returnnil, false } } = strings.TrimPrefix(, "*") , , := splitDocName()varstringif { , , _ = splitDocName() }if != "" {if , = .lookupPkg(); ! {returnnil, false } } else {if = .lookupSym(, ); ! {returnnil, false } } = &DocLink{ImportPath: ,Recv: ,Name: , }return , true}// If text is of the form before.Name, where Name is a capitalized Go identifier,// then splitDocName returns before, name, true.// Otherwise it returns text, "", false.func splitDocName( string) (, string, bool) { := strings.LastIndex(, ".") = [+1:]if !isName() {return , "", false }if >= 0 { = [:] }return , , true}// parseText parses s as text and returns the result of appending// those parsed Text elements to out.// parseText does not handle explicit links like [math.Sin] or [Go home page]:// those are handled by parseLinkedText.// If autoLink is true, then parseText recognizes URLs and words from d.Words// and converts those to links as appropriate.func ( *parseDoc) ( []Text, string, bool) []Text {varstrings.Builder := 0 := func( int) { .WriteString([:]) = } := func( int) { ()if .Len() > 0 { = append(, Plain(.String())) .Reset() } }for := 0; < len(); { := [:]if {if , := autoURL(); { ()// Note: The old comment parser would look up the URL in words // and replace the target with words[URL] if it was non-empty. // That would allow creating links that display as one URL but // when clicked go to a different URL. Not sure what the point // of that is, so we're not doing that lookup here. = append(, &Link{Auto: true, Text: []Text{Plain()}, URL: }) += len() = continue }if , := ident(); { , := .Words[]if ! { += len()continue } ()if == "" { = append(, Italic()) } else { = append(, &Link{Auto: true, Text: []Text{Italic()}, URL: }) } += len() = continue } }switch {casestrings.HasPrefix(, "``"):iflen() >= 3 && [2] == '`' {// Do not convert `` inside ```, in case people are mistakenly writing Markdown. += 3for < len() && [] == '`' { ++ }break } () .WriteRune('“') += 2 = casestrings.HasPrefix(, "''"): () .WriteRune('”') += 2 = default: ++ } } (len())return}// autoURL checks whether s begins with a URL that should be hyperlinked.// If so, it returns the URL, which is a prefix of s, and ok == true.// Otherwise it returns "", false.// The caller should skip over the first len(url) bytes of s// before further processing.func autoURL( string) ( string, bool) {// Find the ://. Fast path to pick off non-URL, // since we call this at every position in the string. // The shortest possible URL is ftp://x, 7 bytes.varintswitch {caselen() < 7:return"", falsecase [3] == ':': = 3case [4] == ':': = 4case [5] == ':': = 5case [6] == ':': = 6default:return"", false }if +3 > len() || [:+3] != "://" {return"", false }// Check valid scheme.if !isScheme([:]) {return"", false }// Scan host part. Must have at least one byte, // and must start and end in non-punctuation. += 3if >= len() || !isHost([]) || isPunct([]) {return"", false } ++ := for < len() && isHost([]) {if !isPunct([]) { = + 1 } ++ } = // At this point we are definitely returning a URL (scheme://host). // We just have to find the longest path we can add to it. // Heuristics abound. // We allow parens, braces, and brackets, // but only if they match (#5043, #22285). // We allow .,:;?! in the path but not at the end, // to avoid end-of-sentence punctuation (#18139, #16565). := []byte{} = :for ; < len(); ++ {ifisPunct([]) {continue }if !isPath([]) {break }switch [] {case'(': = append(, ')')case'{': = append(, '}')case'[': = append(, ']')case')', '}', ']':iflen() == 0 || [len()-1] != [] {break } = [:len()-1] }iflen() == 0 { = + 1 } }return [:], true}// isScheme reports whether s is a recognized URL scheme.// Note that if strings of new length (beyond 3-7)// are added here, the fast path at the top of autoURL will need updating.func isScheme( string) bool {switch {case"file","ftp","gopher","http","https","mailto","nntp":returntrue }returnfalse}// isHost reports whether c is a byte that can appear in a URL host,// like www.example.com or user@[::1]:8080func isHost( byte) bool {// mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false.const = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' |1<<'_' |1<<'@' |1<<'-' |1<<'.' |1<<'[' |1<<']' |1<<':'return ((uint64(1)<<)&(&(1<<64-1)) | (uint64(1)<<(-64))&(>>64)) != 0}// isPunct reports whether c is a punctuation byte that can appear// inside a path but not at the end.func isPunct( byte) bool {// mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false.const = 0 |1<<'.' |1<<',' |1<<':' |1<<';' |1<<'?' |1<<'!'return ((uint64(1)<<)&(&(1<<64-1)) | (uint64(1)<<(-64))&(>>64)) != 0}// isPath reports whether c is a (non-punctuation) path byte.func isPath( byte) bool {// mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false.const = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' |1<<'$' |1<<'\'' |1<<'(' |1<<')' |1<<'*' |1<<'+' |1<<'&' |1<<'#' |1<<'=' |1<<'@' |1<<'~' |1<<'_' |1<<'/' |1<<'-' |1<<'[' |1<<']' |1<<'{' |1<<'}' |1<<'%'return ((uint64(1)<<)&(&(1<<64-1)) | (uint64(1)<<(-64))&(>>64)) != 0}// isName reports whether s is a capitalized Go identifier (like Name).func isName( string) bool { , := ident()if ! || != {returnfalse } , := utf8.DecodeRuneInString()returnunicode.IsUpper()}// ident checks whether s begins with a Go identifier.// If so, it returns the identifier, which is a prefix of s, and ok == true.// Otherwise it returns "", false.// The caller should skip over the first len(id) bytes of s// before further processing.func ident( string) ( string, bool) {// Scan [\pL_][\pL_0-9]* := 0for < len() {if := []; < utf8.RuneSelf {ifisIdentASCII() && ( > 0 || < '0' || > '9') { ++continue }break } , := utf8.DecodeRuneInString([:])ifunicode.IsLetter() { += continue }break }return [:], > 0}// isIdentASCII reports whether c is an ASCII identifier byte.func isIdentASCII( byte) bool {// mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false.const = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' |1<<'_'return ((uint64(1)<<)&(&(1<<64-1)) | (uint64(1)<<(-64))&(>>64)) != 0}// validImportPath reports whether path is a valid import path.// It is a lightly edited copy of golang.org/x/mod/module.CheckImportPath.func validImportPath( string) bool {if !utf8.ValidString() {returnfalse }if == "" {returnfalse }if [0] == '-' {returnfalse }ifstrings.Contains(, "//") {returnfalse }if [len()-1] == '/' {returnfalse } := 0for , := range {if == '/' {if !validImportPathElem([:]) {returnfalse } = + 1 } }returnvalidImportPathElem([:])}func validImportPathElem( string) bool {if == "" || [0] == '.' || [len()-1] == '.' {returnfalse }for := 0; < len(); ++ {if !importPathOK([]) {returnfalse } }returntrue}func importPathOK( byte) bool {// mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false.const = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' |1<<'-' |1<<'.' |1<<'~' |1<<'_' |1<<'+'return ((uint64(1)<<)&(&(1<<64-1)) | (uint64(1)<<(-64))&(>>64)) != 0}
The pages are generated with Goldsv0.7.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 @zigo_101 (reachable from the left QR code) to get the latest news of Golds.