mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2024-12-11 13:20:19 +01:00
cb6e5cb0c7
* use dep for vendoring * lets encrypt * moved web to transfer.sh-web repo * single command install * added first tests
359 lines
8.6 KiB
Go
359 lines
8.6 KiB
Go
// 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
|
|
}
|