From b1247d2d0d51108c910a73891ff3116e5f032ab1 Mon Sep 17 00:00:00 2001 From: "Kali Kaneko (leap communications)" Date: Sat, 12 Jan 2019 18:39:45 +0100 Subject: [pkg] all your deps are vendored to us --- .../golang.org/x/tools/go/ast/astutil/enclosing.go | 627 +++++++ .../x/tools/go/ast/astutil/enclosing_test.go | 195 +++ .../golang.org/x/tools/go/ast/astutil/imports.go | 470 +++++ .../x/tools/go/ast/astutil/imports_test.go | 1818 ++++++++++++++++++++ .../golang.org/x/tools/go/ast/astutil/rewrite.go | 477 +++++ .../x/tools/go/ast/astutil/rewrite_test.go | 248 +++ vendor/golang.org/x/tools/go/ast/astutil/util.go | 14 + 7 files changed, 3849 insertions(+) create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing_test.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports_test.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite_test.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/util.go (limited to 'vendor/golang.org/x/tools/go/ast/astutil') diff --git a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go new file mode 100644 index 0000000..6b7052b --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go @@ -0,0 +1,627 @@ +// 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: + if len(n.Args) == 1 && !n.Ellipsis.IsValid() { + return "function call (or conversion)" + } + return "function call" + 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)) +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/enclosing_test.go b/vendor/golang.org/x/tools/go/ast/astutil/enclosing_test.go new file mode 100644 index 0000000..107f87c --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/enclosing_test.go @@ -0,0 +1,195 @@ +// 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_test + +// This file defines tests of PathEnclosingInterval. + +// TODO(adonovan): exhaustive tests that run over the whole input +// tree, not just handcrafted examples. + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" + + "golang.org/x/tools/go/ast/astutil" +) + +// pathToString returns a string containing the concrete types of the +// nodes in path. +func pathToString(path []ast.Node) string { + var buf bytes.Buffer + fmt.Fprint(&buf, "[") + for i, n := range path { + if i > 0 { + fmt.Fprint(&buf, " ") + } + fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) + } + fmt.Fprint(&buf, "]") + return buf.String() +} + +// findInterval parses input and returns the [start, end) positions of +// the first occurrence of substr in input. f==nil indicates failure; +// an error has already been reported in that case. +// +func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { + f, err := parser.ParseFile(fset, "", input, 0) + if err != nil { + t.Errorf("parse error: %s", err) + return + } + + i := strings.Index(input, substr) + if i < 0 { + t.Errorf("%q is not a substring of input", substr) + f = nil + return + } + + filePos := fset.File(f.Package) + return f, filePos.Pos(i), filePos.Pos(i + len(substr)) +} + +// Common input for following tests. +const input = ` +// Hello. +package main +import "fmt" +func f() {} +func main() { + z := (x + y) // add them + f() // NB: ExprStmt and its CallExpr have same Pos/End +} +` + +func TestPathEnclosingInterval_Exact(t *testing.T) { + // For the exact tests, we check that a substring is mapped to + // the canonical string for the node it denotes. + tests := []struct { + substr string // first occurrence of this string indicates interval + node string // complete text of expected containing node + }{ + {"package", + input[11 : len(input)-1]}, + {"\npack", + input[11 : len(input)-1]}, + {"main", + "main"}, + {"import", + "import \"fmt\""}, + {"\"fmt\"", + "\"fmt\""}, + {"\nfunc f() {}\n", + "func f() {}"}, + {"x ", + "x"}, + {" y", + "y"}, + {"z", + "z"}, + {" + ", + "x + y"}, + {" :=", + "z := (x + y)"}, + {"x + y", + "x + y"}, + {"(x + y)", + "(x + y)"}, + {" (x + y) ", + "(x + y)"}, + {" (x + y) // add", + "(x + y)"}, + {"func", + "func f() {}"}, + {"func f() {}", + "func f() {}"}, + {"\nfun", + "func f() {}"}, + {" f", + "f"}, + } + for _, test := range tests { + f, start, end := findInterval(t, new(token.FileSet), input, test.substr) + if f == nil { + continue + } + + path, exact := astutil.PathEnclosingInterval(f, start, end) + if !exact { + t.Errorf("PathEnclosingInterval(%q) not exact", test.substr) + continue + } + + if len(path) == 0 { + if test.node != "" { + t.Errorf("PathEnclosingInterval(%q).path: got [], want %q", + test.substr, test.node) + } + continue + } + + if got := input[path[0].Pos():path[0].End()]; got != test.node { + t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)", + test.substr, got, test.node, pathToString(path)) + continue + } + } +} + +func TestPathEnclosingInterval_Paths(t *testing.T) { + // For these tests, we check only the path of the enclosing + // node, but not its complete text because it's often quite + // large when !exact. + tests := []struct { + substr string // first occurrence of this string indicates interval + path string // the pathToString(),exact of the expected path + }{ + {"// add", + "[BlockStmt FuncDecl File],false"}, + {"(x + y", + "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, + {"x +", + "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, + {"z := (x", + "[AssignStmt BlockStmt FuncDecl File],false"}, + {"func f", + "[FuncDecl File],false"}, + {"func f()", + "[FuncDecl File],false"}, + {" f()", + "[FuncDecl File],false"}, + {"() {}", + "[FuncDecl File],false"}, + {"// Hello", + "[File],false"}, + {" f", + "[Ident FuncDecl File],true"}, + {"func ", + "[FuncDecl File],true"}, + {"mai", + "[Ident File],true"}, + {"f() // NB", + "[CallExpr ExprStmt BlockStmt FuncDecl File],true"}, + } + for _, test := range tests { + f, start, end := findInterval(t, new(token.FileSet), input, test.substr) + if f == nil { + continue + } + + path, exact := astutil.PathEnclosingInterval(f, start, end) + if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path { + t.Errorf("PathEnclosingInterval(%q): got %q, want %q", + test.substr, got, test.path) + continue + } + } +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go new file mode 100644 index 0000000..83f196c --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -0,0 +1,470 @@ +// 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 "golang.org/x/tools/go/ast/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 + + isThirdPartyPath = isThirdParty(ipath) + ) + 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 and find best + // matched import spec. + // 1. Always prefer import spec with longest shared prefix. + // 2. While match length is 0, + // - for stdlib package: prefer first import spec. + // - for third party package: prefer first third party import spec. + // We cannot use last import spec as best match for third party package + // because grouped imports are usually placed last by goimports -local + // flag. + // See issue #19190. + seenAnyThirdParty := false + for j, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + p := importPath(impspec) + n := matchLen(p, ipath) + if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { + bestMatch = n + impDecl = gen + impIndex = j + } + seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p) + } + } + } + + // 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) + + if len(f.Decls) <= 1 { + return true + } + + // Merge all the import declarations into the first one. + var first *ast.GenDecl + for i := 0; i < len(f.Decls); i++ { + decl := f.Decls[i] + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { + continue + } + if first == nil { + first = gen + continue // Don't touch the first one. + } + // We now know there is more than one package in this import + // declaration. Ensure that it ends up parenthesized. + first.Lparen = first.Pos() + // Move the imports of the other import declaration to the first one. + for _, spec := range gen.Specs { + spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() + first.Specs = append(first.Specs, spec) + } + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- + } + + return true +} + +func isThirdParty(importPath string) bool { + // Third party package import path usually contains "." (".com", ".org", ...) + // This logic is taken from golang.org/x/tools/imports package. + return strings.Contains(importPath, ".") +} + +// 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 + var delcomments []*ast.CommentGroup + + // 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 { + if impspec.Doc != nil { + delcomments = append(delcomments, impspec.Doc) + } + if impspec.Comment != nil { + delcomments = append(delcomments, impspec.Comment) + } + for _, cg := range f.Comments { + // Found comment on the same line as the import spec. + if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line { + delcomments = append(delcomments, cg) + break + } + } + + spec := gen.Specs[0].(*ast.ImportSpec) + + // Move the documentation right after the import decl. + if spec.Doc != nil { + for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line { + fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) + } + } + for _, cg := range f.Comments { + if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line { + for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line { + fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) + } + break + } + } + } + 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 if line != fset.File(gen.Rparen).LineCount() { + // There was no blank line. Close the hole. + fset.File(gen.Rparen).MergeLine(line) + } + } + j-- + } + } + + // Delete imports 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 + } + } + } + + // Delete comments from f.Comments. + for i := 0; i < len(f.Comments); i++ { + cg := f.Comments[i] + for j, del := range delcomments { + if cg == del { + copy(f.Comments[i:], f.Comments[i+1:]) + f.Comments = f.Comments[:len(f.Comments)-1] + copy(delcomments[j:], delcomments[j+1:]) + delcomments = delcomments[:len(delcomments)-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 "": + // 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 +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports_test.go b/vendor/golang.org/x/tools/go/ast/astutil/imports_test.go new file mode 100644 index 0000000..8bc3480 --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports_test.go @@ -0,0 +1,1818 @@ +// 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 + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "reflect" + "strconv" + "testing" +) + +var fset = token.NewFileSet() + +func parse(t *testing.T, name, in string) *ast.File { + file, err := parser.ParseFile(fset, name, in, parser.ParseComments) + if err != nil { + t.Fatalf("%s parse: %v", name, err) + } + return file +} + +func print(t *testing.T, name string, f *ast.File) string { + var buf bytes.Buffer + if err := format.Node(&buf, fset, f); err != nil { + t.Fatalf("%s gofmt: %v", name, err) + } + return string(buf.Bytes()) +} + +type test struct { + name string + renamedPkg string + pkg string + in string + out string + broken bool // known broken +} + +var addTests = []test{ + { + name: "leave os alone", + pkg: "os", + in: `package main + +import ( + "os" +) +`, + out: `package main + +import ( + "os" +) +`, + }, + { + name: "import.1", + pkg: "os", + in: `package main +`, + out: `package main + +import "os" +`, + }, + { + name: "import.2", + pkg: "os", + in: `package main + +// Comment +import "C" +`, + out: `package main + +// Comment +import "C" +import "os" +`, + }, + { + name: "import.3", + pkg: "os", + in: `package main + +// Comment +import "C" + +import ( + "io" + "utf8" +) +`, + out: `package main + +// Comment +import "C" + +import ( + "io" + "os" + "utf8" +) +`, + }, + { + name: "import.17", + pkg: "x/y/z", + in: `package main + +// Comment +import "C" + +import ( + "a" + "b" + + "x/w" + + "d/f" +) +`, + out: `package main + +// Comment +import "C" + +import ( + "a" + "b" + + "x/w" + "x/y/z" + + "d/f" +) +`, + }, + { + name: "issue #19190", + pkg: "x.org/y/z", + in: `package main + +// Comment +import "C" + +import ( + "bytes" + "os" + + "d.com/f" +) +`, + out: `package main + +// Comment +import "C" + +import ( + "bytes" + "os" + + "d.com/f" + "x.org/y/z" +) +`, + }, + { + name: "issue #19190 with existing grouped import packages", + pkg: "x.org/y/z", + in: `package main + +// Comment +import "C" + +import ( + "bytes" + "os" + + "c.com/f" + "d.com/f" + + "y.com/a" + "y.com/b" + "y.com/c" +) +`, + out: `package main + +// Comment +import "C" + +import ( + "bytes" + "os" + + "c.com/f" + "d.com/f" + "x.org/y/z" + + "y.com/a" + "y.com/b" + "y.com/c" +) +`, + }, + { + name: "issue #19190 - match score is still respected", + pkg: "y.org/c", + in: `package main + +import ( + "x.org/a" + + "y.org/b" +) +`, + out: `package main + +import ( + "x.org/a" + + "y.org/b" + "y.org/c" +) +`, + }, + { + name: "import into singular group", + pkg: "bytes", + in: `package main + +import "os" + +`, + out: `package main + +import ( + "bytes" + "os" +) +`, + }, + { + name: "import into singular group with comment", + pkg: "bytes", + in: `package main + +import /* why */ /* comment here? */ "os" + +`, + out: `package main + +import /* why */ /* comment here? */ ( + "bytes" + "os" +) +`, + }, + { + name: "import into group with leading comment", + pkg: "strings", + in: `package main + +import ( + // comment before bytes + "bytes" + "os" +) + +`, + out: `package main + +import ( + // comment before bytes + "bytes" + "os" + "strings" +) +`, + }, + { + name: "", + renamedPkg: "fmtpkg", + pkg: "fmt", + in: `package main + +import "os" + +`, + out: `package main + +import ( + fmtpkg "fmt" + "os" +) +`, + }, + { + name: "struct comment", + pkg: "time", + in: `package main + +// This is a comment before a struct. +type T struct { + t time.Time +} +`, + out: `package main + +import "time" + +// This is a comment before a struct. +type T struct { + t time.Time +} +`, + }, + { + name: "issue 8729 import C", + pkg: "time", + in: `package main + +import "C" + +// comment +type T time.Time +`, + out: `package main + +import "C" +import "time" + +// comment +type T time.Time +`, + }, + { + name: "issue 8729 empty import", + pkg: "time", + in: `package main + +import () + +// comment +type T time.Time +`, + out: `package main + +import "time" + +// comment +type T time.Time +`, + }, + { + name: "issue 8729 comment on package line", + pkg: "time", + in: `package main // comment + +type T time.Time +`, + out: `package main // comment +import "time" + +type T time.Time +`, + }, + { + name: "issue 8729 comment after package", + pkg: "time", + in: `package main +// comment + +type T time.Time +`, + out: `package main + +import "time" + +// comment + +type T time.Time +`, + }, + { + name: "issue 8729 comment before and on package line", + pkg: "time", + in: `// comment before +package main // comment on + +type T time.Time +`, + out: `// comment before +package main // comment on +import "time" + +type T time.Time +`, + }, + + // Issue 9961: Match prefixes using path segments rather than bytes + { + name: "issue 9961", + pkg: "regexp", + in: `package main + +import ( + "flag" + "testing" + + "rsc.io/p" +) +`, + out: `package main + +import ( + "flag" + "regexp" + "testing" + + "rsc.io/p" +) +`, + }, + // Issue 10337: Preserve comment position + { + name: "issue 10337", + pkg: "fmt", + in: `package main + +import ( + "bytes" // a + "log" // c +) +`, + out: `package main + +import ( + "bytes" // a + "fmt" + "log" // c +) +`, + }, + { + name: "issue 10337 new import at the start", + pkg: "bytes", + in: `package main + +import ( + "fmt" // b + "log" // c +) +`, + out: `package main + +import ( + "bytes" + "fmt" // b + "log" // c +) +`, + }, + { + name: "issue 10337 new import at the end", + pkg: "log", + in: `package main + +import ( + "bytes" // a + "fmt" // b +) +`, + out: `package main + +import ( + "bytes" // a + "fmt" // b + "log" +) +`, + }, + // Issue 14075: Merge import declarations + { + name: "issue 14075", + pkg: "bufio", + in: `package main + +import "bytes" +import "fmt" +`, + out: `package main + +import ( + "bufio" + "bytes" + "fmt" +) +`, + }, + { + name: "issue 14075 update position", + pkg: "bufio", + in: `package main + +import "bytes" +import ( + "fmt" +) +`, + out: `package main + +import ( + "bufio" + "bytes" + "fmt" +) +`, + }, + { + name: `issue 14075 ignore import "C"`, + pkg: "bufio", + in: `package main + +// Comment +import "C" + +import "bytes" +import "fmt" +`, + out: `package main + +// Comment +import "C" + +import ( + "bufio" + "bytes" + "fmt" +) +`, + }, + { + name: `issue 14075 ignore adjacent import "C"`, + pkg: "bufio", + in: `package main + +// Comment +import "C" +import "fmt" +`, + out: `package main + +// Comment +import "C" +import ( + "bufio" + "fmt" +) +`, + }, + { + name: `issue 14075 ignore adjacent import "C" (without factored import)`, + pkg: "bufio", + in: `package main + +// Comment +import "C" +import "fmt" +`, + out: `package main + +// Comment +import "C" +import ( + "bufio" + "fmt" +) +`, + }, + { + name: `issue 14075 ignore single import "C"`, + pkg: "bufio", + in: `package main + +// Comment +import "C" +`, + out: `package main + +// Comment +import "C" +import "bufio" +`, + }, + { + name: `issue 17212 several single-import lines with shared prefix ending in a slash`, + pkg: "net/http", + in: `package main + +import "bufio" +import "net/url" +`, + out: `package main + +import ( + "bufio" + "net/http" + "net/url" +) +`, + }, + { + name: `issue 17212 block imports lines with shared prefix ending in a slash`, + pkg: "net/http", + in: `package main + +import ( + "bufio" + "net/url" +) +`, + out: `package main + +import ( + "bufio" + "net/http" + "net/url" +) +`, + }, + { + name: `issue 17213 many single-import lines`, + pkg: "fmt", + in: `package main + +import "bufio" +import "bytes" +import "errors" +`, + out: `package main + +import ( + "bufio" + "bytes" + "errors" + "fmt" +) +`, + }, +} + +func TestAddImport(t *testing.T) { + for _, test := range addTests { + file := parse(t, test.name, test.in) + var before bytes.Buffer + ast.Fprint(&before, fset, file, nil) + AddNamedImport(fset, file, test.renamedPkg, test.pkg) + if got := print(t, test.name, file); got != test.out { + if test.broken { + t.Logf("%s is known broken:\ngot: %s\nwant: %s", test.name, got, test.out) + } else { + t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out) + } + var after bytes.Buffer + ast.Fprint(&after, fset, file, nil) + + t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String()) + } + } +} + +func TestDoubleAddImport(t *testing.T) { + file := parse(t, "doubleimport", "package main\n") + AddImport(fset, file, "os") + AddImport(fset, file, "bytes") + want := `package main + +import ( + "bytes" + "os" +) +` + if got := print(t, "doubleimport", file); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} + +func TestDoubleAddNamedImport(t *testing.T) { + file := parse(t, "doublenamedimport", "package main\n") + AddNamedImport(fset, file, "o", "os") + AddNamedImport(fset, file, "i", "io") + want := `package main + +import ( + i "io" + o "os" +) +` + if got := print(t, "doublenamedimport", file); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} + +// Part of issue 8729. +func TestDoubleAddImportWithDeclComment(t *testing.T) { + file := parse(t, "doubleimport", `package main + +import ( +) + +// comment +type I int +`) + // The AddImport order here matters. + AddImport(fset, file, "golang.org/x/tools/go/ast/astutil") + AddImport(fset, file, "os") + want := `package main + +import ( + "golang.org/x/tools/go/ast/astutil" + "os" +) + +// comment +type I int +` + if got := print(t, "doubleimport_with_decl_comment", file); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} + +var deleteTests = []test{ + { + name: "import.4", + pkg: "os", + in: `package main + +import ( + "os" +) +`, + out: `package main +`, + }, + { + name: "import.5", + pkg: "os", + in: `package main + +// Comment +import "C" +import "os" +`, + out: `package main + +// Comment +import "C" +`, + }, + { + name: "import.6", + pkg: "os", + in: `package main + +// Comment +import "C" + +import ( + "io" + "os" + "utf8" +) +`, + out: `package main + +// Comment +import "C" + +import ( + "io" + "utf8" +) +`, + }, + { + name: "import.7", + pkg: "io", + in: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + out: `package main + +import ( + // a + "os" // b + "utf8" // c +) +`, + }, + { + name: "import.8", + pkg: "os", + in: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + out: `package main + +import ( + "io" // a + // b + "utf8" // c +) +`, + }, + { + name: "import.9", + pkg: "utf8", + in: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + out: `package main + +import ( + "io" // a + "os" // b + // c +) +`, + }, + { + name: "import.10", + pkg: "io", + in: `package main + +import ( + "io" + "os" + "utf8" +) +`, + out: `package main + +import ( + "os" + "utf8" +) +`, + }, + { + name: "import.11", + pkg: "os", + in: `package main + +import ( + "io" + "os" + "utf8" +) +`, + out: `package main + +import ( + "io" + "utf8" +) +`, + }, + { + name: "import.12", + pkg: "utf8", + in: `package main + +import ( + "io" + "os" + "utf8" +) +`, + out: `package main + +import ( + "io" + "os" +) +`, + }, + { + name: "handle.raw.quote.imports", + pkg: "os", + in: "package main\n\nimport `os`", + out: `package main +`, + }, + { + name: "import.13", + pkg: "io", + in: `package main + +import ( + "fmt" + + "io" + "os" + "utf8" + + "go/format" +) +`, + out: `package main + +import ( + "fmt" + + "os" + "utf8" + + "go/format" +) +`, + }, + { + name: "import.14", + pkg: "io", + in: `package main + +import ( + "fmt" // a + + "io" // b + "os" // c + "utf8" // d + + "go/format" // e +) +`, + out: `package main + +import ( + "fmt" // a + + // b + "os" // c + "utf8" // d + + "go/format" // e +) +`, + }, + { + name: "import.15", + pkg: "double", + in: `package main + +import ( + "double" + "double" +) +`, + out: `package main +`, + }, + { + name: "import.16", + pkg: "bubble", + in: `package main + +import ( + "toil" + "bubble" + "bubble" + "trouble" +) +`, + out: `package main + +import ( + "toil" + "trouble" +) +`, + }, + { + name: "import.17", + pkg: "quad", + in: `package main + +import ( + "quad" + "quad" +) + +import ( + "quad" + "quad" +) +`, + out: `package main +`, + }, + { + name: "import.18", + renamedPkg: "x", + pkg: "fmt", + in: `package main + +import ( + "fmt" + x "fmt" +) +`, + out: `package main + +import ( + "fmt" +) +`, + }, + { + name: "import.18", + renamedPkg: "x", + pkg: "fmt", + in: `package main + +import x "fmt" +import y "fmt" +`, + out: `package main + +import y "fmt" +`, + }, + // Issue #15432, #18051 + { + name: "import.19", + pkg: "fmt", + in: `package main + +import ( + "fmt" + + // Some comment. + "io" +)`, + out: `package main + +import ( + // Some comment. + "io" +) +`, + }, + { + name: "import.20", + pkg: "fmt", + in: `package main + +import ( + "fmt" + + // Some + // comment. + "io" +)`, + out: `package main + +import ( + // Some + // comment. + "io" +) +`, + }, + { + name: "import.21", + pkg: "fmt", + in: `package main + +import ( + "fmt" + + /* + Some + comment. + */ + "io" +)`, + out: `package main + +import ( + /* + Some + comment. + */ + "io" +) +`, + }, + { + name: "import.22", + pkg: "fmt", + in: `package main + +import ( + /* Some */ + // comment. + "io" + "fmt" +)`, + out: `package main + +import ( + /* Some */ + // comment. + "io" +) +`, + }, + { + name: "import.23", + pkg: "fmt", + in: `package main + +import ( + // comment 1 + "fmt" + // comment 2 + "io" +)`, + out: `package main + +import ( + // comment 2 + "io" +) +`, + }, + { + name: "import.24", + pkg: "fmt", + in: `package main + +import ( + "fmt" // comment 1 + "io" // comment 2 +)`, + out: `package main + +import ( + "io" // comment 2 +) +`, + }, + { + name: "import.25", + pkg: "fmt", + in: `package main + +import ( + "fmt" + /* comment */ "io" +)`, + out: `package main + +import ( + /* comment */ "io" +) +`, + }, + { + name: "import.26", + pkg: "fmt", + in: `package main + +import ( + "fmt" + "io" /* comment */ +)`, + out: `package main + +import ( + "io" /* comment */ +) +`, + }, + { + name: "import.27", + pkg: "fmt", + in: `package main + +import ( + "fmt" /* comment */ + "io" +)`, + out: `package main + +import ( + "io" +) +`, + }, + { + name: "import.28", + pkg: "fmt", + in: `package main + +import ( + /* comment */ "fmt" + "io" +)`, + out: `package main + +import ( + "io" +) +`, + }, + { + name: "import.29", + pkg: "fmt", + in: `package main + +// comment 1 +import ( + "fmt" + "io" // comment 2 +)`, + out: `package main + +// comment 1 +import ( + "io" // comment 2 +) +`, + }, + { + name: "import.30", + pkg: "fmt", + in: `package main + +// comment 1 +import ( + "fmt" // comment 2 + "io" +)`, + out: `package main + +// comment 1 +import ( + "io" +) +`, + }, + { + name: "import.31", + pkg: "fmt", + in: `package main + +// comment 1 +import ( + "fmt" + /* comment 2 */ "io" +)`, + out: `package main + +// comment 1 +import ( + /* comment 2 */ "io" +) +`, + }, + { + name: "import.32", + pkg: "fmt", + renamedPkg: "f", + in: `package main + +// comment 1 +import ( + f "fmt" + /* comment 2 */ i "io" +)`, + out: `package main + +// comment 1 +import ( + /* comment 2 */ i "io" +) +`, + }, + { + name: "import.33", + pkg: "fmt", + renamedPkg: "f", + in: `package main + +// comment 1 +import ( + /* comment 2 */ f "fmt" + i "io" +)`, + out: `package main + +// comment 1 +import ( + i "io" +) +`, + }, + { + name: "import.34", + pkg: "fmt", + renamedPkg: "f", + in: `package main + +// comment 1 +import ( + f "fmt" /* comment 2 */ + i "io" +)`, + out: `package main + +// comment 1 +import ( + i "io" +) +`, + }, + { + name: "import.35", + pkg: "fmt", + in: `package main + +// comment 1 +import ( + "fmt" + // comment 2 + "io" +)`, + out: `package main + +// comment 1 +import ( + // comment 2 + "io" +) +`, + }, + { + name: "import.36", + pkg: "fmt", + in: `package main + +/* comment 1 */ +import ( + "fmt" + /* comment 2 */ + "io" +)`, + out: `package main + +/* comment 1 */ +import ( + /* comment 2 */ + "io" +) +`, + }, + + // Issue 20229: MergeLine panic on weird input + { + name: "import.37", + pkg: "io", + in: `package main +import("_" +"io")`, + out: `package main + +import ( + "_" +) +`, + }, +} + +func TestDeleteImport(t *testing.T) { + for _, test := range deleteTests { + file := parse(t, test.name, test.in) + DeleteNamedImport(fset, file, test.renamedPkg, test.pkg) + if got := print(t, test.name, file); got != test.out { + t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out) + } + } +} + +type rewriteTest struct { + name string + srcPkg string + dstPkg string + in string + out string +} + +var rewriteTests = []rewriteTest{ + { + name: "import.13", + srcPkg: "utf8", + dstPkg: "encoding/utf8", + in: `package main + +import ( + "io" + "os" + "utf8" // thanks ken +) +`, + out: `package main + +import ( + "encoding/utf8" // thanks ken + "io" + "os" +) +`, + }, + { + name: "import.14", + srcPkg: "asn1", + dstPkg: "encoding/asn1", + in: `package main + +import ( + "asn1" + "crypto" + "crypto/rsa" + _ "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "time" +) + +var x = 1 +`, + out: `package main + +import ( + "crypto" + "crypto/rsa" + _ "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "time" +) + +var x = 1 +`, + }, + { + name: "import.15", + srcPkg: "url", + dstPkg: "net/url", + in: `package main + +import ( + "bufio" + "net" + "path" + "url" +) + +var x = 1 // comment on x, not on url +`, + out: `package main + +import ( + "bufio" + "net" + "net/url" + "path" +) + +var x = 1 // comment on x, not on url +`, + }, + { + name: "import.16", + srcPkg: "http", + dstPkg: "net/http", + in: `package main + +import ( + "flag" + "http" + "log" + "text/template" +) + +var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 +`, + out: `package main + +import ( + "flag" + "log" + "net/http" + "text/template" +) + +var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 +`, + }, +} + +func TestRewriteImport(t *testing.T) { + for _, test := range rewriteTests { + file := parse(t, test.name, test.in) + RewriteImport(fset, file, test.srcPkg, test.dstPkg) + if got := print(t, test.name, file); got != test.out { + t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out) + } + } +} + +var importsTests = []struct { + name string + in string + want [][]string +}{ + { + name: "no packages", + in: `package foo +`, + want: nil, + }, + { + name: "one group", + in: `package foo + +import ( + "fmt" + "testing" +) +`, + want: [][]string{{"fmt", "testing"}}, + }, + { + name: "four groups", + in: `package foo + +import "C" +import ( + "fmt" + "testing" + + "appengine" + + "myproject/mylib1" + "myproject/mylib2" +) +`, + want: [][]string{ + {"C"}, + {"fmt", "testing"}, + {"appengine"}, + {"myproject/mylib1", "myproject/mylib2"}, + }, + }, + { + name: "multiple factored groups", + in: `package foo + +import ( + "fmt" + "testing" + + "appengine" +) +import ( + "reflect" + + "bytes" +) +`, + want: [][]string{ + {"fmt", "testing"}, + {"appengine"}, + {"reflect"}, + {"bytes"}, + }, + }, +} + +func unquote(s string) string { + res, err := strconv.Unquote(s) + if err != nil { + return "could_not_unquote" + } + return res +} + +func TestImports(t *testing.T) { + fset := token.NewFileSet() + for _, test := range importsTests { + f, err := parser.ParseFile(fset, "test.go", test.in, 0) + if err != nil { + t.Errorf("%s: %v", test.name, err) + continue + } + var got [][]string + for _, group := range Imports(fset, f) { + var b []string + for _, spec := range group { + b = append(b, unquote(spec.Path.Value)) + } + got = append(got, b) + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Imports(%s)=%v, want %v", test.name, got, test.want) + } + } +} + +var usesImportTests = []struct { + name string + path string + in string + want bool +}{ + { + name: "no packages", + path: "io", + in: `package foo +`, + want: false, + }, + { + name: "import.1", + path: "io", + in: `package foo + +import "io" + +var _ io.Writer +`, + want: true, + }, + { + name: "import.2", + path: "io", + in: `package foo + +import "io" +`, + want: false, + }, + { + name: "import.3", + path: "io", + in: `package foo + +import "io" + +var io = 42 +`, + want: false, + }, + { + name: "import.4", + path: "io", + in: `package foo + +import i "io" + +var _ i.Writer +`, + want: true, + }, + { + name: "import.5", + path: "io", + in: `package foo + +import i "io" +`, + want: false, + }, + { + name: "import.6", + path: "io", + in: `package foo + +import i "io" + +var i = 42 +var io = 42 +`, + want: false, + }, + { + name: "import.7", + path: "encoding/json", + in: `package foo + +import "encoding/json" + +var _ json.Encoder +`, + want: true, + }, + { + name: "import.8", + path: "encoding/json", + in: `package foo + +import "encoding/json" +`, + want: false, + }, + { + name: "import.9", + path: "encoding/json", + in: `package foo + +import "encoding/json" + +var json = 42 +`, + want: false, + }, + { + name: "import.10", + path: "encoding/json", + in: `package foo + +import j "encoding/json" + +var _ j.Encoder +`, + want: true, + }, + { + name: "import.11", + path: "encoding/json", + in: `package foo + +import j "encoding/json" +`, + want: false, + }, + { + name: "import.12", + path: "encoding/json", + in: `package foo + +import j "encoding/json" + +var j = 42 +var json = 42 +`, + want: false, + }, + { + name: "import.13", + path: "io", + in: `package foo + +import _ "io" +`, + want: true, + }, + { + name: "import.14", + path: "io", + in: `package foo + +import . "io" +`, + want: true, + }, +} + +func TestUsesImport(t *testing.T) { + fset := token.NewFileSet() + for _, test := range usesImportTests { + f, err := parser.ParseFile(fset, "test.go", test.in, 0) + if err != nil { + t.Errorf("%s: %v", test.name, err) + continue + } + got := UsesImport(f, test.path) + if got != test.want { + t.Errorf("UsesImport(%s)=%v, want %v", test.name, got, test.want) + } + } +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go b/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go new file mode 100644 index 0000000..cf72ea9 --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go @@ -0,0 +1,477 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "reflect" + "sort" +) + +// An ApplyFunc is invoked by Apply for each node n, even if n is nil, +// before and/or after the node's children, using a Cursor describing +// the current node and providing operations on it. +// +// The return value of ApplyFunc controls the syntax tree traversal. +// See Apply for details. +type ApplyFunc func(*Cursor) bool + +// Apply traverses a syntax tree recursively, starting with root, +// and calling pre and post for each node as described below. +// Apply returns the syntax tree, possibly modified. +// +// If pre is not nil, it is called for each node before the node's +// children are traversed (pre-order). If pre returns false, no +// children are traversed, and post is not called for that node. +// +// If post is not nil, and a prior call of pre didn't return false, +// post is called for each node after its children are traversed +// (post-order). If post returns false, traversal is terminated and +// Apply returns immediately. +// +// Only fields that refer to AST nodes are considered children; +// i.e., token.Pos, Scopes, Objects, and fields of basic types +// (strings, etc.) are ignored. +// +// Children are traversed in the order in which they appear in the +// respective node's struct definition. A package's files are +// traversed in the filenames' alphabetical order. +// +func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) { + parent := &struct{ ast.Node }{root} + defer func() { + if r := recover(); r != nil && r != abort { + panic(r) + } + result = parent.Node + }() + a := &application{pre: pre, post: post} + a.apply(parent, "Node", nil, root) + return +} + +var abort = new(int) // singleton, to signal termination of Apply + +// A Cursor describes a node encountered during Apply. +// Information about the node and its parent is available +// from the Node, Parent, Name, and Index methods. +// +// If p is a variable of type and value of the current parent node +// c.Parent(), and f is the field identifier with name c.Name(), +// the following invariants hold: +// +// p.f == c.Node() if c.Index() < 0 +// p.f[c.Index()] == c.Node() if c.Index() >= 0 +// +// The methods Replace, Delete, InsertBefore, and InsertAfter +// can be used to change the AST without disrupting Apply. +type Cursor struct { + parent ast.Node + name string + iter *iterator // valid if non-nil + node ast.Node +} + +// Node returns the current Node. +func (c *Cursor) Node() ast.Node { return c.node } + +// Parent returns the parent of the current Node. +func (c *Cursor) Parent() ast.Node { return c.parent } + +// Name returns the name of the parent Node field that contains the current Node. +// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns +// the filename for the current Node. +func (c *Cursor) Name() string { return c.name } + +// Index reports the index >= 0 of the current Node in the slice of Nodes that +// contains it, or a value < 0 if the current Node is not part of a slice. +// The index of the current node changes if InsertBefore is called while +// processing the current node. +func (c *Cursor) Index() int { + if c.iter != nil { + return c.iter.index + } + return -1 +} + +// field returns the current node's parent field value. +func (c *Cursor) field() reflect.Value { + return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name) +} + +// Replace replaces the current Node with n. +// The replacement node is not walked by Apply. +func (c *Cursor) Replace(n ast.Node) { + if _, ok := c.node.(*ast.File); ok { + file, ok := n.(*ast.File) + if !ok { + panic("attempt to replace *ast.File with non-*ast.File") + } + c.parent.(*ast.Package).Files[c.name] = file + return + } + + v := c.field() + if i := c.Index(); i >= 0 { + v = v.Index(i) + } + v.Set(reflect.ValueOf(n)) +} + +// Delete deletes the current Node from its containing slice. +// If the current Node is not part of a slice, Delete panics. +// As a special case, if the current node is a package file, +// Delete removes it from the package's Files map. +func (c *Cursor) Delete() { + if _, ok := c.node.(*ast.File); ok { + delete(c.parent.(*ast.Package).Files, c.name) + return + } + + i := c.Index() + if i < 0 { + panic("Delete node not contained in slice") + } + v := c.field() + l := v.Len() + reflect.Copy(v.Slice(i, l), v.Slice(i+1, l)) + v.Index(l - 1).Set(reflect.Zero(v.Type().Elem())) + v.SetLen(l - 1) + c.iter.step-- +} + +// InsertAfter inserts n after the current Node in its containing slice. +// If the current Node is not part of a slice, InsertAfter panics. +// Apply does not walk n. +func (c *Cursor) InsertAfter(n ast.Node) { + i := c.Index() + if i < 0 { + panic("InsertAfter node not contained in slice") + } + v := c.field() + v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) + l := v.Len() + reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l)) + v.Index(i + 1).Set(reflect.ValueOf(n)) + c.iter.step++ +} + +// InsertBefore inserts n before the current Node in its containing slice. +// If the current Node is not part of a slice, InsertBefore panics. +// Apply will not walk n. +func (c *Cursor) InsertBefore(n ast.Node) { + i := c.Index() + if i < 0 { + panic("InsertBefore node not contained in slice") + } + v := c.field() + v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) + l := v.Len() + reflect.Copy(v.Slice(i+1, l), v.Slice(i, l)) + v.Index(i).Set(reflect.ValueOf(n)) + c.iter.index++ +} + +// application carries all the shared data so we can pass it around cheaply. +type application struct { + pre, post ApplyFunc + cursor Cursor + iter iterator +} + +func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) { + // convert typed nil into untyped nil + if v := reflect.ValueOf(n); v.Kind() == reflect.Ptr && v.IsNil() { + n = nil + } + + // avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead + saved := a.cursor + a.cursor.parent = parent + a.cursor.name = name + a.cursor.iter = iter + a.cursor.node = n + + if a.pre != nil && !a.pre(&a.cursor) { + a.cursor = saved + return + } + + // walk children + // (the order of the cases matches the order of the corresponding node types in go/ast) + switch n := n.(type) { + case nil: + // nothing to do + + // Comments and fields + case *ast.Comment: + // nothing to do + + case *ast.CommentGroup: + if n != nil { + a.applyList(n, "List") + } + + case *ast.Field: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Names") + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Tag", nil, n.Tag) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.FieldList: + a.applyList(n, "List") + + // Expressions + case *ast.BadExpr, *ast.Ident, *ast.BasicLit: + // nothing to do + + case *ast.Ellipsis: + a.apply(n, "Elt", nil, n.Elt) + + case *ast.FuncLit: + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Body", nil, n.Body) + + case *ast.CompositeLit: + a.apply(n, "Type", nil, n.Type) + a.applyList(n, "Elts") + + case *ast.ParenExpr: + a.apply(n, "X", nil, n.X) + + case *ast.SelectorExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Sel", nil, n.Sel) + + case *ast.IndexExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Index", nil, n.Index) + + case *ast.SliceExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Low", nil, n.Low) + a.apply(n, "High", nil, n.High) + a.apply(n, "Max", nil, n.Max) + + case *ast.TypeAssertExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Type", nil, n.Type) + + case *ast.CallExpr: + a.apply(n, "Fun", nil, n.Fun) + a.applyList(n, "Args") + + case *ast.StarExpr: + a.apply(n, "X", nil, n.X) + + case *ast.UnaryExpr: + a.apply(n, "X", nil, n.X) + + case *ast.BinaryExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Y", nil, n.Y) + + case *ast.KeyValueExpr: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + + // Types + case *ast.ArrayType: + a.apply(n, "Len", nil, n.Len) + a.apply(n, "Elt", nil, n.Elt) + + case *ast.StructType: + a.apply(n, "Fields", nil, n.Fields) + + case *ast.FuncType: + a.apply(n, "Params", nil, n.Params) + a.apply(n, "Results", nil, n.Results) + + case *ast.InterfaceType: + a.apply(n, "Methods", nil, n.Methods) + + case *ast.MapType: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + + case *ast.ChanType: + a.apply(n, "Value", nil, n.Value) + + // Statements + case *ast.BadStmt: + // nothing to do + + case *ast.DeclStmt: + a.apply(n, "Decl", nil, n.Decl) + + case *ast.EmptyStmt: + // nothing to do + + case *ast.LabeledStmt: + a.apply(n, "Label", nil, n.Label) + a.apply(n, "Stmt", nil, n.Stmt) + + case *ast.ExprStmt: + a.apply(n, "X", nil, n.X) + + case *ast.SendStmt: + a.apply(n, "Chan", nil, n.Chan) + a.apply(n, "Value", nil, n.Value) + + case *ast.IncDecStmt: + a.apply(n, "X", nil, n.X) + + case *ast.AssignStmt: + a.applyList(n, "Lhs") + a.applyList(n, "Rhs") + + case *ast.GoStmt: + a.apply(n, "Call", nil, n.Call) + + case *ast.DeferStmt: + a.apply(n, "Call", nil, n.Call) + + case *ast.ReturnStmt: + a.applyList(n, "Results") + + case *ast.BranchStmt: + a.apply(n, "Label", nil, n.Label) + + case *ast.BlockStmt: + a.applyList(n, "List") + + case *ast.IfStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Cond", nil, n.Cond) + a.apply(n, "Body", nil, n.Body) + a.apply(n, "Else", nil, n.Else) + + case *ast.CaseClause: + a.applyList(n, "List") + a.applyList(n, "Body") + + case *ast.SwitchStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Tag", nil, n.Tag) + a.apply(n, "Body", nil, n.Body) + + case *ast.TypeSwitchStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Assign", nil, n.Assign) + a.apply(n, "Body", nil, n.Body) + + case *ast.CommClause: + a.apply(n, "Comm", nil, n.Comm) + a.applyList(n, "Body") + + case *ast.SelectStmt: + a.apply(n, "Body", nil, n.Body) + + case *ast.ForStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Cond", nil, n.Cond) + a.apply(n, "Post", nil, n.Post) + a.apply(n, "Body", nil, n.Body) + + case *ast.RangeStmt: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + a.apply(n, "X", nil, n.X) + a.apply(n, "Body", nil, n.Body) + + // Declarations + case *ast.ImportSpec: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + a.apply(n, "Path", nil, n.Path) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.ValueSpec: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Names") + a.apply(n, "Type", nil, n.Type) + a.applyList(n, "Values") + a.apply(n, "Comment", nil, n.Comment) + + case *ast.TypeSpec: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.BadDecl: + // nothing to do + + case *ast.GenDecl: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Specs") + + case *ast.FuncDecl: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Recv", nil, n.Recv) + a.apply(n, "Name", nil, n.Name) + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Body", nil, n.Body) + + // Files and packages + case *ast.File: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + a.applyList(n, "Decls") + // Don't walk n.Comments; they have either been walked already if + // they are Doc comments, or they can be easily walked explicitly. + + case *ast.Package: + // collect and sort names for reproducible behavior + var names []string + for name := range n.Files { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + a.apply(n, name, nil, n.Files[name]) + } + + default: + panic(fmt.Sprintf("Apply: unexpected node type %T", n)) + } + + if a.post != nil && !a.post(&a.cursor) { + panic(abort) + } + + a.cursor = saved +} + +// An iterator controls iteration over a slice of nodes. +type iterator struct { + index, step int +} + +func (a *application) applyList(parent ast.Node, name string) { + // avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead + saved := a.iter + a.iter.index = 0 + for { + // must reload parent.name each time, since cursor modifications might change it + v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name) + if a.iter.index >= v.Len() { + break + } + + // element x may be nil in a bad AST - be cautious + var x ast.Node + if e := v.Index(a.iter.index); e.IsValid() { + x = e.Interface().(ast.Node) + } + + a.iter.step = 1 + a.apply(parent, name, &a.iter, x) + a.iter.index += a.iter.step + } + a.iter = saved +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/rewrite_test.go b/vendor/golang.org/x/tools/go/ast/astutil/rewrite_test.go new file mode 100644 index 0000000..1c86970 --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/rewrite_test.go @@ -0,0 +1,248 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil_test + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "testing" + + "golang.org/x/tools/go/ast/astutil" +) + +var rewriteTests = [...]struct { + name string + orig, want string + pre, post astutil.ApplyFunc +}{ + {name: "nop", orig: "package p\n", want: "package p\n"}, + + {name: "replace", + orig: `package p + +var x int +`, + want: `package p + +var t T +`, + post: func(c *astutil.Cursor) bool { + if _, ok := c.Node().(*ast.ValueSpec); ok { + c.Replace(valspec("t", "T")) + return false + } + return true + }, + }, + + {name: "set doc strings", + orig: `package p + +const z = 0 + +type T struct{} + +var x int +`, + want: `package p +// a foo is a foo +const z = 0 +// a foo is a foo +type T struct{} +// a foo is a foo +var x int +`, + post: func(c *astutil.Cursor) bool { + if _, ok := c.Parent().(*ast.GenDecl); ok && c.Name() == "Doc" && c.Node() == nil { + c.Replace(&ast.CommentGroup{List: []*ast.Comment{{Text: "// a foo is a foo"}}}) + } + return true + }, + }, + + {name: "insert names", + orig: `package p + +const a = 1 +`, + want: `package p + +const a, b, c = 1, 2, 3 +`, + pre: func(c *astutil.Cursor) bool { + if _, ok := c.Parent().(*ast.ValueSpec); ok { + switch c.Name() { + case "Names": + c.InsertAfter(ast.NewIdent("c")) + c.InsertAfter(ast.NewIdent("b")) + case "Values": + c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "3"}) + c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "2"}) + } + } + return true + }, + }, + + {name: "insert", + orig: `package p + +var ( + x int + y int +) +`, + want: `package p + +var before1 int +var before2 int + +var ( + x int + y int +) +var after2 int +var after1 int +`, + pre: func(c *astutil.Cursor) bool { + if _, ok := c.Node().(*ast.GenDecl); ok { + c.InsertBefore(vardecl("before1", "int")) + c.InsertAfter(vardecl("after1", "int")) + c.InsertAfter(vardecl("after2", "int")) + c.InsertBefore(vardecl("before2", "int")) + } + return true + }, + }, + + {name: "delete", + orig: `package p + +var x int +var y int +var z int +`, + want: `package p + +var y int +var z int +`, + pre: func(c *astutil.Cursor) bool { + n := c.Node() + if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { + c.Delete() + } + return true + }, + }, + + {name: "insertafter-delete", + orig: `package p + +var x int +var y int +var z int +`, + want: `package p + +var x1 int + +var y int +var z int +`, + pre: func(c *astutil.Cursor) bool { + n := c.Node() + if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { + c.InsertAfter(vardecl("x1", "int")) + c.Delete() + } + return true + }, + }, + + {name: "delete-insertafter", + orig: `package p + +var x int +var y int +var z int +`, + want: `package p + +var y int +var x1 int +var z int +`, + pre: func(c *astutil.Cursor) bool { + n := c.Node() + if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { + c.Delete() + // The cursor is now effectively atop the 'var y int' node. + c.InsertAfter(vardecl("x1", "int")) + } + return true + }, + }, +} + +func valspec(name, typ string) *ast.ValueSpec { + return &ast.ValueSpec{Names: []*ast.Ident{ast.NewIdent(name)}, + Type: ast.NewIdent(typ), + } +} + +func vardecl(name, typ string) *ast.GenDecl { + return &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{valspec(name, typ)}, + } +} + +func TestRewrite(t *testing.T) { + t.Run("*", func(t *testing.T) { + for _, test := range rewriteTests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + n := astutil.Apply(f, test.pre, test.post) + var buf bytes.Buffer + if err := format.Node(&buf, fset, n); err != nil { + t.Fatal(err) + } + got := buf.String() + if got != test.want { + t.Errorf("got:\n\n%s\nwant:\n\n%s\n", got, test.want) + } + }) + } + }) +} + +var sink ast.Node + +func BenchmarkRewrite(b *testing.B) { + for _, test := range rewriteTests { + b.Run(test.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) + if err != nil { + b.Fatal(err) + } + b.StartTimer() + sink = astutil.Apply(f, test.pre, test.post) + } + }) + } +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/util.go b/vendor/golang.org/x/tools/go/ast/astutil/util.go new file mode 100644 index 0000000..7630629 --- /dev/null +++ b/vendor/golang.org/x/tools/go/ast/astutil/util.go @@ -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 + } +} -- cgit v1.2.3