// Copyright 2017 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 pkgsite import ( "bytes" "fmt" "go/ast" "go/format" "go/token" "strings" ) const maxSynopsisNodeDepth = 10 // Synopsis returns a one-line summary of the given input node. func Synopsis(fset *token.FileSet, n ast.Node) string { return oneLineNodeDepth(fset, n, 0) } // oneLineNodeDepth returns a one-line summary of the given input node. // The depth specifies the current depth when traversing the AST and the // function will stop traversing once depth reaches maxSynopsisNodeDepth. func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string { const dotDotDot = "..." if depth == maxSynopsisNodeDepth { return dotDotDot } depth++ switch n := node.(type) { case nil: return "" case *ast.GenDecl: trailer := "" if len(n.Specs) > 1 { trailer = " " + dotDotDot } switch n.Tok { case token.CONST, token.VAR: typ := "" for i, spec := range n.Specs { valueSpec := spec.(*ast.ValueSpec) // must succeed; we can't mix types in one GenDecl. if len(valueSpec.Names) > 1 || len(valueSpec.Values) > 1 { trailer = " " + dotDotDot } // The type name may carry over from a previous specification in the // case of constants and iota. if valueSpec.Type != nil { typ = fmt.Sprintf(" %s", oneLineNodeDepth(fset, valueSpec.Type, depth)) } else if len(valueSpec.Values) > 0 { typ = "" } val := "" if i < len(valueSpec.Values) && valueSpec.Values[i] != nil { val = fmt.Sprintf(" = %s", oneLineNodeDepth(fset, valueSpec.Values[i], depth)) } return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer) } case token.TYPE: if len(n.Specs) > 0 { return oneLineNodeDepth(fset, n.Specs[0], depth) + trailer } case token.IMPORT: if len(n.Specs) > 0 { pkg := n.Specs[0].(*ast.ImportSpec).Path.Value return fmt.Sprintf("%s %s%s", n.Tok, pkg, trailer) } } return fmt.Sprintf("%s ()", n.Tok) case *ast.FuncDecl: // Formats func declarations. name := n.Name.Name recv := oneLineNodeDepth(fset, n.Recv, depth) if len(recv) > 0 { recv = "(" + recv + ") " } fnc := oneLineNodeDepth(fset, n.Type, depth) if strings.Index(fnc, "func") == 0 { fnc = fnc[4:] } return fmt.Sprintf("func %s%s%s", recv, name, fnc) case *ast.TypeSpec: sep := " " if n.Assign.IsValid() { sep = " = " } return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, oneLineNodeDepth(fset, n.Type, depth)) case *ast.FuncType: var params []string if n.Params != nil { for _, field := range n.Params.List { params = append(params, oneLineField(fset, field, depth)) } } needParens := false var results []string if n.Results != nil { needParens = needParens || len(n.Results.List) > 1 for _, field := range n.Results.List { needParens = needParens || len(field.Names) > 0 results = append(results, oneLineField(fset, field, depth)) } } param := joinStrings(params) if len(results) == 0 { return fmt.Sprintf("func(%s)", param) } result := joinStrings(results) if !needParens { return fmt.Sprintf("func(%s) %s", param, result) } return fmt.Sprintf("func(%s) (%s)", param, result) case *ast.StructType: if n.Fields == nil || len(n.Fields.List) == 0 { return "struct{}" } return "struct{ ... }" case *ast.InterfaceType: if n.Methods == nil || len(n.Methods.List) == 0 { return "interface{}" } return "interface{ ... }" case *ast.FieldList: if n == nil || len(n.List) == 0 { return "" } if len(n.List) == 1 { return oneLineField(fset, n.List[0], depth) } return dotDotDot case *ast.FuncLit: return oneLineNodeDepth(fset, n.Type, depth) + " { ... }" case *ast.CompositeLit: typ := oneLineNodeDepth(fset, n.Type, depth) if len(n.Elts) == 0 { return fmt.Sprintf("%s{}", typ) } return fmt.Sprintf("%s{ %s }", typ, dotDotDot) case *ast.ArrayType: length := oneLineNodeDepth(fset, n.Len, depth) element := oneLineNodeDepth(fset, n.Elt, depth) return fmt.Sprintf("[%s]%s", length, element) case *ast.MapType: key := oneLineNodeDepth(fset, n.Key, depth) value := oneLineNodeDepth(fset, n.Value, depth) return fmt.Sprintf("map[%s]%s", key, value) case *ast.CallExpr: fnc := oneLineNodeDepth(fset, n.Fun, depth) var args []string for _, arg := range n.Args { args = append(args, oneLineNodeDepth(fset, arg, depth)) } return fmt.Sprintf("%s(%s)", fnc, joinStrings(args)) case *ast.UnaryExpr: return fmt.Sprintf("%s%s", n.Op, oneLineNodeDepth(fset, n.X, depth)) case *ast.Ident: return n.Name default: // As a fallback, use default formatter for all unknown node types. buf := new(bytes.Buffer) format.Node(buf, fset, node) s := buf.String() if strings.Contains(s, "\n") { return dotDotDot } return s } } // oneLineField returns a one-line summary of the field. func oneLineField(fset *token.FileSet, field *ast.Field, depth int) string { var names []string for _, name := range field.Names { names = append(names, name.Name) } if len(names) == 0 { return oneLineNodeDepth(fset, field.Type, depth) } return joinStrings(names) + " " + oneLineNodeDepth(fset, field.Type, depth) } // joinStrings formats the input as a comma-separated list, // but truncates the list at some reasonable length if necessary. func joinStrings(ss []string) string { const widthLimit = 80 var n int for i, s := range ss { n += len(s) + len(", ") if n > widthLimit { ss = append(ss[:i:i], "...") break } } return strings.Join(ss, ", ") }