mirror of https://github.com/ethereum/go-ethereum
parent
75c86f8646
commit
72826bb5ad
@ -0,0 +1,27 @@ |
||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,22 @@ |
||||
Additional IP Rights Grant (Patents) |
||||
|
||||
"This implementation" means the copyrightable works distributed by |
||||
Google as part of the Go project. |
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
||||
patent license to make, have made, use, offer to sell, sell, import, |
||||
transfer and otherwise run, modify and propagate the contents of this |
||||
implementation of Go, where such license applies only to those patent |
||||
claims, both currently owned or controlled by Google and acquired in |
||||
the future, licensable by Google that are necessarily infringed by this |
||||
implementation of Go. This grant does not include claims that would be |
||||
infringed only as a consequence of further modification of this |
||||
implementation. If you or your agent or exclusive licensee institute or |
||||
order or agree to the institution of patent litigation against any |
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
||||
that this implementation of Go or any code incorporated within this |
||||
implementation of Go constitutes direct or contributory patent |
||||
infringement, or inducement of patent infringement, then any patent |
||||
rights granted to you under this License for this implementation of Go |
||||
shall terminate as of the date such litigation is filed. |
@ -0,0 +1,624 @@ |
||||
// 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 astutil |
||||
|
||||
// This file defines utilities for working with source positions.
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/token" |
||||
"sort" |
||||
) |
||||
|
||||
// PathEnclosingInterval returns the node that encloses the source
|
||||
// interval [start, end), and all its ancestors up to the AST root.
|
||||
//
|
||||
// The definition of "enclosing" used by this function considers
|
||||
// additional whitespace abutting a node to be enclosed by it.
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <-A->
|
||||
// <----B----->
|
||||
//
|
||||
// the ast.BinaryExpr(+) node is considered to enclose interval B
|
||||
// even though its [Pos()..End()) is actually only interval A.
|
||||
// This behaviour makes user interfaces more tolerant of imperfect
|
||||
// input.
|
||||
//
|
||||
// This function treats tokens as nodes, though they are not included
|
||||
// in the result. e.g. PathEnclosingInterval("+") returns the
|
||||
// enclosing ast.BinaryExpr("x + y").
|
||||
//
|
||||
// If start==end, the 1-char interval following start is used instead.
|
||||
//
|
||||
// The 'exact' result is true if the interval contains only path[0]
|
||||
// and perhaps some adjacent whitespace. It is false if the interval
|
||||
// overlaps multiple children of path[0], or if it contains only
|
||||
// interior whitespace of path[0].
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <--C--> <---E-->
|
||||
// ^
|
||||
// D
|
||||
//
|
||||
// intervals C, D and E are inexact. C is contained by the
|
||||
// z-assignment statement, because it spans three of its children (:=,
|
||||
// x, +). So too is the 1-char interval D, because it contains only
|
||||
// interior whitespace of the assignment. E is considered interior
|
||||
// whitespace of the BlockStmt containing the assignment.
|
||||
//
|
||||
// Precondition: [start, end) both lie within the same file as root.
|
||||
// TODO(adonovan): return (nil, false) in this case and remove precond.
|
||||
// Requires FileSet; see loader.tokenFileContainsPos.
|
||||
//
|
||||
// Postcondition: path is never nil; it always contains at least 'root'.
|
||||
//
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { |
||||
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
|
||||
|
||||
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
|
||||
var visit func(node ast.Node) bool |
||||
visit = func(node ast.Node) bool { |
||||
path = append(path, node) |
||||
|
||||
nodePos := node.Pos() |
||||
nodeEnd := node.End() |
||||
|
||||
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
|
||||
|
||||
// Intersect [start, end) with interval of node.
|
||||
if start < nodePos { |
||||
start = nodePos |
||||
} |
||||
if end > nodeEnd { |
||||
end = nodeEnd |
||||
} |
||||
|
||||
// Find sole child that contains [start, end).
|
||||
children := childrenOf(node) |
||||
l := len(children) |
||||
for i, child := range children { |
||||
// [childPos, childEnd) is unaugmented interval of child.
|
||||
childPos := child.Pos() |
||||
childEnd := child.End() |
||||
|
||||
// [augPos, augEnd) is whitespace-augmented interval of child.
|
||||
augPos := childPos |
||||
augEnd := childEnd |
||||
if i > 0 { |
||||
augPos = children[i-1].End() // start of preceding whitespace
|
||||
} |
||||
if i < l-1 { |
||||
nextChildPos := children[i+1].Pos() |
||||
// Does [start, end) lie between child and next child?
|
||||
if start >= augEnd && end <= nextChildPos { |
||||
return false // inexact match
|
||||
} |
||||
augEnd = nextChildPos // end of following whitespace
|
||||
} |
||||
|
||||
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
|
||||
// i, augPos, augEnd, start, end) // debugging
|
||||
|
||||
// Does augmented child strictly contain [start, end)?
|
||||
if augPos <= start && end <= augEnd { |
||||
_, isToken := child.(tokenNode) |
||||
return isToken || visit(child) |
||||
} |
||||
|
||||
// Does [start, end) overlap multiple children?
|
||||
// i.e. left-augmented child contains start
|
||||
// but LR-augmented child does not contain end.
|
||||
if start < childEnd && end > augEnd { |
||||
break |
||||
} |
||||
} |
||||
|
||||
// No single child contained [start, end),
|
||||
// so node is the result. Is it exact?
|
||||
|
||||
// (It's tempting to put this condition before the
|
||||
// child loop, but it gives the wrong result in the
|
||||
// case where a node (e.g. ExprStmt) and its sole
|
||||
// child have equal intervals.)
|
||||
if start == nodePos && end == nodeEnd { |
||||
return true // exact match
|
||||
} |
||||
|
||||
return false // inexact: overlaps multiple children
|
||||
} |
||||
|
||||
if start > end { |
||||
start, end = end, start |
||||
} |
||||
|
||||
if start < root.End() && end > root.Pos() { |
||||
if start == end { |
||||
end = start + 1 // empty interval => interval of size 1
|
||||
} |
||||
exact = visit(root) |
||||
|
||||
// Reverse the path:
|
||||
for i, l := 0, len(path); i < l/2; i++ { |
||||
path[i], path[l-1-i] = path[l-1-i], path[i] |
||||
} |
||||
} else { |
||||
// Selection lies within whitespace preceding the
|
||||
// first (or following the last) declaration in the file.
|
||||
// The result nonetheless always includes the ast.File.
|
||||
path = append(path, root) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// tokenNode is a dummy implementation of ast.Node for a single token.
|
||||
// They are used transiently by PathEnclosingInterval but never escape
|
||||
// this package.
|
||||
//
|
||||
type tokenNode struct { |
||||
pos token.Pos |
||||
end token.Pos |
||||
} |
||||
|
||||
func (n tokenNode) Pos() token.Pos { |
||||
return n.pos |
||||
} |
||||
|
||||
func (n tokenNode) End() token.Pos { |
||||
return n.end |
||||
} |
||||
|
||||
func tok(pos token.Pos, len int) ast.Node { |
||||
return tokenNode{pos, pos + token.Pos(len)} |
||||
} |
||||
|
||||
// childrenOf returns the direct non-nil children of ast.Node n.
|
||||
// It may include fake ast.Node implementations for bare tokens.
|
||||
// it is not safe to call (e.g.) ast.Walk on such nodes.
|
||||
//
|
||||
func childrenOf(n ast.Node) []ast.Node { |
||||
var children []ast.Node |
||||
|
||||
// First add nodes for all true subtrees.
|
||||
ast.Inspect(n, func(node ast.Node) bool { |
||||
if node == n { // push n
|
||||
return true // recur
|
||||
} |
||||
if node != nil { // push child
|
||||
children = append(children, node) |
||||
} |
||||
return false // no recursion
|
||||
}) |
||||
|
||||
// Then add fake Nodes for bare tokens.
|
||||
switch n := n.(type) { |
||||
case *ast.ArrayType: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("[")), |
||||
tok(n.Elt.End(), len("]"))) |
||||
|
||||
case *ast.AssignStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.BasicLit: |
||||
children = append(children, |
||||
tok(n.ValuePos, len(n.Value))) |
||||
|
||||
case *ast.BinaryExpr: |
||||
children = append(children, tok(n.OpPos, len(n.Op.String()))) |
||||
|
||||
case *ast.BlockStmt: |
||||
children = append(children, |
||||
tok(n.Lbrace, len("{")), |
||||
tok(n.Rbrace, len("}"))) |
||||
|
||||
case *ast.BranchStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.CallExpr: |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
if n.Ellipsis != 0 { |
||||
children = append(children, tok(n.Ellipsis, len("..."))) |
||||
} |
||||
|
||||
case *ast.CaseClause: |
||||
if n.List == nil { |
||||
children = append(children, |
||||
tok(n.Case, len("default"))) |
||||
} else { |
||||
children = append(children, |
||||
tok(n.Case, len("case"))) |
||||
} |
||||
children = append(children, tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.ChanType: |
||||
switch n.Dir { |
||||
case ast.RECV: |
||||
children = append(children, tok(n.Begin, len("<-chan"))) |
||||
case ast.SEND: |
||||
children = append(children, tok(n.Begin, len("chan<-"))) |
||||
case ast.RECV | ast.SEND: |
||||
children = append(children, tok(n.Begin, len("chan"))) |
||||
} |
||||
|
||||
case *ast.CommClause: |
||||
if n.Comm == nil { |
||||
children = append(children, |
||||
tok(n.Case, len("default"))) |
||||
} else { |
||||
children = append(children, |
||||
tok(n.Case, len("case"))) |
||||
} |
||||
children = append(children, tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.Comment: |
||||
// nop
|
||||
|
||||
case *ast.CommentGroup: |
||||
// nop
|
||||
|
||||
case *ast.CompositeLit: |
||||
children = append(children, |
||||
tok(n.Lbrace, len("{")), |
||||
tok(n.Rbrace, len("{"))) |
||||
|
||||
case *ast.DeclStmt: |
||||
// nop
|
||||
|
||||
case *ast.DeferStmt: |
||||
children = append(children, |
||||
tok(n.Defer, len("defer"))) |
||||
|
||||
case *ast.Ellipsis: |
||||
children = append(children, |
||||
tok(n.Ellipsis, len("..."))) |
||||
|
||||
case *ast.EmptyStmt: |
||||
// nop
|
||||
|
||||
case *ast.ExprStmt: |
||||
// nop
|
||||
|
||||
case *ast.Field: |
||||
// TODO(adonovan): Field.{Doc,Comment,Tag}?
|
||||
|
||||
case *ast.FieldList: |
||||
children = append(children, |
||||
tok(n.Opening, len("(")), |
||||
tok(n.Closing, len(")"))) |
||||
|
||||
case *ast.File: |
||||
// TODO test: Doc
|
||||
children = append(children, |
||||
tok(n.Package, len("package"))) |
||||
|
||||
case *ast.ForStmt: |
||||
children = append(children, |
||||
tok(n.For, len("for"))) |
||||
|
||||
case *ast.FuncDecl: |
||||
// TODO(adonovan): FuncDecl.Comment?
|
||||
|
||||
// Uniquely, FuncDecl breaks the invariant that
|
||||
// preorder traversal yields tokens in lexical order:
|
||||
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
|
||||
//
|
||||
// As a workaround, we inline the case for FuncType
|
||||
// here and order things correctly.
|
||||
//
|
||||
children = nil // discard ast.Walk(FuncDecl) info subtrees
|
||||
children = append(children, tok(n.Type.Func, len("func"))) |
||||
if n.Recv != nil { |
||||
children = append(children, n.Recv) |
||||
} |
||||
children = append(children, n.Name) |
||||
if n.Type.Params != nil { |
||||
children = append(children, n.Type.Params) |
||||
} |
||||
if n.Type.Results != nil { |
||||
children = append(children, n.Type.Results) |
||||
} |
||||
if n.Body != nil { |
||||
children = append(children, n.Body) |
||||
} |
||||
|
||||
case *ast.FuncLit: |
||||
// nop
|
||||
|
||||
case *ast.FuncType: |
||||
if n.Func != 0 { |
||||
children = append(children, |
||||
tok(n.Func, len("func"))) |
||||
} |
||||
|
||||
case *ast.GenDecl: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
if n.Lparen != 0 { |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
} |
||||
|
||||
case *ast.GoStmt: |
||||
children = append(children, |
||||
tok(n.Go, len("go"))) |
||||
|
||||
case *ast.Ident: |
||||
children = append(children, |
||||
tok(n.NamePos, len(n.Name))) |
||||
|
||||
case *ast.IfStmt: |
||||
children = append(children, |
||||
tok(n.If, len("if"))) |
||||
|
||||
case *ast.ImportSpec: |
||||
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
|
||||
|
||||
case *ast.IncDecStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.IndexExpr: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("{")), |
||||
tok(n.Rbrack, len("}"))) |
||||
|
||||
case *ast.InterfaceType: |
||||
children = append(children, |
||||
tok(n.Interface, len("interface"))) |
||||
|
||||
case *ast.KeyValueExpr: |
||||
children = append(children, |
||||
tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.LabeledStmt: |
||||
children = append(children, |
||||
tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.MapType: |
||||
children = append(children, |
||||
tok(n.Map, len("map"))) |
||||
|
||||
case *ast.ParenExpr: |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
|
||||
case *ast.RangeStmt: |
||||
children = append(children, |
||||
tok(n.For, len("for")), |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.ReturnStmt: |
||||
children = append(children, |
||||
tok(n.Return, len("return"))) |
||||
|
||||
case *ast.SelectStmt: |
||||
children = append(children, |
||||
tok(n.Select, len("select"))) |
||||
|
||||
case *ast.SelectorExpr: |
||||
// nop
|
||||
|
||||
case *ast.SendStmt: |
||||
children = append(children, |
||||
tok(n.Arrow, len("<-"))) |
||||
|
||||
case *ast.SliceExpr: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("[")), |
||||
tok(n.Rbrack, len("]"))) |
||||
|
||||
case *ast.StarExpr: |
||||
children = append(children, tok(n.Star, len("*"))) |
||||
|
||||
case *ast.StructType: |
||||
children = append(children, tok(n.Struct, len("struct"))) |
||||
|
||||
case *ast.SwitchStmt: |
||||
children = append(children, tok(n.Switch, len("switch"))) |
||||
|
||||
case *ast.TypeAssertExpr: |
||||
children = append(children, |
||||
tok(n.Lparen-1, len(".")), |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
|
||||
case *ast.TypeSpec: |
||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.TypeSwitchStmt: |
||||
children = append(children, tok(n.Switch, len("switch"))) |
||||
|
||||
case *ast.UnaryExpr: |
||||
children = append(children, tok(n.OpPos, len(n.Op.String()))) |
||||
|
||||
case *ast.ValueSpec: |
||||
// TODO(adonovan): ValueSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt: |
||||
// nop
|
||||
} |
||||
|
||||
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
|
||||
// the switch above so we can make interleaved callbacks for
|
||||
// both Nodes and Tokens in the right order and avoid the need
|
||||
// to sort.
|
||||
sort.Sort(byPos(children)) |
||||
|
||||
return children |
||||
} |
||||
|
||||
type byPos []ast.Node |
||||
|
||||
func (sl byPos) Len() int { |
||||
return len(sl) |
||||
} |
||||
func (sl byPos) Less(i, j int) bool { |
||||
return sl[i].Pos() < sl[j].Pos() |
||||
} |
||||
func (sl byPos) Swap(i, j int) { |
||||
sl[i], sl[j] = sl[j], sl[i] |
||||
} |
||||
|
||||
// NodeDescription returns a description of the concrete type of n suitable
|
||||
// for a user interface.
|
||||
//
|
||||
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
|
||||
// StarExpr) we could be much more specific given the path to the AST
|
||||
// root. Perhaps we should do that.
|
||||
//
|
||||
func NodeDescription(n ast.Node) string { |
||||
switch n := n.(type) { |
||||
case *ast.ArrayType: |
||||
return "array type" |
||||
case *ast.AssignStmt: |
||||
return "assignment" |
||||
case *ast.BadDecl: |
||||
return "bad declaration" |
||||
case *ast.BadExpr: |
||||
return "bad expression" |
||||
case *ast.BadStmt: |
||||
return "bad statement" |
||||
case *ast.BasicLit: |
||||
return "basic literal" |
||||
case *ast.BinaryExpr: |
||||
return fmt.Sprintf("binary %s operation", n.Op) |
||||
case *ast.BlockStmt: |
||||
return "block" |
||||
case *ast.BranchStmt: |
||||
switch n.Tok { |
||||
case token.BREAK: |
||||
return "break statement" |
||||
case token.CONTINUE: |
||||
return "continue statement" |
||||
case token.GOTO: |
||||
return "goto statement" |
||||
case token.FALLTHROUGH: |
||||
return "fall-through statement" |
||||
} |
||||
case *ast.CallExpr: |
||||
return "function call (or conversion)" |
||||
case *ast.CaseClause: |
||||
return "case clause" |
||||
case *ast.ChanType: |
||||
return "channel type" |
||||
case *ast.CommClause: |
||||
return "communication clause" |
||||
case *ast.Comment: |
||||
return "comment" |
||||
case *ast.CommentGroup: |
||||
return "comment group" |
||||
case *ast.CompositeLit: |
||||
return "composite literal" |
||||
case *ast.DeclStmt: |
||||
return NodeDescription(n.Decl) + " statement" |
||||
case *ast.DeferStmt: |
||||
return "defer statement" |
||||
case *ast.Ellipsis: |
||||
return "ellipsis" |
||||
case *ast.EmptyStmt: |
||||
return "empty statement" |
||||
case *ast.ExprStmt: |
||||
return "expression statement" |
||||
case *ast.Field: |
||||
// Can be any of these:
|
||||
// struct {x, y int} -- struct field(s)
|
||||
// struct {T} -- anon struct field
|
||||
// interface {I} -- interface embedding
|
||||
// interface {f()} -- interface method
|
||||
// func (A) func(B) C -- receiver, param(s), result(s)
|
||||
return "field/method/parameter" |
||||
case *ast.FieldList: |
||||
return "field/method/parameter list" |
||||
case *ast.File: |
||||
return "source file" |
||||
case *ast.ForStmt: |
||||
return "for loop" |
||||
case *ast.FuncDecl: |
||||
return "function declaration" |
||||
case *ast.FuncLit: |
||||
return "function literal" |
||||
case *ast.FuncType: |
||||
return "function type" |
||||
case *ast.GenDecl: |
||||
switch n.Tok { |
||||
case token.IMPORT: |
||||
return "import declaration" |
||||
case token.CONST: |
||||
return "constant declaration" |
||||
case token.TYPE: |
||||
return "type declaration" |
||||
case token.VAR: |
||||
return "variable declaration" |
||||
} |
||||
case *ast.GoStmt: |
||||
return "go statement" |
||||
case *ast.Ident: |
||||
return "identifier" |
||||
case *ast.IfStmt: |
||||
return "if statement" |
||||
case *ast.ImportSpec: |
||||
return "import specification" |
||||
case *ast.IncDecStmt: |
||||
if n.Tok == token.INC { |
||||
return "increment statement" |
||||
} |
||||
return "decrement statement" |
||||
case *ast.IndexExpr: |
||||
return "index expression" |
||||
case *ast.InterfaceType: |
||||
return "interface type" |
||||
case *ast.KeyValueExpr: |
||||
return "key/value association" |
||||
case *ast.LabeledStmt: |
||||
return "statement label" |
||||
case *ast.MapType: |
||||
return "map type" |
||||
case *ast.Package: |
||||
return "package" |
||||
case *ast.ParenExpr: |
||||
return "parenthesized " + NodeDescription(n.X) |
||||
case *ast.RangeStmt: |
||||
return "range loop" |
||||
case *ast.ReturnStmt: |
||||
return "return statement" |
||||
case *ast.SelectStmt: |
||||
return "select statement" |
||||
case *ast.SelectorExpr: |
||||
return "selector" |
||||
case *ast.SendStmt: |
||||
return "channel send" |
||||
case *ast.SliceExpr: |
||||
return "slice expression" |
||||
case *ast.StarExpr: |
||||
return "*-operation" // load/store expr or pointer type
|
||||
case *ast.StructType: |
||||
return "struct type" |
||||
case *ast.SwitchStmt: |
||||
return "switch statement" |
||||
case *ast.TypeAssertExpr: |
||||
return "type assertion" |
||||
case *ast.TypeSpec: |
||||
return "type specification" |
||||
case *ast.TypeSwitchStmt: |
||||
return "type switch" |
||||
case *ast.UnaryExpr: |
||||
return fmt.Sprintf("unary %s operation", n.Op) |
||||
case *ast.ValueSpec: |
||||
return "value specification" |
||||
|
||||
} |
||||
panic(fmt.Sprintf("unexpected node type: %T", n)) |
||||
} |
@ -0,0 +1,376 @@ |
||||
// 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 astutil contains common utilities for working with the Go AST.
|
||||
package astutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/token" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// AddImport adds the import path to the file f, if absent.
|
||||
func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) { |
||||
return AddNamedImport(fset, f, "", ipath) |
||||
} |
||||
|
||||
// AddNamedImport adds the import path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
// adds
|
||||
// import pathpkg "path"
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) { |
||||
if imports(f, ipath) { |
||||
return false |
||||
} |
||||
|
||||
newImport := &ast.ImportSpec{ |
||||
Path: &ast.BasicLit{ |
||||
Kind: token.STRING, |
||||
Value: strconv.Quote(ipath), |
||||
}, |
||||
} |
||||
if name != "" { |
||||
newImport.Name = &ast.Ident{Name: name} |
||||
} |
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with ipath.
|
||||
var ( |
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
) |
||||
for i, decl := range f.Decls { |
||||
gen, ok := decl.(*ast.GenDecl) |
||||
if ok && gen.Tok == token.IMPORT { |
||||
lastImport = i |
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") { |
||||
continue |
||||
} |
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 { |
||||
impDecl = gen |
||||
} |
||||
|
||||
// Compute longest shared prefix with imports in this group.
|
||||
for j, spec := range gen.Specs { |
||||
impspec := spec.(*ast.ImportSpec) |
||||
n := matchLen(importPath(impspec), ipath) |
||||
if n > bestMatch { |
||||
bestMatch = n |
||||
impDecl = gen |
||||
impIndex = j |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil { |
||||
impDecl = &ast.GenDecl{ |
||||
Tok: token.IMPORT, |
||||
} |
||||
if lastImport >= 0 { |
||||
impDecl.TokPos = f.Decls[lastImport].End() |
||||
} else { |
||||
// There are no existing imports.
|
||||
// Our new import goes after the package declaration and after
|
||||
// the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package |
||||
|
||||
file := fset.File(f.Package) |
||||
pkgLine := file.Line(f.Package) |
||||
for _, c := range f.Comments { |
||||
if file.Line(c.Pos()) > pkgLine { |
||||
break |
||||
} |
||||
impDecl.TokPos = c.End() |
||||
} |
||||
} |
||||
f.Decls = append(f.Decls, nil) |
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) |
||||
f.Decls[lastImport+1] = impDecl |
||||
} |
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0 |
||||
if impIndex >= 0 { |
||||
// insert after the found import
|
||||
insertAt = impIndex + 1 |
||||
} |
||||
impDecl.Specs = append(impDecl.Specs, nil) |
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) |
||||
impDecl.Specs[insertAt] = newImport |
||||
pos := impDecl.Pos() |
||||
if insertAt > 0 { |
||||
// If there is a comment after an existing import, preserve the comment
|
||||
// position by adding the new import after the comment.
|
||||
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { |
||||
pos = spec.Comment.End() |
||||
} else { |
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos() |
||||
} |
||||
} |
||||
if newImport.Name != nil { |
||||
newImport.Name.NamePos = pos |
||||
} |
||||
newImport.Path.ValuePos = pos |
||||
newImport.EndPos = pos |
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 { |
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos |
||||
} else if !impDecl.Lparen.IsValid() { |
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos() |
||||
} |
||||
|
||||
f.Imports = append(f.Imports, newImport) |
||||
return true |
||||
} |
||||
|
||||
// DeleteImport deletes the import path from the file f, if present.
|
||||
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { |
||||
return DeleteNamedImport(fset, f, "", path) |
||||
} |
||||
|
||||
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
|
||||
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { |
||||
var delspecs []*ast.ImportSpec |
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ { |
||||
decl := f.Decls[i] |
||||
gen, ok := decl.(*ast.GenDecl) |
||||
if !ok || gen.Tok != token.IMPORT { |
||||
continue |
||||
} |
||||
for j := 0; j < len(gen.Specs); j++ { |
||||
spec := gen.Specs[j] |
||||
impspec := spec.(*ast.ImportSpec) |
||||
if impspec.Name == nil && name != "" { |
||||
continue |
||||
} |
||||
if impspec.Name != nil && impspec.Name.Name != name { |
||||
continue |
||||
} |
||||
if importPath(impspec) != path { |
||||
continue |
||||
} |
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs = append(delspecs, impspec) |
||||
deleted = true |
||||
copy(gen.Specs[j:], gen.Specs[j+1:]) |
||||
gen.Specs = gen.Specs[:len(gen.Specs)-1] |
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 { |
||||
copy(f.Decls[i:], f.Decls[i+1:]) |
||||
f.Decls = f.Decls[:len(f.Decls)-1] |
||||
i-- |
||||
break |
||||
} else if len(gen.Specs) == 1 { |
||||
gen.Lparen = token.NoPos // drop parens
|
||||
} |
||||
if j > 0 { |
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) |
||||
lastLine := fset.Position(lastImpspec.Path.ValuePos).Line |
||||
line := fset.Position(impspec.Path.ValuePos).Line |
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 { |
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole.
|
||||
// Do nothing.
|
||||
} else { |
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line) |
||||
} |
||||
} |
||||
j-- |
||||
} |
||||
} |
||||
|
||||
// Delete them from f.Imports.
|
||||
for i := 0; i < len(f.Imports); i++ { |
||||
imp := f.Imports[i] |
||||
for j, del := range delspecs { |
||||
if imp == del { |
||||
copy(f.Imports[i:], f.Imports[i+1:]) |
||||
f.Imports = f.Imports[:len(f.Imports)-1] |
||||
copy(delspecs[j:], delspecs[j+1:]) |
||||
delspecs = delspecs[:len(delspecs)-1] |
||||
i-- |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
if len(delspecs) > 0 { |
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// RewriteImport rewrites any import of path oldPath to path newPath.
|
||||
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { |
||||
for _, imp := range f.Imports { |
||||
if importPath(imp) == oldPath { |
||||
rewrote = true |
||||
// record old End, because the default is to compute
|
||||
// it using the length of imp.Path.Value.
|
||||
imp.EndPos = imp.End() |
||||
imp.Path.Value = strconv.Quote(newPath) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// UsesImport reports whether a given import is used.
|
||||
func UsesImport(f *ast.File, path string) (used bool) { |
||||
spec := importSpec(f, path) |
||||
if spec == nil { |
||||
return |
||||
} |
||||
|
||||
name := spec.Name.String() |
||||
switch name { |
||||
case "<nil>": |
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/") |
||||
if lastSlash == -1 { |
||||
name = path |
||||
} else { |
||||
name = path[lastSlash+1:] |
||||
} |
||||
case "_", ".": |
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true |
||||
} |
||||
|
||||
ast.Walk(visitFn(func(n ast.Node) { |
||||
sel, ok := n.(*ast.SelectorExpr) |
||||
if ok && isTopName(sel.X, name) { |
||||
used = true |
||||
} |
||||
}), f) |
||||
|
||||
return |
||||
} |
||||
|
||||
type visitFn func(node ast.Node) |
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor { |
||||
fn(node) |
||||
return fn |
||||
} |
||||
|
||||
// imports returns true if f imports path.
|
||||
func imports(f *ast.File, path string) bool { |
||||
return importSpec(f, path) != nil |
||||
} |
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec { |
||||
for _, s := range f.Imports { |
||||
if importPath(s) == path { |
||||
return s |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string { |
||||
t, err := strconv.Unquote(s.Path.Value) |
||||
if err == nil { |
||||
return t |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// declImports reports whether gen contains an import of path.
|
||||
func declImports(gen *ast.GenDecl, path string) bool { |
||||
if gen.Tok != token.IMPORT { |
||||
return false |
||||
} |
||||
for _, spec := range gen.Specs { |
||||
impspec := spec.(*ast.ImportSpec) |
||||
if importPath(impspec) == path { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int { |
||||
n := 0 |
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { |
||||
if x[i] == '/' { |
||||
n++ |
||||
} |
||||
} |
||||
return n |
||||
} |
||||
|
||||
// isTopName returns true if n is a top-level unresolved identifier with the given name.
|
||||
func isTopName(n ast.Expr, name string) bool { |
||||
id, ok := n.(*ast.Ident) |
||||
return ok && id.Name == name && id.Obj == nil |
||||
} |
||||
|
||||
// Imports returns the file imports grouped by paragraph.
|
||||
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { |
||||
var groups [][]*ast.ImportSpec |
||||
|
||||
for _, decl := range f.Decls { |
||||
genDecl, ok := decl.(*ast.GenDecl) |
||||
if !ok || genDecl.Tok != token.IMPORT { |
||||
break |
||||
} |
||||
|
||||
group := []*ast.ImportSpec{} |
||||
|
||||
var lastLine int |
||||
for _, spec := range genDecl.Specs { |
||||
importSpec := spec.(*ast.ImportSpec) |
||||
pos := importSpec.Path.ValuePos |
||||
line := fset.Position(pos).Line |
||||
if lastLine > 0 && pos > 0 && line-lastLine > 1 { |
||||
groups = append(groups, group) |
||||
group = []*ast.ImportSpec{} |
||||
} |
||||
group = append(group, importSpec) |
||||
lastLine = line |
||||
} |
||||
groups = append(groups, group) |
||||
} |
||||
|
||||
return groups |
||||
} |
@ -0,0 +1,14 @@ |
||||
package astutil |
||||
|
||||
import "go/ast" |
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
func Unparen(e ast.Expr) ast.Expr { |
||||
for { |
||||
p, ok := e.(*ast.ParenExpr) |
||||
if !ok { |
||||
return e |
||||
} |
||||
e = p.X |
||||
} |
||||
} |
@ -0,0 +1,419 @@ |
||||
// 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 imports |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/build" |
||||
"go/parser" |
||||
"go/token" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"golang.org/x/tools/go/ast/astutil" |
||||
) |
||||
|
||||
// importToGroup is a list of functions which map from an import path to
|
||||
// a group number.
|
||||
var importToGroup = []func(importPath string) (num int, ok bool){ |
||||
func(importPath string) (num int, ok bool) { |
||||
if strings.HasPrefix(importPath, "appengine") { |
||||
return 2, true |
||||
} |
||||
return |
||||
}, |
||||
func(importPath string) (num int, ok bool) { |
||||
if strings.Contains(importPath, ".") { |
||||
return 1, true |
||||
} |
||||
return |
||||
}, |
||||
} |
||||
|
||||
func importGroup(importPath string) int { |
||||
for _, fn := range importToGroup { |
||||
if n, ok := fn(importPath); ok { |
||||
return n |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []string, err error) { |
||||
// refs are a set of possible package references currently unsatisfied by imports.
|
||||
// first key: either base package (e.g. "fmt") or renamed package
|
||||
// second key: referenced package symbol (e.g. "Println")
|
||||
refs := make(map[string]map[string]bool) |
||||
|
||||
// decls are the current package imports. key is base package or renamed package.
|
||||
decls := make(map[string]*ast.ImportSpec) |
||||
|
||||
// collect potential uses of packages.
|
||||
var visitor visitFn |
||||
visitor = visitFn(func(node ast.Node) ast.Visitor { |
||||
if node == nil { |
||||
return visitor |
||||
} |
||||
switch v := node.(type) { |
||||
case *ast.ImportSpec: |
||||
if v.Name != nil { |
||||
decls[v.Name.Name] = v |
||||
} else { |
||||
local := importPathToName(strings.Trim(v.Path.Value, `\"`)) |
||||
decls[local] = v |
||||
} |
||||
case *ast.SelectorExpr: |
||||
xident, ok := v.X.(*ast.Ident) |
||||
if !ok { |
||||
break |
||||
} |
||||
if xident.Obj != nil { |
||||
// if the parser can resolve it, it's not a package ref
|
||||
break |
||||
} |
||||
pkgName := xident.Name |
||||
if refs[pkgName] == nil { |
||||
refs[pkgName] = make(map[string]bool) |
||||
} |
||||
if decls[pkgName] == nil { |
||||
refs[pkgName][v.Sel.Name] = true |
||||
} |
||||
} |
||||
return visitor |
||||
}) |
||||
ast.Walk(visitor, f) |
||||
|
||||
// Nil out any unused ImportSpecs, to be removed in following passes
|
||||
unusedImport := map[string]string{} |
||||
for pkg, is := range decls { |
||||
if refs[pkg] == nil && pkg != "_" && pkg != "." { |
||||
name := "" |
||||
if is.Name != nil { |
||||
name = is.Name.Name |
||||
} |
||||
unusedImport[strings.Trim(is.Path.Value, `"`)] = name |
||||
} |
||||
} |
||||
for ipath, name := range unusedImport { |
||||
if ipath == "C" { |
||||
// Don't remove cgo stuff.
|
||||
continue |
||||
} |
||||
astutil.DeleteNamedImport(fset, f, name, ipath) |
||||
} |
||||
|
||||
// Search for imports matching potential package references.
|
||||
searches := 0 |
||||
type result struct { |
||||
ipath string |
||||
name string |
||||
err error |
||||
} |
||||
results := make(chan result) |
||||
for pkgName, symbols := range refs { |
||||
if len(symbols) == 0 { |
||||
continue // skip over packages already imported
|
||||
} |
||||
go func(pkgName string, symbols map[string]bool) { |
||||
ipath, rename, err := findImport(pkgName, symbols, filename) |
||||
r := result{ipath: ipath, err: err} |
||||
if rename { |
||||
r.name = pkgName |
||||
} |
||||
results <- r |
||||
}(pkgName, symbols) |
||||
searches++ |
||||
} |
||||
for i := 0; i < searches; i++ { |
||||
result := <-results |
||||
if result.err != nil { |
||||
return nil, result.err |
||||
} |
||||
if result.ipath != "" { |
||||
if result.name != "" { |
||||
astutil.AddNamedImport(fset, f, result.name, result.ipath) |
||||
} else { |
||||
astutil.AddImport(fset, f, result.ipath) |
||||
} |
||||
added = append(added, result.ipath) |
||||
} |
||||
} |
||||
|
||||
return added, nil |
||||
} |
||||
|
||||
// importPathToName returns the package name for the given import path.
|
||||
var importPathToName = importPathToNameGoPath |
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath string) (packageName string) { |
||||
return path.Base(importPath) |
||||
} |
||||
|
||||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath string) (packageName string) { |
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil { |
||||
return buildPkg.Name |
||||
} else { |
||||
return importPathToNameBasic(importPath) |
||||
} |
||||
} |
||||
|
||||
type pkg struct { |
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
} |
||||
|
||||
var pkgIndexOnce sync.Once |
||||
|
||||
var pkgIndex struct { |
||||
sync.Mutex |
||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||
} |
||||
|
||||
// gate is a semaphore for limiting concurrency.
|
||||
type gate chan struct{} |
||||
|
||||
func (g gate) enter() { g <- struct{}{} } |
||||
func (g gate) leave() { <-g } |
||||
|
||||
// fsgate protects the OS & filesystem from too much concurrency.
|
||||
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||
var fsgate = make(gate, 8) |
||||
|
||||
func loadPkgIndex() { |
||||
pkgIndex.Lock() |
||||
pkgIndex.m = make(map[string][]pkg) |
||||
pkgIndex.Unlock() |
||||
|
||||
var wg sync.WaitGroup |
||||
for _, path := range build.Default.SrcDirs() { |
||||
fsgate.enter() |
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
fsgate.leave() |
||||
fmt.Fprint(os.Stderr, err) |
||||
continue |
||||
} |
||||
children, err := f.Readdir(-1) |
||||
f.Close() |
||||
fsgate.leave() |
||||
if err != nil { |
||||
fmt.Fprint(os.Stderr, err) |
||||
continue |
||||
} |
||||
for _, child := range children { |
||||
if child.IsDir() { |
||||
wg.Add(1) |
||||
go func(path, name string) { |
||||
defer wg.Done() |
||||
loadPkg(&wg, path, name) |
||||
}(path, child.Name()) |
||||
} |
||||
} |
||||
} |
||||
wg.Wait() |
||||
} |
||||
|
||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) { |
||||
importpath := filepath.ToSlash(pkgrelpath) |
||||
dir := filepath.Join(root, importpath) |
||||
|
||||
fsgate.enter() |
||||
defer fsgate.leave() |
||||
pkgDir, err := os.Open(dir) |
||||
if err != nil { |
||||
return |
||||
} |
||||
children, err := pkgDir.Readdir(-1) |
||||
pkgDir.Close() |
||||
if err != nil { |
||||
return |
||||
} |
||||
// hasGo tracks whether a directory actually appears to be a
|
||||
// Go source code directory. If $GOPATH == $HOME, and
|
||||
// $HOME/src has lots of other large non-Go projects in it,
|
||||
// then the calls to importPathToName below can be expensive.
|
||||
hasGo := false |
||||
for _, child := range children { |
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
name := child.Name() |
||||
if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" { |
||||
continue |
||||
} |
||||
if strings.HasSuffix(name, ".go") { |
||||
hasGo = true |
||||
} |
||||
if child.IsDir() { |
||||
wg.Add(1) |
||||
go func(root, name string) { |
||||
defer wg.Done() |
||||
loadPkg(wg, root, name) |
||||
}(root, filepath.Join(importpath, name)) |
||||
} |
||||
} |
||||
if hasGo { |
||||
shortName := importPathToName(importpath) |
||||
pkgIndex.Lock() |
||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{ |
||||
importpath: importpath, |
||||
dir: dir, |
||||
}) |
||||
pkgIndex.Unlock() |
||||
} |
||||
|
||||
} |
||||
|
||||
// loadExports returns a list exports for a package.
|
||||
var loadExports = loadExportsGoPath |
||||
|
||||
func loadExportsGoPath(dir string) map[string]bool { |
||||
exports := make(map[string]bool) |
||||
buildPkg, err := build.ImportDir(dir, 0) |
||||
if err != nil { |
||||
if strings.Contains(err.Error(), "no buildable Go source files in") { |
||||
return nil |
||||
} |
||||
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err) |
||||
return nil |
||||
} |
||||
fset := token.NewFileSet() |
||||
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} { |
||||
for _, file := range files { |
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) |
||||
if err != nil { |
||||
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err) |
||||
continue |
||||
} |
||||
for name := range f.Scope.Objects { |
||||
if ast.IsExported(name) { |
||||
exports[name] = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return exports |
||||
} |
||||
|
||||
// findImport searches for a package with the given symbols.
|
||||
// If no package is found, findImport returns "".
|
||||
// Declared as a variable rather than a function so goimports can be easily
|
||||
// extended by adding a file with an init function.
|
||||
var findImport = findImportGoPath |
||||
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool, filename string) (string, bool, error) { |
||||
// Fast path for the standard library.
|
||||
// In the common case we hopefully never have to scan the GOPATH, which can
|
||||
// be slow with moving disks.
|
||||
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok { |
||||
return pkg, rename, nil |
||||
} |
||||
|
||||
// TODO(sameer): look at the import lines for other Go files in the
|
||||
// local directory, since the user is likely to import the same packages
|
||||
// in the current Go file. Return rename=true when the other Go files
|
||||
// use a renamed package that's also used in the current file.
|
||||
|
||||
pkgIndexOnce.Do(loadPkgIndex) |
||||
|
||||
// Collect exports for packages with matching names.
|
||||
var ( |
||||
wg sync.WaitGroup |
||||
mu sync.Mutex |
||||
shortest string |
||||
) |
||||
pkgIndex.Lock() |
||||
for _, pkg := range pkgIndex.m[pkgName] { |
||||
if !canUse(filename, pkg.dir) { |
||||
continue |
||||
} |
||||
wg.Add(1) |
||||
go func(importpath, dir string) { |
||||
defer wg.Done() |
||||
exports := loadExports(dir) |
||||
if exports == nil { |
||||
return |
||||
} |
||||
// If it doesn't have the right symbols, stop.
|
||||
for symbol := range symbols { |
||||
if !exports[symbol] { |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Devendorize for use in import statement.
|
||||
if i := strings.LastIndex(importpath, "/vendor/"); i >= 0 { |
||||
importpath = importpath[i+len("/vendor/"):] |
||||
} else if strings.HasPrefix(importpath, "vendor/") { |
||||
importpath = importpath[len("vendor/"):] |
||||
} |
||||
|
||||
// Save as the answer.
|
||||
// If there are multiple candidates, the shortest wins,
|
||||
// to prefer "bytes" over "github.com/foo/bytes".
|
||||
mu.Lock() |
||||
if shortest == "" || len(importpath) < len(shortest) || len(importpath) == len(shortest) && importpath < shortest { |
||||
shortest = importpath |
||||
} |
||||
mu.Unlock() |
||||
}(pkg.importpath, pkg.dir) |
||||
} |
||||
pkgIndex.Unlock() |
||||
wg.Wait() |
||||
|
||||
return shortest, false, nil |
||||
} |
||||
|
||||
func canUse(filename, dir string) bool { |
||||
dirSlash := filepath.ToSlash(dir) |
||||
if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { |
||||
return true |
||||
} |
||||
// Vendor or internal directory only visible from children of parent.
|
||||
// That means the path from the current directory to the target directory
|
||||
// can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal
|
||||
// or bar/vendor or bar/internal.
|
||||
// After stripping all the leading ../, the only okay place to see vendor or internal
|
||||
// is at the very beginning of the path.
|
||||
abs, err := filepath.Abs(filename) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
rel, err := filepath.Rel(abs, dir) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
relSlash := filepath.ToSlash(rel) |
||||
if i := strings.LastIndex(relSlash, "../"); i >= 0 { |
||||
relSlash = relSlash[i+len("../"):] |
||||
} |
||||
return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") |
||||
} |
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor |
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor { |
||||
return fn(node) |
||||
} |
||||
|
||||
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) { |
||||
for symbol := range symbols { |
||||
path := stdlib[shortPkg+"."+symbol] |
||||
if path == "" { |
||||
return "", false, false |
||||
} |
||||
if importPath != "" && importPath != path { |
||||
// Ambiguous. Symbols pointed to different things.
|
||||
return "", false, false |
||||
} |
||||
importPath = path |
||||
} |
||||
return importPath, false, importPath != "" |
||||
} |
@ -0,0 +1,283 @@ |
||||
// 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 imports implements a Go pretty-printer (like package "go/format")
|
||||
// that also adds or removes import statements as necessary.
|
||||
package imports |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"go/ast" |
||||
"go/format" |
||||
"go/parser" |
||||
"go/printer" |
||||
"go/token" |
||||
"io" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"golang.org/x/tools/go/ast/astutil" |
||||
) |
||||
|
||||
// Options specifies options for processing files.
|
||||
type Options struct { |
||||
Fragment bool // Accept fragment of a source file (no package statement)
|
||||
AllErrors bool // Report all errors (not just the first 10 on different lines)
|
||||
|
||||
Comments bool // Print comments (true if nil *Options provided)
|
||||
TabIndent bool // Use tabs for indent (true if nil *Options provided)
|
||||
TabWidth int // Tab width (8 if nil *Options provided)
|
||||
} |
||||
|
||||
// Process formats and adjusts imports for the provided file.
|
||||
// If opt is nil the defaults are used.
|
||||
//
|
||||
// Note that filename's directory influences which imports can be chosen,
|
||||
// so it is important that filename be accurate.
|
||||
// To process data ``as if'' it were in filename, pass the data as a non-nil src.
|
||||
func Process(filename string, src []byte, opt *Options) ([]byte, error) { |
||||
if opt == nil { |
||||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} |
||||
} |
||||
|
||||
fileSet := token.NewFileSet() |
||||
file, adjust, err := parse(fileSet, filename, src, opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
_, err = fixImports(fileSet, file, filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
sortImports(fileSet, file) |
||||
imps := astutil.Imports(fileSet, file) |
||||
|
||||
var spacesBefore []string // import paths we need spaces before
|
||||
for _, impSection := range imps { |
||||
// Within each block of contiguous imports, see if any
|
||||
// import lines are in different group numbers. If so,
|
||||
// we'll need to put a space between them so it's
|
||||
// compatible with gofmt.
|
||||
lastGroup := -1 |
||||
for _, importSpec := range impSection { |
||||
importPath, _ := strconv.Unquote(importSpec.Path.Value) |
||||
groupNum := importGroup(importPath) |
||||
if groupNum != lastGroup && lastGroup != -1 { |
||||
spacesBefore = append(spacesBefore, importPath) |
||||
} |
||||
lastGroup = groupNum |
||||
} |
||||
|
||||
} |
||||
|
||||
printerMode := printer.UseSpaces |
||||
if opt.TabIndent { |
||||
printerMode |= printer.TabIndent |
||||
} |
||||
printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} |
||||
|
||||
var buf bytes.Buffer |
||||
err = printConfig.Fprint(&buf, fileSet, file) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
out := buf.Bytes() |
||||
if adjust != nil { |
||||
out = adjust(src, out) |
||||
} |
||||
if len(spacesBefore) > 0 { |
||||
out = addImportSpaces(bytes.NewReader(out), spacesBefore) |
||||
} |
||||
|
||||
out, err = format.Source(out) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return out, nil |
||||
} |
||||
|
||||
// parse parses src, which was read from filename,
|
||||
// as a Go source file or statement list.
|
||||
func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) { |
||||
parserMode := parser.Mode(0) |
||||
if opt.Comments { |
||||
parserMode |= parser.ParseComments |
||||
} |
||||
if opt.AllErrors { |
||||
parserMode |= parser.AllErrors |
||||
} |
||||
|
||||
// Try as whole source file.
|
||||
file, err := parser.ParseFile(fset, filename, src, parserMode) |
||||
if err == nil { |
||||
return file, nil, nil |
||||
} |
||||
// If the error is that the source file didn't begin with a
|
||||
// package line and we accept fragmented input, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package main;"), src...) |
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode) |
||||
if err == nil { |
||||
// If a main function exists, we will assume this is a main
|
||||
// package and leave the file.
|
||||
if containsMainFunc(file) { |
||||
return file, nil, nil |
||||
} |
||||
|
||||
adjust := func(orig, src []byte) []byte { |
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ; into a \n.
|
||||
src = src[len("package main\n"):] |
||||
return matchSpace(orig, src) |
||||
} |
||||
return file, adjust, nil |
||||
} |
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '}') |
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode) |
||||
if err == nil { |
||||
adjust := func(orig, src []byte) []byte { |
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the ; into a \n\n.
|
||||
src = src[len("package p\n\nfunc _() {"):] |
||||
src = src[:len(src)-len("}\n")] |
||||
// Gofmt has also indented the function body one level.
|
||||
// Remove that indent.
|
||||
src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1) |
||||
return matchSpace(orig, src) |
||||
} |
||||
return file, adjust, nil |
||||
} |
||||
|
||||
// Failed, and out of options.
|
||||
return nil, nil, err |
||||
} |
||||
|
||||
// containsMainFunc checks if a file contains a function declaration with the
|
||||
// function signature 'func main()'
|
||||
func containsMainFunc(file *ast.File) bool { |
||||
for _, decl := range file.Decls { |
||||
if f, ok := decl.(*ast.FuncDecl); ok { |
||||
if f.Name.Name != "main" { |
||||
continue |
||||
} |
||||
|
||||
if len(f.Type.Params.List) != 0 { |
||||
continue |
||||
} |
||||
|
||||
if f.Type.Results != nil && len(f.Type.Results.List) != 0 { |
||||
continue |
||||
} |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func cutSpace(b []byte) (before, middle, after []byte) { |
||||
i := 0 |
||||
for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') { |
||||
i++ |
||||
} |
||||
j := len(b) |
||||
for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { |
||||
j-- |
||||
} |
||||
if i <= j { |
||||
return b[:i], b[i:j], b[j:] |
||||
} |
||||
return nil, nil, b[j:] |
||||
} |
||||
|
||||
// matchSpace reformats src to use the same space context as orig.
|
||||
// 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
|
||||
// 2) matchSpace copies the indentation of the first non-blank line in orig
|
||||
// to every non-blank line in src.
|
||||
// 3) matchSpace copies the trailing space from orig and uses it in place
|
||||
// of src's trailing space.
|
||||
func matchSpace(orig []byte, src []byte) []byte { |
||||
before, _, after := cutSpace(orig) |
||||
i := bytes.LastIndex(before, []byte{'\n'}) |
||||
before, indent := before[:i+1], before[i+1:] |
||||
|
||||
_, src, _ = cutSpace(src) |
||||
|
||||
var b bytes.Buffer |
||||
b.Write(before) |
||||
for len(src) > 0 { |
||||
line := src |
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 { |
||||
line, src = line[:i+1], line[i+1:] |
||||
} else { |
||||
src = nil |
||||
} |
||||
if len(line) > 0 && line[0] != '\n' { // not blank
|
||||
b.Write(indent) |
||||
} |
||||
b.Write(line) |
||||
} |
||||
b.Write(after) |
||||
return b.Bytes() |
||||
} |
||||
|
||||
var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`) |
||||
|
||||
func addImportSpaces(r io.Reader, breaks []string) []byte { |
||||
var out bytes.Buffer |
||||
sc := bufio.NewScanner(r) |
||||
inImports := false |
||||
done := false |
||||
for sc.Scan() { |
||||
s := sc.Text() |
||||
|
||||
if !inImports && !done && strings.HasPrefix(s, "import") { |
||||
inImports = true |
||||
} |
||||
if inImports && (strings.HasPrefix(s, "var") || |
||||
strings.HasPrefix(s, "func") || |
||||
strings.HasPrefix(s, "const") || |
||||
strings.HasPrefix(s, "type")) { |
||||
done = true |
||||
inImports = false |
||||
} |
||||
if inImports && len(breaks) > 0 { |
||||
if m := impLine.FindStringSubmatch(s); m != nil { |
||||
if m[1] == string(breaks[0]) { |
||||
out.WriteByte('\n') |
||||
breaks = breaks[1:] |
||||
} |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintln(&out, s) |
||||
} |
||||
return out.Bytes() |
||||
} |
@ -0,0 +1,173 @@ |
||||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
// Command mkindex creates the file "pkgindex.go" containing an index of the Go
|
||||
// standard library. The file is intended to be built as part of the imports
|
||||
// package, so that the package may be used in environments where a GOROOT is
|
||||
// not available (such as App Engine).
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"go/ast" |
||||
"go/build" |
||||
"go/format" |
||||
"go/parser" |
||||
"go/token" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
var ( |
||||
pkgIndex = make(map[string][]pkg) |
||||
exports = make(map[string]map[string]bool) |
||||
) |
||||
|
||||
func main() { |
||||
// Don't use GOPATH.
|
||||
ctx := build.Default |
||||
ctx.GOPATH = "" |
||||
|
||||
// Populate pkgIndex global from GOROOT.
|
||||
for _, path := range ctx.SrcDirs() { |
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
log.Print(err) |
||||
continue |
||||
} |
||||
children, err := f.Readdir(-1) |
||||
f.Close() |
||||
if err != nil { |
||||
log.Print(err) |
||||
continue |
||||
} |
||||
for _, child := range children { |
||||
if child.IsDir() { |
||||
loadPkg(path, child.Name()) |
||||
} |
||||
} |
||||
} |
||||
// Populate exports global.
|
||||
for _, ps := range pkgIndex { |
||||
for _, p := range ps { |
||||
e := loadExports(p.dir) |
||||
if e != nil { |
||||
exports[p.dir] = e |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Construct source file.
|
||||
var buf bytes.Buffer |
||||
fmt.Fprint(&buf, pkgIndexHead) |
||||
fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex) |
||||
fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports) |
||||
src := buf.Bytes() |
||||
|
||||
// Replace main.pkg type name with pkg.
|
||||
src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1) |
||||
// Replace actual GOROOT with "/go".
|
||||
src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1) |
||||
// Add some line wrapping.
|
||||
src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1) |
||||
src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1) |
||||
|
||||
var err error |
||||
src, err = format.Source(src) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
// Write out source file.
|
||||
err = ioutil.WriteFile("pkgindex.go", src, 0644) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
const pkgIndexHead = `package imports |
||||
|
||||
func init() { |
||||
pkgIndexOnce.Do(func() { |
||||
pkgIndex.m = pkgIndexMaster |
||||
}) |
||||
loadExports = func(dir string) map[string]bool { |
||||
return exportsMaster[dir] |
||||
} |
||||
} |
||||
` |
||||
|
||||
type pkg struct { |
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
} |
||||
|
||||
var fset = token.NewFileSet() |
||||
|
||||
func loadPkg(root, importpath string) { |
||||
shortName := path.Base(importpath) |
||||
if shortName == "testdata" { |
||||
return |
||||
} |
||||
|
||||
dir := filepath.Join(root, importpath) |
||||
pkgIndex[shortName] = append(pkgIndex[shortName], pkg{ |
||||
importpath: importpath, |
||||
dir: dir, |
||||
}) |
||||
|
||||
pkgDir, err := os.Open(dir) |
||||
if err != nil { |
||||
return |
||||
} |
||||
children, err := pkgDir.Readdir(-1) |
||||
pkgDir.Close() |
||||
if err != nil { |
||||
return |
||||
} |
||||
for _, child := range children { |
||||
name := child.Name() |
||||
if name == "" { |
||||
continue |
||||
} |
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') { |
||||
continue |
||||
} |
||||
if child.IsDir() { |
||||
loadPkg(root, filepath.Join(importpath, name)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func loadExports(dir string) map[string]bool { |
||||
exports := make(map[string]bool) |
||||
buildPkg, err := build.ImportDir(dir, 0) |
||||
if err != nil { |
||||
if strings.Contains(err.Error(), "no buildable Go source files in") { |
||||
return nil |
||||
} |
||||
log.Printf("could not import %q: %v", dir, err) |
||||
return nil |
||||
} |
||||
for _, file := range buildPkg.GoFiles { |
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) |
||||
if err != nil { |
||||
log.Printf("could not parse %q: %v", file, err) |
||||
continue |
||||
} |
||||
for name := range f.Scope.Objects { |
||||
if ast.IsExported(name) { |
||||
exports[name] = true |
||||
} |
||||
} |
||||
} |
||||
return exports |
||||
} |
@ -0,0 +1,93 @@ |
||||
// +build ignore
|
||||
|
||||
// mkstdlib generates the zstdlib.go file, containing the Go standard
|
||||
// library API symbols. It's baked into the binary to avoid scanning
|
||||
// GOPATH in the common case.
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"go/format" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"regexp" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
func mustOpen(name string) io.Reader { |
||||
f, err := os.Open(name) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
return f |
||||
} |
||||
|
||||
func api(base string) string { |
||||
return filepath.Join(os.Getenv("GOROOT"), "api", base) |
||||
} |
||||
|
||||
var sym = regexp.MustCompile(`^pkg (\S+).*?, (?:var|func|type|const) ([A-Z]\w*)`) |
||||
|
||||
func main() { |
||||
var buf bytes.Buffer |
||||
outf := func(format string, args ...interface{}) { |
||||
fmt.Fprintf(&buf, format, args...) |
||||
} |
||||
outf("// AUTO-GENERATED BY mkstdlib.go\n\n") |
||||
outf("package imports\n") |
||||
outf("var stdlib = map[string]string{\n") |
||||
f := io.MultiReader( |
||||
mustOpen(api("go1.txt")), |
||||
mustOpen(api("go1.1.txt")), |
||||
mustOpen(api("go1.2.txt")), |
||||
mustOpen(api("go1.3.txt")), |
||||
mustOpen(api("go1.4.txt")), |
||||
mustOpen(api("go1.5.txt")), |
||||
) |
||||
sc := bufio.NewScanner(f) |
||||
fullImport := map[string]string{} // "zip.NewReader" => "archive/zip"
|
||||
ambiguous := map[string]bool{} |
||||
var keys []string |
||||
for sc.Scan() { |
||||
l := sc.Text() |
||||
has := func(v string) bool { return strings.Contains(l, v) } |
||||
if has("struct, ") || has("interface, ") || has(", method (") { |
||||
continue |
||||
} |
||||
if m := sym.FindStringSubmatch(l); m != nil { |
||||
full := m[1] |
||||
key := path.Base(full) + "." + m[2] |
||||
if exist, ok := fullImport[key]; ok { |
||||
if exist != full { |
||||
ambiguous[key] = true |
||||
} |
||||
} else { |
||||
fullImport[key] = full |
||||
keys = append(keys, key) |
||||
} |
||||
} |
||||
} |
||||
if err := sc.Err(); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
sort.Strings(keys) |
||||
for _, key := range keys { |
||||
if ambiguous[key] { |
||||
outf("\t// %q is ambiguous\n", key) |
||||
} else { |
||||
outf("\t%q: %q,\n", key, fullImport[key]) |
||||
} |
||||
} |
||||
outf("}\n") |
||||
fmtbuf, err := format.Source(buf.Bytes()) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
os.Stdout.Write(fmtbuf) |
||||
} |
@ -0,0 +1,212 @@ |
||||
// 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.
|
||||
|
||||
// Hacked up copy of go/ast/import.go
|
||||
|
||||
package imports |
||||
|
||||
import ( |
||||
"go/ast" |
||||
"go/token" |
||||
"sort" |
||||
"strconv" |
||||
) |
||||
|
||||
// sortImports sorts runs of consecutive import lines in import blocks in f.
|
||||
// It also removes duplicate imports when it is possible to do so without data loss.
|
||||
func sortImports(fset *token.FileSet, f *ast.File) { |
||||
for i, d := range f.Decls { |
||||
d, ok := d.(*ast.GenDecl) |
||||
if !ok || d.Tok != token.IMPORT { |
||||
// Not an import declaration, so we're done.
|
||||
// Imports are always first.
|
||||
break |
||||
} |
||||
|
||||
if len(d.Specs) == 0 { |
||||
// Empty import block, remove it.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) |
||||
} |
||||
|
||||
if !d.Lparen.IsValid() { |
||||
// Not a block: sorted by default.
|
||||
continue |
||||
} |
||||
|
||||
// Identify and sort runs of specs on successive lines.
|
||||
i := 0 |
||||
specs := d.Specs[:0] |
||||
for j, s := range d.Specs { |
||||
if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { |
||||
// j begins a new run. End this one.
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) |
||||
i = j |
||||
} |
||||
} |
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) |
||||
d.Specs = specs |
||||
|
||||
// Deduping can leave a blank line before the rparen; clean that up.
|
||||
if len(d.Specs) > 0 { |
||||
lastSpec := d.Specs[len(d.Specs)-1] |
||||
lastLine := fset.Position(lastSpec.Pos()).Line |
||||
if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { |
||||
fset.File(d.Rparen).MergeLine(rParenLine - 1) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func importPath(s ast.Spec) string { |
||||
t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) |
||||
if err == nil { |
||||
return t |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func importName(s ast.Spec) string { |
||||
n := s.(*ast.ImportSpec).Name |
||||
if n == nil { |
||||
return "" |
||||
} |
||||
return n.Name |
||||
} |
||||
|
||||
func importComment(s ast.Spec) string { |
||||
c := s.(*ast.ImportSpec).Comment |
||||
if c == nil { |
||||
return "" |
||||
} |
||||
return c.Text() |
||||
} |
||||
|
||||
// collapse indicates whether prev may be removed, leaving only next.
|
||||
func collapse(prev, next ast.Spec) bool { |
||||
if importPath(next) != importPath(prev) || importName(next) != importName(prev) { |
||||
return false |
||||
} |
||||
return prev.(*ast.ImportSpec).Comment == nil |
||||
} |
||||
|
||||
type posSpan struct { |
||||
Start token.Pos |
||||
End token.Pos |
||||
} |
||||
|
||||
func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { |
||||
// Can't short-circuit here even if specs are already sorted,
|
||||
// since they might yet need deduplication.
|
||||
// A lone import, however, may be safely ignored.
|
||||
if len(specs) <= 1 { |
||||
return specs |
||||
} |
||||
|
||||
// Record positions for specs.
|
||||
pos := make([]posSpan, len(specs)) |
||||
for i, s := range specs { |
||||
pos[i] = posSpan{s.Pos(), s.End()} |
||||
} |
||||
|
||||
// Identify comments in this range.
|
||||
// Any comment from pos[0].Start to the final line counts.
|
||||
lastLine := fset.Position(pos[len(pos)-1].End).Line |
||||
cstart := len(f.Comments) |
||||
cend := len(f.Comments) |
||||
for i, g := range f.Comments { |
||||
if g.Pos() < pos[0].Start { |
||||
continue |
||||
} |
||||
if i < cstart { |
||||
cstart = i |
||||
} |
||||
if fset.Position(g.End()).Line > lastLine { |
||||
cend = i |
||||
break |
||||
} |
||||
} |
||||
comments := f.Comments[cstart:cend] |
||||
|
||||
// Assign each comment to the import spec preceding it.
|
||||
importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} |
||||
specIndex := 0 |
||||
for _, g := range comments { |
||||
for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { |
||||
specIndex++ |
||||
} |
||||
s := specs[specIndex].(*ast.ImportSpec) |
||||
importComment[s] = append(importComment[s], g) |
||||
} |
||||
|
||||
// Sort the import specs by import path.
|
||||
// Remove duplicates, when possible without data loss.
|
||||
// Reassign the import paths to have the same position sequence.
|
||||
// Reassign each comment to abut the end of its spec.
|
||||
// Sort the comments by new position.
|
||||
sort.Sort(byImportSpec(specs)) |
||||
|
||||
// Dedup. Thanks to our sorting, we can just consider
|
||||
// adjacent pairs of imports.
|
||||
deduped := specs[:0] |
||||
for i, s := range specs { |
||||
if i == len(specs)-1 || !collapse(s, specs[i+1]) { |
||||
deduped = append(deduped, s) |
||||
} else { |
||||
p := s.Pos() |
||||
fset.File(p).MergeLine(fset.Position(p).Line) |
||||
} |
||||
} |
||||
specs = deduped |
||||
|
||||
// Fix up comment positions
|
||||
for i, s := range specs { |
||||
s := s.(*ast.ImportSpec) |
||||
if s.Name != nil { |
||||
s.Name.NamePos = pos[i].Start |
||||
} |
||||
s.Path.ValuePos = pos[i].Start |
||||
s.EndPos = pos[i].End |
||||
for _, g := range importComment[s] { |
||||
for _, c := range g.List { |
||||
c.Slash = pos[i].End |
||||
} |
||||
} |
||||
} |
||||
|
||||
sort.Sort(byCommentPos(comments)) |
||||
|
||||
return specs |
||||
} |
||||
|
||||
type byImportSpec []ast.Spec // slice of *ast.ImportSpec
|
||||
|
||||
func (x byImportSpec) Len() int { return len(x) } |
||||
func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
||||
func (x byImportSpec) Less(i, j int) bool { |
||||
ipath := importPath(x[i]) |
||||
jpath := importPath(x[j]) |
||||
|
||||
igroup := importGroup(ipath) |
||||
jgroup := importGroup(jpath) |
||||
if igroup != jgroup { |
||||
return igroup < jgroup |
||||
} |
||||
|
||||
if ipath != jpath { |
||||
return ipath < jpath |
||||
} |
||||
iname := importName(x[i]) |
||||
jname := importName(x[j]) |
||||
|
||||
if iname != jname { |
||||
return iname < jname |
||||
} |
||||
return importComment(x[i]) < importComment(x[j]) |
||||
} |
||||
|
||||
type byCommentPos []*ast.CommentGroup |
||||
|
||||
func (x byCommentPos) Len() int { return len(x) } |
||||
func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
||||
func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() } |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,247 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"math/big" |
||||
"sync" |
||||
"sync/atomic" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/ethereum/go-ethereum/rpc" |
||||
) |
||||
|
||||
// ContractCaller defines the methods needed to allow operating with contract on a read
|
||||
// only basis.
|
||||
type ContractCaller interface { |
||||
// ContractCall executes an Ethereum contract call with the specified data as
|
||||
// the input.
|
||||
ContractCall(contract common.Address, data []byte) ([]byte, error) |
||||
} |
||||
|
||||
// ContractTransactor defines the methods needed to allow operating with contract
|
||||
// on a write only basis. Beside the transacting method, the remainder are helpers
|
||||
// used when the user does not provide some needed values, but rather leaves it up
|
||||
// to the transactor to decide.
|
||||
type ContractTransactor interface { |
||||
// Nonce retrieves the current pending nonce associated with an account.
|
||||
AccountNonce(account common.Address) (uint64, error) |
||||
|
||||
// GasPrice retrieves the currently suggested gas price to allow a timely execution
|
||||
// of a transaction.
|
||||
GasPrice() (*big.Int, error) |
||||
|
||||
// GasLimit tries to estimate the gas needed to execute a specific transaction.
|
||||
GasLimit(sender, contract common.Address, value *big.Int, data []byte) (*big.Int, error) |
||||
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(*types.Transaction) error |
||||
} |
||||
|
||||
// ContractBackend defines the methods needed to allow operating with contract
|
||||
// on a read-write basis.
|
||||
type ContractBackend interface { |
||||
ContractCaller |
||||
ContractTransactor |
||||
} |
||||
|
||||
// nilBackend implements bind.ContractBackend, but panics on any method call.
|
||||
// Its sole purpose is to support the binding tests to construct the generated
|
||||
// wrappers without calling any methods on them.
|
||||
type nilBackend struct{} |
||||
|
||||
func (*nilBackend) ContractCall(common.Address, []byte) ([]byte, error) { panic("not implemented") } |
||||
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") } |
||||
func (*nilBackend) AccountNonce(common.Address) (uint64, error) { panic("not implemented") } |
||||
func (*nilBackend) GasPrice() (*big.Int, error) { panic("not implemented") } |
||||
func (*nilBackend) GasLimit(common.Address, common.Address, *big.Int, []byte) (*big.Int, error) { |
||||
panic("not implemented") |
||||
} |
||||
|
||||
// Helper backend for internal tests. Will panic on any invocation!
|
||||
var NilBackend = new(nilBackend) |
||||
|
||||
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
|
||||
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
|
||||
// all its functionality.
|
||||
//
|
||||
// Note: The current implementation is a blocking one. This should be replaced
|
||||
// by a proper async version when a real RPC client is created.
|
||||
type rpcBackend struct { |
||||
client rpc.Client // RPC client connection to interact with an API server
|
||||
autoid uint32 // ID number to use for the next API request
|
||||
lock sync.Mutex // Singleton access until we get to request multiplexing
|
||||
} |
||||
|
||||
// NewRPCBackend creates a new binding backend to an RPC provider that can be
|
||||
// used to interact with remote contracts.
|
||||
func NewRPCBackend(client rpc.Client) ContractBackend { |
||||
return &rpcBackend{ |
||||
client: client, |
||||
} |
||||
} |
||||
|
||||
// request is a JSON RPC request package assembled internally from the client
|
||||
// method calls.
|
||||
type request struct { |
||||
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
Id int `json:"id"` // Auto incrementing ID number for this request
|
||||
Method string `json:"method"` // Remote procedure name to invoke on the server
|
||||
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
|
||||
} |
||||
|
||||
// response is a JSON RPC response package sent back from the API server.
|
||||
type response struct { |
||||
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
Id int `json:"id"` // Auto incrementing ID number for this request
|
||||
Error json.RawMessage `json:"error"` // Any error returned by the remote side
|
||||
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
|
||||
} |
||||
|
||||
// request forwards an API request to the RPC server, and parses the response.
|
||||
//
|
||||
// This is currently painfully non-concurrent, but it will have to do until we
|
||||
// find the time for niceties like this :P
|
||||
func (backend *rpcBackend) request(method string, params []interface{}) (json.RawMessage, error) { |
||||
backend.lock.Lock() |
||||
defer backend.lock.Unlock() |
||||
|
||||
// Ugly hack to serialize an empty list properly
|
||||
if params == nil { |
||||
params = []interface{}{} |
||||
} |
||||
// Assemble the request object
|
||||
req := &request{ |
||||
JsonRpc: "2.0", |
||||
Id: int(atomic.AddUint32(&backend.autoid, 1)), |
||||
Method: method, |
||||
Params: params, |
||||
} |
||||
if err := backend.client.Send(req); err != nil { |
||||
return nil, err |
||||
} |
||||
res := new(response) |
||||
if err := backend.client.Recv(res); err != nil { |
||||
return nil, err |
||||
} |
||||
if len(res.Error) > 0 { |
||||
return nil, fmt.Errorf("remote error: %s", string(res.Error)) |
||||
} |
||||
return res.Result, nil |
||||
} |
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
||||
// a contract call to the remote node, returning the reply to for local processing.
|
||||
func (b *rpcBackend) ContractCall(contract common.Address, data []byte) ([]byte, error) { |
||||
// Pack up the request into an RPC argument
|
||||
args := struct { |
||||
To common.Address `json:"to"` |
||||
Data string `json:"data"` |
||||
}{ |
||||
To: contract, |
||||
Data: common.ToHex(data), |
||||
} |
||||
// Execute the RPC call and retrieve the response
|
||||
res, err := b.request("eth_call", []interface{}{args, "pending"}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
// Convert the response back to a Go byte slice and return
|
||||
return common.FromHex(hex), nil |
||||
} |
||||
|
||||
// AccountNonce implements ContractTransactor.AccountNonce, delegating the
|
||||
// current account nonce retrieval to the remote node.
|
||||
func (b *rpcBackend) AccountNonce(account common.Address) (uint64, error) { |
||||
res, err := b.request("eth_getTransactionCount", []interface{}{account.Hex(), "pending"}) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return 0, err |
||||
} |
||||
return new(big.Int).SetBytes(common.FromHex(hex)).Uint64(), nil |
||||
} |
||||
|
||||
// GasPrice implements ContractTransactor.GasPrice, delegating the gas price
|
||||
// oracle request to the remote node.
|
||||
func (b *rpcBackend) GasPrice() (*big.Int, error) { |
||||
res, err := b.request("eth_gasPrice", nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
return new(big.Int).SetBytes(common.FromHex(hex)), nil |
||||
} |
||||
|
||||
// GasLimit implements ContractTransactor.GasLimit, delegating the gas estimation
|
||||
// to the remote node.
|
||||
func (b *rpcBackend) GasLimit(sender, contract common.Address, value *big.Int, data []byte) (*big.Int, error) { |
||||
// Pack up the request into an RPC argument
|
||||
args := struct { |
||||
From common.Address `json:"from"` |
||||
To common.Address `json:"to"` |
||||
Value *rpc.HexNumber `json:"value"` |
||||
Data string `json:"data"` |
||||
}{ |
||||
From: sender, |
||||
To: contract, |
||||
Data: common.ToHex(data), |
||||
Value: rpc.NewHexNumber(value), |
||||
} |
||||
// Execute the RPC call and retrieve the response
|
||||
res, err := b.request("eth_estimateGas", []interface{}{args}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
// Convert the response back to a Go byte slice and return
|
||||
return new(big.Int).SetBytes(common.FromHex(hex)), nil |
||||
} |
||||
|
||||
// Transact implements ContractTransactor.SendTransaction, delegating the raw
|
||||
// transaction injection to the remote node.
|
||||
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error { |
||||
data, err := rlp.EncodeToBytes(tx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,130 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// SignerFn is a signer function callback when a contract requires a method to
|
||||
// sign the transaction before submission.
|
||||
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) |
||||
|
||||
// AuthOpts is the authorization data required to create a valid Ethereum transaction.
|
||||
type AuthOpts struct { |
||||
Account common.Address // Ethereum account to send the transaction from
|
||||
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
||||
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
||||
|
||||
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
|
||||
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
||||
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%)
|
||||
} |
||||
|
||||
// BoundContract is the base wrapper object that reflects a contract on the
|
||||
// Ethereum network. It contains a collection of methods that are used by the
|
||||
// higher level contract bindings to operate.
|
||||
type BoundContract struct { |
||||
address common.Address // Deployment address of the contract on the Ethereum blockchain
|
||||
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
||||
caller ContractCaller // Read interface to interact with the blockchain
|
||||
transactor ContractTransactor // Write interface to interact with the blockchain
|
||||
} |
||||
|
||||
// NewBoundContract creates a low level contract interface through which calls
|
||||
// and transactions may be made through.
|
||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract { |
||||
return &BoundContract{ |
||||
address: address, |
||||
abi: abi, |
||||
caller: caller, |
||||
transactor: transactor, |
||||
} |
||||
} |
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// sets the output to result. The result type might be a single field for simple
|
||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||
// returns.
|
||||
func (c *BoundContract) Call(result interface{}, method string, params ...interface{}) error { |
||||
input, err := c.abi.Pack(method, params...) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
output, err := c.caller.ContractCall(c.address, input) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return c.abi.Unpack(result, method, output) |
||||
} |
||||
|
||||
// Transact invokes the (paid) contract method with params as input values and
|
||||
// value as the fund transfer to the contract.
|
||||
func (c *BoundContract) Transact(opts *AuthOpts, method string, params ...interface{}) (*types.Transaction, error) { |
||||
input, err := c.abi.Pack(method, params...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// Ensure a valid value field and resolve the account nonce
|
||||
value := opts.Value |
||||
if value == nil { |
||||
value = new(big.Int) |
||||
} |
||||
nonce := uint64(0) |
||||
if opts.Nonce == nil { |
||||
nonce, err = c.transactor.AccountNonce(opts.Account) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) |
||||
} |
||||
} else { |
||||
nonce = opts.Nonce.Uint64() |
||||
} |
||||
// Figure out the gas allowance and gas price values
|
||||
gasPrice := opts.GasPrice |
||||
if gasPrice == nil { |
||||
gasPrice, err = c.transactor.GasPrice() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to suggest gas price: %v", err) |
||||
} |
||||
} |
||||
gasLimit := opts.GasLimit |
||||
if gasLimit == nil { |
||||
gasLimit, err = c.transactor.GasLimit(opts.Account, c.address, value, input) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) |
||||
} |
||||
} |
||||
// Create the transaction, sign it and schedule it for execution
|
||||
rawTx := types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) |
||||
if opts.Signer == nil { |
||||
return nil, errors.New("no signer to authorize the transaction with") |
||||
} |
||||
signedTx, err := opts.Signer(opts.Account, rawTx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := c.transactor.SendTransaction(signedTx); err != nil { |
||||
return nil, err |
||||
} |
||||
return signedTx, nil |
||||
} |
@ -0,0 +1,317 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package bind generates Ethereum contract Go bindings.
|
||||
package bind |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"sort" |
||||
"strings" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi" |
||||
"golang.org/x/tools/imports" |
||||
) |
||||
|
||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||
// to be used as is in client code, but rather as an intermediate struct which
|
||||
// enforces compile time type safety and naming convention opposed to having to
|
||||
// manually maintain hard coded strings that break on runtime.
|
||||
func Bind(jsonABI string, pkg string, kind string) (string, error) { |
||||
// Parse the actual ABI to generate the binding for
|
||||
abi, err := abi.JSON(strings.NewReader(jsonABI)) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
// Generate the contract type, fields and methods
|
||||
code := new(bytes.Buffer) |
||||
kind = strings.ToUpper(kind[:1]) + kind[1:] |
||||
fmt.Fprintf(code, "%s\n", bindContract(kind, jsonABI)) |
||||
|
||||
methods := make([]string, 0, len(abi.Methods)) |
||||
for name, _ := range abi.Methods { |
||||
methods = append(methods, name) |
||||
} |
||||
sort.Strings(methods) |
||||
|
||||
for _, method := range methods { |
||||
fmt.Fprintf(code, "%s\n", bindMethod(kind, abi.Methods[method])) |
||||
} |
||||
// Format the code with goimports and return
|
||||
buffer := new(bytes.Buffer) |
||||
|
||||
fmt.Fprintf(buffer, "package %s\n\n", pkg) |
||||
fmt.Fprintf(buffer, "%s\n\n", string(code.Bytes())) |
||||
|
||||
blob, err := imports.Process("", buffer.Bytes(), nil) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return string(blob), nil |
||||
} |
||||
|
||||
// bindContract generates the basic wrapper code for interacting with an Ethereum
|
||||
// contract via the abi package. All contract methods will call into the generic
|
||||
// ones generated here.
|
||||
func bindContract(kind string, abi string) string { |
||||
code := "" |
||||
|
||||
// Generate the hard coded ABI used for Ethereum interaction
|
||||
code += fmt.Sprintf("// Ethereum ABI used to generate the binding from.\nconst %sABI = `%s`\n\n", kind, strings.TrimSpace(abi)) |
||||
|
||||
// Generate the Go struct with all the maintenance fields
|
||||
code += fmt.Sprintf("// %s is an auto generated Go binding around an Ethereum contract.\n", kind) |
||||
code += fmt.Sprintf("type %s struct {\n", kind) |
||||
code += fmt.Sprintf(" %sCaller // Read-only binding to the contract\n", kind) |
||||
code += fmt.Sprintf(" %sTransactor // Write-only binding to the contract\n", kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// %sCaller is an auto generated read-only Go binding around an Ethereum contract.\n", kind) |
||||
code += fmt.Sprintf("type %sCaller struct {\n", kind) |
||||
code += fmt.Sprintf(" common *common%s // Contract binding common to callers and transactors\n", kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// %sTransactor is an auto generated write-only Go binding around an Ethereum contract.\n", kind) |
||||
code += fmt.Sprintf("type %sTransactor struct {\n", kind) |
||||
code += fmt.Sprintf(" common *common%s // Contract binding common to callers and transactors\n", kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// common%s is an auto generated Go binding around an Ethereum contract.\n", kind) |
||||
code += fmt.Sprintf("type common%s struct {\n", kind) |
||||
code += fmt.Sprintf(" contract *bind.BoundContract // Generic contract wrapper for the low level calls\n") |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
// Generate the constructor to create a bound contract
|
||||
code += fmt.Sprintf("// New%s creates a new instance of %s, bound to a specific deployed contract.\n", kind, kind) |
||||
code += fmt.Sprintf("func New%s(address common.Address, backend bind.ContractBackend) (*%s, error) {\n", kind, kind) |
||||
code += fmt.Sprintf(" common, err := newCommon%s(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor))\n", kind) |
||||
code += fmt.Sprintf(" if err != nil {\n") |
||||
code += fmt.Sprintf(" return nil, err\n") |
||||
code += fmt.Sprintf(" }\n") |
||||
code += fmt.Sprintf(" return &%s{%sCaller: %sCaller{common: common}, %sTransactor: %sTransactor{common: common}}, nil\n", kind, kind, kind, kind, kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// New%sCaller creates a new read-only instance of %s, bound to a specific deployed contract.\n", kind, kind) |
||||
code += fmt.Sprintf("func New%sCaller(address common.Address, caller bind.ContractCaller) (*%sCaller, error) {\n", kind, kind) |
||||
code += fmt.Sprintf(" common, err := newCommon%s(address, caller, nil)\n", kind) |
||||
code += fmt.Sprintf(" if err != nil {\n") |
||||
code += fmt.Sprintf(" return nil, err\n") |
||||
code += fmt.Sprintf(" }\n") |
||||
code += fmt.Sprintf(" return &%sCaller{common: common}, nil\n", kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// New%sTransactor creates a new write-only instance of %s, bound to a specific deployed contract.\n", kind, kind) |
||||
code += fmt.Sprintf("func New%sTransactor(address common.Address, transactor bind.ContractTransactor) (*%sTransactor, error) {\n", kind, kind) |
||||
code += fmt.Sprintf(" common, err := newCommon%s(address, nil, transactor)\n", kind) |
||||
code += fmt.Sprintf(" if err != nil {\n") |
||||
code += fmt.Sprintf(" return nil, err\n") |
||||
code += fmt.Sprintf(" }\n") |
||||
code += fmt.Sprintf(" return &%sTransactor{common: common}, nil\n", kind) |
||||
code += fmt.Sprintf("}\n\n") |
||||
|
||||
code += fmt.Sprintf("// newCommon%s creates an internal instance of %s, bound to a specific deployed contract.\n", kind, kind) |
||||
code += fmt.Sprintf("func newCommon%s(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*common%s, error) {\n", kind, kind) |
||||
code += fmt.Sprintf(" parsed, err := abi.JSON(strings.NewReader(%sABI))\n", kind) |
||||
code += fmt.Sprintf(" if err != nil {\n") |
||||
code += fmt.Sprintf(" return nil, err\n") |
||||
code += fmt.Sprintf(" }\n") |
||||
code += fmt.Sprintf(" return &common%s{\n", kind) |
||||
code += fmt.Sprintf(" contract: bind.NewBoundContract(address, parsed, caller, transactor),\n") |
||||
code += fmt.Sprintf(" }, nil\n") |
||||
code += fmt.Sprintf("}") |
||||
|
||||
return code |
||||
} |
||||
|
||||
// bindMethod
|
||||
func bindMethod(kind string, method abi.Method) string { |
||||
var ( |
||||
name = strings.ToUpper(method.Name[:1]) + method.Name[1:] |
||||
prologue = new(bytes.Buffer) |
||||
) |
||||
// Generate the argument and return list for the function
|
||||
args := make([]string, 0, len(method.Inputs)) |
||||
for i, arg := range method.Inputs { |
||||
param := arg.Name |
||||
if param == "" { |
||||
param = fmt.Sprintf("arg%d", i) |
||||
} |
||||
args = append(args, fmt.Sprintf("%s %s", param, bindType(arg.Type))) |
||||
} |
||||
returns, _ := bindReturn(prologue, name, method.Outputs) |
||||
|
||||
// Generate the docs to help with coding against the binding
|
||||
callTypeDoc := "free data retrieval call" |
||||
if !method.Const { |
||||
callTypeDoc = "paid mutator transaction" |
||||
} |
||||
docs := fmt.Sprintf("// %s is a %s binding the contract method 0x%x.\n", name, callTypeDoc, method.Id()) |
||||
docs += fmt.Sprintf("// \n") |
||||
docs += fmt.Sprintf("// Solidity: %s", strings.TrimPrefix(method.String(), "function ")) |
||||
|
||||
// Generate the method itself for both the read/write version and the combo too
|
||||
code := fmt.Sprintf("%s\n", prologue) |
||||
if method.Const { |
||||
code += fmt.Sprintf("%s\nfunc (_%s *%sCaller) %s(%s) (%s) {\n%s\n}\n", docs, kind, kind, name, strings.Join(args, ","), strings.Join(returns, ","), bindCallBody(kind, method.Name, args, returns)) |
||||
} else { |
||||
args = append([]string{"auth *bind.AuthOpts"}, args...) |
||||
code += fmt.Sprintf("%s\nfunc (_%s *%sTransactor) %s(%s) (*types.Transaction, error) {\n%s\n}\n", docs, kind, kind, name, strings.Join(args, ","), bindTransactionBody(kind, method.Name, args)) |
||||
} |
||||
return code |
||||
} |
||||
|
||||
// bindType converts a Solidity type to a Go one. Since there is no clear mapping
|
||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||
// mapped will use an upscaled type (e.g. *big.Int).
|
||||
func bindType(kind abi.Type) string { |
||||
stringKind := kind.String() |
||||
|
||||
switch { |
||||
case stringKind == "address": |
||||
return "common.Address" |
||||
|
||||
case stringKind == "hash": |
||||
return "common.Hash" |
||||
|
||||
case strings.HasPrefix(stringKind, "bytes"): |
||||
if stringKind == "bytes" { |
||||
return "[]byte" |
||||
} |
||||
return fmt.Sprintf("[%s]byte", stringKind[5:]) |
||||
|
||||
case strings.HasPrefix(stringKind, "int"): |
||||
switch stringKind[:3] { |
||||
case "8", "16", "32", "64": |
||||
return stringKind |
||||
} |
||||
return "*big.Int" |
||||
|
||||
case strings.HasPrefix(stringKind, "uint"): |
||||
switch stringKind[:4] { |
||||
case "8", "16", "32", "64": |
||||
return stringKind |
||||
} |
||||
return "*big.Int" |
||||
|
||||
default: |
||||
return stringKind |
||||
} |
||||
} |
||||
|
||||
// bindReturn creates the list of return parameters for a method invocation. If
|
||||
// all the fields of the return type are named, and there is more than one value
|
||||
// being returned, the returns are wrapped in a result struct.
|
||||
func bindReturn(prologue *bytes.Buffer, method string, outputs []abi.Argument) ([]string, string) { |
||||
// Generate the anonymous return list for when a struct is not needed/possible
|
||||
var ( |
||||
returns = make([]string, 0, len(outputs)+1) |
||||
anonymous = false |
||||
) |
||||
for _, ret := range outputs { |
||||
returns = append(returns, bindType(ret.Type)) |
||||
if ret.Name == "" { |
||||
anonymous = true |
||||
} |
||||
} |
||||
if anonymous || len(returns) < 2 { |
||||
returns = append(returns, "error") |
||||
return returns, "" |
||||
} |
||||
// If the returns are named and numerous, wrap in a result struct
|
||||
wrapper, impl := bindReturnStruct(method, outputs) |
||||
prologue.WriteString(impl + "\n") |
||||
return []string{"*" + wrapper, "error"}, wrapper |
||||
} |
||||
|
||||
// bindReturnStruct creates a Go structure with the specified fields to be used
|
||||
// as the return type from a method call.
|
||||
func bindReturnStruct(method string, returns []abi.Argument) (string, string) { |
||||
fields := make([]string, 0, len(returns)) |
||||
for _, ret := range returns { |
||||
fields = append(fields, fmt.Sprintf("%s %s", strings.ToUpper(ret.Name[:1])+ret.Name[1:], bindType(ret.Type))) |
||||
} |
||||
kind := fmt.Sprintf("%sResult", method) |
||||
docs := fmt.Sprintf("// %s is the result of the %s invocation.", kind, method) |
||||
|
||||
return kind, fmt.Sprintf("%s\ntype %s struct {\n%s\n}", docs, kind, strings.Join(fields, "\n")) |
||||
} |
||||
|
||||
// bindCallBody creates the Go code to declare a batch of return values, invoke
|
||||
// an Ethereum method call with the requested parameters, parse the binary output
|
||||
// into the return values and return them.
|
||||
func bindCallBody(kind string, method string, params []string, returns []string) string { |
||||
body := "" |
||||
|
||||
// Allocate memory for each of the return values
|
||||
rets := make([]string, 0, len(returns)-1) |
||||
if len(returns) > 1 { |
||||
body += "var (" |
||||
for i, kind := range returns[:len(returns)-1] { // Omit the final error
|
||||
name := fmt.Sprintf("ret%d", i) |
||||
|
||||
rets = append(rets, name) |
||||
body += fmt.Sprintf("%s = new(%s)\n", name, strings.TrimPrefix(kind, "*")) |
||||
} |
||||
body += ")\n" |
||||
} |
||||
// Assemble a single collector variable for the result ABI initialization
|
||||
result := strings.Join(rets, ",") |
||||
if len(returns) > 2 { |
||||
result = "[]interface{}{" + result + "}" |
||||
} |
||||
// Extract the parameter list into a flat variable name list
|
||||
inputs := make([]string, len(params)) |
||||
for i, param := range params { |
||||
inputs[i] = strings.Split(param, " ")[0] |
||||
} |
||||
input := "" |
||||
if len(inputs) > 0 { |
||||
input = "," + strings.Join(inputs, ",") |
||||
} |
||||
// Request executing the contract call and return the results with the errors
|
||||
body += fmt.Sprintf("err := _%s.common.contract.Call(%s, \"%s\" %s)\n", kind, result, method, input) |
||||
|
||||
outs := make([]string, 0, len(returns)) |
||||
for i, ret := range returns[:len(returns)-1] { // Handle th final error separately
|
||||
if strings.HasPrefix(ret, "*") { |
||||
outs = append(outs, rets[i]) |
||||
} else { |
||||
outs = append(outs, "*"+rets[i]) |
||||
} |
||||
} |
||||
outs = append(outs, "err") |
||||
|
||||
body += fmt.Sprintf("return %s", strings.Join(outs, ",")) |
||||
|
||||
return body |
||||
} |
||||
|
||||
// bindTransactionBody creates the Go code to invoke an Ethereum transaction call
|
||||
// with the requested parameters, and return the assembled transaction object.
|
||||
func bindTransactionBody(kind string, method string, params []string) string { |
||||
// Extract the parameter list into a flat variable name list
|
||||
inputs := make([]string, len(params)-1) // Omit the auth options
|
||||
for i, param := range params[1:] { |
||||
inputs[i] = strings.Split(param, " ")[0] |
||||
} |
||||
input := "" |
||||
if len(inputs) > 0 { |
||||
input = "," + strings.Join(inputs, ",") |
||||
} |
||||
// Request executing the contract call and return the results with the errors
|
||||
return fmt.Sprintf("return _%s.common.contract.Transact(auth, \"%s\" %s)", kind, method, input) |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,71 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
) |
||||
|
||||
var ( |
||||
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind") |
||||
pkgFlag = flag.String("pkg", "", "Go package name to generate the binding into") |
||||
typFlag = flag.String("type", "", "Go struct name for the binding (default = package name)") |
||||
outFlag = flag.String("out", "", "Output path for the generated binding") |
||||
) |
||||
|
||||
func main() { |
||||
// Parse and validate the command line flags
|
||||
flag.Parse() |
||||
|
||||
if *abiFlag == "" { |
||||
fmt.Printf("No contract ABI path specified (--abi)\n") |
||||
os.Exit(-1) |
||||
} |
||||
if *pkgFlag == "" { |
||||
fmt.Printf("No destination Go package specified (--pkg)\n") |
||||
os.Exit(-1) |
||||
} |
||||
// Generate the contract binding
|
||||
in, err := ioutil.ReadFile(*abiFlag) |
||||
if err != nil { |
||||
fmt.Printf("Failed to read input ABI: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
kind := *typFlag |
||||
if kind == "" { |
||||
kind = *pkgFlag |
||||
} |
||||
code, err := bind.Bind(string(in), *pkgFlag, kind) |
||||
if err != nil { |
||||
fmt.Printf("Failed to generate ABI binding: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
// Either flush it out to a file or display on the standard output
|
||||
if *outFlag == "" { |
||||
fmt.Printf("%s\n", code) |
||||
return |
||||
} |
||||
if err := ioutil.WriteFile(*outFlag, []byte(code), 0600); err != nil { |
||||
fmt.Printf("Failed to write ABI binding: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
} |
Loading…
Reference in new issue