// 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 or at // https://developers.google.com/open-source/licenses/bsd. package doc import ( "bytes" "fmt" "go/ast" "go/doc" "go/printer" "go/scanner" "go/token" "math" "strconv" ) const ( notPredeclared = iota predeclaredType predeclaredConstant predeclaredFunction ) // predeclared represents the set of all predeclared identifiers. var predeclared = map[string]int{ "bool": predeclaredType, "byte": predeclaredType, "complex128": predeclaredType, "complex64": predeclaredType, "error": predeclaredType, "float32": predeclaredType, "float64": predeclaredType, "int16": predeclaredType, "int32": predeclaredType, "int64": predeclaredType, "int8": predeclaredType, "int": predeclaredType, "rune": predeclaredType, "string": predeclaredType, "uint16": predeclaredType, "uint32": predeclaredType, "uint64": predeclaredType, "uint8": predeclaredType, "uint": predeclaredType, "uintptr": predeclaredType, "true": predeclaredConstant, "false": predeclaredConstant, "iota": predeclaredConstant, "nil": predeclaredConstant, "append": predeclaredFunction, "cap": predeclaredFunction, "close": predeclaredFunction, "complex": predeclaredFunction, "copy": predeclaredFunction, "delete": predeclaredFunction, "imag": predeclaredFunction, "len": predeclaredFunction, "make": predeclaredFunction, "new": predeclaredFunction, "panic": predeclaredFunction, "print": predeclaredFunction, "println": predeclaredFunction, "real": predeclaredFunction, "recover": predeclaredFunction, } type AnnotationKind int16 const ( // Link to export in package specified by Paths[PathIndex] with fragment // Text[strings.LastIndex(Text[Pos:End], ".")+1:End]. LinkAnnotation AnnotationKind = iota // Anchor with name specified by Text[Pos:End] or typeName + "." + // Text[Pos:End] for type declarations. AnchorAnnotation // Comment. CommentAnnotation // Link to package specified by Paths[PathIndex]. PackageLinkAnnotation // Link to builtin entity with name Text[Pos:End]. BuiltinAnnotation ) type Annotation struct { Pos, End int32 Kind AnnotationKind PathIndex int16 } type Code struct { Text string Annotations []Annotation Paths []string } // declVisitor modifies a declaration AST for printing and collects annotations. type declVisitor struct { annotations []Annotation paths []string pathIndex map[string]int comments []*ast.CommentGroup } func (v *declVisitor) add(kind AnnotationKind, importPath string) { pathIndex := -1 if importPath != "" { var ok bool pathIndex, ok = v.pathIndex[importPath] if !ok { pathIndex = len(v.paths) v.paths = append(v.paths, importPath) v.pathIndex[importPath] = pathIndex } } v.annotations = append(v.annotations, Annotation{Kind: kind, PathIndex: int16(pathIndex)}) } func (v *declVisitor) ignoreName() { v.add(-1, "") } func (v *declVisitor) Visit(n ast.Node) ast.Visitor { switch n := n.(type) { case *ast.TypeSpec: v.ignoreName() switch n := n.Type.(type) { case *ast.InterfaceType: for _, f := range n.Methods.List { for _ = range f.Names { v.add(AnchorAnnotation, "") } ast.Walk(v, f.Type) } case *ast.StructType: for _, f := range n.Fields.List { for _ = range f.Names { v.add(AnchorAnnotation, "") } ast.Walk(v, f.Type) } default: ast.Walk(v, n) } case *ast.FuncDecl: if n.Recv != nil { ast.Walk(v, n.Recv) } v.ignoreName() ast.Walk(v, n.Type) case *ast.Field: for _ = range n.Names { v.ignoreName() } ast.Walk(v, n.Type) case *ast.ValueSpec: for _ = range n.Names { v.add(AnchorAnnotation, "") } if n.Type != nil { ast.Walk(v, n.Type) } for _, x := range n.Values { ast.Walk(v, x) } case *ast.Ident: switch { case n.Obj == nil && predeclared[n.Name] != notPredeclared: v.add(BuiltinAnnotation, "") case n.Obj != nil && ast.IsExported(n.Name): v.add(LinkAnnotation, "") default: v.ignoreName() } case *ast.SelectorExpr: if x, _ := n.X.(*ast.Ident); x != nil { if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg { if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil { if path, err := strconv.Unquote(spec.Path.Value); err == nil { v.add(PackageLinkAnnotation, path) if path == "C" { v.ignoreName() } else { v.add(LinkAnnotation, path) } return nil } } } } ast.Walk(v, n.X) v.ignoreName() case *ast.BasicLit: if n.Kind == token.STRING && len(n.Value) > 128 { v.comments = append(v.comments, &ast.CommentGroup{List: []*ast.Comment{{ Slash: n.Pos(), Text: fmt.Sprintf("/* %d byte string literal not displayed */", len(n.Value)), }}}) n.Value = `""` } else { return v } case *ast.CompositeLit: if len(n.Elts) > 100 { if n.Type != nil { ast.Walk(v, n.Type) } v.comments = append(v.comments, &ast.CommentGroup{List: []*ast.Comment{{ Slash: n.Lbrace, Text: fmt.Sprintf("/* %d elements not displayed */", len(n.Elts)), }}}) n.Elts = n.Elts[:0] } else { return v } default: return v } return nil } func (b *builder) printDecl(decl ast.Decl) (d Code) { v := &declVisitor{pathIndex: make(map[string]int)} ast.Walk(v, decl) b.buf = b.buf[:0] err := (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint( sliceWriter{&b.buf}, b.fset, &printer.CommentedNode{Node: decl, Comments: v.comments}) if err != nil { return Code{Text: err.Error()} } var annotations []Annotation var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(b.buf)) s.Init(file, b.buf, nil, scanner.ScanComments) prevTok := token.ILLEGAL loop: for { pos, tok, lit := s.Scan() switch tok { case token.EOF: break loop case token.COMMENT: p := file.Offset(pos) e := p + len(lit) if prevTok == token.COMMENT { annotations[len(annotations)-1].End = int32(e) } else { annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int32(p), End: int32(e)}) } case token.IDENT: if len(v.annotations) == 0 { // Oops! break loop } annotation := v.annotations[0] v.annotations = v.annotations[1:] if annotation.Kind == -1 { continue } p := file.Offset(pos) e := p + len(lit) annotation.Pos = int32(p) annotation.End = int32(e) annotations = append(annotations, annotation) } prevTok = tok } return Code{Text: string(b.buf), Annotations: annotations, Paths: v.paths} } func (b *builder) position(n ast.Node) Pos { var position Pos pos := b.fset.Position(n.Pos()) src := b.srcs[pos.Filename] if src != nil { position.File = int16(src.index) position.Line = int32(pos.Line) end := b.fset.Position(n.End()) if src == b.srcs[end.Filename] { n := end.Line - pos.Line if n >= 0 && n <= math.MaxUint16 { position.N = uint16(n) } } } return position } func (b *builder) printExample(e *doc.Example) (code Code, output string) { output = e.Output b.buf = b.buf[:0] var n interface{} if _, ok := e.Code.(*ast.File); ok { n = e.Play } else { n = &printer.CommentedNode{Node: e.Code, Comments: e.Comments} } err := (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(sliceWriter{&b.buf}, b.fset, n) if err != nil { return Code{Text: err.Error()}, output } // additional formatting if this is a function body if i := len(b.buf); i >= 2 && b.buf[0] == '{' && b.buf[i-1] == '}' { // remove surrounding braces b.buf = b.buf[1 : i-1] // unindent b.buf = bytes.Replace(b.buf, []byte("\n "), []byte("\n"), -1) // remove output comment if j := exampleOutputRx.FindIndex(b.buf); j != nil { b.buf = bytes.TrimSpace(b.buf[:j[0]]) } } else { // drop output, as the output comment will appear in the code output = "" } var annotations []Annotation var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(b.buf)) s.Init(file, b.buf, nil, scanner.ScanComments) prevTok := token.ILLEGAL scanLoop: for { pos, tok, lit := s.Scan() switch tok { case token.EOF: break scanLoop case token.COMMENT: p := file.Offset(pos) e := p + len(lit) if prevTok == token.COMMENT { annotations[len(annotations)-1].End = int32(e) } else { annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int32(p), End: int32(e)}) } } prevTok = tok } return Code{Text: string(b.buf), Annotations: annotations}, output }