X-Git-Url: https://git.josue.xyz/?a=blobdiff_plain;f=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Fgodoc%2Fgodoc.go;fp=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Fgodoc%2Fgodoc.go;h=6d4d1159a649bba4f3802c1dd57772f81600031b;hb=4d07c77cf4d78cab8639e13ddc3c22495e585b0b;hp=0000000000000000000000000000000000000000;hpb=b3950616b54221c40a7dab9099bda675007e5b6e;p=dotfiles%2F.git diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/godoc/godoc.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/godoc/godoc.go new file mode 100644 index 00000000..6d4d1159 --- /dev/null +++ b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/godoc/godoc.go @@ -0,0 +1,939 @@ +// Copyright 2013 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 godoc is a work-in-progress (2013-07-17) package to +// begin splitting up the godoc binary into multiple pieces. +// +// This package comment will evolve over time as this package splits +// into smaller pieces. +package godoc // import "golang.org/x/tools/godoc" + +import ( + "bufio" + "bytes" + "fmt" + "go/ast" + "go/doc" + "go/format" + "go/printer" + "go/token" + htmltemplate "html/template" + "io" + "log" + "os" + pathpkg "path" + "regexp" + "strconv" + "strings" + "text/template" + "time" + "unicode" + "unicode/utf8" +) + +// Fake relative package path for built-ins. Documentation for all globals +// (not just exported ones) will be shown for packages in this directory, +// and there will be no association of consts, vars, and factory functions +// with types (see issue 6645). +const builtinPkgPath = "builtin" + +// FuncMap defines template functions used in godoc templates. +// +// Convention: template function names ending in "_html" or "_url" produce +// HTML- or URL-escaped strings; all other function results may +// require explicit escaping in the template. +func (p *Presentation) FuncMap() template.FuncMap { + p.initFuncMapOnce.Do(p.initFuncMap) + return p.funcMap +} + +func (p *Presentation) TemplateFuncs() template.FuncMap { + p.initFuncMapOnce.Do(p.initFuncMap) + return p.templateFuncs +} + +func (p *Presentation) initFuncMap() { + if p.Corpus == nil { + panic("nil Presentation.Corpus") + } + p.templateFuncs = template.FuncMap{ + "code": p.code, + } + p.funcMap = template.FuncMap{ + // various helpers + "filename": filenameFunc, + "repeat": strings.Repeat, + "since": p.Corpus.pkgAPIInfo.sinceVersionFunc, + + // access to FileInfos (directory listings) + "fileInfoName": fileInfoNameFunc, + "fileInfoTime": fileInfoTimeFunc, + + // access to search result information + "infoKind_html": infoKind_htmlFunc, + "infoLine": p.infoLineFunc, + "infoSnippet_html": p.infoSnippet_htmlFunc, + + // formatting of AST nodes + "node": p.nodeFunc, + "node_html": p.node_htmlFunc, + "comment_html": comment_htmlFunc, + "sanitize": sanitizeFunc, + + // support for URL attributes + "pkgLink": pkgLinkFunc, + "srcLink": srcLinkFunc, + "posLink_url": newPosLink_urlFunc(srcPosLinkFunc), + "docLink": docLinkFunc, + "queryLink": queryLinkFunc, + "srcBreadcrumb": srcBreadcrumbFunc, + "srcToPkgLink": srcToPkgLinkFunc, + + // formatting of Examples + "example_html": p.example_htmlFunc, + "example_name": p.example_nameFunc, + "example_suffix": p.example_suffixFunc, + + // formatting of analysis information + "callgraph_html": p.callgraph_htmlFunc, + "implements_html": p.implements_htmlFunc, + "methodset_html": p.methodset_htmlFunc, + + // formatting of Notes + "noteTitle": noteTitle, + + // Number operation + "multiply": multiply, + + // formatting of PageInfoMode query string + "modeQueryString": modeQueryString, + + // check whether to display third party section or not + "hasThirdParty": hasThirdParty, + + // get the no. of columns to split the toc in search page + "tocColCount": tocColCount, + } + if p.URLForSrc != nil { + p.funcMap["srcLink"] = p.URLForSrc + } + if p.URLForSrcPos != nil { + p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos) + } + if p.URLForSrcQuery != nil { + p.funcMap["queryLink"] = p.URLForSrcQuery + } +} + +func multiply(a, b int) int { return a * b } + +func filenameFunc(path string) string { + _, localname := pathpkg.Split(path) + return localname +} + +func fileInfoNameFunc(fi os.FileInfo) string { + name := fi.Name() + if fi.IsDir() { + name += "/" + } + return name +} + +func fileInfoTimeFunc(fi os.FileInfo) string { + if t := fi.ModTime(); t.Unix() != 0 { + return t.Local().String() + } + return "" // don't return epoch if time is obviously not set +} + +// The strings in infoKinds must be properly html-escaped. +var infoKinds = [nKinds]string{ + PackageClause: "package clause", + ImportDecl: "import decl", + ConstDecl: "const decl", + TypeDecl: "type decl", + VarDecl: "var decl", + FuncDecl: "func decl", + MethodDecl: "method decl", + Use: "use", +} + +func infoKind_htmlFunc(info SpotInfo) string { + return infoKinds[info.Kind()] // infoKind entries are html-escaped +} + +func (p *Presentation) infoLineFunc(info SpotInfo) int { + line := info.Lori() + if info.IsIndex() { + index, _ := p.Corpus.searchIndex.Get() + if index != nil { + line = index.(*Index).Snippet(line).Line + } else { + // no line information available because + // we don't have an index - this should + // never happen; be conservative and don't + // crash + line = 0 + } + } + return line +} + +func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string { + if info.IsIndex() { + index, _ := p.Corpus.searchIndex.Get() + // Snippet.Text was HTML-escaped when it was generated + return index.(*Index).Snippet(info.Lori()).Text + } + return `no snippet text available` +} + +func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string { + var buf bytes.Buffer + p.writeNode(&buf, info, info.FSet, node) + return buf.String() +} + +func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string { + var buf1 bytes.Buffer + p.writeNode(&buf1, info, info.FSet, node) + + var buf2 bytes.Buffer + if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks { + LinkifyText(&buf2, buf1.Bytes(), n) + if st, name := isStructTypeDecl(n); st != nil { + addStructFieldIDAttributes(&buf2, name, st) + } + } else { + FormatText(&buf2, buf1.Bytes(), -1, true, "", nil) + } + + return buf2.String() +} + +// isStructTypeDecl checks whether n is a struct declaration. +// It either returns a non-nil StructType and its name, or zero values. +func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) { + gd, ok := n.(*ast.GenDecl) + if !ok || gd.Tok != token.TYPE { + return nil, "" + } + if gd.Lparen > 0 { + // Parenthesized type. Who does that, anyway? + // TODO: Reportedly gri does. Fix this to handle that too. + return nil, "" + } + if len(gd.Specs) != 1 { + return nil, "" + } + ts, ok := gd.Specs[0].(*ast.TypeSpec) + if !ok { + return nil, "" + } + st, ok = ts.Type.(*ast.StructType) + if !ok { + return nil, "" + } + return st, ts.Name.Name +} + +// addStructFieldIDAttributes modifies the contents of buf such that +// all struct fields of the named struct have +// in them, so people can link to /#Struct.Field. +func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) { + if st.Fields == nil { + return + } + // needsLink is a set of identifiers that still need to be + // linked, where value == key, to avoid an allocation in func + // linkedField. + needsLink := make(map[string]string) + + for _, f := range st.Fields.List { + if len(f.Names) == 0 { + continue + } + fieldName := f.Names[0].Name + needsLink[fieldName] = fieldName + } + var newBuf bytes.Buffer + foreachLine(buf.Bytes(), func(line []byte) { + if fieldName := linkedField(line, needsLink); fieldName != "" { + fmt.Fprintf(&newBuf, ``, name, fieldName) + delete(needsLink, fieldName) + } + newBuf.Write(line) + }) + buf.Reset() + buf.Write(newBuf.Bytes()) +} + +// foreachLine calls fn for each line of in, where a line includes +// the trailing "\n", except on the last line, if it doesn't exist. +func foreachLine(in []byte, fn func(line []byte)) { + for len(in) > 0 { + nl := bytes.IndexByte(in, '\n') + if nl == -1 { + fn(in) + return + } + fn(in[:nl+1]) + in = in[nl+1:] + } +} + +// commentPrefix is the line prefix for comments after they've been HTMLified. +var commentPrefix = []byte(`// `) + +// linkedField determines whether the given line starts with an +// identifier in the provided ids map (mapping from identifier to the +// same identifier). The line can start with either an identifier or +// an identifier in a comment. If one matches, it returns the +// identifier that matched. Otherwise it returns the empty string. +func linkedField(line []byte, ids map[string]string) string { + line = bytes.TrimSpace(line) + + // For fields with a doc string of the + // conventional form, we put the new span into + // the comment instead of the field. + // The "conventional" form is a complete sentence + // per https://golang.org/s/style#comment-sentences like: + // + // // Foo is an optional Fooer to foo the foos. + // Foo Fooer + // + // In this case, we want the #StructName.Foo + // link to make the browser go to the comment + // line "Foo is an optional Fooer" instead of + // the "Foo Fooer" line, which could otherwise + // obscure the docs above the browser's "fold". + // + // TODO: do this better, so it works for all + // comments, including unconventional ones. + line = bytes.TrimPrefix(line, commentPrefix) + id := scanIdentifier(line) + if len(id) == 0 { + // No leading identifier. Avoid map lookup for + // somewhat common case. + return "" + } + return ids[string(id)] +} + +// scanIdentifier scans a valid Go identifier off the front of v and +// either returns a subslice of v if there's a valid identifier, or +// returns a zero-length slice. +func scanIdentifier(v []byte) []byte { + var n int // number of leading bytes of v belonging to an identifier + for { + r, width := utf8.DecodeRune(v[n:]) + if !(isLetter(r) || n > 0 && isDigit(r)) { + break + } + n += width + } + return v[:n] +} + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch) +} + +func comment_htmlFunc(comment string) string { + var buf bytes.Buffer + // TODO(gri) Provide list of words (e.g. function parameters) + // to be emphasized by ToHTML. + doc.ToHTML(&buf, comment, nil) // does html-escaping + return buf.String() +} + +// sanitizeFunc sanitizes the argument src by replacing newlines with +// blanks, removing extra blanks, and by removing trailing whitespace +// and commas before closing parentheses. +func sanitizeFunc(src string) string { + buf := make([]byte, len(src)) + j := 0 // buf index + comma := -1 // comma index if >= 0 + for i := 0; i < len(src); i++ { + ch := src[i] + switch ch { + case '\t', '\n', ' ': + // ignore whitespace at the beginning, after a blank, or after opening parentheses + if j == 0 { + continue + } + if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' { + continue + } + // replace all whitespace with blanks + ch = ' ' + case ',': + comma = j + case ')', '}', ']': + // remove any trailing comma + if comma >= 0 { + j = comma + } + // remove any trailing whitespace + if j > 0 && buf[j-1] == ' ' { + j-- + } + default: + comma = -1 + } + buf[j] = ch + j++ + } + // remove trailing blank, if any + if j > 0 && buf[j-1] == ' ' { + j-- + } + return string(buf[:j]) +} + +type PageInfo struct { + Dirname string // directory containing the package + Err error // error or nil + GoogleCN bool // page is being served from golang.google.cn + + Mode PageInfoMode // display metadata from query string + + // package info + FSet *token.FileSet // nil if no package documentation + PDoc *doc.Package // nil if no package documentation + Examples []*doc.Example // nil if no example code + Notes map[string][]*doc.Note // nil if no package Notes + PAst map[string]*ast.File // nil if no AST with package exports + IsMain bool // true for package main + IsFiltered bool // true if results were filtered + + // analysis info + TypeInfoIndex map[string]int // index of JSON datum for type T (if -analysis=type) + AnalysisData htmltemplate.JS // array of TypeInfoJSON values + CallGraph htmltemplate.JS // array of PCGNodeJSON values (if -analysis=pointer) + CallGraphIndex map[string]int // maps func name to index in CallGraph + + // directory info + Dirs *DirList // nil if no directory information + DirTime time.Time // directory time stamp + DirFlat bool // if set, show directory in a flat (non-indented) manner +} + +func (info *PageInfo) IsEmpty() bool { + return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil +} + +func pkgLinkFunc(path string) string { + // because of the irregular mapping under goroot + // we need to correct certain relative paths + path = strings.TrimPrefix(path, "/") + path = strings.TrimPrefix(path, "src/") + path = strings.TrimPrefix(path, "pkg/") + return "pkg/" + path +} + +// srcToPkgLinkFunc builds an tag linking to the package +// documentation of relpath. +func srcToPkgLinkFunc(relpath string) string { + relpath = pkgLinkFunc(relpath) + relpath = pathpkg.Dir(relpath) + if relpath == "pkg" { + return `Index` + } + return fmt.Sprintf(`%s`, relpath, relpath[len("pkg/"):]) +} + +// srcBreadcrumbFun converts each segment of relpath to a HTML . +// Each segment links to its corresponding src directories. +func srcBreadcrumbFunc(relpath string) string { + segments := strings.Split(relpath, "/") + var buf bytes.Buffer + var selectedSegment string + var selectedIndex int + + if strings.HasSuffix(relpath, "/") { + // relpath is a directory ending with a "/". + // Selected segment is the segment before the last slash. + selectedIndex = len(segments) - 2 + selectedSegment = segments[selectedIndex] + "/" + } else { + selectedIndex = len(segments) - 1 + selectedSegment = segments[selectedIndex] + } + + for i := range segments[:selectedIndex] { + buf.WriteString(fmt.Sprintf(`%s/`, + strings.Join(segments[:i+1], "/"), + segments[i], + )) + } + + buf.WriteString(``) + buf.WriteString(selectedSegment) + buf.WriteString(``) + return buf.String() +} + +func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string { + // n must be an ast.Node or a *doc.Note + return func(info *PageInfo, n interface{}) string { + var pos, end token.Pos + + switch n := n.(type) { + case ast.Node: + pos = n.Pos() + end = n.End() + case *doc.Note: + pos = n.Pos + end = n.End + default: + panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n)) + } + + var relpath string + var line int + var low, high int // selection offset range + + if pos.IsValid() { + p := info.FSet.Position(pos) + relpath = p.Filename + line = p.Line + low = p.Offset + } + if end.IsValid() { + high = info.FSet.Position(end).Offset + } + + return srcPosLinkFunc(relpath, line, low, high) + } +} + +func srcPosLinkFunc(s string, line, low, high int) string { + s = srcLinkFunc(s) + var buf bytes.Buffer + template.HTMLEscape(&buf, []byte(s)) + // selection ranges are of form "s=low:high" + if low < high { + fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping + // if we have a selection, position the page + // such that the selection is a bit below the top + line -= 10 + if line < 1 { + line = 1 + } + } + // line id's in html-printed source are of the + // form "L%d" where %d stands for the line number + if line > 0 { + fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping + } + return buf.String() +} + +func srcLinkFunc(s string) string { + s = pathpkg.Clean("/" + s) + if !strings.HasPrefix(s, "/src/") { + s = "/src" + s + } + return s +} + +// queryLinkFunc returns a URL for a line in a source file with a highlighted +// query term. +// s is expected to be a path to a source file. +// query is expected to be a string that has already been appropriately escaped +// for use in a URL query. +func queryLinkFunc(s, query string, line int) string { + url := pathpkg.Clean("/"+s) + "?h=" + query + if line > 0 { + url += "#L" + strconv.Itoa(line) + } + return url +} + +func docLinkFunc(s string, ident string) string { + return pathpkg.Clean("/pkg/"+s) + "/#" + ident +} + +func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string { + var buf bytes.Buffer + for _, eg := range info.Examples { + name := stripExampleSuffix(eg.Name) + + if name != funcName { + continue + } + + // print code + cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} + code := p.node_htmlFunc(info, cnode, true) + out := eg.Output + wholeFile := true + + // Additional formatting if this is a function body. + if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' { + wholeFile = false + // remove surrounding braces + code = code[1 : n-1] + // unindent + code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "") + // remove output comment + if loc := exampleOutputRx.FindStringIndex(code); loc != nil { + code = strings.TrimSpace(code[:loc[0]]) + } + } + + // Write out the playground code in standard Go style + // (use tabs, no comment highlight, etc). + play := "" + if eg.Play != nil && p.ShowPlayground { + var buf bytes.Buffer + eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments) + if err := format.Node(&buf, info.FSet, eg.Play); err != nil { + log.Print(err) + } else { + play = buf.String() + } + } + + // Drop output, as the output comment will appear in the code. + if wholeFile && play == "" { + out = "" + } + + if p.ExampleHTML == nil { + out = "" + return "" + } + + err := p.ExampleHTML.Execute(&buf, struct { + Name, Doc, Code, Play, Output string + GoogleCN bool + }{eg.Name, eg.Doc, code, play, out, info.GoogleCN}) + if err != nil { + log.Print(err) + } + } + return buf.String() +} + +func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup { + if len(cg) == 0 { + return cg + } + + for i := range cg { + if !strings.HasPrefix(cg[i].Text(), "+build ") { + // Found the first non-build tag, return from here until the end + // of the slice. + return cg[i:] + } + } + + // There weren't any non-build tags, return an empty slice. + return []*ast.CommentGroup{} +} + +// example_nameFunc takes an example function name and returns its display +// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)". +func (p *Presentation) example_nameFunc(s string) string { + name, suffix := splitExampleName(s) + // replace _ with . for method names + name = strings.Replace(name, "_", ".", 1) + // use "Package" if no name provided + if name == "" { + name = "Package" + } + return name + suffix +} + +// example_suffixFunc takes an example function name and returns its suffix in +// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)". +func (p *Presentation) example_suffixFunc(name string) string { + _, suffix := splitExampleName(name) + return suffix +} + +// implements_html returns the "> Implements" toggle for a package-level named type. +// Its contents are populated from JSON data by client-side JS at load time. +func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string { + if p.ImplementsHTML == nil { + return "" + } + index, ok := info.TypeInfoIndex[typeName] + if !ok { + return "" + } + var buf bytes.Buffer + err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index}) + if err != nil { + log.Print(err) + } + return buf.String() +} + +// methodset_html returns the "> Method set" toggle for a package-level named type. +// Its contents are populated from JSON data by client-side JS at load time. +func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string { + if p.MethodSetHTML == nil { + return "" + } + index, ok := info.TypeInfoIndex[typeName] + if !ok { + return "" + } + var buf bytes.Buffer + err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index}) + if err != nil { + log.Print(err) + } + return buf.String() +} + +// callgraph_html returns the "> Call graph" toggle for a package-level func. +// Its contents are populated from JSON data by client-side JS at load time. +func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string { + if p.CallGraphHTML == nil { + return "" + } + if recv != "" { + // Format must match (*ssa.Function).RelString(). + name = fmt.Sprintf("(%s).%s", recv, name) + } + index, ok := info.CallGraphIndex[name] + if !ok { + return "" + } + var buf bytes.Buffer + err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index}) + if err != nil { + log.Print(err) + } + return buf.String() +} + +func noteTitle(note string) string { + return strings.Title(strings.ToLower(note)) +} + +func startsWithUppercase(s string) bool { + r, _ := utf8.DecodeRuneInString(s) + return unicode.IsUpper(r) +} + +var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`) + +// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name +// while keeping uppercase Braz in Foo_Braz. +func stripExampleSuffix(name string) string { + if i := strings.LastIndex(name, "_"); i != -1 { + if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { + name = name[:i] + } + } + return name +} + +func splitExampleName(s string) (name, suffix string) { + i := strings.LastIndex(s, "_") + if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) { + name = s[:i] + suffix = " (" + strings.Title(s[i+1:]) + ")" + return + } + name = s + return +} + +// replaceLeadingIndentation replaces oldIndent at the beginning of each line +// with newIndent. This is used for formatting examples. Raw strings that +// span multiple lines are handled specially: oldIndent is not removed (since +// go/printer will not add any indentation there), but newIndent is added +// (since we may still want leading indentation). +func replaceLeadingIndentation(body, oldIndent, newIndent string) string { + // Handle indent at the beginning of the first line. After this, we handle + // indentation only after a newline. + var buf bytes.Buffer + if strings.HasPrefix(body, oldIndent) { + buf.WriteString(newIndent) + body = body[len(oldIndent):] + } + + // Use a state machine to keep track of whether we're in a string or + // rune literal while we process the rest of the code. + const ( + codeState = iota + runeState + interpretedStringState + rawStringState + ) + searchChars := []string{ + "'\"`\n", // codeState + `\'`, // runeState + `\"`, // interpretedStringState + "`\n", // rawStringState + // newlineState does not need to search + } + state := codeState + for { + i := strings.IndexAny(body, searchChars[state]) + if i < 0 { + buf.WriteString(body) + break + } + c := body[i] + buf.WriteString(body[:i+1]) + body = body[i+1:] + switch state { + case codeState: + switch c { + case '\'': + state = runeState + case '"': + state = interpretedStringState + case '`': + state = rawStringState + case '\n': + if strings.HasPrefix(body, oldIndent) { + buf.WriteString(newIndent) + body = body[len(oldIndent):] + } + } + + case runeState: + switch c { + case '\\': + r, size := utf8.DecodeRuneInString(body) + buf.WriteRune(r) + body = body[size:] + case '\'': + state = codeState + } + + case interpretedStringState: + switch c { + case '\\': + r, size := utf8.DecodeRuneInString(body) + buf.WriteRune(r) + body = body[size:] + case '"': + state = codeState + } + + case rawStringState: + switch c { + case '`': + state = codeState + case '\n': + buf.WriteString(newIndent) + } + } + } + return buf.String() +} + +// writeNode writes the AST node x to w. +// +// The provided fset must be non-nil. The pageInfo is optional. If +// present, the pageInfo is used to add comments to struct fields to +// say which version of Go introduced them. +func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) { + // convert trailing tabs into spaces using a tconv filter + // to ensure a good outcome in most browsers (there may still + // be tabs in comments and strings, but converting those into + // the right number of spaces is much harder) + // + // TODO(gri) rethink printer flags - perhaps tconv can be eliminated + // with an another printer mode (which is more efficiently + // implemented in the printer than here with another layer) + + var pkgName, structName string + var apiInfo pkgAPIVersions + if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil && + p.Corpus != nil && + gd.Tok == token.TYPE && len(gd.Specs) != 0 { + pkgName = pageInfo.PDoc.ImportPath + if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok { + if _, ok := ts.Type.(*ast.StructType); ok { + structName = ts.Name.Name + } + } + apiInfo = p.Corpus.pkgAPIInfo[pkgName] + } + + var out = w + var buf bytes.Buffer + if structName != "" { + out = &buf + } + + mode := printer.TabIndent | printer.UseSpaces + err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x) + if err != nil { + log.Print(err) + } + + // Add comments to struct fields saying which Go version introduced them. + if structName != "" { + fieldSince := apiInfo.fieldSince[structName] + typeSince := apiInfo.typeSince[structName] + // Add/rewrite comments on struct fields to note which Go version added them. + var buf2 bytes.Buffer + buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10) + bs := bufio.NewScanner(&buf) + for bs.Scan() { + line := bs.Bytes() + field := firstIdent(line) + var since string + if field != "" { + since = fieldSince[field] + if since != "" && since == typeSince { + // Don't highlight field versions if they were the + // same as the struct itself. + since = "" + } + } + if since == "" { + buf2.Write(line) + } else { + if bytes.Contains(line, slashSlash) { + line = bytes.TrimRight(line, " \t.") + buf2.Write(line) + buf2.WriteString("; added in Go ") + } else { + buf2.Write(line) + buf2.WriteString(" // Go ") + } + buf2.WriteString(since) + } + buf2.WriteByte('\n') + } + w.Write(buf2.Bytes()) + } +} + +var slashSlash = []byte("//") + +// WriteNode writes x to w. +// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode. +func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) { + p.writeNode(w, nil, fset, x) +} + +// firstIdent returns the first identifier in x. +// This actually parses "identifiers" that begin with numbers too, but we +// never feed it such input, so it's fine. +func firstIdent(x []byte) string { + x = bytes.TrimSpace(x) + i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }) + if i == -1 { + return string(x) + } + return string(x[:i]) +}