├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README.md.tpl ├── clone-generated.go ├── clone.go ├── contributing.md ├── decorations-node-generated.go ├── decorations-types-generated.go ├── decorations.go ├── decorations_test.go ├── decorator ├── decorator-fragment-generated.go ├── decorator-fragment.go ├── decorator-fragment_test.go ├── decorator-node-generated.go ├── decorator.go ├── decorator_pos_test.go ├── decorator_resolver_test.go ├── decorator_test.go ├── helpers.go ├── load.go ├── load_test.go ├── map.go ├── resolver │ ├── goast │ │ ├── resolver.go │ │ └── resolver_test.go │ ├── gobuild │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ └── util_test.go │ ├── gopackages │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ └── util_test.go │ ├── gotypes │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ └── util_test.go │ ├── guess │ │ ├── resolver.go │ │ └── resolver_test.go │ ├── resolver.go │ └── simple │ │ ├── resolver.go │ │ └── resolver_test.go ├── restorer-generated.go ├── restorer.go ├── restorer_apply_test.go ├── restorer_clone_test.go ├── restorer_data_test.go ├── restorer_func_test.go ├── restorer_resolver_test.go ├── restorer_std_test.go ├── restorer_test.go └── util_test.go ├── dst.go ├── dstutil ├── decorations-generated.go ├── decorations.go ├── rewrite.go ├── rewrite_test.go └── util.go ├── example_test.go ├── gendst ├── README.md ├── clone.go ├── data │ ├── data.go │ ├── positions.go │ └── positions_test.go ├── decorator.go ├── dst.go ├── fragger.go ├── gendst_test.go ├── main.go └── restorer.go ├── go.mod ├── go.sum ├── print.go ├── print_test.go ├── readme.go ├── resolve.go ├── scope.go ├── util_test.go └── walk.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .DS_Store 3 | .idea/ 4 | coverage.out 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.x 4 | notificaitons: 5 | email: 6 | recipients: dave@brophy.uk 7 | on_failure: always 8 | install: 9 | # - go get -u github.com/dave/courtney 10 | - go get -t -v ./... 11 | script: 12 | - go test ./... 13 | # - courtney -v -timeout 20m 14 | #after_success: 15 | # - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 David Brophy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | This package was forked from https://github.com/golang/go/tree/master/src/go/ast - original license: 24 | 25 | Copyright (c) 2009 The Go Authors. All rights reserved. 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions are 29 | met: 30 | 31 | * Redistributions of source code must retain the above copyright 32 | notice, this list of conditions and the following disclaimer. 33 | * Redistributions in binary form must reproduce the above 34 | copyright notice, this list of conditions and the following disclaimer 35 | in the documentation and/or other materials provided with the 36 | distribution. 37 | * Neither the name of Google Inc. nor the names of its 38 | contributors may be used to endorse or promote products derived from 39 | this software without specific prior written permission. 40 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 42 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 43 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 44 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 45 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 48 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 49 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 50 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 51 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md.tpl: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dave/dst.svg?branch=master)](https://travis-ci.org/dave/dst) 2 | [![Documentation](https://img.shields.io/badge/godoc-documentation-brightgreen.svg)](https://godoc.org/github.com/dave/dst/decorator) 3 | [![codecov](https://img.shields.io/badge/codecov-92%25-brightgreen.svg)](https://codecov.io/gh/dave/dst) 4 | ![stability-stable](https://img.shields.io/badge/stability-stable-brightgreen.svg) 5 | [![Sourcegraph](https://sourcegraph.com/github.com/dave/dst/-/badge.svg)](https://sourcegraph.com/github.com/dave/dst?badge) 6 | 7 | # Decorated Syntax Tree 8 | 9 | The `dst` package enables manipulation of a Go syntax tree with high fidelity. Decorations (e.g. 10 | comments and line spacing) remain attached to the correct nodes as the tree is modified. 11 | 12 | ## Where does `go/ast` break? 13 | 14 | The `go/ast` package wasn't created with source manipulation as an intended use-case. Comments are 15 | stored by their byte offset instead of attached to nodes, so re-arranging nodes breaks the output. 16 | See [this Go issue](https://github.com/golang/go/issues/20744) for more information. 17 | 18 | Consider this example where we want to reverse the order of the two statements. As you can see the 19 | comments don't remain attached to the correct nodes: 20 | 21 | {{ "ExampleAstBroken" | example }} 22 | 23 | Here's the same example using `dst`: 24 | 25 | {{ "ExampleDstFixed" | example }} 26 | 27 | ## Usage 28 | 29 | Parsing a source file to `dst` and printing the results after modification can be accomplished with 30 | several `Parse` and `Print` convenience functions in the [decorator](https://godoc.org/github.com/dave/dst/decorator) 31 | package. 32 | 33 | For more fine-grained control you can use [Decorator](https://godoc.org/github.com/dave/dst/decorator#Decorator) 34 | to convert from `ast` to `dst`, and [Restorer](https://godoc.org/github.com/dave/dst/decorator#Restorer) 35 | to convert back again. 36 | 37 | ### Comments 38 | 39 | Comments are added at decoration attachment points. [See here](https://github.com/dave/dst/blob/master/decorations-types-generated.go) 40 | for a full list of these points, along with demonstration code of where they are rendered in the 41 | output. 42 | 43 | The decoration attachment points have convenience functions `Append`, `Prepend`, `Replace`, `Clear` 44 | and `All` to accomplish common tasks. Use the full text of your comment including the `//` or `/**/` 45 | markers. When adding a line comment, a newline is automatically rendered. 46 | 47 | {{ "ExampleComment" | example }} 48 | 49 | ### Spacing 50 | 51 | The `Before` property marks the node as having a line space (new line or empty line) before the node. 52 | These spaces are rendered before any decorations attached to the `Start` decoration point. The `After` 53 | property is similar but rendered after the node (and after any `End` decorations). 54 | 55 | {{ "ExampleSpace" | example }} 56 | 57 | ### Decorations 58 | 59 | The common decoration properties (`Start`, `End`, `Before` and `After`) occur on all nodes, and can be 60 | accessed with the `Decorations()` method on the `Node` interface: 61 | 62 | {{ "ExampleDecorated" | example }} 63 | 64 | #### dstutil.Decorations 65 | 66 | While debugging, it is often useful to have a list of all decorations attached to a node. The 67 | [dstutil](https://github.com/dave/dst/tree/master/dstutil) package provides a helper function `Decorations` which 68 | returns a list of the attachment points and all decorations for any node: 69 | 70 | {{ "ExampleDecorationPoints" | example }} 71 | 72 | ### Newlines 73 | 74 | The `Before` and `After` properties cover the majority of cases, but occasionally a newline needs to 75 | be rendered inside a node. Simply add a `\n` decoration to accomplish this. 76 | 77 | ### Clone 78 | 79 | Re-using an existing node elsewhere in the tree will panic when the tree is restored to `ast`. Instead, 80 | use the `Clone` function to make a deep copy of the node before re-use: 81 | 82 | {{ "ExampleClone" | example }} 83 | 84 | ### Apply 85 | 86 | The [dstutil](https://github.com/dave/dst/tree/master/dstutil) package is a fork of `golang.org/x/tools/go/ast/astutil`, 87 | and provides the `Apply` function with similar semantics. 88 | 89 | ### Imports 90 | 91 | The decorator can automatically manage the `import` block, which is a non-trivial task. 92 | 93 | Use [NewDecoratorWithImports](https://godoc.org/github.com/dave/dst/decorator#NewDecoratorWithImports) 94 | and [NewRestorerWithImports](https://godoc.org/github.com/dave/dst/decorator#NewRestorerWithImports) 95 | to create an import aware decorator / restorer. 96 | 97 | During decoration, remote identifiers are normalised - `*ast.SelectorExpr` nodes that represent 98 | qualified identifiers are replaced with `*dst.Ident` nodes with the `Path` field set to the path of 99 | the imported package. 100 | 101 | When adding a qualified identifier node, there is no need to use `*dst.SelectorExpr` - just add a 102 | `*dst.Ident` and set `Path` to the imported package path. The restorer will wrap it in a 103 | `*ast.SelectorExpr` where appropriate when converting back to ast, and also update the import 104 | block. 105 | 106 | To enable import management, the decorator must be able to resolve the imported package for 107 | selector expressions and identifiers, and the restorer must be able to resolve the name of a 108 | package given it's path. Several implementations for these resolvers are provided, and the best 109 | method will depend on the environment. [See below](#resolvers) for more details. 110 | 111 | ### Load 112 | 113 | The [Load](https://godoc.org/github.com/dave/dst/decorator#Load) convenience function uses 114 | `go/packages` to load packages and decorate all loaded ast files, with import management enabled: 115 | 116 | {{ "ExampleImports" | example }} 117 | 118 | ### Mappings 119 | 120 | The decorator exposes `Dst.Nodes` and `Ast.Nodes` which map between `ast.Node` and `dst.Node`. This 121 | enables systems that refer to `ast` nodes (such as `go/types`) to be used: 122 | 123 | {{ "ExampleTypes" | example }} 124 | 125 | ## Resolvers 126 | 127 | There are two separate interfaces defined by the [resolver package](https://github.com/dave/dst/tree/master/decorator/resolver) 128 | which allow the decorator and restorer to automatically manage the imports block. 129 | 130 | The decorator uses a `DecoratorResolver` which resolves the package path of any `*ast.Ident`. This is 131 | complicated by dot-import syntax ([see below](#dot-imports)). 132 | 133 | The restorer uses a `RestorerResolver` which resolves the name of any package given the path. This 134 | is complicated by vendoring and Go modules. 135 | 136 | When `Resolver` is set on `Decorator` or `Restorer`, the `Path` property must be set to the local 137 | package path. 138 | 139 | Several implementations of both interfaces that are suitable for different environments are 140 | provided: 141 | 142 | ### DecoratorResolver 143 | 144 | #### gotypes 145 | 146 | The [gotypes](https://github.com/dave/dst/blob/master/decorator/resolver/gotypes/resolver.go) 147 | package provides a `DecoratorResolver` with full dot-import compatibility. However it requires full 148 | export data for all imported packages, so the `Uses` map from `go/types.Info` is required. There 149 | are several methods of generating `go/types.Info`. Using `golang.org/x/tools/go/packages.Load` is 150 | recommended for full Go modules compatibility. See the [decorator.Load](https://godoc.org/github.com/dave/dst/decorator#Load) 151 | convenience function to automate this. 152 | 153 | #### goast 154 | 155 | The [goast](https://github.com/dave/dst/blob/master/decorator/resolver/goast/resolver.go) package 156 | provides a simplified `DecoratorResolver` that only needs to scan a single ast file. This is unable 157 | to resolve identifiers from dot-imported packages, so will panic if a dot-import is encountered in 158 | the import block. It uses the provided `RestorerResolver` to resolve the names of all imported 159 | packages. If no `RestorerResolver` is provided, the [guess](#guess-and-simple) implementation is used. 160 | 161 | ### RestorerResolver 162 | 163 | #### gopackages 164 | 165 | The [gopackages](https://github.com/dave/dst/blob/master/decorator/resolver/gopackages/resolver.go) 166 | package provides a `RestorerResolver` with full compatibility with Go modules. It uses 167 | `golang.org/x/tools/go/packages` to load the package data. This may be very slow, and uses the `go` 168 | command line tool to query package data, so may not be compatible with some environments. 169 | 170 | #### gobuild 171 | 172 | The [gobuild](https://github.com/dave/dst/blob/master/decorator/resolver/gobuild/resolver.go) 173 | package provides an alternative `RestorerResolver` that uses the legacy `go/build` system to load 174 | the imported package data. This may be needed in some circumstances and provides better performance 175 | than `go/packages`. However, this is not Go modules aware. 176 | 177 | #### guess and simple 178 | 179 | The [guess](https://github.com/dave/dst/blob/master/decorator/resolver/guess/resolver.go) and 180 | [simple](https://github.com/dave/dst/blob/master/decorator/resolver/simple/resolver.go) packages 181 | provide simple `RestorerResolver` implementations that may be useful in certain circumstances, or 182 | where performance is critical. `simple` resolves paths only if they occur in a provided map. 183 | `guess` guesses the package name based on the last part of the path. 184 | 185 | ### Example 186 | 187 | Here's an example of supplying resolvers for the decorator and restorer: 188 | 189 | {{ "ExampleManualImports" | example }} 190 | 191 | ### Alias 192 | 193 | To control the alias of imports, use a `FileRestorer`: 194 | 195 | {{ "ExampleAlias" | example }} 196 | 197 | ### Details 198 | 199 | For more information on exactly how the imports block is managed, read through the [test 200 | cases](https://github.com/dave/dst/blob/master/decorator/restorer_resolver_test.go). 201 | 202 | ### Dot-imports 203 | 204 | Consider this file... 205 | 206 | ```go 207 | package main 208 | 209 | import ( 210 | . "a" 211 | ) 212 | 213 | func main() { 214 | B() 215 | C() 216 | } 217 | ``` 218 | 219 | `B` and `C` could be local identifiers from a different file in this package, 220 | or from the imported package `a`. If only one is from `a` and it is removed, we should remove the 221 | import when we restore to `ast`. Thus the resolver needs to be able to resolve the package using 222 | the full info from `go/types`. 223 | 224 | ## Status 225 | 226 | This package is well tested and used in many projects. The API should be considered stable going forward. 227 | 228 | ## Chat? 229 | 230 | Feel free to create an [issue](https://github.com/dave/dst/issues) or chat in the 231 | [#dst](https://gophers.slack.com/messages/CCVL24MTQ) Gophers Slack channel. 232 | 233 | ## Contributing 234 | 235 | For further developing or contributing to `dst`, check out [these notes](https://github.com/dave/dst/blob/master/contributing.md). 236 | 237 | ## Special thanks 238 | 239 | Thanks very much to [hawkinsw](https://github.com/hawkinsw) for taking on the task of adding generics compatibility to `dst`. -------------------------------------------------------------------------------- /clone.go: -------------------------------------------------------------------------------- 1 | package dst 2 | 3 | // CloneObject returns nil: After cloning a node, it should not be attached to the same object / scope. 4 | func CloneObject(o *Object) *Object { 5 | return nil 6 | } 7 | 8 | // CloneScope returns nil: After cloning a node, it should not be attached to the same object / scope. 9 | func CloneScope(s *Scope) *Scope { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Building / Developing / Contributing 2 | 3 | The `dst` package relies heavily on code generation. So heavily, in fact, that `README.md` is 4 | itself generated by a preprocessor! Here are a few tips/tricks to get started developing `dst`. 5 | 6 | ## Adding Features, Fixing Bugs 7 | 8 | Generally speaking, code handling node annotation is generated from the files in the `gendst` directory. 9 | In order to add/change decorations, edit the `gendst/data.go` file. Once you have made your edits, you can 10 | regenerate the library's source code based on those changes by 11 | 12 | 1. `go run github.com/dave/dst/gendst`: This command will build and run a program generated from the 13 | source code in the `gendst` folder that regenerates all the dst source fiels to reflect the changes 14 | that you just made in the `gendst/data.go` file. 15 | 16 | Whenever you make a change to the `gendst/data.go` file, you must perform the aforementioned step. 17 | 18 | If you add a Node to the `gendst/data.go` file, you will need to make corresponding additions to non-generated 19 | files as well. Those files include, but are not limited to,: 20 | 21 | - `dst.go` 22 | - `walk.go` 23 | - `dstutil/rewrite.go` 24 | 25 | Depending on the changes that you have made to the description of the decorated Nodes, you may have to change some, none, 26 | or all of those files listed above (or even others that are not listed!). 27 | 28 | ## Adding/Running Tests 29 | 30 | Any changes to the decorations of Nodes (or the addition of new Nodes) should be accompanied 31 | by an entry in `gendst/positions.go`. 32 | 33 | The format of the file is ... 34 | 35 | ``` 36 | // 37 | // %T %s\n", currentNodeType, currentTestIndex, text, n, point.Name) 125 | } 126 | allDecorations[currentNodeName][currentTestIndex][text] = true 127 | } 128 | } 129 | } 130 | return true 131 | }) 132 | 133 | for nodeType, decorationData := range allDecorations { 134 | combined := getAllDecorations(nodeType) 135 | for testIndex, decorations := range decorationData { 136 | // Start and End must occur in each test index: 137 | if !decorations["Start"] { 138 | t.Errorf("can't find \"Start\" decoration for %s (%d) in positions.go", nodeType, testIndex) 139 | } 140 | if !decorations["End"] && nodeType != "File" { 141 | // We don't have the End decoration on the File node type. 142 | t.Errorf("can't find \"End\" decoration for %s (%d) in positions.go", nodeType, testIndex) 143 | } 144 | for decorationName, value := range decorations { 145 | if value { 146 | combined[decorationName] = true 147 | } 148 | } 149 | } 150 | for decorationName, value := range combined { 151 | // All decorations must appear at least once for each node type: 152 | if !value { 153 | if decorationName == "Start" || decorationName == "End" { 154 | // Missing Start or End will already trigger an error above 155 | continue 156 | } 157 | if decorationName == "TypeParams" && nodeType == "FuncType" { 158 | // We can't have an example for FuncType.TypeParams because type parameters are only 159 | // valid when FuncType is embedded in a FuncDecl. When that happens, the decoration is 160 | // accessed by FuncDecl.TypeParams. 161 | continue 162 | } 163 | t.Errorf("can't find \"%s\" decoration for %s in positions.go", decorationName, nodeType) 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /decorator/decorator_resolver_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/dave/dst" 9 | "golang.org/x/tools/go/packages" 10 | ) 11 | 12 | func TestDecoratorResolver(t *testing.T) { 13 | type tc struct { 14 | expect string 15 | get func(*dst.File) *dst.Ident 16 | resolveLocalPath bool 17 | } 18 | tests := []struct { 19 | skip, solo bool 20 | name string 21 | src map[string]string 22 | cases []tc 23 | }{ 24 | { 25 | name: "simple", 26 | src: map[string]string{ 27 | "main/main.go": `package main 28 | 29 | import ( 30 | "root/a" 31 | . "root/b" 32 | ) 33 | 34 | func main(){ 35 | a.A() 36 | B() 37 | C() 38 | }`, 39 | "main/c.go": "package main \n\n func C(){}", 40 | "a/a.go": "package a \n\n func A(){}", 41 | "b/b.go": "package b \n\n func B(){}", 42 | "go.mod": "module root", 43 | }, 44 | cases: []tc{ 45 | { 46 | "root/a", 47 | func(f *dst.File) *dst.Ident { 48 | d := f.Decls[1] 49 | return d.(*dst.FuncDecl).Body.List[0].(*dst.ExprStmt).X.(*dst.CallExpr).Fun.(*dst.Ident) 50 | }, 51 | false, 52 | }, 53 | { 54 | "root/b", 55 | func(f *dst.File) *dst.Ident { 56 | return f.Decls[1].(*dst.FuncDecl).Body.List[1].(*dst.ExprStmt).X.(*dst.CallExpr).Fun.(*dst.Ident) 57 | }, 58 | false, 59 | }, 60 | { 61 | "", 62 | func(f *dst.File) *dst.Ident { 63 | return f.Decls[1].(*dst.FuncDecl).Body.List[2].(*dst.ExprStmt).X.(*dst.CallExpr).Fun.(*dst.Ident) 64 | }, 65 | false, 66 | }, 67 | { 68 | "root/main", 69 | func(f *dst.File) *dst.Ident { 70 | return f.Decls[1].(*dst.FuncDecl).Body.List[2].(*dst.ExprStmt).X.(*dst.CallExpr).Fun.(*dst.Ident) 71 | }, 72 | true, 73 | }, 74 | }, 75 | }, 76 | } 77 | var solo bool 78 | for _, test := range tests { 79 | if test.solo { 80 | solo = true 81 | break 82 | } 83 | } 84 | for _, test := range tests { 85 | t.Run(test.name, func(t *testing.T) { 86 | if solo && !test.solo { 87 | t.Skip() 88 | } 89 | if test.skip { 90 | t.Skip() 91 | } 92 | 93 | root, err := tempDir(test.src) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | pkgs, err := packages.Load( 99 | &packages.Config{ 100 | Mode: packages.LoadSyntax, 101 | Dir: filepath.Join(root, "main"), 102 | }, 103 | "root/main", 104 | ) 105 | _ = os.RemoveAll(root) // ignore error 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | if len(pkgs) != 1 { 110 | t.Fatalf("expected 1 package, found %d", len(pkgs)) 111 | } 112 | pkg := pkgs[0] 113 | 114 | for _, c := range test.cases { 115 | d := NewDecoratorFromPackage(pkg) 116 | d.ResolveLocalPath = c.resolveLocalPath 117 | 118 | var file *dst.File 119 | for _, sf := range pkg.Syntax { 120 | if _, name := filepath.Split(pkg.Fset.File(sf.Pos()).Name()); name == "main.go" { 121 | var err error 122 | file, err = d.DecorateFile(sf) 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | break 127 | } 128 | } 129 | 130 | id := c.get(file) 131 | if id.Path != c.expect { 132 | t.Errorf("expected %q, found %q", c.expect, id.Path) 133 | } 134 | } 135 | 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /decorator/helpers.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "go/ast" 5 | "go/format" 6 | "go/parser" 7 | "go/token" 8 | "io" 9 | "os" 10 | 11 | "github.com/dave/dst" 12 | ) 13 | 14 | // Parse uses parser.ParseFile to parse and decorate a Go source file. The src parameter should 15 | // be string, []byte, or io.Reader. 16 | func Parse(src interface{}) (*dst.File, error) { 17 | return NewDecorator(token.NewFileSet()).Parse(src) 18 | } 19 | 20 | // ParseFile uses parser.ParseFile to parse and decorate a Go source file. The ParseComments flag is 21 | // added to mode if it doesn't exist. 22 | func ParseFile(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (*dst.File, error) { 23 | return NewDecorator(fset).ParseFile(filename, src, mode) 24 | } 25 | 26 | // ParseDir uses parser.ParseDir to parse and decorate a directory containing Go source. The 27 | // ParseComments flag is added to mode if it doesn't exist. 28 | func ParseDir(fset *token.FileSet, dir string, filter func(os.FileInfo) bool, mode parser.Mode) (map[string]*dst.Package, error) { 29 | return NewDecorator(fset).ParseDir(dir, filter, mode) 30 | } 31 | 32 | // Decorate decorates an ast.Node and returns a dst.Node. 33 | func Decorate(fset *token.FileSet, n ast.Node) (dst.Node, error) { 34 | return NewDecorator(fset).DecorateNode(n) 35 | } 36 | 37 | // Decorate decorates a *ast.File and returns a *dst.File. 38 | func DecorateFile(fset *token.FileSet, f *ast.File) (*dst.File, error) { 39 | return NewDecorator(fset).DecorateFile(f) 40 | } 41 | 42 | // Print uses format.Node to print a *dst.File to stdout 43 | func Print(f *dst.File) error { 44 | return Fprint(os.Stdout, f) 45 | } 46 | 47 | // Fprint uses format.Node to print a *dst.File to a writer 48 | func Fprint(w io.Writer, f *dst.File) error { 49 | fset, af, err := RestoreFile(f) 50 | if err != nil { 51 | return err 52 | } 53 | return format.Node(w, fset, af) 54 | } 55 | 56 | // RestoreFile restores a *dst.File to a *token.FileSet and a *ast.File 57 | func RestoreFile(file *dst.File) (*token.FileSet, *ast.File, error) { 58 | r := NewRestorer() 59 | f, err := r.RestoreFile(file) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | return r.Fset, f, nil 64 | } 65 | -------------------------------------------------------------------------------- /decorator/load.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/dave/dst" 11 | "github.com/dave/dst/decorator/resolver" 12 | "github.com/dave/dst/decorator/resolver/gopackages" 13 | "golang.org/x/tools/go/packages" 14 | ) 15 | 16 | func Load(cfg *packages.Config, patterns ...string) ([]*Package, error) { 17 | 18 | if cfg == nil { 19 | cfg = &packages.Config{Mode: packages.LoadSyntax} 20 | } 21 | 22 | if cfg.Mode&packages.NeedSyntax == 0 { 23 | return nil, errors.New("config mode should include NeedSyntax") 24 | } 25 | 26 | pkgs, err := packages.Load(cfg, patterns...) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | dpkgs := map[*packages.Package]*Package{} 32 | 33 | var convert func(p *packages.Package) (*Package, error) 34 | convert = func(pkg *packages.Package) (*Package, error) { 35 | if dp, ok := dpkgs[pkg]; ok { 36 | return dp, nil 37 | } 38 | p := &Package{ 39 | Package: pkg, 40 | Imports: map[string]*Package{}, 41 | } 42 | dpkgs[pkg] = p 43 | if len(pkg.Syntax) > 0 { 44 | 45 | // Only decorate files in the GoFiles list. Syntax also has preprocessed cgo files which 46 | // break things. 47 | goFiles := make(map[string]bool, len(pkg.GoFiles)) 48 | for _, fpath := range pkg.GoFiles { 49 | goFiles[fpath] = true 50 | } 51 | 52 | p.Decorator = NewDecoratorFromPackage(pkg) 53 | for _, f := range pkg.Syntax { 54 | fpath := pkg.Fset.File(f.Pos()).Name() 55 | if !goFiles[fpath] { 56 | continue 57 | } 58 | file, err := p.Decorator.DecorateFile(f) 59 | if err != nil { 60 | return nil, err 61 | } 62 | p.Syntax = append(p.Syntax, file) 63 | } 64 | 65 | dir, _ := filepath.Split(pkg.Fset.File(pkg.Syntax[0].Pos()).Name()) 66 | p.Dir = dir 67 | 68 | for path, imp := range pkg.Imports { 69 | dimp, err := convert(imp) 70 | if err != nil { 71 | return nil, err 72 | } 73 | p.Imports[path] = dimp 74 | } 75 | } 76 | return p, nil 77 | } 78 | 79 | var out []*Package 80 | for _, pkg := range pkgs { 81 | p, err := convert(pkg) 82 | if err != nil { 83 | return nil, err 84 | } 85 | out = append(out, p) 86 | } 87 | 88 | return out, nil 89 | } 90 | 91 | type Package struct { 92 | *packages.Package 93 | Dir string 94 | Decorator *Decorator 95 | Imports map[string]*Package 96 | Syntax []*dst.File 97 | } 98 | 99 | func (p *Package) Save() error { 100 | return p.save(gopackages.New(p.Dir), ioutil.WriteFile) 101 | } 102 | 103 | func (p *Package) SaveWithResolver(resolver resolver.RestorerResolver) error { 104 | return p.save(resolver, ioutil.WriteFile) 105 | } 106 | 107 | func (p *Package) save(resolver resolver.RestorerResolver, writeFile func(filename string, data []byte, perm os.FileMode) error) error { 108 | r := NewRestorerWithImports(p.PkgPath, resolver) 109 | for _, file := range p.Syntax { 110 | buf := &bytes.Buffer{} 111 | if err := r.Fprint(buf, file); err != nil { 112 | return err 113 | } 114 | if err := writeFile(p.Decorator.Filenames[file], buf.Bytes(), 0666); err != nil { 115 | return err 116 | } 117 | } 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /decorator/load_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dave/dst" 7 | "github.com/dave/dst/decorator/resolver/simple" 8 | "golang.org/x/tools/go/packages" 9 | ) 10 | 11 | func TestLoad(t *testing.T) { 12 | code := map[string]string{ 13 | "a.go": `package a 14 | 15 | import "fmt" 16 | 17 | func a() { 18 | fmt.Println("a") 19 | } 20 | `, 21 | "go.mod": "module root\n\ngo 1.14", 22 | } 23 | expect := map[string]string{ 24 | "a.go": `package a 25 | 26 | import "fmt" 27 | 28 | func a() { 29 | fmt.Println("a") // a 30 | } 31 | `, 32 | "go.mod": "module root\n\ngo 1.14", 33 | } 34 | dir, err := tempDir(code) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | cfg := &packages.Config{ 39 | Mode: packages.LoadSyntax, 40 | Dir: dir, 41 | } 42 | pkgs, err := Load(cfg, "root") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | for _, pkg := range pkgs { 47 | for _, f := range pkg.Syntax { 48 | dst.Inspect(f, func(n dst.Node) bool { 49 | switch n.(type) { 50 | case *dst.CallExpr: 51 | n.Decorations().End.Append("// a") 52 | } 53 | return true 54 | }) 55 | } 56 | if err := pkg.Save(); err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | compareDir(t, dir, expect) 61 | } 62 | 63 | func TestPackage_SaveWithResolver(t *testing.T) { 64 | code := map[string]string{ 65 | "a.go": `package a 66 | 67 | import "fmt" 68 | 69 | func a() { 70 | fmt.Println("a") 71 | } 72 | `, 73 | "go.mod": "module root\n\ngo 1.14", 74 | } 75 | expect := map[string]string{ 76 | "a.go": `package a 77 | 78 | import "fmt" 79 | 80 | func a() { 81 | alternate_pkg_name.Println("a") 82 | } 83 | `, 84 | "go.mod": "module root\n\ngo 1.14", 85 | } 86 | dir, err := tempDir(code) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | cfg := &packages.Config{ 91 | Mode: packages.LoadSyntax, 92 | Dir: dir, 93 | } 94 | pkgs, err := Load(cfg, "root") 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | res := simple.New(map[string]string{"fmt": "alternate_pkg_name"}) 99 | for _, pkg := range pkgs { 100 | if err := pkg.SaveWithResolver(res); err != nil { 101 | t.Fatal(err) 102 | } 103 | } 104 | compareDir(t, dir, expect) 105 | } 106 | -------------------------------------------------------------------------------- /decorator/map.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "go/ast" 5 | 6 | "github.com/dave/dst" 7 | ) 8 | 9 | func newMap() Map { 10 | return Map{ 11 | Ast: AstMap{ 12 | Nodes: map[dst.Node]ast.Node{}, 13 | Scopes: map[*dst.Scope]*ast.Scope{}, 14 | Objects: map[*dst.Object]*ast.Object{}, 15 | }, 16 | Dst: DstMap{ 17 | Nodes: map[ast.Node]dst.Node{}, 18 | Scopes: map[*ast.Scope]*dst.Scope{}, 19 | Objects: map[*ast.Object]*dst.Object{}, 20 | }, 21 | } 22 | } 23 | 24 | // Map holds a record of the mapping between ast and dst nodes, objects and scopes. 25 | type Map struct { 26 | Ast AstMap 27 | Dst DstMap 28 | } 29 | 30 | // AstMap holds a record of the mapping from dst to ast nodes, objects and scopes. 31 | type AstMap struct { 32 | Nodes map[dst.Node]ast.Node // Mapping from dst to ast Nodes 33 | Objects map[*dst.Object]*ast.Object // Mapping from dst to ast Objects 34 | Scopes map[*dst.Scope]*ast.Scope // Mapping from dst to ast Scopes 35 | } 36 | 37 | // DstMap holds a record of the mapping from ast to dst nodes, objects and scopes. 38 | type DstMap struct { 39 | Nodes map[ast.Node]dst.Node // Mapping from ast to dst Nodes 40 | Objects map[*ast.Object]*dst.Object // Mapping from ast to dst Objects 41 | Scopes map[*ast.Scope]*dst.Scope // Mapping from ast to dst Scopes 42 | } 43 | -------------------------------------------------------------------------------- /decorator/resolver/goast/resolver.go: -------------------------------------------------------------------------------- 1 | package goast 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | "strconv" 8 | "sync" 9 | 10 | "github.com/dave/dst/decorator/resolver" 11 | "github.com/dave/dst/decorator/resolver/guess" 12 | ) 13 | 14 | func New() *DecoratorResolver { 15 | return &DecoratorResolver{} 16 | } 17 | 18 | func WithResolver(resolver resolver.RestorerResolver) *DecoratorResolver { 19 | return &DecoratorResolver{RestorerResolver: resolver} 20 | } 21 | 22 | // DecoratorResolver is a simple ident resolver that parses the imports block of the file and resolves 23 | // qualified identifiers using resolved package names. It is not possible to resolve identifiers in 24 | // dot-imported packages without the full export data of the imported package, so this resolver will 25 | // return an error if it encounters a dot-import. See gotypes.DecoratorResolver for a dot-imports 26 | // capable ident resolver. 27 | type DecoratorResolver struct { 28 | RestorerResolver resolver.RestorerResolver 29 | filesM sync.Mutex 30 | files map[*ast.File]map[string]string 31 | } 32 | 33 | func (r *DecoratorResolver) ResolveIdent(file *ast.File, parent ast.Node, parentField string, id *ast.Ident) (string, error) { 34 | 35 | if r.RestorerResolver == nil { 36 | r.RestorerResolver = guess.New() 37 | } 38 | 39 | imports, err := r.imports(file) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | se, ok := parent.(*ast.SelectorExpr) 45 | if !ok || parentField != "Sel" { 46 | return "", nil 47 | } 48 | 49 | xid, ok := se.X.(*ast.Ident) 50 | if !ok { 51 | return "", nil 52 | } 53 | 54 | if xid.Obj != nil { 55 | // Obj != nil -> not a qualified ident 56 | return "", nil 57 | } 58 | 59 | path, ok := imports[xid.Name] 60 | if !ok { 61 | return "", nil 62 | } 63 | 64 | return path, nil 65 | } 66 | 67 | func (r *DecoratorResolver) imports(file *ast.File) (map[string]string, error) { 68 | r.filesM.Lock() 69 | defer r.filesM.Unlock() 70 | 71 | if r.files == nil { 72 | r.files = map[*ast.File]map[string]string{} 73 | } 74 | 75 | imports, ok := r.files[file] 76 | if ok { 77 | return imports, nil 78 | } 79 | 80 | imports = map[string]string{} 81 | var done bool 82 | var outer error 83 | ast.Inspect(file, func(node ast.Node) bool { 84 | if done || outer != nil { 85 | return false 86 | } 87 | switch node := node.(type) { 88 | case *ast.FuncDecl: 89 | // Import decls must come before all other decls, so as soon as we find a func decl, we 90 | // can finish. 91 | done = true 92 | return false 93 | case *ast.GenDecl: 94 | if node.Tok != token.IMPORT { 95 | // Import decls must come before all other decls, so as soon as we find a non-import 96 | // gen decl, we can finish. 97 | done = true 98 | return false 99 | } 100 | return true 101 | case *ast.ImportSpec: 102 | path := mustUnquote(node.Path.Value) 103 | if path == "C" { 104 | return false 105 | } 106 | var name string 107 | if node.Name != nil { 108 | name = node.Name.Name 109 | } 110 | switch name { 111 | case ".": 112 | // We can't resolve "." imports, so throw an error 113 | outer = fmt.Errorf("goast.DecoratorResolver unsupported dot-import found for %s", path) 114 | return false 115 | case "_": 116 | // Don't need to worry about _ imports 117 | return false 118 | case "": 119 | var err error 120 | name, err = r.RestorerResolver.ResolvePackage(path) 121 | if err != nil { 122 | outer = err 123 | return false 124 | } 125 | } 126 | if p, ok := imports[name]; ok { 127 | outer = fmt.Errorf("goast.DecoratorResolver found multiple packages using name %s: %s and %s", name, p, path) 128 | return false 129 | } 130 | imports[name] = path 131 | } 132 | return true 133 | }) 134 | if outer != nil { 135 | return nil, outer 136 | } 137 | 138 | r.files[file] = imports 139 | 140 | return imports, nil 141 | } 142 | 143 | func mustUnquote(s string) string { 144 | out, err := strconv.Unquote(s) 145 | if err != nil { 146 | panic(err) 147 | } 148 | return out 149 | } 150 | -------------------------------------------------------------------------------- /decorator/resolver/goast/resolver_test.go: -------------------------------------------------------------------------------- 1 | package goast 2 | 3 | import ( 4 | "go/token" 5 | "testing" 6 | 7 | "github.com/dave/dst" 8 | "github.com/dave/dst/decorator" 9 | ) 10 | 11 | func TestGoAstDecoratorResolver(t *testing.T) { 12 | type tc struct{ id, expect string } 13 | tests := []struct { 14 | skip, solo bool 15 | name string 16 | src string 17 | cases []tc 18 | }{ 19 | { 20 | name: "simple", 21 | src: `package main 22 | 23 | import ( 24 | "root/a" 25 | ) 26 | 27 | func main(){ 28 | a.A() 29 | }`, 30 | cases: []tc{ 31 | {"A", "root/a"}, 32 | }, 33 | }, 34 | { 35 | name: "shadow", 36 | src: `package main 37 | 38 | import ( 39 | "root/a" 40 | ) 41 | 42 | func main(a T){ 43 | a.A() 44 | } 45 | 46 | type T struct{} 47 | func (T) A()`, 48 | cases: []tc{ 49 | {"A", ""}, 50 | }, 51 | }, 52 | } 53 | var solo bool 54 | for _, test := range tests { 55 | if test.solo { 56 | solo = true 57 | break 58 | } 59 | } 60 | for _, test := range tests { 61 | t.Run(test.name, func(t *testing.T) { 62 | if solo && !test.solo { 63 | t.Skip() 64 | } 65 | if test.skip { 66 | t.Skip() 67 | } 68 | 69 | d := decorator.NewDecoratorWithImports(token.NewFileSet(), "main", New()) 70 | 71 | f, err := d.Parse(test.src) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | nodes := map[string]string{} 77 | dst.Inspect(f, func(n dst.Node) bool { 78 | switch n := n.(type) { 79 | case *dst.Ident: 80 | nodes[n.Name] = n.Path 81 | } 82 | return true 83 | }) 84 | 85 | for _, c := range test.cases { 86 | found, ok := nodes[c.id] 87 | if !ok { 88 | t.Errorf("node %s not found", c.id) 89 | } 90 | if found != c.expect { 91 | t.Errorf("expect %q, found %q", c.expect, found) 92 | } 93 | } 94 | 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /decorator/resolver/gobuild/resolver.go: -------------------------------------------------------------------------------- 1 | package gobuild 2 | 3 | import ( 4 | "go/build" 5 | 6 | "github.com/dave/dst/decorator/resolver" 7 | ) 8 | 9 | func New(dir string) *RestorerResolver { 10 | return &RestorerResolver{Dir: dir} 11 | } 12 | 13 | func WithContext(dir string, context *build.Context) *RestorerResolver { 14 | return &RestorerResolver{Dir: dir, Context: context} 15 | } 16 | 17 | func WithHints(dir string, hints map[string]string) *RestorerResolver { 18 | return &RestorerResolver{Dir: dir, Hints: hints} 19 | } 20 | 21 | type RestorerResolver struct { 22 | // FindPackage is called during Load to create the build.Package for a given import path from a 23 | // given directory. If FindPackage is nil, (*build.Context).Import is used. A client may use 24 | // this hook to adapt to a proprietary build system that does not follow the "go build" layout 25 | // conventions, for example. It must be safe to call concurrently from multiple goroutines. 26 | // 27 | // It should be noted that Manager only uses the Name from the returned *build.Package, so all 28 | // other fields can be left empty (as in SimpleFinder). 29 | FindPackage func(ctxt *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error) 30 | Context *build.Context 31 | Dir string 32 | 33 | // Hints (package path -> name) is first checked before asking the build package 34 | Hints map[string]string 35 | } 36 | 37 | func (r *RestorerResolver) ResolvePackage(importPath string) (string, error) { 38 | 39 | if name, ok := r.Hints[importPath]; ok { 40 | return name, nil 41 | } 42 | 43 | fp := r.FindPackage 44 | if fp == nil { 45 | fp = (*build.Context).Import 46 | } 47 | 48 | bc := r.Context 49 | if bc == nil { 50 | bc = &build.Default 51 | } 52 | 53 | p, err := fp(bc, importPath, r.Dir, 0) 54 | if err != nil { 55 | return "", err 56 | } 57 | 58 | if p == nil { 59 | return "", resolver.ErrPackageNotFound 60 | } 61 | 62 | return p.Name, nil 63 | } 64 | -------------------------------------------------------------------------------- /decorator/resolver/gobuild/resolver_test.go: -------------------------------------------------------------------------------- 1 | package gobuild_test 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/dave/dst/decorator/resolver" 8 | "github.com/dave/dst/decorator/resolver/gobuild" 9 | ) 10 | 11 | func TestRestorerResolver(t *testing.T) { 12 | type tc struct{ importPath, fromDir, expectName string } 13 | tests := []struct { 14 | skip, solo bool 15 | name string 16 | resolve func() (end func(), root string, r *gobuild.RestorerResolver) 17 | cases []tc 18 | }{ 19 | { 20 | name: "gobuild.Resolver", 21 | resolve: func() (end func(), root string, r *gobuild.RestorerResolver) { 22 | src := map[string]string{ 23 | "main1/vendor/a/a.go": "package a1 \n\n func A(){}", 24 | "main1/main1.go": "package main \n\n func main(){}", 25 | "main2/main2.go": "package main \n\n func main(){}", 26 | "a/a.go": "package a2 \n\n func A(){}", 27 | } 28 | bc, err := buildContext(src) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | r = &gobuild.RestorerResolver{Context: bc} 33 | root = "/gopath/src" 34 | return 35 | }, 36 | cases: []tc{ 37 | {"a", "/main1", "a1"}, 38 | {"a", "/main2", "a2"}, 39 | }, 40 | }, 41 | } 42 | var solo bool 43 | for _, test := range tests { 44 | if test.solo { 45 | solo = true 46 | break 47 | } 48 | } 49 | for _, test := range tests { 50 | t.Run(test.name, func(t *testing.T) { 51 | if solo && !test.solo { 52 | t.Skip() 53 | } 54 | if test.skip { 55 | t.Skip() 56 | } 57 | for _, c := range test.cases { 58 | end, root, r := test.resolve() 59 | fromDir := filepath.Join(root, c.fromDir) 60 | r.Dir = fromDir 61 | name, err := r.ResolvePackage(c.importPath) 62 | if end != nil { 63 | end() // delete temp dir if created 64 | } 65 | if err == resolver.ErrPackageNotFound { 66 | name = "" 67 | } else if err != nil { 68 | t.Errorf("error resolving path %s from dir %s: %v", c.importPath, fromDir, err) 69 | } 70 | if name != c.expectName { 71 | t.Errorf("package %s, dir %s - expected %s, got %s", c.importPath, c.fromDir, c.expectName, name) 72 | } 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /decorator/resolver/gobuild/util_test.go: -------------------------------------------------------------------------------- 1 | package gobuild_test 2 | 3 | import ( 4 | "bytes" 5 | "go/build" 6 | "go/format" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "gopkg.in/src-d/go-billy.v4/memfs" 14 | ) 15 | 16 | func buildContext(m map[string]string) (*build.Context, error) { 17 | 18 | goroot := "/goroot/" 19 | gopath := "/gopath/" 20 | dir := filepath.Join(gopath, "src") 21 | 22 | fs := memfs.New() 23 | if err := fs.MkdirAll(dir, 0777); err != nil { 24 | return nil, err 25 | } 26 | 27 | for fpathrel, src := range m { 28 | if strings.HasSuffix(fpathrel, "/") { 29 | // just a dir 30 | if err := fs.MkdirAll(filepath.Join(dir, fpathrel), 0777); err != nil { 31 | return nil, err 32 | } 33 | } else { 34 | fpath := filepath.Join(dir, fpathrel) 35 | fdir, _ := filepath.Split(fpath) 36 | if err := fs.MkdirAll(fdir, 0777); err != nil { 37 | return nil, err 38 | } 39 | 40 | var formatted []byte 41 | if strings.HasSuffix(fpath, ".go") { 42 | var err error 43 | formatted, err = format.Source([]byte(src)) 44 | if err != nil { 45 | return nil, err 46 | } 47 | } else { 48 | formatted = []byte(src) 49 | } 50 | 51 | f, err := fs.Create(fpath) 52 | if err != nil { 53 | return nil, err 54 | } 55 | if _, err := io.Copy(f, bytes.NewReader(formatted)); err != nil { 56 | _ = f.Close() 57 | return nil, err 58 | } 59 | if err := f.Close(); err != nil { 60 | return nil, err 61 | } 62 | } 63 | } 64 | 65 | // This is from build.hasSubdir - which reports if dir is within root by performing 66 | // lexical analysis only. 67 | hasSubDir := func(root, dir string) (rel string, ok bool) { 68 | const sep = string(filepath.Separator) 69 | root = filepath.Clean(root) 70 | if !strings.HasSuffix(root, sep) { 71 | root += sep 72 | } 73 | dir = filepath.Clean(dir) 74 | if !strings.HasPrefix(dir, root) { 75 | return "", false 76 | } 77 | return filepath.ToSlash(dir[len(root):]), true 78 | } 79 | 80 | convertGoroot := func(fpath string) (string, error) { 81 | rel, err := filepath.Rel(goroot, fpath) 82 | if err != nil { 83 | return "", err 84 | } 85 | return filepath.Join(build.Default.GOROOT, rel), nil 86 | } 87 | 88 | bc := &build.Context{ 89 | GOARCH: build.Default.GOARCH, 90 | GOOS: build.Default.GOOS, 91 | GOROOT: goroot, 92 | GOPATH: gopath, 93 | CgoEnabled: build.Default.CgoEnabled, 94 | UseAllFiles: build.Default.UseAllFiles, 95 | Compiler: build.Default.Compiler, 96 | BuildTags: build.Default.BuildTags, 97 | ReleaseTags: build.Default.ReleaseTags, 98 | InstallSuffix: build.Default.InstallSuffix, 99 | 100 | // By default, Import uses the operating system's file system calls 101 | // to read directories and files. To read from other sources, 102 | // callers can set the following functions. They all have default 103 | // behaviors that use the local file system, so clients need only set 104 | // the functions whose behaviors they wish to change. 105 | 106 | // JoinPath joins the sequence of path fragments into a single path. 107 | // If JoinPath is nil, Import uses filepath.Join. 108 | JoinPath: filepath.Join, 109 | 110 | // SplitPathList splits the path list into a slice of individual paths. 111 | // If SplitPathList is nil, Import uses filepath.SplitList. 112 | SplitPathList: filepath.SplitList, 113 | 114 | // IsAbsPath reports whether path is an absolute path. 115 | // If IsAbsPath is nil, Import uses filepath.IsAbs. 116 | IsAbsPath: filepath.IsAbs, 117 | 118 | // IsDir reports whether the path names a directory. 119 | // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. 120 | IsDir: func(name string) bool { 121 | if _, ok := hasSubDir(goroot, name); ok { 122 | converted, _ := convertGoroot(name) 123 | fi, err := os.Stat(converted) 124 | return err == nil && fi.IsDir() 125 | } 126 | info, err := fs.Lstat(name) 127 | if err != nil { 128 | return false 129 | } 130 | return info.IsDir() 131 | }, 132 | 133 | // HasSubdir reports whether dir is lexically a subdirectory of 134 | // root, perhaps multiple levels below. It does not try to check 135 | // whether dir exists. 136 | // If so, HasSubdir sets rel to a slash-separated path that 137 | // can be joined to root to produce a path equivalent to dir. 138 | // If HasSubdir is nil, Import uses an implementation built on 139 | // filepath.EvalSymlinks. 140 | HasSubdir: hasSubDir, 141 | 142 | // ReadDir returns a slice of os.FileInfo, sorted by Name, 143 | // describing the content of the named directory. 144 | // If ReadDir is nil, Import uses ioutil.ReadDir. 145 | ReadDir: func(name string) (fi []os.FileInfo, err error) { 146 | if _, ok := hasSubDir(goroot, name); ok { 147 | converted, err := convertGoroot(name) 148 | if err != nil { 149 | return nil, err 150 | } 151 | return ioutil.ReadDir(converted) 152 | } 153 | return fs.ReadDir(name) 154 | }, 155 | 156 | // OpenFile opens a file (not a directory) for reading. 157 | // If OpenFile is nil, Import uses os.Open. 158 | OpenFile: func(path string) (io.ReadCloser, error) { 159 | if _, ok := hasSubDir(goroot, path); ok { 160 | converted, err := convertGoroot(path) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return os.Open(converted) 165 | } 166 | return fs.Open(path) 167 | }, 168 | } 169 | return bc, nil 170 | } 171 | -------------------------------------------------------------------------------- /decorator/resolver/gopackages/resolver.go: -------------------------------------------------------------------------------- 1 | package gopackages 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/dst/decorator/resolver" 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | func New(dir string) *RestorerResolver { 11 | return &RestorerResolver{Dir: dir} 12 | } 13 | 14 | func WithConfig(dir string, config packages.Config) *RestorerResolver { 15 | return &RestorerResolver{Config: config, Dir: dir} 16 | } 17 | 18 | func WithHints(dir string, hints map[string]string) *RestorerResolver { 19 | return &RestorerResolver{Dir: dir, Hints: hints} 20 | } 21 | 22 | type RestorerResolver struct { 23 | Dir string 24 | Config packages.Config 25 | 26 | // Hints (package path -> name) is first checked before asking the packages package 27 | Hints map[string]string 28 | } 29 | 30 | func (r *RestorerResolver) ResolvePackage(path string) (string, error) { 31 | 32 | if name, ok := r.Hints[path]; ok { 33 | return name, nil 34 | } 35 | 36 | if r.Dir != "" { 37 | r.Config.Dir = r.Dir 38 | } 39 | r.Config.Mode = packages.LoadTypes 40 | r.Config.Tests = false 41 | 42 | pkgs, err := packages.Load(&r.Config, "pattern="+path) 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | if len(pkgs) > 1 { 48 | return "", fmt.Errorf("%d packages found for %s, %s", len(pkgs), path, r.Config.Dir) 49 | } 50 | if len(pkgs) == 0 { 51 | return "", resolver.ErrPackageNotFound 52 | } 53 | 54 | p := pkgs[0] 55 | 56 | if len(p.Errors) > 0 { 57 | return "", p.Errors[0] 58 | } 59 | 60 | return p.Name, nil 61 | } 62 | -------------------------------------------------------------------------------- /decorator/resolver/gopackages/resolver_test.go: -------------------------------------------------------------------------------- 1 | package gopackages_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/dave/dst/decorator/resolver" 9 | "github.com/dave/dst/decorator/resolver/gopackages" 10 | ) 11 | 12 | func TestRestorerResolver(t *testing.T) { 13 | type tc struct{ importPath, fromDir, expectName string } 14 | tests := []struct { 15 | skip, solo bool 16 | name string 17 | resolve func() (end func(), root string, r *gopackages.RestorerResolver) 18 | cases []tc 19 | }{ 20 | { 21 | name: "gopackages.Resolver", 22 | resolve: func() (end func(), root string, r *gopackages.RestorerResolver) { 23 | src := map[string]string{ 24 | "main/main.go": "package main \n\n func main(){}", 25 | "foo/foo.go": "package foo \n\n func A(){}", 26 | "go.mod": "module root", 27 | } 28 | var err error 29 | root, err = tempDir(src) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | end = func() { os.RemoveAll(root) } 34 | r = &gopackages.RestorerResolver{} 35 | return 36 | }, 37 | cases: []tc{ 38 | {"root/foo", "/main", "foo"}, 39 | }, 40 | }, 41 | } 42 | var solo bool 43 | for _, test := range tests { 44 | if test.solo { 45 | solo = true 46 | break 47 | } 48 | } 49 | for _, test := range tests { 50 | t.Run(test.name, func(t *testing.T) { 51 | if solo && !test.solo { 52 | t.Skip() 53 | } 54 | if test.skip { 55 | t.Skip() 56 | } 57 | for _, c := range test.cases { 58 | end, root, r := test.resolve() 59 | fromDir := filepath.Join(root, c.fromDir) 60 | r.Dir = fromDir 61 | name, err := r.ResolvePackage(c.importPath) 62 | if end != nil { 63 | end() // delete temp dir if created 64 | } 65 | if err == resolver.ErrPackageNotFound { 66 | name = "" 67 | } else if err != nil { 68 | t.Errorf("error resolving path %s from dir %s: %v", c.importPath, fromDir, err) 69 | } 70 | if name != c.expectName { 71 | t.Errorf("package %s, dir %s - expected %s, got %s", c.importPath, c.fromDir, c.expectName, name) 72 | } 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /decorator/resolver/gopackages/util_test.go: -------------------------------------------------------------------------------- 1 | package gopackages_test 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func tempDir(m map[string]string) (dir string, err error) { 13 | if dir, err = ioutil.TempDir("", ""); err != nil { 14 | return 15 | } 16 | for fpathrel, src := range m { 17 | if strings.HasSuffix(fpathrel, "/") { 18 | // just a dir 19 | if err = os.MkdirAll(filepath.Join(dir, fpathrel), 0777); err != nil { 20 | return 21 | } 22 | } else { 23 | fpath := filepath.Join(dir, fpathrel) 24 | fdir, _ := filepath.Split(fpath) 25 | if err = os.MkdirAll(fdir, 0777); err != nil { 26 | return 27 | } 28 | 29 | var formatted []byte 30 | if strings.HasSuffix(fpath, ".go") { 31 | formatted, err = format.Source([]byte(src)) 32 | if err != nil { 33 | err = fmt.Errorf("formatting %s: %v", fpathrel, err) 34 | return 35 | } 36 | } else { 37 | formatted = []byte(src) 38 | } 39 | 40 | if err = ioutil.WriteFile(fpath, formatted, 0666); err != nil { 41 | return 42 | } 43 | } 44 | } 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /decorator/resolver/gotypes/resolver.go: -------------------------------------------------------------------------------- 1 | package gotypes 2 | 3 | import ( 4 | "errors" 5 | "go/ast" 6 | "go/types" 7 | ) 8 | 9 | func New(uses map[*ast.Ident]types.Object) *DecoratorResolver { 10 | return &DecoratorResolver{Uses: uses} 11 | } 12 | 13 | type DecoratorResolver struct { 14 | Uses map[*ast.Ident]types.Object // Types info - must include Uses 15 | } 16 | 17 | func (r *DecoratorResolver) ResolveIdent(file *ast.File, parent ast.Node, parentField string, id *ast.Ident) (string, error) { 18 | 19 | if r.Uses == nil { 20 | return "", errors.New("gotypes.DecoratorResolver needs Uses in types info") 21 | } 22 | 23 | if se, ok := parent.(*ast.SelectorExpr); ok && parentField == "Sel" { 24 | 25 | // if the parent is a SelectorExpr and this Ident is in the Sel field, only resolve the path 26 | // if X is a package identifier 27 | 28 | xid, ok := se.X.(*ast.Ident) 29 | if !ok { 30 | // x is not an ident -> not a qualified identifier 31 | return "", nil 32 | } 33 | obj, ok := r.Uses[xid] 34 | if !ok { 35 | // not found in uses -> not a qualified identifier 36 | return "", nil 37 | } 38 | pn, ok := obj.(*types.PkgName) 39 | if !ok { 40 | // not a pkgname -> not a remote identifier 41 | return "", nil 42 | } 43 | return pn.Imported().Path(), nil 44 | } 45 | 46 | obj, ok := r.Uses[id] 47 | if !ok { 48 | // not found in uses -> not a remote identifier 49 | return "", nil 50 | } 51 | 52 | if v, ok := obj.(*types.Var); ok && v.IsField() { 53 | // field ident (e.g. name of a field in a composite literal) -> doesn't need qualified ident 54 | return "", nil 55 | } 56 | 57 | pkg := obj.Pkg() 58 | if pkg == nil { 59 | // pre-defined idents in the universe scope - e.g. "byte" 60 | return "", nil 61 | } 62 | 63 | return pkg.Path(), nil 64 | } 65 | -------------------------------------------------------------------------------- /decorator/resolver/gotypes/resolver_test.go: -------------------------------------------------------------------------------- 1 | package gotypes_test 2 | 3 | import ( 4 | "go/ast" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/dave/dst/decorator/resolver/gotypes" 10 | "golang.org/x/tools/go/packages" 11 | ) 12 | 13 | func TestDecoratorResolver(t *testing.T) { 14 | type tc struct{ id, expect string } 15 | tests := []struct { 16 | skip, solo bool 17 | name string 18 | src map[string]string 19 | cases []tc 20 | }{ 21 | { 22 | name: "simple", 23 | src: map[string]string{ 24 | "main/main.go": `package main 25 | 26 | import ( 27 | "root/a" 28 | ) 29 | 30 | func main(){ 31 | a.A() 32 | }`, 33 | "a/a.go": "package a \n\n func A(){}", 34 | "go.mod": "module root", 35 | }, 36 | cases: []tc{ 37 | {"A", "root/a"}, 38 | }, 39 | }, 40 | { 41 | name: "non-qualified-ident", 42 | src: map[string]string{ 43 | "main/main.go": `package main 44 | 45 | import ( 46 | "root/a" 47 | ) 48 | 49 | func main(){ 50 | t.A() 51 | } 52 | 53 | var t a.T`, 54 | "a/a.go": "package a \n\n type T struct{} \n\n func (T)A(){}", 55 | "go.mod": "module root", 56 | }, 57 | cases: []tc{ 58 | {"A", ""}, 59 | }, 60 | }, 61 | { 62 | name: "field", 63 | src: map[string]string{ 64 | "main/main.go": `package main 65 | 66 | import ( 67 | "root/a" 68 | ) 69 | 70 | func main(){ 71 | t := a.T{ 72 | B: 0, 73 | } 74 | }`, 75 | "a/a.go": "package a \n\n type T struct{B int}", 76 | "go.mod": "module root", 77 | }, 78 | cases: []tc{ 79 | {"B", ""}, 80 | }, 81 | }, 82 | { 83 | name: "more", 84 | src: map[string]string{ 85 | "main/main.go": `package main 86 | 87 | import ( 88 | "root/a" 89 | . "root/b" 90 | ) 91 | 92 | func main(){ 93 | a.A() 94 | B() 95 | C() 96 | }`, 97 | "main/c.go": "package main \n\n func C(){}", 98 | "a/a.go": "package a \n\n func A(){}", 99 | "b/b.go": "package b \n\n func B(){}", 100 | "go.mod": "module root", 101 | }, 102 | cases: []tc{ 103 | {"A", "root/a"}, 104 | {"B", "root/b"}, 105 | {"C", "root/main"}, 106 | }, 107 | }, 108 | } 109 | var solo bool 110 | for _, test := range tests { 111 | if test.solo { 112 | solo = true 113 | break 114 | } 115 | } 116 | for _, test := range tests { 117 | t.Run(test.name, func(t *testing.T) { 118 | if solo && !test.solo { 119 | t.Skip() 120 | } 121 | if test.skip { 122 | t.Skip() 123 | } 124 | 125 | root, err := tempDir(test.src) 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | 130 | pkgs, err := packages.Load( 131 | &packages.Config{ 132 | Mode: packages.LoadSyntax, 133 | Dir: filepath.Join(root, "main"), 134 | }, 135 | "root/main", 136 | ) 137 | os.RemoveAll(root) 138 | if err != nil { 139 | t.Fatal(err) 140 | } 141 | if len(pkgs) != 1 { 142 | t.Fatalf("expected 1 package, found %d", len(pkgs)) 143 | } 144 | pkg := pkgs[0] 145 | 146 | res := gotypes.New(pkg.TypesInfo.Uses) 147 | 148 | parents := map[string]ast.Node{} 149 | parentFields := map[string]string{} 150 | nodes := map[string]*ast.Ident{} 151 | for _, f := range pkg.Syntax { 152 | _, fname := filepath.Split(pkg.Fset.File(f.Pos()).Name()) 153 | if fname != "main.go" { 154 | continue 155 | } 156 | ast.Inspect(f, func(n ast.Node) bool { 157 | switch n := n.(type) { 158 | case *ast.SelectorExpr: 159 | nodes[n.Sel.Name] = n.Sel 160 | parents[n.Sel.Name] = n 161 | parentFields[n.Sel.Name] = "Sel" 162 | case *ast.Ident: 163 | if _, ok := nodes[n.Name]; !ok { 164 | nodes[n.Name] = n 165 | parents[n.Name] = nil 166 | parentFields[n.Name] = "" 167 | } 168 | } 169 | return true 170 | }) 171 | } 172 | 173 | for _, c := range test.cases { 174 | //ast.Print(pkg.Fset, parents[c.id]) 175 | //ast.Print(pkg.Fset, nodes[c.id]) 176 | path, err := res.ResolveIdent(nil, parents[c.id], parentFields[c.id], nodes[c.id]) 177 | if err != nil { 178 | t.Error(err) 179 | } 180 | if path != c.expect { 181 | t.Errorf("expect %q, found %q", c.expect, path) 182 | } 183 | } 184 | 185 | }) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /decorator/resolver/gotypes/util_test.go: -------------------------------------------------------------------------------- 1 | package gotypes_test 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func tempDir(m map[string]string) (dir string, err error) { 13 | if dir, err = ioutil.TempDir("", ""); err != nil { 14 | return 15 | } 16 | for fpathrel, src := range m { 17 | if strings.HasSuffix(fpathrel, "/") { 18 | // just a dir 19 | if err = os.MkdirAll(filepath.Join(dir, fpathrel), 0777); err != nil { 20 | return 21 | } 22 | } else { 23 | fpath := filepath.Join(dir, fpathrel) 24 | fdir, _ := filepath.Split(fpath) 25 | if err = os.MkdirAll(fdir, 0777); err != nil { 26 | return 27 | } 28 | 29 | var formatted []byte 30 | if strings.HasSuffix(fpath, ".go") { 31 | formatted, err = format.Source([]byte(src)) 32 | if err != nil { 33 | err = fmt.Errorf("formatting %s: %v", fpathrel, err) 34 | return 35 | } 36 | } else { 37 | formatted = []byte(src) 38 | } 39 | 40 | if err = ioutil.WriteFile(fpath, formatted, 0666); err != nil { 41 | return 42 | } 43 | } 44 | } 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /decorator/resolver/guess/resolver.go: -------------------------------------------------------------------------------- 1 | package guess 2 | 3 | import "strings" 4 | 5 | func New() RestorerResolver { 6 | return RestorerResolver{} 7 | } 8 | 9 | func WithMap(m map[string]string) RestorerResolver { 10 | return RestorerResolver(m) 11 | } 12 | 13 | // RestorerResolver is a map of package path -> package name. Names are resolved from this map, and 14 | // if a name doesn't exist in the map, the package name is guessed from the last part of the path 15 | // (after the last slash). 16 | type RestorerResolver map[string]string 17 | 18 | func (r RestorerResolver) ResolvePackage(importPath string) (string, error) { 19 | if n, ok := r[importPath]; ok { 20 | return n, nil 21 | } 22 | if !strings.Contains(importPath, "/") { 23 | return importPath, nil 24 | } 25 | return importPath[strings.LastIndex(importPath, "/")+1:], nil 26 | } 27 | -------------------------------------------------------------------------------- /decorator/resolver/guess/resolver_test.go: -------------------------------------------------------------------------------- 1 | package guess_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dave/dst/decorator/resolver" 7 | "github.com/dave/dst/decorator/resolver/guess" 8 | ) 9 | 10 | func TestRestorerResolver(t *testing.T) { 11 | type tc struct{ importPath, expectName string } 12 | tests := []struct { 13 | skip, solo bool 14 | name string 15 | resolve func() (end func(), r resolver.RestorerResolver) 16 | cases []tc 17 | }{ 18 | { 19 | name: "guess.RestorerResolver", 20 | resolve: func() (end func(), r resolver.RestorerResolver) { 21 | r = guess.RestorerResolver{ 22 | "a/b/c": "d", 23 | } 24 | return 25 | }, 26 | cases: []tc{ 27 | {"a/b/c", "d"}, 28 | {"d/e/f", "f"}, 29 | }, 30 | }, 31 | } 32 | var solo bool 33 | for _, test := range tests { 34 | if test.solo { 35 | solo = true 36 | break 37 | } 38 | } 39 | for _, test := range tests { 40 | t.Run(test.name, func(t *testing.T) { 41 | if solo && !test.solo { 42 | t.Skip() 43 | } 44 | if test.skip { 45 | t.Skip() 46 | } 47 | for _, c := range test.cases { 48 | end, r := test.resolve() 49 | name, err := r.ResolvePackage(c.importPath) 50 | if end != nil { 51 | end() // delete temp dir if created 52 | } 53 | if err == resolver.ErrPackageNotFound { 54 | name = "" 55 | } else if err != nil { 56 | t.Errorf("error resolving path %s: %v", c.importPath, err) 57 | } 58 | if name != c.expectName { 59 | t.Errorf("package %s - expected %s, got %s", c.importPath, c.expectName, name) 60 | } 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /decorator/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "errors" 5 | "go/ast" 6 | ) 7 | 8 | // RestorerResolver resolves a package path to a package name. 9 | type RestorerResolver interface { 10 | ResolvePackage(path string) (string, error) 11 | } 12 | 13 | // DecoratorResolver resolves an identifier to a local or remote reference. 14 | // 15 | // Returns path == "" if the node is not a local or remote reference (e.g. a field in a composite 16 | // literal, the selector in a selector expression etc.). 17 | // 18 | // Returns path == "" is the node is a local reference. 19 | // 20 | // Returns path != "" is the node is a remote reference. 21 | type DecoratorResolver interface { 22 | ResolveIdent(file *ast.File, parent ast.Node, parentField string, id *ast.Ident) (path string, err error) 23 | } 24 | 25 | // ErrPackageNotFound means the package is not found 26 | var ErrPackageNotFound = errors.New("package not found") 27 | -------------------------------------------------------------------------------- /decorator/resolver/simple/resolver.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import "github.com/dave/dst/decorator/resolver" 4 | 5 | func New(m map[string]string) RestorerResolver { 6 | return RestorerResolver(m) 7 | } 8 | 9 | // RestorerResolver is a map of package path -> package name. Names are resolved from this map, and 10 | // if a name doesn't exist in the map, an error is returned. Note that Guess is not a NodeResolver, 11 | // so can't properly resolve identifiers in dot import packages. 12 | type RestorerResolver map[string]string 13 | 14 | func (r RestorerResolver) ResolvePackage(importPath string) (string, error) { 15 | if n, ok := r[importPath]; ok { 16 | return n, nil 17 | } 18 | return "", resolver.ErrPackageNotFound 19 | } 20 | -------------------------------------------------------------------------------- /decorator/resolver/simple/resolver_test.go: -------------------------------------------------------------------------------- 1 | package simple_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dave/dst/decorator/resolver" 7 | "github.com/dave/dst/decorator/resolver/simple" 8 | ) 9 | 10 | func TestRestorerResolver(t *testing.T) { 11 | type tc struct{ importPath, expectName string } 12 | tests := []struct { 13 | skip, solo bool 14 | name string 15 | resolve func() (end func(), r resolver.RestorerResolver) 16 | cases []tc 17 | }{ 18 | { 19 | name: "simple.RestorerResolver", 20 | resolve: func() (end func(), r resolver.RestorerResolver) { 21 | r = simple.RestorerResolver{ 22 | "a/b/c": "d", 23 | } 24 | return 25 | }, 26 | cases: []tc{ 27 | {"a/b/c", "d"}, 28 | {"d/e/f", ""}, 29 | }, 30 | }, 31 | } 32 | var solo bool 33 | for _, test := range tests { 34 | if test.solo { 35 | solo = true 36 | break 37 | } 38 | } 39 | for _, test := range tests { 40 | t.Run(test.name, func(t *testing.T) { 41 | if solo && !test.solo { 42 | t.Skip() 43 | } 44 | if test.skip { 45 | t.Skip() 46 | } 47 | for _, c := range test.cases { 48 | end, r := test.resolve() 49 | name, err := r.ResolvePackage(c.importPath) 50 | if end != nil { 51 | end() // delete temp dir if created 52 | } 53 | if err == resolver.ErrPackageNotFound { 54 | name = "" 55 | } else if err != nil { 56 | t.Errorf("error resolving path %s: %v", c.importPath, err) 57 | } 58 | if name != c.expectName { 59 | t.Errorf("package %s - expected %s, got %s", c.importPath, c.expectName, name) 60 | } 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /decorator/restorer_apply_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/dave/dst" 10 | "github.com/dave/dst/decorator/resolver/guess" 11 | "github.com/dave/dst/dstutil" 12 | ) 13 | 14 | func TestApply(t *testing.T) { 15 | testPackageRestoresCorrectlyWithApplyClone( 16 | t, 17 | "github.com/dave/dst/gendst/data", 18 | "fmt", 19 | "bytes", 20 | "io", 21 | ) 22 | } 23 | 24 | func testPackageRestoresCorrectlyWithApplyClone(t *testing.T, path ...string) { 25 | t.Helper() 26 | pkgs, err := Load(nil, path...) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | if len(pkgs) == 0 { 31 | t.Fatal("No packages loaded") 32 | } 33 | for _, p := range pkgs { 34 | if len(p.Syntax) == 0 { 35 | t.Fatalf("Package %s has no syntax", p.PkgPath) 36 | } 37 | t.Run(p.PkgPath, func(t *testing.T) { 38 | 39 | r := NewRestorer() 40 | r.Path = p.PkgPath 41 | r.Resolver = &guess.RestorerResolver{} 42 | 43 | for _, file := range p.Syntax { 44 | 45 | fpath := p.Decorator.Filenames[file] 46 | _, fname := filepath.Split(fpath) 47 | 48 | t.Run(fname, func(t *testing.T) { 49 | 50 | cloned1 := dst.Clone(file).(*dst.File) 51 | cloned2 := dst.Clone(file).(*dst.File) 52 | 53 | cloned1 = dstutil.Apply(cloned1, func(c *dstutil.Cursor) bool { 54 | switch n := c.Node().(type) { 55 | case *dst.Ident: 56 | n1 := dst.Clone(c.Node()) 57 | n1.Decorations().End.Replace(fmt.Sprintf("/* %s */", n.Name)) 58 | c.Replace(n1) 59 | } 60 | return true 61 | }, nil).(*dst.File) 62 | 63 | // same with dst.Inspect 64 | dst.Inspect(cloned2, func(n dst.Node) bool { 65 | switch n := n.(type) { 66 | case *dst.Ident: 67 | n.Decorations().End.Replace(fmt.Sprintf("/* %s */", n.Name)) 68 | } 69 | return true 70 | }) 71 | 72 | buf1 := &bytes.Buffer{} 73 | if err := r.Fprint(buf1, cloned1); err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | buf2 := &bytes.Buffer{} 78 | if err := r.Fprint(buf2, cloned2); err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | if buf1.String() != buf2.String() { 83 | t.Errorf("diff:\n%s", diff(buf2.String(), buf1.String())) 84 | } 85 | }) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /decorator/restorer_clone_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "io/ioutil" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/dave/dst" 11 | "github.com/dave/dst/decorator/resolver/gobuild" 12 | ) 13 | 14 | func TestClone(t *testing.T) { 15 | testPackageRestoresCorrectlyWithClone( 16 | t, 17 | "github.com/dave/dst/gendst/data", 18 | "fmt", 19 | "bytes", 20 | "io", 21 | ) 22 | } 23 | 24 | func testPackageRestoresCorrectlyWithClone(t *testing.T, path ...string) { 25 | t.Helper() 26 | pkgs, err := Load(nil, path...) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | if len(pkgs) == 0 { 31 | t.Fatal("No packages loaded") 32 | } 33 | for _, p := range pkgs { 34 | if len(p.Syntax) == 0 { 35 | t.Fatalf("Package %s has no syntax", p.PkgPath) 36 | } 37 | t.Run(p.PkgPath, func(t *testing.T) { 38 | 39 | // must use go/build package resolver for standard library because of https://github.com/golang/go/issues/26924 40 | r := NewRestorer() 41 | r.Path = p.PkgPath 42 | r.Resolver = &gobuild.RestorerResolver{Dir: p.Dir} 43 | 44 | for _, file := range p.Syntax { 45 | 46 | fpath := p.Decorator.Filenames[file] 47 | _, fname := filepath.Split(fpath) 48 | 49 | t.Run(fname, func(t *testing.T) { 50 | 51 | cloned := dst.Clone(file).(*dst.File) 52 | 53 | buf := &bytes.Buffer{} 54 | if err := r.Fprint(buf, cloned); err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | existing, err := ioutil.ReadFile(fpath) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | expect, err := format.Source(existing) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if string(expect) != buf.String() { 67 | t.Errorf("diff:\n%s", diff(string(expect), buf.String())) 68 | } 69 | }) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /decorator/restorer_data_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import "testing" 4 | 5 | func TestData(t *testing.T) { 6 | testPackageRestoresCorrectlyWithImports(t, "github.com/dave/dst/gendst/data") 7 | } 8 | -------------------------------------------------------------------------------- /decorator/restorer_func_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "testing" 7 | 8 | "github.com/dave/dst" 9 | "github.com/dave/dst/dstutil" 10 | ) 11 | 12 | func TestRestorerFunc(t *testing.T) { 13 | tests := []struct { 14 | skip, solo bool 15 | name string 16 | code string 17 | f func(*dst.File) 18 | pre, post func(*dstutil.Cursor) bool 19 | expect string 20 | }{ 21 | 22 | { 23 | name: "func-decl-edge-case", 24 | code: `package a 25 | 26 | type T func(a int) (b int) 27 | `, 28 | f: func(f *dst.File) { 29 | ft := f.Decls[0].(*dst.GenDecl).Specs[0].(*dst.TypeSpec).Type.(*dst.FuncType) 30 | ft.Decs.Start.Replace("/*Start*/") 31 | ft.Decs.Func.Replace("/*Func*/") 32 | ft.Decs.Params.Replace("/*Params*/") 33 | ft.Decs.End.Replace("/*End*/") 34 | fd := &dst.FuncDecl{ 35 | Name: dst.NewIdent("foo"), 36 | Type: ft, 37 | Body: &dst.BlockStmt{}, 38 | Decs: dst.FuncDeclDecorations{NodeDecs: dst.NodeDecs{Before: dst.EmptyLine}}, 39 | } 40 | f.Decls = nil 41 | f.Decls = append(f.Decls, fd) 42 | }, 43 | expect: `package a 44 | 45 | /*Start*/ 46 | func /*Func*/ foo(a int) /*Params*/ (b int)/*End*/ {}`, 47 | }, 48 | { 49 | name: "func-decl-generic-edge-case", 50 | code: `package a 51 | 52 | type T[P any] func(a int) (b int) 53 | `, 54 | f: func(f *dst.File) { 55 | tt := f.Decls[0].(*dst.GenDecl).Specs[0].(*dst.TypeSpec) 56 | ft := f.Decls[0].(*dst.GenDecl).Specs[0].(*dst.TypeSpec).Type.(*dst.FuncType) 57 | ft.Decs.Start.Replace("/*Start*/") 58 | ft.Decs.Func.Replace("/*Func*/") 59 | ft.TypeParams = tt.TypeParams 60 | ft.Decs.TypeParams = append(ft.Decs.TypeParams, "/*TypeParams*/") 61 | ft.Decs.Params.Replace("/*Params*/") 62 | ft.Decs.End.Replace("/*End*/") 63 | fd := &dst.FuncDecl{ 64 | Name: dst.NewIdent("foo"), 65 | Type: ft, 66 | Body: &dst.BlockStmt{}, 67 | Decs: dst.FuncDeclDecorations{NodeDecs: dst.NodeDecs{Before: dst.EmptyLine}}, 68 | } 69 | f.Decls = nil 70 | f.Decls = append(f.Decls, fd) 71 | }, 72 | expect: `package a 73 | 74 | /*Start*/ 75 | func /*Func*/ foo[P any] /*TypeParams*/ (a int) /*Params*/ (b int)/*End*/ {}`, 76 | }, 77 | { 78 | name: "node-reuse", 79 | code: `package a 80 | 81 | var i /*a*/ int`, 82 | f: func(f *dst.File) { 83 | gd := dst.Clone(f.Decls[0]).(*dst.GenDecl) 84 | gd.Decs.Before = dst.NewLine 85 | gd.Specs[0].(*dst.ValueSpec).Names[0].Name = "j" 86 | gd.Specs[0].(*dst.ValueSpec).Names[0].Decs.End.Replace("/*b*/") 87 | f.Decls = append(f.Decls, gd) 88 | }, 89 | expect: `package a 90 | 91 | var i /*a*/ int 92 | var j /*b*/ int`, 93 | }, 94 | { 95 | name: "simple", 96 | code: `package a 97 | 98 | var i int`, 99 | pre: func(c *dstutil.Cursor) bool { 100 | switch n := c.Node().(type) { 101 | case *dst.Ident: 102 | if n.Name == "i" { 103 | n.Name = "j" 104 | } 105 | } 106 | return true 107 | }, 108 | expect: `package a 109 | 110 | var j int`, 111 | }, 112 | } 113 | var solo bool 114 | for _, test := range tests { 115 | if test.solo { 116 | solo = true 117 | break 118 | } 119 | } 120 | for _, test := range tests { 121 | t.Run(test.name, func(t *testing.T) { 122 | if solo && !test.solo { 123 | t.Skip() 124 | } 125 | if test.skip { 126 | t.Skip() 127 | } 128 | 129 | // format code and check it hasn't changed 130 | bCode, err := format.Source([]byte(test.code)) 131 | if err != nil { 132 | t.Fatal(err) 133 | } 134 | if normalize(string(bCode)) != normalize(test.code) { 135 | t.Fatalf("code changed after gofmt. before: \n%s\nafter:\n%s", test.code, string(bCode)) 136 | } 137 | // use the formatted version (correct indents etc.) 138 | test.code = string(bCode) 139 | 140 | // format expect and check it hasn't changed 141 | bExpect, err := format.Source([]byte(test.expect)) 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | if normalize(string(bExpect)) != normalize(test.expect) { 146 | t.Fatalf("expect changed after gofmt. before: \n%s\nafter:\n%s", test.expect, string(bExpect)) 147 | } 148 | // use the formatted version (correct indents etc.) 149 | test.expect = string(bExpect) 150 | 151 | file, err := Parse(test.code) 152 | if err != nil { 153 | t.Fatal(err) 154 | } 155 | 156 | if test.f != nil { 157 | test.f(file) 158 | } 159 | 160 | if test.pre != nil || test.post != nil { 161 | file = dstutil.Apply(file, test.pre, test.post).(*dst.File) 162 | } 163 | 164 | restoredFset, restoredFile, err := RestoreFile(file) 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | 169 | buf := &bytes.Buffer{} 170 | if err := format.Node(buf, restoredFset, restoredFile); err != nil { 171 | t.Fatal(err) 172 | } 173 | 174 | if buf.String() != test.expect { 175 | t.Errorf("diff:\n%s", diff(test.expect, buf.String())) 176 | } 177 | }) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /decorator/restorer_std_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/build" 7 | "go/format" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/dave/dst/decorator/resolver/gobuild" 16 | ) 17 | 18 | func TestLoadStdLibAll(t *testing.T) { 19 | 20 | if testing.Short() { 21 | t.Skip("skipping standard library load test in short mode.") 22 | } 23 | 24 | home, err := os.UserHomeDir() 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | cmd := exec.Command("go", "list", "./...") 30 | cmd.Env = []string{ 31 | fmt.Sprintf("GOPATH=%s", build.Default.GOPATH), 32 | fmt.Sprintf("GOROOT=%s", build.Default.GOROOT), 33 | fmt.Sprintf("HOME=%s", home), 34 | } 35 | cmd.Dir = filepath.Join(build.Default.GOROOT, "src") 36 | b, err := cmd.CombinedOutput() 37 | if err != nil { 38 | t.Fatalf("%s: %v", string(b), err) 39 | } 40 | all := strings.Split(strings.TrimSpace(string(b)), "\n") 41 | 42 | testPackageRestoresCorrectlyWithImports(t, all...) 43 | 44 | } 45 | 46 | func testPackageRestoresCorrectlyWithImports(t *testing.T, path ...string) { 47 | t.Helper() 48 | pkgs, err := Load(nil, path...) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if len(pkgs) == 0 { 53 | t.Fatal("No packages loaded") 54 | } 55 | // we skip some packages because they have no source: 56 | skip := map[string]bool{ 57 | "unsafe": true, 58 | "embed/internal/embedtest": true, 59 | "os/signal/internal/pty": true, 60 | } 61 | for _, p := range pkgs { 62 | if skip[p.PkgPath] { 63 | continue 64 | } 65 | if len(p.Syntax) == 0 { 66 | t.Fatalf("Package %s has no syntax", p.PkgPath) 67 | } 68 | t.Run(p.PkgPath, func(t *testing.T) { 69 | 70 | // must use go/build package resolver for standard library because of https://github.com/golang/go/issues/26924 71 | r := NewRestorer() 72 | r.Path = p.PkgPath 73 | r.Resolver = &gobuild.RestorerResolver{Dir: p.Dir} 74 | 75 | for _, file := range p.Syntax { 76 | 77 | fpath := p.Decorator.Filenames[file] 78 | _, fname := filepath.Split(fpath) 79 | 80 | t.Run(fname, func(t *testing.T) { 81 | 82 | if (p.PkgPath == "net/http" && (fname == "server.go" || fname == "request.go")) || (p.PkgPath == "crypto/x509" && fname == "x509.go") { 83 | t.Skip("TODO: In net/http/server.go, net/http/request.go, and crypto/x509/x509.go we multiple imports with the same path and different aliases. This edge case would need a complete rewrite of the import management block to support - see see https://github.com/dave/dst/issues/45") 84 | } 85 | 86 | buf := &bytes.Buffer{} 87 | if err := r.Fprint(buf, file); err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | existing, err := ioutil.ReadFile(fpath) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | expect, err := format.Source(existing) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | if string(expect) != buf.String() { 100 | t.Errorf("diff:\n%s", diff(string(expect), buf.String())) 101 | } 102 | }) 103 | } 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /decorator/util_test.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "sort" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func tempDir(m map[string]string) (dir string, err error) { 15 | if dir, err = ioutil.TempDir("", ""); err != nil { 16 | return 17 | } 18 | for fpathrel, src := range m { 19 | if strings.HasSuffix(fpathrel, "/") { 20 | // just a dir 21 | if err = os.MkdirAll(filepath.Join(dir, fpathrel), 0777); err != nil { 22 | return 23 | } 24 | } else { 25 | fpath := filepath.Join(dir, fpathrel) 26 | fdir, _ := filepath.Split(fpath) 27 | if err = os.MkdirAll(fdir, 0777); err != nil { 28 | return 29 | } 30 | 31 | var formatted []byte 32 | if strings.HasSuffix(fpath, ".go") { 33 | formatted, err = format.Source([]byte(src)) 34 | if err != nil { 35 | err = fmt.Errorf("formatting %s: %v", fpathrel, err) 36 | return 37 | } 38 | } else { 39 | formatted = []byte(src) 40 | } 41 | 42 | if err = ioutil.WriteFile(fpath, formatted, 0666); err != nil { 43 | return 44 | } 45 | } 46 | } 47 | return 48 | } 49 | 50 | func compareDir(t *testing.T, dir string, expect map[string]string) { 51 | t.Helper() 52 | found := map[string]string{} 53 | walk := func(fpath string, info os.FileInfo, err error) error { 54 | if info.IsDir() { 55 | return nil 56 | } 57 | relfpath, err := filepath.Rel(dir, fpath) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | b, err := ioutil.ReadFile(fpath) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | found[relfpath] = string(b) 66 | return nil 67 | } 68 | if err := filepath.Walk(dir, walk); err != nil { 69 | t.Fatal(err) 70 | } 71 | var keysFound []string 72 | var keysExpect []string 73 | for k := range found { 74 | keysFound = append(keysFound, k) 75 | } 76 | for k := range expect { 77 | keysExpect = append(keysExpect, k) 78 | } 79 | sort.Strings(keysFound) 80 | sort.Strings(keysExpect) 81 | keysFoundJoined := strings.Join(keysFound, " ") 82 | keysExpectJoined := strings.Join(keysExpect, " ") 83 | t.Run("files", func(t *testing.T) { 84 | compare(t, keysExpectJoined, keysFoundJoined) 85 | }) 86 | done := map[string]bool{} 87 | for k, v := range found { 88 | if done[k] { 89 | continue 90 | } 91 | t.Run(k, func(t *testing.T) { 92 | if strings.HasSuffix(k, ".go") { 93 | compareSrc(t, expect[k], v) 94 | } else { 95 | compare(t, strings.TrimSpace(expect[k]), strings.TrimSpace(v)) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func compare(t *testing.T, expect, found string) { 102 | t.Helper() 103 | if expect != found { 104 | t.Errorf("\nexpect: %q\nfound : %q", expect, found) 105 | } 106 | } 107 | 108 | func compareSrc(t *testing.T, expect, found string) { 109 | t.Helper() 110 | bFound, err := format.Source([]byte(found)) 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | bExpect, err := format.Source([]byte(expect)) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | expect = string(bExpect) 119 | found = string(bFound) 120 | if expect != found { 121 | t.Errorf("\nexpect: %q\nfound : %q", expect, found) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /dstutil/decorations.go: -------------------------------------------------------------------------------- 1 | package dstutil 2 | 3 | import "github.com/dave/dst" 4 | 5 | // Decorations returns information about all the decoration attachment points associated with a node 6 | func Decorations(n dst.Node) (before, after dst.SpaceType, info []DecorationPoint) { 7 | return decorations(n) 8 | } 9 | 10 | // DecorationPoint contains the name of the decoration attachment point and a list of decorations attached there 11 | type DecorationPoint struct { 12 | Name string 13 | Decs []string 14 | } 15 | -------------------------------------------------------------------------------- /dstutil/rewrite.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dstutil 6 | 7 | import ( 8 | "fmt" 9 | 10 | "reflect" 11 | "sort" 12 | 13 | "github.com/dave/dst" 14 | ) 15 | 16 | // An ApplyFunc is invoked by Apply for each node n, even if n is nil, 17 | // before and/or after the node's children, using a Cursor describing 18 | // the current node and providing operations on it. 19 | // 20 | // The return value of ApplyFunc controls the syntax tree traversal. 21 | // See Apply for details. 22 | type ApplyFunc func(*Cursor) bool 23 | 24 | // Apply traverses a syntax tree recursively, starting with root, 25 | // and calling pre and post for each node as described below. 26 | // Apply returns the syntax tree, possibly modified. 27 | // 28 | // If pre is not nil, it is called for each node before the node's 29 | // children are traversed (pre-order). If pre returns false, no 30 | // children are traversed, and post is not called for that node. 31 | // 32 | // If post is not nil, and a prior call of pre didn't return false, 33 | // post is called for each node after its children are traversed 34 | // (post-order). If post returns false, traversal is terminated and 35 | // Apply returns immediately. 36 | // 37 | // Only fields that refer to AST nodes are considered children; 38 | // i.e., token.Pos, Scopes, Objects, and fields of basic types 39 | // (strings, etc.) are ignored. 40 | // 41 | // Children are traversed in the order in which they appear in the 42 | // respective node's struct definition. A package's files are 43 | // traversed in the filenames' alphabetical order. 44 | // 45 | func Apply(root dst.Node, pre, post ApplyFunc) (result dst.Node) { 46 | parent := &struct{ dst.Node }{root} 47 | defer func() { 48 | if r := recover(); r != nil && r != abort { 49 | panic(r) 50 | } 51 | result = parent.Node 52 | }() 53 | a := &application{pre: pre, post: post} 54 | a.apply(parent, "Node", nil, root) 55 | return 56 | } 57 | 58 | var abort = new(int) // singleton, to signal termination of Apply 59 | 60 | // A Cursor describes a node encountered during Apply. 61 | // Information about the node and its parent is available 62 | // from the Node, Parent, Name, and Index methods. 63 | // 64 | // If p is a variable of type and value of the current parent node 65 | // c.Parent(), and f is the field identifier with name c.Name(), 66 | // the following invariants hold: 67 | // 68 | // p.f == c.Node() if c.Index() < 0 69 | // p.f[c.Index()] == c.Node() if c.Index() >= 0 70 | // 71 | // The methods Replace, Delete, InsertBefore, and InsertAfter 72 | // can be used to change the AST without disrupting Apply. 73 | type Cursor struct { 74 | parent dst.Node 75 | name string 76 | iter *iterator // valid if non-nil 77 | node dst.Node 78 | } 79 | 80 | // Node returns the current Node. 81 | func (c *Cursor) Node() dst.Node { return c.node } 82 | 83 | // Parent returns the parent of the current Node. 84 | func (c *Cursor) Parent() dst.Node { return c.parent } 85 | 86 | // Name returns the name of the parent Node field that contains the current Node. 87 | // If the parent is a *dst.Package and the current Node is a *dst.File, Name returns 88 | // the filename for the current Node. 89 | func (c *Cursor) Name() string { return c.name } 90 | 91 | // Index reports the index >= 0 of the current Node in the slice of Nodes that 92 | // contains it, or a value < 0 if the current Node is not part of a slice. 93 | // The index of the current node changes if InsertBefore is called while 94 | // processing the current node. 95 | func (c *Cursor) Index() int { 96 | if c.iter != nil { 97 | return c.iter.index 98 | } 99 | return -1 100 | } 101 | 102 | // field returns the current node's parent field value. 103 | func (c *Cursor) field() reflect.Value { 104 | return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name) 105 | } 106 | 107 | // Replace replaces the current Node with n. 108 | // The replacement node is not walked by Apply. 109 | func (c *Cursor) Replace(n dst.Node) { 110 | if _, ok := c.node.(*dst.File); ok { 111 | file, ok := n.(*dst.File) 112 | if !ok { 113 | panic("attempt to replace *dst.File with non-*dst.File") 114 | } 115 | c.parent.(*dst.Package).Files[c.name] = file 116 | return 117 | } 118 | 119 | v := c.field() 120 | if i := c.Index(); i >= 0 { 121 | v = v.Index(i) 122 | } 123 | v.Set(reflect.ValueOf(n)) 124 | } 125 | 126 | // Delete deletes the current Node from its containing slice. 127 | // If the current Node is not part of a slice, Delete panics. 128 | // As a special case, if the current node is a package file, 129 | // Delete removes it from the package's Files map. 130 | func (c *Cursor) Delete() { 131 | if _, ok := c.node.(*dst.File); ok { 132 | delete(c.parent.(*dst.Package).Files, c.name) 133 | return 134 | } 135 | 136 | i := c.Index() 137 | if i < 0 { 138 | panic("Delete node not contained in slice") 139 | } 140 | v := c.field() 141 | l := v.Len() 142 | reflect.Copy(v.Slice(i, l), v.Slice(i+1, l)) 143 | v.Index(l - 1).Set(reflect.Zero(v.Type().Elem())) 144 | v.SetLen(l - 1) 145 | c.iter.step-- 146 | } 147 | 148 | // InsertAfter inserts n after the current Node in its containing slice. 149 | // If the current Node is not part of a slice, InsertAfter panics. 150 | // Apply does not walk n. 151 | func (c *Cursor) InsertAfter(n dst.Node) { 152 | i := c.Index() 153 | if i < 0 { 154 | panic("InsertAfter node not contained in slice") 155 | } 156 | v := c.field() 157 | v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) 158 | l := v.Len() 159 | reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l)) 160 | v.Index(i + 1).Set(reflect.ValueOf(n)) 161 | c.iter.step++ 162 | } 163 | 164 | // InsertBefore inserts n before the current Node in its containing slice. 165 | // If the current Node is not part of a slice, InsertBefore panics. 166 | // Apply will not walk n. 167 | func (c *Cursor) InsertBefore(n dst.Node) { 168 | i := c.Index() 169 | if i < 0 { 170 | panic("InsertBefore node not contained in slice") 171 | } 172 | v := c.field() 173 | v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) 174 | l := v.Len() 175 | reflect.Copy(v.Slice(i+1, l), v.Slice(i, l)) 176 | v.Index(i).Set(reflect.ValueOf(n)) 177 | c.iter.index++ 178 | } 179 | 180 | // application carries all the shared data so we can pass it around cheaply. 181 | type application struct { 182 | pre, post ApplyFunc 183 | cursor Cursor 184 | iter iterator 185 | } 186 | 187 | func (a *application) apply(parent dst.Node, name string, iter *iterator, n dst.Node) { 188 | // convert typed nil into untyped nil 189 | if v := reflect.ValueOf(n); v.Kind() == reflect.Ptr && v.IsNil() { 190 | n = nil 191 | } 192 | 193 | // avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead 194 | saved := a.cursor 195 | a.cursor.parent = parent 196 | a.cursor.name = name 197 | a.cursor.iter = iter 198 | a.cursor.node = n 199 | 200 | if a.pre != nil && !a.pre(&a.cursor) { 201 | a.cursor = saved 202 | return 203 | } 204 | 205 | // walk children 206 | // (the order of the cases matches the order of the corresponding node types in go/ast) 207 | switch n := n.(type) { 208 | case nil: 209 | // nothing to do 210 | 211 | case *dst.Field: 212 | a.applyList(n, "Names") 213 | a.apply(n, "Type", nil, n.Type) 214 | a.apply(n, "Tag", nil, n.Tag) 215 | 216 | case *dst.FieldList: 217 | a.applyList(n, "List") 218 | 219 | // Expressions 220 | case *dst.BadExpr, *dst.Ident, *dst.BasicLit: 221 | // nothing to do 222 | 223 | case *dst.Ellipsis: 224 | a.apply(n, "Elt", nil, n.Elt) 225 | 226 | case *dst.FuncLit: 227 | a.apply(n, "Type", nil, n.Type) 228 | a.apply(n, "Body", nil, n.Body) 229 | 230 | case *dst.CompositeLit: 231 | a.apply(n, "Type", nil, n.Type) 232 | a.applyList(n, "Elts") 233 | 234 | case *dst.ParenExpr: 235 | a.apply(n, "X", nil, n.X) 236 | 237 | case *dst.SelectorExpr: 238 | a.apply(n, "X", nil, n.X) 239 | a.apply(n, "Sel", nil, n.Sel) 240 | 241 | case *dst.IndexExpr: 242 | a.apply(n, "X", nil, n.X) 243 | a.apply(n, "Index", nil, n.Index) 244 | 245 | case *dst.IndexListExpr: 246 | a.apply(n, "X", nil, n.X) 247 | a.applyList(n, "Indices") 248 | 249 | case *dst.SliceExpr: 250 | a.apply(n, "X", nil, n.X) 251 | a.apply(n, "Low", nil, n.Low) 252 | a.apply(n, "High", nil, n.High) 253 | a.apply(n, "Max", nil, n.Max) 254 | 255 | case *dst.TypeAssertExpr: 256 | a.apply(n, "X", nil, n.X) 257 | a.apply(n, "Type", nil, n.Type) 258 | 259 | case *dst.CallExpr: 260 | a.apply(n, "Fun", nil, n.Fun) 261 | a.applyList(n, "Args") 262 | 263 | case *dst.StarExpr: 264 | a.apply(n, "X", nil, n.X) 265 | 266 | case *dst.UnaryExpr: 267 | a.apply(n, "X", nil, n.X) 268 | 269 | case *dst.BinaryExpr: 270 | a.apply(n, "X", nil, n.X) 271 | a.apply(n, "Y", nil, n.Y) 272 | 273 | case *dst.KeyValueExpr: 274 | a.apply(n, "Key", nil, n.Key) 275 | a.apply(n, "Value", nil, n.Value) 276 | 277 | // Types 278 | case *dst.ArrayType: 279 | a.apply(n, "Len", nil, n.Len) 280 | a.apply(n, "Elt", nil, n.Elt) 281 | 282 | case *dst.StructType: 283 | a.apply(n, "Fields", nil, n.Fields) 284 | 285 | case *dst.FuncType: 286 | a.apply(n, "TypeParams", nil, n.TypeParams) 287 | a.apply(n, "Params", nil, n.Params) 288 | a.apply(n, "Results", nil, n.Results) 289 | 290 | case *dst.InterfaceType: 291 | a.apply(n, "Methods", nil, n.Methods) 292 | 293 | case *dst.MapType: 294 | a.apply(n, "Key", nil, n.Key) 295 | a.apply(n, "Value", nil, n.Value) 296 | 297 | case *dst.ChanType: 298 | a.apply(n, "Value", nil, n.Value) 299 | 300 | // Statements 301 | case *dst.BadStmt: 302 | // nothing to do 303 | 304 | case *dst.DeclStmt: 305 | a.apply(n, "Decl", nil, n.Decl) 306 | 307 | case *dst.EmptyStmt: 308 | // nothing to do 309 | 310 | case *dst.LabeledStmt: 311 | a.apply(n, "Label", nil, n.Label) 312 | a.apply(n, "Stmt", nil, n.Stmt) 313 | 314 | case *dst.ExprStmt: 315 | a.apply(n, "X", nil, n.X) 316 | 317 | case *dst.SendStmt: 318 | a.apply(n, "Chan", nil, n.Chan) 319 | a.apply(n, "Value", nil, n.Value) 320 | 321 | case *dst.IncDecStmt: 322 | a.apply(n, "X", nil, n.X) 323 | 324 | case *dst.AssignStmt: 325 | a.applyList(n, "Lhs") 326 | a.applyList(n, "Rhs") 327 | 328 | case *dst.GoStmt: 329 | a.apply(n, "Call", nil, n.Call) 330 | 331 | case *dst.DeferStmt: 332 | a.apply(n, "Call", nil, n.Call) 333 | 334 | case *dst.ReturnStmt: 335 | a.applyList(n, "Results") 336 | 337 | case *dst.BranchStmt: 338 | a.apply(n, "Label", nil, n.Label) 339 | 340 | case *dst.BlockStmt: 341 | a.applyList(n, "List") 342 | 343 | case *dst.IfStmt: 344 | a.apply(n, "Init", nil, n.Init) 345 | a.apply(n, "Cond", nil, n.Cond) 346 | a.apply(n, "Body", nil, n.Body) 347 | a.apply(n, "Else", nil, n.Else) 348 | 349 | case *dst.CaseClause: 350 | a.applyList(n, "List") 351 | a.applyList(n, "Body") 352 | 353 | case *dst.SwitchStmt: 354 | a.apply(n, "Init", nil, n.Init) 355 | a.apply(n, "Tag", nil, n.Tag) 356 | a.apply(n, "Body", nil, n.Body) 357 | 358 | case *dst.TypeSwitchStmt: 359 | a.apply(n, "Init", nil, n.Init) 360 | a.apply(n, "Assign", nil, n.Assign) 361 | a.apply(n, "Body", nil, n.Body) 362 | 363 | case *dst.CommClause: 364 | a.apply(n, "Comm", nil, n.Comm) 365 | a.applyList(n, "Body") 366 | 367 | case *dst.SelectStmt: 368 | a.apply(n, "Body", nil, n.Body) 369 | 370 | case *dst.ForStmt: 371 | a.apply(n, "Init", nil, n.Init) 372 | a.apply(n, "Cond", nil, n.Cond) 373 | a.apply(n, "Post", nil, n.Post) 374 | a.apply(n, "Body", nil, n.Body) 375 | 376 | case *dst.RangeStmt: 377 | a.apply(n, "Key", nil, n.Key) 378 | a.apply(n, "Value", nil, n.Value) 379 | a.apply(n, "X", nil, n.X) 380 | a.apply(n, "Body", nil, n.Body) 381 | 382 | // Declarations 383 | case *dst.ImportSpec: 384 | a.apply(n, "Name", nil, n.Name) 385 | a.apply(n, "Path", nil, n.Path) 386 | 387 | case *dst.ValueSpec: 388 | a.applyList(n, "Names") 389 | a.apply(n, "Type", nil, n.Type) 390 | a.applyList(n, "Values") 391 | 392 | case *dst.TypeSpec: 393 | a.apply(n, "Name", nil, n.Name) 394 | a.apply(n, "TypeParams", nil, n.TypeParams) 395 | a.apply(n, "Type", nil, n.Type) 396 | 397 | case *dst.BadDecl: 398 | // nothing to do 399 | 400 | case *dst.GenDecl: 401 | a.applyList(n, "Specs") 402 | 403 | case *dst.FuncDecl: 404 | a.apply(n, "Recv", nil, n.Recv) 405 | a.apply(n, "Name", nil, n.Name) 406 | a.apply(n, "Type", nil, n.Type) 407 | a.apply(n, "Body", nil, n.Body) 408 | 409 | // Files and packages 410 | case *dst.File: 411 | a.apply(n, "Name", nil, n.Name) 412 | a.applyList(n, "Decls") 413 | // Don't walk n.Comments; they have either been walked already if 414 | // they are Doc comments, or they can be easily walked explicitly. 415 | 416 | case *dst.Package: 417 | // collect and sort names for reproducible behavior 418 | var names []string 419 | for name := range n.Files { 420 | names = append(names, name) 421 | } 422 | sort.Strings(names) 423 | for _, name := range names { 424 | a.apply(n, name, nil, n.Files[name]) 425 | } 426 | 427 | default: 428 | panic(fmt.Sprintf("Apply: unexpected node type %T", n)) 429 | } 430 | 431 | if a.post != nil && !a.post(&a.cursor) { 432 | panic(abort) 433 | } 434 | 435 | a.cursor = saved 436 | } 437 | 438 | // An iterator controls iteration over a slice of nodes. 439 | type iterator struct { 440 | index, step int 441 | } 442 | 443 | func (a *application) applyList(parent dst.Node, name string) { 444 | // avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead 445 | saved := a.iter 446 | a.iter.index = 0 447 | for { 448 | // must reload parent.name each time, since cursor modifications might change it 449 | v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name) 450 | if a.iter.index >= v.Len() { 451 | break 452 | } 453 | 454 | // element x may be nil in a bad AST - be cautious 455 | var x dst.Node 456 | if e := v.Index(a.iter.index); e.IsValid() { 457 | x = e.Interface().(dst.Node) 458 | } 459 | 460 | a.iter.step = 1 461 | a.apply(parent, name, &a.iter, x) 462 | a.iter.index += a.iter.step 463 | } 464 | a.iter = saved 465 | } 466 | -------------------------------------------------------------------------------- /dstutil/rewrite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dstutil_test 6 | 7 | import ( 8 | "bytes" 9 | 10 | "go/format" 11 | "go/parser" 12 | "go/token" 13 | "testing" 14 | 15 | "github.com/dave/dst" 16 | "github.com/dave/dst/decorator" 17 | "github.com/dave/dst/dstutil" 18 | ) 19 | 20 | var rewriteTests = [...]struct { 21 | name string 22 | orig, want string 23 | pre, post dstutil.ApplyFunc 24 | }{ 25 | {name: "nop", orig: "package p\n", want: "package p\n"}, 26 | 27 | {name: "replace", 28 | orig: `package p 29 | 30 | var x int 31 | `, 32 | want: `package p 33 | 34 | var t T 35 | `, 36 | post: func(c *dstutil.Cursor) bool { 37 | if _, ok := c.Node().(*dst.ValueSpec); ok { 38 | c.Replace(valspec("t", "T")) 39 | return false 40 | } 41 | return true 42 | }, 43 | }, 44 | 45 | {name: "set doc strings", 46 | orig: `package p 47 | 48 | const z = 0 49 | 50 | type T struct{} 51 | 52 | var x int 53 | `, 54 | want: `package p 55 | 56 | // a foo is a foo 57 | const z = 0 58 | 59 | // a foo is a foo 60 | type T struct{} 61 | 62 | // a foo is a foo 63 | var x int 64 | `, 65 | post: func(c *dstutil.Cursor) bool { 66 | if gd, ok := c.Node().(*dst.GenDecl); ok { 67 | gd.Decs.Start.Append("// a foo is a foo") 68 | } 69 | return true 70 | }, 71 | }, 72 | 73 | {name: "insert names", 74 | orig: `package p 75 | 76 | const a = 1 77 | `, 78 | want: `package p 79 | 80 | const a, b, c = 1, 2, 3 81 | `, 82 | pre: func(c *dstutil.Cursor) bool { 83 | if _, ok := c.Parent().(*dst.ValueSpec); ok { 84 | switch c.Name() { 85 | case "Names": 86 | c.InsertAfter(dst.NewIdent("c")) 87 | c.InsertAfter(dst.NewIdent("b")) 88 | case "Values": 89 | c.InsertAfter(&dst.BasicLit{Kind: token.INT, Value: "3"}) 90 | c.InsertAfter(&dst.BasicLit{Kind: token.INT, Value: "2"}) 91 | } 92 | } 93 | return true 94 | }, 95 | }, 96 | 97 | {name: "insert", 98 | orig: `package p 99 | 100 | var ( 101 | x int 102 | y int 103 | ) 104 | `, 105 | want: `package p 106 | 107 | var before1 int 108 | var before2 int 109 | var ( 110 | x int 111 | y int 112 | ) 113 | var after2 int 114 | var after1 int 115 | `, 116 | pre: func(c *dstutil.Cursor) bool { 117 | if gd, ok := c.Node().(*dst.GenDecl); ok { 118 | gd.Decs.Before = dst.NewLine 119 | c.InsertBefore(vardecl("before1", "int")) 120 | c.InsertAfter(vardecl("after1", "int")) 121 | c.InsertAfter(vardecl("after2", "int")) 122 | c.InsertBefore(vardecl("before2", "int")) 123 | } 124 | return true 125 | }, 126 | }, 127 | 128 | {name: "delete", 129 | orig: `package p 130 | 131 | var x int 132 | var y int 133 | var z int 134 | `, 135 | want: `package p 136 | 137 | var y int 138 | var z int 139 | `, 140 | pre: func(c *dstutil.Cursor) bool { 141 | n := c.Node() 142 | if d, ok := n.(*dst.GenDecl); ok && d.Specs[0].(*dst.ValueSpec).Names[0].Name == "x" { 143 | c.Delete() 144 | } 145 | return true 146 | }, 147 | }, 148 | 149 | {name: "insertafter-delete", 150 | orig: `package p 151 | 152 | var x int 153 | var y int 154 | var z int 155 | `, 156 | want: `package p 157 | 158 | var x1 int 159 | var y int 160 | var z int 161 | `, 162 | pre: func(c *dstutil.Cursor) bool { 163 | n := c.Node() 164 | if d, ok := n.(*dst.GenDecl); ok && d.Specs[0].(*dst.ValueSpec).Names[0].Name == "x" { 165 | c.InsertAfter(vardecl("x1", "int")) 166 | c.Delete() 167 | } 168 | return true 169 | }, 170 | }, 171 | 172 | {name: "delete-insertafter", 173 | orig: `package p 174 | 175 | var x int 176 | var y int 177 | var z int 178 | `, 179 | want: `package p 180 | 181 | var y int 182 | var x1 int 183 | var z int 184 | `, 185 | pre: func(c *dstutil.Cursor) bool { 186 | n := c.Node() 187 | if d, ok := n.(*dst.GenDecl); ok && d.Specs[0].(*dst.ValueSpec).Names[0].Name == "x" { 188 | c.Delete() 189 | // The cursor is now effectively atop the 'var y int' node. 190 | c.InsertAfter(vardecl("x1", "int")) 191 | } 192 | return true 193 | }, 194 | }, 195 | } 196 | 197 | func valspec(name, typ string) *dst.ValueSpec { 198 | return &dst.ValueSpec{Names: []*dst.Ident{dst.NewIdent(name)}, 199 | Type: dst.NewIdent(typ), 200 | } 201 | } 202 | 203 | func vardecl(name, typ string) *dst.GenDecl { 204 | return &dst.GenDecl{ 205 | Tok: token.VAR, 206 | Specs: []dst.Spec{valspec(name, typ)}, 207 | } 208 | } 209 | 210 | func TestRewrite(t *testing.T) { 211 | t.Run("*", func(t *testing.T) { 212 | for _, test := range rewriteTests { 213 | test := test 214 | t.Run(test.name, func(t *testing.T) { 215 | t.Parallel() 216 | fset := token.NewFileSet() 217 | f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) 218 | if err != nil { 219 | t.Fatal(err) 220 | } 221 | dstFile, err := decorator.DecorateFile(fset, f) 222 | if err != nil { 223 | t.Fatal(err) 224 | } 225 | dstFile = dstutil.Apply(dstFile, test.pre, test.post).(*dst.File) 226 | restoredFset, restoredFile, err := decorator.RestoreFile(dstFile) 227 | if err != nil { 228 | t.Fatal(err) 229 | } 230 | var buf bytes.Buffer 231 | if err := format.Node(&buf, restoredFset, restoredFile); err != nil { 232 | t.Fatal(err) 233 | } 234 | got := buf.String() 235 | if got != test.want { 236 | t.Errorf("got:\n\n%s\nwant:\n\n%s\n", got, test.want) 237 | } 238 | }) 239 | } 240 | }) 241 | } 242 | 243 | var sink dst.Node 244 | 245 | func BenchmarkRewrite(b *testing.B) { 246 | for _, test := range rewriteTests { 247 | b.Run(test.name, func(b *testing.B) { 248 | for i := 0; i < b.N; i++ { 249 | b.StopTimer() 250 | fset := token.NewFileSet() 251 | f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) 252 | if err != nil { 253 | b.Fatal(err) 254 | } 255 | d, err := decorator.Decorate(fset, f) 256 | if err != nil { 257 | b.Fatal(err) 258 | } 259 | b.StartTimer() 260 | sink = dstutil.Apply(d, test.pre, test.post) 261 | } 262 | }) 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /dstutil/util.go: -------------------------------------------------------------------------------- 1 | package dstutil 2 | 3 | import "github.com/dave/dst" 4 | 5 | // Unparen returns e with any enclosing parentheses stripped. 6 | func Unparen(e dst.Expr) dst.Expr { 7 | for { 8 | p, ok := e.(*dst.ParenExpr) 9 | if !ok { 10 | return e 11 | } 12 | e = p.X 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dst_test 6 | 7 | import ( 8 | "fmt" 9 | "go/token" 10 | 11 | "github.com/dave/dst" 12 | "github.com/dave/dst/decorator" 13 | ) 14 | 15 | // This example demonstrates how to inspect the AST of a Go program. 16 | func ExampleInspect() { 17 | // src is the input for which we want to inspect the AST. 18 | src := ` 19 | package p 20 | const c = 1.0 21 | var X = f(3.14)*2 + c 22 | ` 23 | 24 | // Create the AST by parsing src. 25 | fset := token.NewFileSet() // positions are relative to fset 26 | f, err := decorator.ParseFile(fset, "src.go", src, 0) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // Inspect the AST and print all identifiers and literals. 32 | dst.Inspect(f, func(n dst.Node) bool { 33 | var s string 34 | switch x := n.(type) { 35 | case *dst.BasicLit: 36 | s = x.Value 37 | case *dst.Ident: 38 | s = x.Name 39 | } 40 | if s != "" { 41 | fmt.Println(s) 42 | } 43 | return true 44 | }) 45 | 46 | // Output: 47 | // p 48 | // c 49 | // 1.0 50 | // X 51 | // f 52 | // 3.14 53 | // 2 54 | // c 55 | } 56 | -------------------------------------------------------------------------------- /gendst/README.md: -------------------------------------------------------------------------------- 1 | # gendst 2 | 3 | The `gendst` package is used to create the generated portions of the `dst` and `decorator` packages. 4 | The manually compiled input data is in [data.go](https://github.com/dave/dst/blob/master/gendst/data/data.go). 5 | In addition the code in [positions.go](https://github.com/dave/dst/blob/master/gendst/data/positions.go) 6 | is sliced up automatically to make the documentation for the [decoration holder classes](https://github.com/dave/dst/blob/master/decorations-types-generated.go). 7 | 8 | The following files are generated: 9 | 10 | ### dst 11 | * [decorations-node-generated.go](https://github.com/dave/dst/blob/master/decorations-node-generated.go) 12 | * [decorations-types-generated.go](https://github.com/dave/dst/blob/master/decorations-types-generated.go) 13 | * [clone-generated.go](https://github.com/dave/dst/blob/master/clone-generated.go) 14 | 15 | ### decorator 16 | * [decorator-fragment-generated.go](https://github.com/dave/dst/blob/master/decorator/decorator-fragment-generated.go) 17 | * [decorator-node-generated.go](https://github.com/dave/dst/blob/master/decorator/decorator-node-generated.go) 18 | * [decorator-info-generated.go](https://github.com/dave/dst/blob/master/decorator/decorator-info-generated.go) 19 | * [restorer-generated.go](https://github.com/dave/dst/blob/master/decorator/restorer-generated.go) -------------------------------------------------------------------------------- /gendst/clone.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/dst/gendst/data" 7 | . "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | // notest 11 | 12 | func generateClone(names []string) error { 13 | 14 | f := NewFilePathName(DSTPATH, "dst") 15 | f.Comment("Clone returns a deep copy of the node, ready to be re-used elsewhere in the tree.") 16 | f.Func().Id("Clone").Params(Id("n").Id("Node")).Id("Node").BlockFunc(func(g *Group) { 17 | g.Switch(Id("n").Op(":=").Id("n").Assert(Id("type"))).BlockFunc(func(g *Group) { 18 | for _, nodeName := range names { 19 | g.Case(Op("*").Qual(DSTPATH, nodeName)).BlockFunc(func(g *Group) { 20 | g.Id("out").Op(":=").Op("&").Id(nodeName).Values() 21 | 22 | if nodeName != "Package" { 23 | g.Line() 24 | g.Id("out").Dot("Decs").Dot("Before").Op("=").Id("n").Dot("Decs").Dot("Before") 25 | } 26 | 27 | for _, frag := range data.Info[nodeName] { 28 | switch frag := frag.(type) { 29 | case data.Init: 30 | g.Line().Commentf("Init: %s", frag.Name) 31 | g.Add(frag.Field.Get("out")).Op("=").Op("&").Id(frag.Type.TypeName()).Values() 32 | case data.Decoration: 33 | g.Line().Commentf("Decoration: %s", frag.Name) 34 | g.Id("out").Dot("Decs").Dot(frag.Name).Op("=").Append(Id("out").Dot("Decs").Dot(frag.Name), Id("n").Dot("Decs").Dot(frag.Name).Op("...")) 35 | case data.Token: 36 | if frag.NoPosField != nil { 37 | g.Line().Commentf("Token: %s", frag.Name) 38 | g.Add(frag.NoPosField.Get("out")).Op("=").Add(frag.NoPosField.Get("n")) 39 | } 40 | if frag.TokenField != nil { 41 | g.Line().Commentf("Token: %s", frag.Name) 42 | g.Add(frag.TokenField.Get("out")).Op("=").Add(frag.TokenField.Get("n")) 43 | } 44 | if frag.ExistsField != nil { 45 | g.Line().Commentf("Token: %s", frag.Name) 46 | g.Add(frag.ExistsField.Get("out")).Op("=").Add(frag.ExistsField.Get("n")) 47 | } 48 | case data.String: 49 | g.Line().Commentf("String: %s", frag.Name) 50 | g.Add(frag.ValueField.Get("out")).Op("=").Add(frag.ValueField.Get("n")) 51 | case data.Node: 52 | g.Line().Commentf("Node: %s", frag.Name) 53 | g.If(frag.Field.Get("n").Op("!=").Nil()).Block( 54 | frag.Field.Get("out").Op("=").Id("Clone").Call(frag.Field.Get("n")).Assert(frag.Type.Literal(DSTPATH)), 55 | ) 56 | case data.List: 57 | g.Line().Commentf("List: %s", frag.Name) 58 | g.For(List(Id("_"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).Block( 59 | frag.Field.Get("out").Op("=").Append( 60 | frag.Field.Get("out"), 61 | Id("Clone").Call(Id("v")).Assert(frag.Elem.Literal(DSTPATH)), 62 | ), 63 | ) 64 | case data.Map: 65 | g.Line().Commentf("Map: %s", frag.Name) 66 | g.Add(frag.Field.Get("out")).Op("=").Map(String()).Add(frag.Elem.Literal(DSTPATH)).Values() 67 | g.For(List(Id("k"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).BlockFunc(func(g *Group) { 68 | if frag.Elem.TypeName() == "Object" { 69 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("CloneObject").Call(Id("v")) 70 | } else { 71 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("Clone").Call(Id("v")).Assert(frag.Elem.Literal(DSTPATH)) 72 | } 73 | }) 74 | case data.Value: 75 | g.Line().Commentf("Value: %s", frag.Name) 76 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Field.Get("n")) 77 | case data.Scope: 78 | g.Line().Commentf("Scope: %s", frag.Name) 79 | g.Add(frag.Field.Get("out")).Op("=").Id("CloneScope").Call(frag.Field.Get("n")) 80 | case data.Object: 81 | g.Line().Commentf("Object: %s", frag.Name) 82 | g.Add(frag.Field.Get("out")).Op("=").Id("CloneObject").Call(frag.Field.Get("n")) 83 | case data.Bad: 84 | g.Line().Comment("Bad") 85 | g.Add(frag.LengthField.Get("out")).Op("=").Add(frag.LengthField.Get("n")) 86 | case data.PathDecoration: 87 | g.Line().Commentf("Path: %s", frag.Name) 88 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Field.Get("n")) 89 | case data.SpecialDecoration: 90 | // ignore 91 | default: 92 | panic(fmt.Sprintf("unknown fragment type %T", frag)) 93 | } 94 | } 95 | 96 | if nodeName != "Package" { 97 | g.Line() 98 | g.Id("out").Dot("Decs").Dot("After").Op("=").Id("n").Dot("Decs").Dot("After") 99 | } 100 | 101 | g.Line() 102 | g.Return(Id("out")) 103 | }) 104 | } 105 | g.Default().Block( 106 | Panic(Qual("fmt", "Sprintf").Call(Lit("%T"), Id("n"))), 107 | ) 108 | }) 109 | }) 110 | 111 | return f.Save("./clone-generated.go") 112 | } 113 | -------------------------------------------------------------------------------- /gendst/data/positions.go: -------------------------------------------------------------------------------- 1 | // File 2 | /*Start*/ package /*Package*/ data /*Name*/ 3 | 4 | // notest 5 | 6 | // ImportSpec 7 | import ( 8 | /*Start*/ fmt /*Name*/ "fmt" /*End*/ 9 | ) 10 | 11 | // -- 12 | 13 | var a []int 14 | var i = 1 15 | var b bool 16 | var f interface{} = 1 17 | var p = &i 18 | var c chan int 19 | 20 | // Field 21 | type A struct { 22 | /*Start*/ A int /*Type*/ `a:"a"` /*End*/ 23 | } 24 | 25 | // FieldList 26 | type A1 struct /*Start*/ { /*Opening*/ 27 | a, b int 28 | c string 29 | } /*End*/ 30 | 31 | // Ellipsis 32 | func B(a /*Start*/ ... /*Ellipsis*/ int /*End*/) {} 33 | 34 | // FuncLit 35 | var C = /*Start*/ func(a int, b ...int) (c int) /*Type*/ { return 0 } /*End*/ 36 | 37 | // CompositeLit 38 | var D = /*Start*/ A /*Type*/ { /*Lbrace*/ A: 0} /*End*/ 39 | 40 | // ParenExpr 41 | var E = /*Start*/ ( /*Lparen*/ 1 + 1 /*X*/) /*End*/ / 2 42 | 43 | // SelectorExpr 44 | var F = /*Start*/ tt. /*X*/ F /*End*/ () 45 | 46 | // IndexExpr 47 | var G = /*Start*/ []int{0} /*X*/ [ /*Lbrack*/ 0 /*Index*/] /*End*/ 48 | 49 | // SliceExpr(0) 50 | var H = /*Start*/ []int{0, 1, 2} /*X*/ [ /*Lbrack*/ 1: /*Low*/ 2: /*High*/ 3 /*Max*/] /*End*/ 51 | 52 | // SliceExpr(1) 53 | var H1 = /*Start*/ []int{0, 1, 2} /*X*/ [ /*Lbrack*/ 1: /*Low*/ 2 /*High*/] /*End*/ 54 | 55 | // SliceExpr(2) 56 | var H2 = /*Start*/ []int{0} /*X*/ [: /*Low*/] /*End*/ 57 | 58 | // SliceExpr(3) 59 | var H3 = /*Start*/ []int{0} /*X*/ [ /*Lbrack*/ 1: /*Low*/] /*End*/ 60 | 61 | // SliceExpr(4) 62 | var H4 = /*Start*/ []int{0, 1, 2} /*X*/ [: /*Low*/ 2 /*High*/] /*End*/ 63 | 64 | // SliceExpr(5) 65 | var H5 = /*Start*/ []int{0, 1, 2} /*X*/ [: /*Low*/ 2: /*High*/ 3 /*Max*/] /*End*/ 66 | 67 | // TypeAssertExpr 68 | var J = /*Start*/ f. /*X*/ ( /*Lparen*/ int /*Type*/) /*End*/ 69 | 70 | // CallExpr 71 | var L = /*Start*/ C /*Fun*/ ( /*Lparen*/ 0, []int{}... /*Ellipsis*/) /*End*/ 72 | 73 | // StarExpr 74 | var N = /*Start*/ * /*Star*/ p /*End*/ 75 | 76 | // UnaryExpr 77 | var O = /*Start*/ ^ /*Op*/ 1 /*End*/ 78 | 79 | // BinaryExpr 80 | var P = /*Start*/ 1 /*X*/ & /*Op*/ 2 /*End*/ 81 | 82 | // KeyValueExpr 83 | var Q = map[string]string{ 84 | /*Start*/ "a" /*Key*/ : /*Colon*/ "a", /*End*/ 85 | } 86 | 87 | // ArrayType 88 | type R /*Start*/ [ /*Lbrack*/ 1] /*Len*/ int /*End*/ 89 | 90 | // StructType 91 | type S /*Start*/ struct /*Struct*/ { 92 | A int 93 | } /*End*/ 94 | 95 | // FuncType 96 | type T /*Start*/ func /*Func*/ (a int) /*Params*/ (b int) /*End*/ 97 | 98 | // InterfaceType 99 | type U /*Start*/ interface /*Interface*/ { 100 | A() 101 | } /*End*/ 102 | 103 | // MapType 104 | type V /*Start*/ map[ /*Map*/ int] /*Key*/ int /*End*/ 105 | 106 | // ChanType(0) 107 | type W /*Start*/ chan /*Begin*/ int /*End*/ 108 | 109 | // ChanType(1) 110 | type X /*Start*/ <-chan /*Begin*/ int /*End*/ 111 | 112 | // ChanType(2) 113 | type Y /*Start*/ chan /*Begin*/ <- /*Arrow*/ int /*End*/ 114 | 115 | // -- 116 | 117 | func Z() { 118 | // LabeledStmt 119 | /*Start*/ 120 | A /*Label*/ : /*Colon*/ 121 | print("Stmt") /*End*/ 122 | 123 | // BranchStmt 124 | /*Start*/ 125 | goto /*Tok*/ A /*End*/ 126 | 127 | // Ident(0) 128 | /*Start*/ 129 | i /*End*/ ++ 130 | 131 | // Ident(1) 132 | /*Start*/ 133 | fmt. /*X*/ Print /*End*/ () 134 | 135 | // SendStmt 136 | /*Start*/ 137 | c /*Chan*/ <- /*Arrow*/ 0 /*End*/ 138 | 139 | // IncDecStmt 140 | /*Start*/ 141 | i /*X*/ ++ /*End*/ 142 | 143 | // AssignStmt 144 | /*Start*/ 145 | i = /*Tok*/ 1 /*End*/ 146 | 147 | // GoStmt 148 | /*Start*/ 149 | go /*Go*/ func() {}() /*End*/ 150 | 151 | // DeferStmt 152 | /*Start*/ 153 | defer /*Defer*/ func() {}() /*End*/ 154 | 155 | // ReturnStmt 156 | func() int { 157 | /*Start*/ return /*Return*/ 1 /*End*/ 158 | }() 159 | 160 | // BlockStmt(0) 161 | if true /*Start*/ { /*Lbrace*/ 162 | i++ 163 | } /*End*/ 164 | 165 | // BlockStmt(1) 166 | func() /*Start*/ { /*Lbrace*/ i++ } /*End*/ () 167 | 168 | // IfStmt 169 | /*Start*/ 170 | if /*If*/ a := b; /*Init*/ a /*Cond*/ { 171 | i++ 172 | } else /*Else*/ { 173 | i++ 174 | } /*End*/ 175 | 176 | // CaseClause 177 | switch i { 178 | /*Start*/ case /*Case*/ 1: /*Colon*/ 179 | i++ /*End*/ 180 | } 181 | 182 | // SwitchStmt(0) 183 | /*Start*/ 184 | switch /*Switch*/ i /*Tag*/ { 185 | } /*End*/ 186 | 187 | // SwitchStmt(1) 188 | /*Start*/ 189 | switch /*Switch*/ a := i; /*Init*/ a /*Tag*/ { 190 | } /*End*/ 191 | 192 | // TypeSwitchStmt(0) 193 | /*Start*/ 194 | switch /*Switch*/ f.(type) /*Assign*/ { 195 | } /*End*/ 196 | 197 | // TypeSwitchStmt(1) 198 | /*Start*/ 199 | switch /*Switch*/ g := f.(type) /*Assign*/ { 200 | case int: 201 | print(g) 202 | } /*End*/ 203 | 204 | // TypeSwitchStmt(2) 205 | /*Start*/ 206 | switch /*Switch*/ g := f; /*Init*/ g := g.(type) /*Assign*/ { 207 | case int: 208 | print(g) 209 | } /*End*/ 210 | 211 | // CommClause 212 | select { 213 | /*Start*/ case /*Case*/ a := <-c /*Comm*/ : /*Colon*/ 214 | print(a) /*End*/ 215 | } 216 | 217 | // SelectStmt 218 | /*Start*/ 219 | select /*Select*/ { 220 | } /*End*/ 221 | 222 | // ForStmt(0) 223 | /*Start*/ 224 | for /*For*/ { 225 | i++ 226 | } /*End*/ 227 | 228 | // ForStmt(1) 229 | /*Start*/ 230 | for /*For*/ i < 1 /*Cond*/ { 231 | i++ 232 | } /*End*/ 233 | 234 | // ForStmt(2) 235 | /*Start*/ 236 | for /*For*/ i = 0; /*Init*/ i < 10; /*Cond*/ i++ /*Post*/ { 237 | i++ 238 | } /*End*/ 239 | 240 | // RangeStmt(0) 241 | /*Start*/ 242 | for range /*Range*/ a /*X*/ { 243 | } /*End*/ 244 | 245 | // RangeStmt(1) 246 | /*Start*/ 247 | for /*For*/ k /*Key*/ := range /*Range*/ a /*X*/ { 248 | print(k) 249 | } /*End*/ 250 | 251 | // RangeStmt(2) 252 | /*Start*/ 253 | for /*For*/ k /*Key*/, v /*Value*/ := range /*Range*/ a /*X*/ { 254 | print(k, v) 255 | } /*End*/ 256 | 257 | // ValueSpec(0) 258 | var ( 259 | /*Start*/ j = /*Assign*/ 1 /*End*/ 260 | ) 261 | 262 | // ValueSpec(1) 263 | var ( 264 | /*Start*/ k, l = /*Assign*/ 1, 2 /*End*/ 265 | ) 266 | 267 | // ValueSpec(2) 268 | var ( 269 | /*Start*/ m, n int = /*Assign*/ 1, 2 /*End*/ 270 | ) 271 | 272 | // -- 273 | 274 | print(j, k, l, m, n) 275 | 276 | // TypeSpec(0) 277 | type ( 278 | /*Start*/ T1 /*Name*/ []int /*End*/ 279 | ) 280 | 281 | // TypeSpec(1) 282 | type ( 283 | /*Start*/ T2 = /*Name*/ T1 /*End*/ 284 | ) 285 | 286 | // TypeSpec(2) 287 | type ( 288 | /*Start*/ T3 /*Name*/ [P any, Q any] /*TypeParams*/ []P /*End*/ 289 | ) 290 | 291 | // IndexListExpr 292 | var T4 /*Start*/ T3 /*X*/ [ /*Lbrack*/ int, string /*Indices*/] /*End*/ 293 | 294 | // GenDecl(0) 295 | /*Start*/ 296 | const /*Tok*/ ( /*Lparen*/ 297 | a, b = 1, 2 298 | c = 3 299 | ) /*End*/ 300 | 301 | // GenDecl(1) 302 | /*Start*/ 303 | const /*Tok*/ d = 1 /*End*/ 304 | 305 | // -- 306 | 307 | print(T4) 308 | } 309 | 310 | // FuncDecl(0) 311 | /*Start*/ 312 | func /*Func*/ d /*Name*/ (d, e int) /*Params*/ { 313 | return 314 | } /*End*/ 315 | 316 | // FuncDecl(1) 317 | /*Start*/ 318 | func /*Func*/ TP /*Name*/ [P any] /*TypeParams*/ (a int) /*Params*/ (b P) /*Results*/ { 319 | return b 320 | } /*End*/ 321 | 322 | // FuncDecl(2) 323 | /*Start*/ 324 | func /*Func*/ (a *A) /*Recv*/ e /*Name*/ (d, e int) /*Params*/ { 325 | return 326 | } /*End*/ 327 | 328 | // FuncDecl(3) 329 | /*Start*/ 330 | func /*Func*/ (a *A) /*Recv*/ f /*Name*/ (d, e int) /*Params*/ (f, g int) /*Results*/ { 331 | return 332 | } /*End*/ 333 | 334 | // -- 335 | 336 | type TT int 337 | 338 | func (TT) F() int { return 0 } 339 | 340 | var tt TT 341 | -------------------------------------------------------------------------------- /gendst/data/positions_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "testing" 4 | 5 | func Test(t *testing.T) { 6 | // just test this package compiles 7 | } 8 | -------------------------------------------------------------------------------- /gendst/decorator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/dst/gendst/data" 7 | . "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | // notest 11 | 12 | const DSTPATH = "github.com/dave/dst" 13 | 14 | func generateDecorator(names []string) error { 15 | 16 | f := NewFile("decorator") 17 | f.ImportName(DSTPATH, "dst") 18 | 19 | f.Func().Params(Id("f").Op("*").Id("fileDecorator")).Id("decorateNode").Params( 20 | Id("parent").Qual("go/ast", "Node"), 21 | Id("parentName"), 22 | Id("parentField"), 23 | Id("parentFieldType").String(), 24 | Id("n").Qual("go/ast", "Node"), 25 | ).Params( 26 | Qual(DSTPATH, "Node"), 27 | Error(), 28 | ).BlockFunc(func(g *Group) { 29 | g.If(List(Id("dn"), Id("ok")).Op(":=").Id("f").Dot("Dst").Dot("Nodes").Index(Id("n")), Id("ok")).Block( 30 | Return(Id("dn"), Nil()), 31 | ) 32 | g.Switch(Id("n").Op(":=").Id("n").Assert(Id("type"))).BlockFunc(func(g *Group) { 33 | for _, nodeName := range names { 34 | g.Case(Op("*").Qual("go/ast", nodeName)).BlockFunc(func(g *Group) { 35 | 36 | switch nodeName { 37 | case "SelectorExpr": 38 | g.Line() 39 | g.Comment("Special case for *ast.SelectorExpr - replace with Ident if needed") 40 | g.List(Id("id"), Err()).Op(":=").Id("f").Dot("decorateSelectorExpr").Call(Id("parent"), Id("parentName"), Id("parentField"), Id("parentFieldType"), Id("n")) 41 | g.If(Err().Op("!=").Nil()).Block( 42 | Return(Nil(), Err()), 43 | ) 44 | g.If(Id("id").Op("!=").Nil()).Block( 45 | Return(Id("id"), Nil()), 46 | ) 47 | g.Line() 48 | } 49 | 50 | g.Id("out").Op(":=").Op("&").Qual(DSTPATH, nodeName).Values() 51 | 52 | g.Id("f").Dot("Dst").Dot("Nodes").Index(Id("n")).Op("=").Id("out") 53 | g.Id("f").Dot("Ast").Dot("Nodes").Index(Id("out")).Op("=").Id("n") 54 | 55 | if nodeName != "Package" { 56 | g.Line() 57 | g.Id("out").Dot("Decs").Dot("Before").Op("=").Id("f").Dot("before").Index(Id("n")) 58 | g.Id("out").Dot("Decs").Dot("After").Op("=").Id("f").Dot("after").Index(Id("n")) 59 | } 60 | for _, frag := range data.Info[nodeName] { 61 | switch frag := frag.(type) { 62 | case data.Init: 63 | g.Line().Commentf("Init: %s", frag.Name) 64 | g.Add(frag.Field.Get("out")).Op("=").Op("&").Qual(DSTPATH, frag.Type.TypeName()).Values() 65 | g.Id("f").Dot("Dst").Dot("Nodes").Index(frag.Field.Get("n")).Op("=").Add(frag.Field.Get("out")) 66 | g.Id("f").Dot("Ast").Dot("Nodes").Index(frag.Field.Get("out")).Op("=").Add(frag.Field.Get("n")) 67 | case data.Decoration: 68 | // nothing here 69 | case data.String: 70 | g.Line().Commentf("String: %s", frag.Name) 71 | if frag.ValueField != nil { 72 | g.Add(frag.ValueField.Get("out")).Op("=").Add(frag.ValueField.Get("n")) 73 | } 74 | case data.Token: 75 | g.Line().Commentf("Token: %s", frag.Name) 76 | if frag.PositionField != nil && frag.NoPosField != nil { 77 | g.If(frag.PositionField.Get("n").Op("==").Qual("go/token", "NoPos")).Block( 78 | frag.NoPosField.Get("out").Op("=").True(), 79 | ) 80 | } 81 | if frag.TokenField != nil { 82 | g.Add(frag.TokenField.Get("out")).Op("=").Add(frag.TokenField.Get("n")) 83 | } 84 | if frag.ExistsField != nil { 85 | g.Add(frag.ExistsField.Get("out")).Op("=").Add(frag.Exists.Get("n", true)) 86 | } 87 | case data.List: 88 | g.Line().Commentf("List: %s", frag.Name) 89 | g.For(List(Id("_"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).Block( 90 | List(Id("child"), Err()).Op(":=").Id("f").Dot("decorateNode").Call(Id("n"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Elem.TypeName()), Id("v")), 91 | If(Err().Op("!=").Nil()).Block( 92 | Return(Nil(), Err()), 93 | ), 94 | frag.Field.Get("out").Op("=").Append( 95 | frag.Field.Get("out"), 96 | Id("child").Assert(frag.Elem.Literal(DSTPATH)), 97 | ), 98 | ) 99 | case data.Map: 100 | g.Line().Commentf("Map: %s", frag.Name) 101 | g.Add(frag.Field.Get("out")).Op("=").Map(String()).Add(frag.Elem.Literal(DSTPATH)).Values() 102 | g.For(List(Id("k"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).BlockFunc(func(g *Group) { 103 | if frag.Elem.TypeName() == "Object" { 104 | // Special case for Package.Imports 105 | g.List(Id("ob"), Err()).Op(":=").Id("f").Dot("decorateObject").Call(Id("v")) 106 | g.If(Err().Op("!=").Nil()).Block( 107 | Return(Nil(), Err()), 108 | ) 109 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("ob") 110 | } else { 111 | g.List(Id("child"), Err()).Op(":=").Id("f").Dot("decorateNode").Call(Id("n"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Elem.TypeName()), Id("v")) 112 | g.If(Err().Op("!=").Nil()).Block( 113 | Return(Nil(), Err()), 114 | ) 115 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("child").Assert(frag.Elem.Literal(DSTPATH)) 116 | } 117 | }) 118 | case data.Node: 119 | g.Line().Commentf("Node: %s", frag.Name) 120 | g.If(frag.Field.Get("n").Op("!=").Nil()).Block( 121 | List(Id("child"), Err()).Op(":=").Id("f").Dot("decorateNode").Call(Id("n"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Type.TypeName()), frag.Field.Get("n")), 122 | If(Err().Op("!=").Nil()).Block( 123 | Return(Nil(), Err()), 124 | ), 125 | frag.Field.Get("out").Op("=").Id("child").Assert(frag.Type.Literal(DSTPATH)), 126 | ) 127 | case data.Bad: 128 | g.Line().Comment("Bad") 129 | g.Add(frag.LengthField.Get("out")).Op("=").Add(frag.Length.Get("n", true)) 130 | case data.Value: 131 | g.Line().Commentf("Value: %s", frag.Name) 132 | if frag.Value != nil { 133 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Value.Get("n", true)) 134 | } else { 135 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Field.Get("n")) 136 | } 137 | case data.Scope: 138 | g.Line().Commentf("Scope: %s", frag.Name) 139 | g.List(Id("scope"), Err()).Op(":=").Id("f").Dot("decorateScope").Call(frag.Field.Get("n")) 140 | g.If(Err().Op("!=").Nil()).Block( 141 | Return(Nil(), Err()), 142 | ) 143 | g.Add(frag.Field.Get("out")).Op("=").Id("scope") 144 | case data.Object: 145 | g.Line().Commentf("Object: %s", frag.Name) 146 | g.List(Id("ob"), Err()).Op(":=").Id("f").Dot("decorateObject").Call(frag.Field.Get("n")) 147 | g.If(Err().Op("!=").Nil()).Block( 148 | Return(Nil(), Err()), 149 | ) 150 | g.Add(frag.Field.Get("out")).Op("=").Id("ob") 151 | case data.PathDecoration: 152 | g.Line().Commentf("Path: %s", frag.Name) 153 | g.If(Id("f").Dot("Resolver").Op("!=").Nil()).Block( 154 | List(Id("path"), Err()).Op(":=").Id("f").Dot("resolvePath").Call(False(), Id("parent"), Id("parentName"), Id("parentField"), Id("parentFieldType"), Id("n")), 155 | If(Err().Op("!=").Nil()).Block( 156 | Return(Nil(), Err()), 157 | ), 158 | Add(frag.Field.Get("out")).Op("=").Id("path"), 159 | ) 160 | case data.SpecialDecoration: 161 | // ignore 162 | default: 163 | panic(fmt.Sprintf("unknown fragment type %T", frag)) 164 | } 165 | } 166 | 167 | g.Line() 168 | var found bool 169 | decs := If(List(Id("nd"), Id("ok")).Op(":=").Id("f").Dot("decorations").Index(Id("n")), Id("ok")).BlockFunc(func(g *Group) { 170 | for _, frag := range data.Info[nodeName] { 171 | switch frag := frag.(type) { 172 | case data.Decoration: 173 | found = true 174 | g.If(List(Id("decs"), Id("ok")).Op(":=").Id("nd").Index(Lit(frag.Name)), Id("ok")).Block( 175 | Id("out").Dot("Decs").Dot(frag.Name).Op("=").Id("decs"), 176 | ) 177 | } 178 | } 179 | }) 180 | if found { 181 | g.Add(decs) 182 | } 183 | 184 | g.Line() 185 | g.Return(Id("out"), Nil()) 186 | 187 | }) 188 | } 189 | }) 190 | g.Return(Nil(), Nil()) 191 | 192 | }) 193 | 194 | return f.Save("./decorator/decorator-node-generated.go") 195 | } 196 | 197 | func generateDecoratorTestHelper(names []string) error { 198 | f := NewFile("dstutil") 199 | f.ImportName(DSTPATH, "dst") 200 | f.Func().Id("decorations").Params(Id("n").Qual(DSTPATH, "Node")).Params(Id("before"), Id("after").Qual(DSTPATH, "SpaceType"), Id("points").Index().Id("DecorationPoint")).BlockFunc(func(g *Group) { 201 | g.Switch(Id("n").Op(":=").Id("n").Assert(Id("type"))).BlockFunc(func(g *Group) { 202 | for _, nodeName := range names { 203 | g.Case(Op("*").Qual(DSTPATH, nodeName)).BlockFunc(func(g *Group) { 204 | if nodeName != "Package" { 205 | g.Id("before").Op("=").Id("n").Dot("Decs").Dot("Before") 206 | g.Id("after").Op("=").Id("n").Dot("Decs").Dot("After") 207 | } 208 | for _, frag := range data.Info[nodeName] { 209 | switch frag := frag.(type) { 210 | case data.Decoration: 211 | g.Id("points").Op("=").Append(Id("points"), Id("DecorationPoint").Values(Lit(frag.Name), Id("n").Dot("Decs").Dot(frag.Name))) 212 | } 213 | } 214 | }) 215 | } 216 | }) 217 | g.Return() 218 | }) 219 | return f.Save("./dstutil/decorations-generated.go") 220 | } 221 | -------------------------------------------------------------------------------- /gendst/dst.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "path/filepath" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/dave/dst/gendst/data" 13 | . "github.com/dave/jennifer/jen" 14 | "golang.org/x/tools/go/loader" 15 | ) 16 | 17 | // notest 18 | 19 | func generateDst(names []string) error { 20 | 21 | f := NewFile("dst") 22 | 23 | f.Comment("notest") 24 | f.Line() 25 | 26 | for _, name := range names { 27 | 28 | if name == "Package" { 29 | f.Comment("Decorations is nil for Package nodes.") 30 | } else { 31 | f.Comment("Decorations returns the decorations that are common to all nodes (Before, Start, End, After).") 32 | } 33 | f.Func().Params(Id("n").Op("*").Id(name)).Id("Decorations").Params().Op("*").Id("NodeDecs").BlockFunc(func(g *Group) { 34 | if name == "Package" { 35 | g.Return(Nil()) 36 | } else { 37 | g.Return(Op("&").Id("n").Dot("Decs").Dot("NodeDecs")) 38 | } 39 | }) 40 | } 41 | return f.Save("./decorations-node-generated.go") 42 | } 43 | 44 | func generateDstDecs(names []string) error { 45 | 46 | path := "github.com/dave/dst/gendst/data" 47 | conf := loader.Config{ParserMode: parser.ParseComments} 48 | conf.Import(path) 49 | prog, err := conf.Load() 50 | if err != nil { 51 | panic(err) 52 | } 53 | var astFile *ast.File 54 | for _, v := range prog.Package(path).Files { 55 | _, name := filepath.Split(prog.Fset.File(v.Pos()).Name()) 56 | if name == "positions.go" { 57 | astFile = v 58 | break 59 | } 60 | } 61 | buf := &bytes.Buffer{} 62 | if err := format.Node(buf, prog.Fset, astFile); err != nil { 63 | panic(err) 64 | } 65 | source := buf.String() 66 | reg := regexp.MustCompile(`// ([a-zA-Z]+)`) 67 | type part struct { 68 | name string 69 | start, end int 70 | } 71 | var parts []part 72 | for _, cg := range astFile.Comments { 73 | for _, c := range cg.List { 74 | if strings.HasPrefix(c.Text, "// --") { 75 | if len(parts) > 0 && parts[len(parts)-1].end == -1 { 76 | parts[len(parts)-1].end = int(c.Pos() - 1) 77 | } 78 | continue 79 | } 80 | if matches := reg.FindStringSubmatch(c.Text); matches != nil { 81 | name := matches[1] 82 | pos := c.End() 83 | prev := c.Pos() - 1 84 | if len(parts) > 0 && parts[len(parts)-1].end == -1 { 85 | parts[len(parts)-1].end = int(prev) 86 | } 87 | parts = append(parts, part{name, int(pos), -1}) 88 | } 89 | } 90 | } 91 | if len(parts) > 0 && parts[len(parts)-1].end == -1 { 92 | parts[len(parts)-1].end = int(astFile.End()) 93 | } 94 | 95 | f := NewFile("dst") 96 | for _, name := range names { 97 | // type Decorations struct { 98 | // //... 99 | // } 100 | f.Line() 101 | f.Commentf("%sDecorations holds decorations for %s:", name, name) 102 | f.Comment("") 103 | for _, part := range parts { 104 | if part.name != name { 105 | continue 106 | } 107 | text := source[part.start:part.end] 108 | indented := text[0] == '\t' 109 | text = strings.TrimSpace(text) 110 | var indent string 111 | if !indented || name == "LabeledStmt" { // LabeledStmt special case because comment is in wrong position 112 | indent = "\t" 113 | } 114 | text = "// \t" + strings.Replace(text, "\n", "\n// "+indent, -1) 115 | f.Comment(text) 116 | f.Comment("") 117 | } 118 | f.Type().Id(name + "Decorations").StructFunc(func(g *Group) { 119 | g.Id("NodeDecs") 120 | for _, frag := range data.Info[name] { 121 | switch frag := frag.(type) { 122 | case data.Decoration: 123 | if frag.Name == "Start" || frag.Name == "End" { 124 | continue 125 | } 126 | g.Id(frag.Name).Id("Decorations") 127 | } 128 | } 129 | }) 130 | } 131 | return f.Save("./decorations-types-generated.go") 132 | } 133 | -------------------------------------------------------------------------------- /gendst/fragger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/dst/gendst/data" 7 | . "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | // notest 11 | 12 | func generateFragger(names []string) error { 13 | f := NewFile("decorator") 14 | f.Func().Params(Id("f").Op("*").Id("fileDecorator")).Id("addNodeFragments").Params(Id("n").Qual("go/ast", "Node")).Block( 15 | If(Id("n").Dot("Pos").Call().Dot("IsValid").Call()).Block( 16 | Id("f").Dot("cursor").Op("=").Int().Parens(Id("n").Dot("Pos").Call()), 17 | ), 18 | Switch(Id("n").Op(":=").Id("n").Assert(Type())).BlockFunc(func(g *Group) { 19 | for _, nodeName := range names { 20 | g.Case(Op("*").Qual("go/ast", nodeName)).BlockFunc(func(g *Group) { 21 | for _, frag := range data.Info[nodeName] { 22 | switch frag := frag.(type) { 23 | case data.Decoration: 24 | 25 | if frag.Disable { 26 | continue 27 | } 28 | 29 | g.Line().Commentf("Decoration: %s", frag.Name) 30 | 31 | pos := Qual("go/token", "NoPos") 32 | switch frag.Name { 33 | case "Start": 34 | pos = Id("n").Dot("Pos").Call() 35 | case "End": 36 | pos = Id("n").Dot("End").Call() 37 | } 38 | 39 | process := Id("f").Dot("addDecorationFragment").Call(Id("n"), Lit(frag.Name), pos) 40 | 41 | if frag.Use != nil { 42 | g.If(frag.Use.Get("n", true)).Block(process) 43 | } else { 44 | g.Add(process) 45 | } 46 | case data.Node: 47 | g.Line().Commentf("Node: %s", frag.Name) 48 | g.If(frag.Field.Get("n").Op("!=").Nil()).Block( 49 | Id("f").Dot("addNodeFragments").Call(frag.Field.Get("n")), 50 | ) 51 | case data.List: 52 | g.Line().Commentf("List: %s", frag.Name) 53 | g.For(List(Id("_"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).Block( 54 | Id("f").Dot("addNodeFragments").Call(Id("v")), 55 | ) 56 | case data.Map: 57 | g.Line().Commentf("Map: %s", frag.Name) 58 | if frag.Elem.TypeName() != "Object" { 59 | g.For(List(Id("_"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).Block( 60 | Id("f").Dot("addNodeFragments").Call(Id("v")), 61 | ) 62 | } 63 | case data.Token: 64 | g.Line().Commentf("Token: %s", frag.Name) 65 | pos := Qual("go/token", "NoPos") 66 | if frag.PositionField != nil { 67 | pos = frag.PositionField.Get("n") 68 | } 69 | process := Id("f").Dot("addTokenFragment").Call(Id("n"), frag.Token.Get("n", true), pos) 70 | if frag.Exists != nil { 71 | g.If(frag.Exists.Get("n", true)).Block(process) 72 | } else { 73 | g.Add(process) 74 | } 75 | case data.String: 76 | g.Line().Commentf("String: %s", frag.Name) 77 | pos := Qual("go/token", "NoPos") 78 | if frag.PositionField != nil { 79 | pos = frag.PositionField.Get("n") 80 | } 81 | g.Id("f").Dot("addStringFragment").Call(Id("n"), frag.ValueField.Get("n"), pos) 82 | case data.Bad: 83 | g.Line().Comment("Bad") 84 | g.Id("f").Dot("addBadFragment").Call(Id("n"), frag.FromField.Get("n"), Int().Parens(frag.ToField.Get("n").Op("-").Add(frag.FromField.Get("n")))) 85 | case data.Init, data.Value, data.Scope, data.Object, data.SpecialDecoration, data.PathDecoration: 86 | // do nothing 87 | default: 88 | panic(fmt.Sprintf("unknown fragment type %T", frag)) 89 | } 90 | } 91 | g.Line() 92 | }) 93 | } 94 | }), 95 | ) 96 | return f.Save("./decorator/decorator-fragment-generated.go") 97 | } 98 | -------------------------------------------------------------------------------- /gendst/gendst_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func Test(t *testing.T) { 6 | // just test it compiles 7 | } 8 | -------------------------------------------------------------------------------- /gendst/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/dave/dst/gendst/data" 7 | ) 8 | 9 | // notest 10 | 11 | func main() { 12 | if err := run(); err != nil { 13 | panic(err) 14 | } 15 | } 16 | 17 | func run() error { 18 | var names []string 19 | for name := range data.Info { 20 | names = append(names, name) 21 | } 22 | sort.Strings(names) 23 | 24 | if err := generateDst(names); err != nil { 25 | return err 26 | } 27 | if err := generateDstDecs(names); err != nil { 28 | return err 29 | } 30 | if err := generateFragger(names); err != nil { 31 | return err 32 | } 33 | if err := generateDecorator(names); err != nil { 34 | return err 35 | } 36 | if err := generateDecoratorTestHelper(names); err != nil { 37 | return err 38 | } 39 | if err := generateRestorer(names); err != nil { 40 | return err 41 | } 42 | if err := generateClone(names); err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /gendst/restorer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/dst/gendst/data" 7 | . "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | // notest 11 | 12 | func generateRestorer(names []string) error { 13 | 14 | f := NewFile("decorator") 15 | f.ImportName(DSTPATH, "dst") 16 | // func (r *restorer) restoreNode(n dst.Node, allowDuplicate bool) ast.Node { 17 | // switch n := n.(type) { 18 | // case : 19 | // ... 20 | // default: 21 | // panic(...) 22 | // } 23 | // } 24 | f.Func().Params(Id("r").Op("*").Id("FileRestorer")).Id("restoreNode").Params( 25 | Id("n").Qual(DSTPATH, "Node"), 26 | Id("parentName"), 27 | Id("parentField"), 28 | Id("parentFieldType").String(), 29 | Id("allowDuplicate").Bool(), 30 | ).Qual("go/ast", "Node").BlockFunc(func(g *Group) { 31 | g.If(List(Id("an"), Id("ok")).Op(":=").Id("r").Dot("Ast").Dot("Nodes").Index(Id("n")), Id("ok")).Block( 32 | If(Id("allowDuplicate")).Block( 33 | Return(Id("an")), 34 | ).Else().Block( 35 | Panic(Qual("fmt", "Sprintf").Call(Lit("duplicate node: %#v"), Id("n"))), 36 | ), 37 | ) 38 | g.Switch(Id("n").Op(":=").Id("n").Assert(Id("type"))).BlockFunc(func(g *Group) { 39 | for _, nodeName := range names { 40 | g.Case(Op("*").Qual(DSTPATH, nodeName)).BlockFunc(func(g *Group) { 41 | if nodeName == "Ident" { 42 | g.Line() 43 | g.Comment("Special case for *dst.Ident - replace with SelectorExpr if needed") 44 | g.Id("sel").Op(":=").Id("r").Dot("restoreIdent").Call(Id("n"), Id("parentName"), Id("parentField"), Id("parentFieldType"), Id("allowDuplicate")) 45 | g.If(Id("sel").Op("!=").Nil()).Block( 46 | Return(Id("sel")), 47 | ) 48 | g.Line() 49 | } 50 | g.Id("out").Op(":=").Op("&").Qual("go/ast", nodeName).Values() 51 | g.Id("r").Dot("Ast").Dot("Nodes").Index(Id("n")).Op("=").Id("out") 52 | g.Id("r").Dot("Dst").Dot("Nodes").Index(Id("out")).Op("=").Id("n") 53 | 54 | if nodeName != "Package" { 55 | g.Id("r").Dot("applySpace").Call(Id("n"), Lit("Before"), Id("n").Dot("Decs").Dot("Before")) 56 | } 57 | 58 | for _, frag := range data.Info[nodeName] { 59 | switch frag := frag.(type) { 60 | case data.Init: 61 | g.Line().Commentf("Init: %s", frag.Name) 62 | g.Add(frag.Field.Get("out")).Op("=").Op("&").Qual("go/ast", frag.Type.TypeName()).Values() 63 | case data.Decoration: 64 | g.Line().Commentf("Decoration: %s", frag.Name) 65 | g.Id("r").Dot("applyDecorations").Call(Id("out"), Lit(frag.Name), Id("n").Dot("Decs").Dot(frag.Name), Do(func(s *Statement) { s.Lit(frag.Name == "End") })) 66 | case data.SpecialDecoration: 67 | g.Line().Commentf("Special decoration: %s", frag.Name) 68 | g.Id("r").Dot("applyDecorations").Call(Id("out"), Lit(frag.Name), frag.Decs.Get("n").Dot(frag.Name), Lit(frag.End)) 69 | case data.Token: 70 | g.Line().Commentf("Token: %s", frag.Name) 71 | position := Null() 72 | value := Null() 73 | if frag.PositionField != nil { 74 | if frag.NoPosField != nil { 75 | position = If(frag.NoPosField.Get("n")).Block( 76 | frag.PositionField.Get("out").Op("=").Qual("go/token", "NoPos"), 77 | ).Else().Block( 78 | frag.PositionField.Get("out").Op("=").Id("r").Dot("cursor"), 79 | ) 80 | } else { 81 | position = frag.PositionField.Get("out").Op("=").Id("r").Dot("cursor") 82 | } 83 | } 84 | if frag.TokenField != nil { 85 | value = frag.TokenField.Get("out").Op("=").Add(frag.Token.Get("n", false)) 86 | } 87 | action := Id("r").Dot("cursor").Op("+=").Qual("go/token", "Pos").Parens( 88 | Len(frag.Token.Get("n", false).Dot("String").Call()), 89 | ) 90 | if frag.Exists != nil { 91 | g.If(frag.Exists.Get("n", false)).Block(value, position, action) 92 | } else { 93 | g.Add(value) 94 | g.Add(position) 95 | g.Add(action) 96 | } 97 | case data.String: 98 | g.Line().Commentf("String: %s", frag.Name) 99 | if frag.Literal { 100 | g.Id("r").Dot("applyLiteral").Call(frag.ValueField.Get("n")) 101 | } 102 | if frag.PositionField != nil { 103 | g.Add(frag.PositionField.Get("out")).Op("=").Id("r").Dot("cursor") 104 | } 105 | g.Add(frag.ValueField.Get("out")).Op("=").Add(frag.ValueField.Get("n")) 106 | g.Id("r").Dot("cursor").Op("+=").Qual("go/token", "Pos").Parens( 107 | Len(frag.ValueField.Get("n")), 108 | ) 109 | case data.Node: 110 | g.Line().Commentf("Node: %s", frag.Name) 111 | /* 112 | if n.Elt != nil { 113 | out.Elt = r.restoreNode(n.Elt).(ast.Expr) 114 | } 115 | */ 116 | g.If(frag.Field.Get("n").Op("!=").Nil()).Block( 117 | frag.Field.Get("out").Op("=").Id("r").Dot("restoreNode").Call(frag.Field.Get("n"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Type.TypeName()), Id("allowDuplicate")).Assert(frag.Type.Literal("go/ast")), 118 | ) 119 | case data.List: 120 | if frag.NoRestore { 121 | continue 122 | } 123 | g.Line().Commentf("List: %s", frag.Name) 124 | g.For(List(Id("_"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).Block( 125 | frag.Field.Get("out").Op("=").Append( 126 | frag.Field.Get("out"), 127 | Id("r").Dot("restoreNode").Call(Id("v"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Elem.TypeName()), Id("allowDuplicate")).Assert(frag.Elem.Literal("go/ast")), 128 | ), 129 | ) 130 | case data.Map: 131 | g.Line().Commentf("Map: %s", frag.Name) 132 | g.Add(frag.Field.Get("out")).Op("=").Map(String()).Add(frag.Elem.Literal("go/ast")).Values() 133 | g.For(List(Id("k"), Id("v")).Op(":=").Range().Add(frag.Field.Get("n"))).BlockFunc(func(g *Group) { 134 | if frag.Elem.TypeName() == "Object" { 135 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("r").Dot("restoreObject").Call(Id("v")) 136 | } else { 137 | g.Add(frag.Field.Get("out")).Index(Id("k")).Op("=").Id("r").Dot("restoreNode").Call(Id("v"), Lit(nodeName), Lit(frag.Field.FieldName()), Lit(frag.Elem.TypeName()), Id("allowDuplicate")).Assert(frag.Elem.Literal("go/ast")) 138 | } 139 | }) 140 | case data.Bad: 141 | g.Line().Comment("Bad") 142 | g.Add(frag.FromField.Get("out")).Op("=").Id("r").Dot("cursor") 143 | g.Id("r").Dot("cursor").Op("+=").Qual("go/token", "Pos").Parens(frag.Length.Get("n", false)) 144 | g.Add(frag.ToField.Get("out")).Op("=").Id("r").Dot("cursor") 145 | case data.Value: 146 | g.Line().Commentf("Value: %s", frag.Name) 147 | if frag.Value != nil { 148 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Value.Get("n", false)) 149 | } else { 150 | g.Add(frag.Field.Get("out")).Op("=").Add(frag.Field.Get("n")) 151 | } 152 | case data.Scope: 153 | g.Line().Commentf("Scope: %s", frag.Name) 154 | g.Add(frag.Field.Get("out")).Op("=").Id("r").Dot("restoreScope").Call(frag.Field.Get("n")) 155 | case data.Object: 156 | g.Line().Commentf("Object: %s", frag.Name) 157 | g.Add(frag.Field.Get("out")).Op("=").Id("r").Dot("restoreObject").Call(frag.Field.Get("n")) 158 | case data.PathDecoration: 159 | // nothing 160 | default: 161 | panic(fmt.Sprintf("unknown fragment type %T", frag)) 162 | } 163 | } 164 | 165 | if nodeName != "Package" { 166 | g.Id("r").Dot("applySpace").Call(Id("n"), Lit("After"), Id("n").Dot("Decs").Dot("After")) 167 | } 168 | 169 | g.Line() 170 | g.Return(Id("out")) 171 | }) 172 | } 173 | g.Default().Block( 174 | Panic(Qual("fmt", "Sprintf").Call(Lit("%T"), Id("n"))), 175 | ) 176 | }) 177 | }) 178 | 179 | return f.Save("./decorator/restorer-generated.go") 180 | } 181 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dave/dst 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dave/jennifer v1.5.0 7 | github.com/sergi/go-diff v1.2.0 8 | golang.org/x/tools v0.1.12 9 | gopkg.in/src-d/go-billy.v4 v4.3.2 10 | ) 11 | 12 | require ( 13 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 14 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 2 | github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14/go.mod h1:Sth2QfxfATb/nW4EsrSi2KyJmbcniZ8TgTaji17D6ms= 3 | github.com/dave/brenda v1.1.0/go.mod h1:4wCUr6gSlu5/1Tk7akE5X7UorwiQ8Rij0SKH3/BGMOM= 4 | github.com/dave/courtney v0.3.0/go.mod h1:BAv3hA06AYfNUjfjQr+5gc6vxeBVOupLqrColj+QSD8= 5 | github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= 6 | github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= 7 | github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= 8 | github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= 9 | github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc= 10 | github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 15 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 16 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 17 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 18 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 20 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 24 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 27 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 28 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 29 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 30 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 31 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 32 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 33 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 34 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 36 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 46 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 50 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 51 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 52 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 53 | golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 54 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 55 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 56 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 61 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 62 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 63 | gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= 64 | gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= 65 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 66 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 67 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 68 | -------------------------------------------------------------------------------- /print.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains printing support for ASTs. 6 | 7 | package dst 8 | 9 | import ( 10 | "fmt" 11 | "io" 12 | "os" 13 | "reflect" 14 | ) 15 | 16 | // A FieldFilter may be provided to Fprint to control the output. 17 | type FieldFilter func(name string, value reflect.Value) bool 18 | 19 | // NotNilFilter returns true for field values that are not nil; 20 | // it returns false otherwise. 21 | func NotNilFilter(_ string, v reflect.Value) bool { 22 | switch v.Kind() { 23 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 24 | return !v.IsNil() 25 | } 26 | return true 27 | } 28 | 29 | // Fprint prints the (sub-)tree starting at AST node x to w. 30 | // If fset != nil, position information is interpreted relative 31 | // to that file set. Otherwise positions are printed as integer 32 | // values (file set specific offsets). 33 | // 34 | // A non-nil FieldFilter f may be provided to control the output: 35 | // struct fields for which f(fieldname, fieldvalue) is true are 36 | // printed; all others are filtered from the output. Unexported 37 | // struct fields are never printed. 38 | func Fprint(w io.Writer, x interface{}, f FieldFilter) error { 39 | return fprint(w, x, f) 40 | } 41 | 42 | func fprint(w io.Writer, x interface{}, f FieldFilter) (err error) { 43 | // setup printer 44 | p := printer{ 45 | output: w, 46 | filter: f, 47 | ptrmap: make(map[interface{}]int), 48 | last: '\n', // force printing of line number on first line 49 | } 50 | 51 | // install error handler 52 | defer func() { 53 | if e := recover(); e != nil { 54 | err = e.(localError).err // re-panics if it's not a localError 55 | } 56 | }() 57 | 58 | // print x 59 | if x == nil { 60 | p.printf("nil\n") 61 | return 62 | } 63 | p.print(reflect.ValueOf(x)) 64 | p.printf("\n") 65 | 66 | return 67 | } 68 | 69 | // Print prints x to standard output, skipping nil fields. 70 | // Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter). 71 | func Print(x interface{}) error { 72 | return Fprint(os.Stdout, x, NotNilFilter) 73 | } 74 | 75 | type printer struct { 76 | output io.Writer 77 | filter FieldFilter 78 | ptrmap map[interface{}]int // *T -> line number 79 | indent int // current indentation level 80 | last byte // the last byte processed by Write 81 | line int // current line number 82 | } 83 | 84 | var indent = []byte(". ") 85 | 86 | func (p *printer) Write(data []byte) (n int, err error) { 87 | var m int 88 | for i, b := range data { 89 | // invariant: data[0:n] has been written 90 | if b == '\n' { 91 | m, err = p.output.Write(data[n : i+1]) 92 | n += m 93 | if err != nil { 94 | return 95 | } 96 | p.line++ 97 | } else if p.last == '\n' { 98 | _, err = fmt.Fprintf(p.output, "%6d ", p.line) 99 | if err != nil { 100 | return 101 | } 102 | for j := p.indent; j > 0; j-- { 103 | _, err = p.output.Write(indent) 104 | if err != nil { 105 | return 106 | } 107 | } 108 | } 109 | p.last = b 110 | } 111 | if len(data) > n { 112 | m, err = p.output.Write(data[n:]) 113 | n += m 114 | } 115 | return 116 | } 117 | 118 | // localError wraps locally caught errors so we can distinguish 119 | // them from genuine panics which we don't want to return as errors. 120 | type localError struct { 121 | err error 122 | } 123 | 124 | // printf is a convenience wrapper that takes care of print errors. 125 | func (p *printer) printf(format string, args ...interface{}) { 126 | if _, err := fmt.Fprintf(p, format, args...); err != nil { 127 | panic(localError{err}) 128 | } 129 | } 130 | 131 | // Implementation note: Print is written for AST nodes but could be 132 | // used to print arbitrary data structures; such a version should 133 | // probably be in a different package. 134 | // 135 | // Note: This code detects (some) cycles created via pointers but 136 | // not cycles that are created via slices or maps containing the 137 | // same slice or map. Code for general data structures probably 138 | // should catch those as well. 139 | 140 | func (p *printer) print(x reflect.Value) { 141 | if !NotNilFilter("", x) { 142 | p.printf("nil") 143 | return 144 | } 145 | 146 | switch x.Kind() { 147 | case reflect.Interface: 148 | p.print(x.Elem()) 149 | 150 | case reflect.Map: 151 | p.printf("%s (len = %d) {", x.Type(), x.Len()) 152 | if x.Len() > 0 { 153 | p.indent++ 154 | p.printf("\n") 155 | for _, key := range x.MapKeys() { 156 | p.print(key) 157 | p.printf(": ") 158 | p.print(x.MapIndex(key)) 159 | p.printf("\n") 160 | } 161 | p.indent-- 162 | } 163 | p.printf("}") 164 | 165 | case reflect.Ptr: 166 | p.printf("*") 167 | // type-checked ASTs may contain cycles - use ptrmap 168 | // to keep track of objects that have been printed 169 | // already and print the respective line number instead 170 | ptr := x.Interface() 171 | if line, exists := p.ptrmap[ptr]; exists { 172 | p.printf("(obj @ %d)", line) 173 | } else { 174 | p.ptrmap[ptr] = p.line 175 | p.print(x.Elem()) 176 | } 177 | 178 | case reflect.Array: 179 | p.printf("%s {", x.Type()) 180 | if x.Len() > 0 { 181 | p.indent++ 182 | p.printf("\n") 183 | for i, n := 0, x.Len(); i < n; i++ { 184 | p.printf("%d: ", i) 185 | p.print(x.Index(i)) 186 | p.printf("\n") 187 | } 188 | p.indent-- 189 | } 190 | p.printf("}") 191 | 192 | case reflect.Slice: 193 | if s, ok := x.Interface().([]byte); ok { 194 | p.printf("%#q", s) 195 | return 196 | } 197 | p.printf("%s (len = %d) {", x.Type(), x.Len()) 198 | if x.Len() > 0 { 199 | p.indent++ 200 | p.printf("\n") 201 | for i, n := 0, x.Len(); i < n; i++ { 202 | p.printf("%d: ", i) 203 | p.print(x.Index(i)) 204 | p.printf("\n") 205 | } 206 | p.indent-- 207 | } 208 | p.printf("}") 209 | 210 | case reflect.Struct: 211 | t := x.Type() 212 | p.printf("%s {", t) 213 | p.indent++ 214 | first := true 215 | for i, n := 0, t.NumField(); i < n; i++ { 216 | // exclude non-exported fields because their 217 | // values cannot be accessed via reflection 218 | if name := t.Field(i).Name; IsExported(name) { 219 | value := x.Field(i) 220 | if p.filter == nil || p.filter(name, value) { 221 | if first { 222 | p.printf("\n") 223 | first = false 224 | } 225 | p.printf("%s: ", name) 226 | p.print(value) 227 | p.printf("\n") 228 | } 229 | } 230 | } 231 | p.indent-- 232 | p.printf("}") 233 | 234 | default: 235 | v := x.Interface() 236 | switch v := v.(type) { 237 | case string: 238 | // print strings in quotes 239 | p.printf("%q", v) 240 | return 241 | } 242 | // default 243 | p.printf("%v", v) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /print_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dst 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | var tests = []struct { 14 | x interface{} // x is printed as s 15 | s string 16 | }{ 17 | // basic types 18 | {nil, "0 nil"}, 19 | {true, "0 true"}, 20 | {42, "0 42"}, 21 | {3.14, "0 3.14"}, 22 | {1 + 2.718i, "0 (1+2.718i)"}, 23 | {"foobar", "0 \"foobar\""}, 24 | 25 | // maps 26 | {map[Expr]string{}, `0 map[dst.Expr]string (len = 0) {}`}, 27 | {map[string]int{"a": 1}, 28 | `0 map[string]int (len = 1) { 29 | 1 . "a": 1 30 | 2 }`}, 31 | 32 | // pointers 33 | {new(int), "0 *0"}, 34 | 35 | // arrays 36 | {[0]int{}, `0 [0]int {}`}, 37 | {[3]int{1, 2, 3}, 38 | `0 [3]int { 39 | 1 . 0: 1 40 | 2 . 1: 2 41 | 3 . 2: 3 42 | 4 }`}, 43 | {[...]int{42}, 44 | `0 [1]int { 45 | 1 . 0: 42 46 | 2 }`}, 47 | 48 | // slices 49 | {[]int{}, `0 []int (len = 0) {}`}, 50 | {[]int{1, 2, 3}, 51 | `0 []int (len = 3) { 52 | 1 . 0: 1 53 | 2 . 1: 2 54 | 3 . 2: 3 55 | 4 }`}, 56 | 57 | // structs 58 | {struct{}{}, `0 struct {} {}`}, 59 | {struct{ x int }{007}, `0 struct { x int } {}`}, 60 | {struct{ X, y int }{42, 991}, 61 | `0 struct { X int; y int } { 62 | 1 . X: 42 63 | 2 }`}, 64 | {struct{ X, Y int }{42, 991}, 65 | `0 struct { X int; Y int } { 66 | 1 . X: 42 67 | 2 . Y: 991 68 | 3 }`}, 69 | } 70 | 71 | // Split s into lines, trim whitespace from all lines, and return 72 | // the concatenated non-empty lines. 73 | func trim(s string) string { 74 | lines := strings.Split(s, "\n") 75 | i := 0 76 | for _, line := range lines { 77 | line = strings.TrimSpace(line) 78 | if line != "" { 79 | lines[i] = line 80 | i++ 81 | } 82 | } 83 | return strings.Join(lines[0:i], "\n") 84 | } 85 | 86 | func TestPrint(t *testing.T) { 87 | var buf bytes.Buffer 88 | for _, test := range tests { 89 | buf.Reset() 90 | if err := Fprint(&buf, test.x, nil); err != nil { 91 | t.Errorf("Fprint failed: %s", err) 92 | } 93 | if s, ts := trim(buf.String()), trim(test.s); s != ts { 94 | t.Errorf("got:\n%s\nexpected:\n%s\n", s, ts) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /readme.go: -------------------------------------------------------------------------------- 1 | package dst 2 | 3 | //go:generate go get github.com/dave/rebecca/cmd/becca 4 | //go:generate becca -package=github.com/dave/dst 5 | -------------------------------------------------------------------------------- /resolve.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file implements NewPackage. 6 | 7 | package dst 8 | 9 | import ( 10 | "fmt" 11 | "go/scanner" 12 | "go/token" 13 | "strconv" 14 | ) 15 | 16 | type pkgBuilder struct { 17 | fset *token.FileSet 18 | errors scanner.ErrorList 19 | } 20 | 21 | func (p *pkgBuilder) error(msg string) { 22 | p.errors.Add(p.fset.Position(token.NoPos), msg) 23 | } 24 | 25 | func (p *pkgBuilder) errorf(format string, args ...interface{}) { 26 | p.error(fmt.Sprintf(format, args...)) 27 | } 28 | 29 | func (p *pkgBuilder) declare(scope, altScope *Scope, obj *Object) { 30 | alt := scope.Insert(obj) 31 | if alt == nil && altScope != nil { 32 | // see if there is a conflicting declaration in altScope 33 | alt = altScope.Lookup(obj.Name) 34 | } 35 | if alt != nil { 36 | p.error(fmt.Sprintf("%s redeclared in this block", obj.Name)) 37 | } 38 | } 39 | 40 | func resolve(scope *Scope, ident *Ident) bool { 41 | for ; scope != nil; scope = scope.Outer { 42 | if obj := scope.Lookup(ident.Name); obj != nil { 43 | ident.Obj = obj 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | // An Importer resolves import paths to package Objects. 51 | // The imports map records the packages already imported, 52 | // indexed by package id (canonical import path). 53 | // An Importer must determine the canonical import path and 54 | // check the map to see if it is already present in the imports map. 55 | // If so, the Importer can return the map entry. Otherwise, the 56 | // Importer should load the package data for the given path into 57 | // a new *Object (pkg), record pkg in the imports map, and then 58 | // return pkg. 59 | type Importer func(imports map[string]*Object, path string) (pkg *Object, err error) 60 | 61 | // NewPackage creates a new Package node from a set of File nodes. It resolves 62 | // unresolved identifiers across files and updates each file's Unresolved list 63 | // accordingly. If a non-nil importer and universe scope are provided, they are 64 | // used to resolve identifiers not declared in any of the package files. Any 65 | // remaining unresolved identifiers are reported as undeclared. If the files 66 | // belong to different packages, one package name is selected and files with 67 | // different package names are reported and then ignored. 68 | // The result is a package node and a scanner.ErrorList if there were errors. 69 | // 70 | func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, universe *Scope) (*Package, error) { 71 | var p pkgBuilder 72 | p.fset = fset 73 | 74 | // complete package scope 75 | pkgName := "" 76 | pkgScope := NewScope(universe) 77 | for _, file := range files { 78 | // package names must match 79 | switch name := file.Name.Name; { 80 | case pkgName == "": 81 | pkgName = name 82 | case name != pkgName: 83 | p.errorf("package %s; expected %s", name, pkgName) 84 | continue // ignore this file 85 | } 86 | 87 | // collect top-level file objects in package scope 88 | for _, obj := range file.Scope.Objects { 89 | p.declare(pkgScope, nil, obj) 90 | } 91 | } 92 | 93 | // package global mapping of imported package ids to package objects 94 | imports := make(map[string]*Object) 95 | 96 | // complete file scopes with imports and resolve identifiers 97 | for _, file := range files { 98 | // ignore file if it belongs to a different package 99 | // (error has already been reported) 100 | if file.Name.Name != pkgName { 101 | continue 102 | } 103 | 104 | // build file scope by processing all imports 105 | importErrors := false 106 | fileScope := NewScope(pkgScope) 107 | for _, spec := range file.Imports { 108 | if importer == nil { 109 | importErrors = true 110 | continue 111 | } 112 | path, _ := strconv.Unquote(spec.Path.Value) 113 | pkg, err := importer(imports, path) 114 | if err != nil { 115 | p.errorf("could not import %s (%s)", path, err) 116 | importErrors = true 117 | continue 118 | } 119 | // TODO(gri) If a local package name != "." is provided, 120 | // global identifier resolution could proceed even if the 121 | // import failed. Consider adjusting the logic here a bit. 122 | 123 | // local name overrides imported package name 124 | name := pkg.Name 125 | if spec.Name != nil { 126 | name = spec.Name.Name 127 | } 128 | 129 | // add import to file scope 130 | if name == "." { 131 | // merge imported scope with file scope 132 | for _, obj := range pkg.Data.(*Scope).Objects { 133 | p.declare(fileScope, pkgScope, obj) 134 | } 135 | } else if name != "_" { 136 | // declare imported package object in file scope 137 | // (do not re-use pkg in the file scope but create 138 | // a new object instead; the Decl field is different 139 | // for different files) 140 | obj := NewObj(Pkg, name) 141 | obj.Decl = spec 142 | obj.Data = pkg.Data 143 | p.declare(fileScope, pkgScope, obj) 144 | } 145 | } 146 | 147 | // resolve identifiers 148 | if importErrors { 149 | // don't use the universe scope without correct imports 150 | // (objects in the universe may be shadowed by imports; 151 | // with missing imports, identifiers might get resolved 152 | // incorrectly to universe objects) 153 | pkgScope.Outer = nil 154 | } 155 | i := 0 156 | for _, ident := range file.Unresolved { 157 | if !resolve(fileScope, ident) { 158 | p.errorf("undeclared name: %s", ident.Name) 159 | file.Unresolved[i] = ident 160 | i++ 161 | } 162 | 163 | } 164 | file.Unresolved = file.Unresolved[0:i] 165 | pkgScope.Outer = universe // reset universe scope 166 | } 167 | 168 | p.errors.Sort() 169 | return &Package{pkgName, pkgScope, imports, files}, p.errors.Err() 170 | } 171 | -------------------------------------------------------------------------------- /scope.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file implements scopes and the objects they contain. 6 | 7 | package dst 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | ) 13 | 14 | // A Scope maintains the set of named language entities declared 15 | // in the scope and a link to the immediately surrounding (outer) 16 | // scope. 17 | // 18 | type Scope struct { 19 | Outer *Scope 20 | Objects map[string]*Object 21 | } 22 | 23 | // NewScope creates a new scope nested in the outer scope. 24 | func NewScope(outer *Scope) *Scope { 25 | const n = 4 // initial scope capacity 26 | return &Scope{outer, make(map[string]*Object, n)} 27 | } 28 | 29 | // Lookup returns the object with the given name if it is 30 | // found in scope s, otherwise it returns nil. Outer scopes 31 | // are ignored. 32 | // 33 | func (s *Scope) Lookup(name string) *Object { 34 | return s.Objects[name] 35 | } 36 | 37 | // Insert attempts to insert a named object obj into the scope s. 38 | // If the scope already contains an object alt with the same name, 39 | // Insert leaves the scope unchanged and returns alt. Otherwise 40 | // it inserts obj and returns nil. 41 | // 42 | func (s *Scope) Insert(obj *Object) (alt *Object) { 43 | if alt = s.Objects[obj.Name]; alt == nil { 44 | s.Objects[obj.Name] = obj 45 | } 46 | return 47 | } 48 | 49 | // Debugging support 50 | func (s *Scope) String() string { 51 | var buf bytes.Buffer 52 | fmt.Fprintf(&buf, "scope %p {", s) 53 | if s != nil && len(s.Objects) > 0 { 54 | fmt.Fprintln(&buf) 55 | for _, obj := range s.Objects { 56 | fmt.Fprintf(&buf, "\t%s %s\n", obj.Kind, obj.Name) 57 | } 58 | } 59 | fmt.Fprintf(&buf, "}\n") 60 | return buf.String() 61 | } 62 | 63 | // ---------------------------------------------------------------------------- 64 | // Objects 65 | 66 | // An Object describes a named language entity such as a package, 67 | // constant, type, variable, function (incl. methods), or label. 68 | // 69 | // The Data fields contains object-specific data: 70 | // 71 | // Kind Data type Data value 72 | // Pkg *Scope package scope 73 | // Con int iota for the respective declaration 74 | // 75 | type Object struct { 76 | Kind ObjKind 77 | Name string // declared name 78 | Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil 79 | Data interface{} // object-specific data; or nil 80 | Type interface{} // placeholder for type information; may be nil 81 | } 82 | 83 | // NewObj creates a new object of a given kind and name. 84 | func NewObj(kind ObjKind, name string) *Object { 85 | return &Object{Kind: kind, Name: name} 86 | } 87 | 88 | // ObjKind describes what an object represents. 89 | type ObjKind int 90 | 91 | // The list of possible Object kinds. 92 | const ( 93 | Bad ObjKind = iota // for error handling 94 | Pkg // package 95 | Con // constant 96 | Typ // type 97 | Var // variable 98 | Fun // function or method 99 | Lbl // label 100 | ) 101 | 102 | var objKindStrings = [...]string{ 103 | Bad: "bad", 104 | Pkg: "package", 105 | Con: "const", 106 | Typ: "type", 107 | Var: "var", 108 | Fun: "func", 109 | Lbl: "label", 110 | } 111 | 112 | func (kind ObjKind) String() string { return objKindStrings[kind] } 113 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package dst_test 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func tempDir(m map[string]string) (dir string, err error) { 13 | if dir, err = ioutil.TempDir("", ""); err != nil { 14 | return 15 | } 16 | for fpathrel, src := range m { 17 | if strings.HasSuffix(fpathrel, "/") { 18 | // just a dir 19 | if err = os.MkdirAll(filepath.Join(dir, fpathrel), 0777); err != nil { 20 | return 21 | } 22 | } else { 23 | fpath := filepath.Join(dir, fpathrel) 24 | fdir, _ := filepath.Split(fpath) 25 | if err = os.MkdirAll(fdir, 0777); err != nil { 26 | return 27 | } 28 | 29 | var formatted []byte 30 | if strings.HasSuffix(fpath, ".go") { 31 | formatted, err = format.Source([]byte(src)) 32 | if err != nil { 33 | err = fmt.Errorf("formatting %s: %v", fpathrel, err) 34 | return 35 | } 36 | } else { 37 | formatted = []byte(src) 38 | } 39 | 40 | if err = ioutil.WriteFile(fpath, formatted, 0666); err != nil { 41 | return 42 | } 43 | } 44 | } 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /walk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dst 6 | 7 | import "fmt" 8 | 9 | // A Visitor's Visit method is invoked for each node encountered by Walk. 10 | // If the result visitor w is not nil, Walk visits each of the children 11 | // of node with the visitor w, followed by a call of w.Visit(nil). 12 | type Visitor interface { 13 | Visit(node Node) (w Visitor) 14 | } 15 | 16 | // Helper functions for common node lists. They may be empty. 17 | 18 | func walkIdentList(v Visitor, list []*Ident) { 19 | for _, x := range list { 20 | Walk(v, x) 21 | } 22 | } 23 | 24 | func walkExprList(v Visitor, list []Expr) { 25 | for _, x := range list { 26 | Walk(v, x) 27 | } 28 | } 29 | 30 | func walkStmtList(v Visitor, list []Stmt) { 31 | for _, x := range list { 32 | Walk(v, x) 33 | } 34 | } 35 | 36 | func walkDeclList(v Visitor, list []Decl) { 37 | for _, x := range list { 38 | Walk(v, x) 39 | } 40 | } 41 | 42 | // TODO(gri): Investigate if providing a closure to Walk leads to 43 | // simpler use (and may help eliminate Inspect in turn). 44 | 45 | // Walk traverses an AST in depth-first order: It starts by calling 46 | // v.Visit(node); node must not be nil. If the visitor w returned by 47 | // v.Visit(node) is not nil, Walk is invoked recursively with visitor 48 | // w for each of the non-nil children of node, followed by a call of 49 | // w.Visit(nil). 50 | // 51 | func Walk(v Visitor, node Node) { 52 | if v = v.Visit(node); v == nil { 53 | return 54 | } 55 | 56 | // walk children 57 | // (the order of the cases matches the order 58 | // of the corresponding node types in ast.go) 59 | switch n := node.(type) { 60 | case *Field: 61 | walkIdentList(v, n.Names) 62 | Walk(v, n.Type) 63 | if n.Tag != nil { 64 | Walk(v, n.Tag) 65 | } 66 | 67 | case *FieldList: 68 | for _, f := range n.List { 69 | Walk(v, f) 70 | } 71 | 72 | // Expressions 73 | case *BadExpr, *Ident, *BasicLit: 74 | // nothing to do 75 | 76 | case *Ellipsis: 77 | if n.Elt != nil { 78 | Walk(v, n.Elt) 79 | } 80 | 81 | case *FuncLit: 82 | Walk(v, n.Type) 83 | Walk(v, n.Body) 84 | 85 | case *CompositeLit: 86 | if n.Type != nil { 87 | Walk(v, n.Type) 88 | } 89 | walkExprList(v, n.Elts) 90 | 91 | case *ParenExpr: 92 | Walk(v, n.X) 93 | 94 | case *SelectorExpr: 95 | Walk(v, n.X) 96 | Walk(v, n.Sel) 97 | 98 | case *IndexExpr: 99 | Walk(v, n.X) 100 | Walk(v, n.Index) 101 | 102 | case *IndexListExpr: 103 | Walk(v, n.X) 104 | walkExprList(v, n.Indices) 105 | 106 | case *SliceExpr: 107 | Walk(v, n.X) 108 | if n.Low != nil { 109 | Walk(v, n.Low) 110 | } 111 | if n.High != nil { 112 | Walk(v, n.High) 113 | } 114 | if n.Max != nil { 115 | Walk(v, n.Max) 116 | } 117 | 118 | case *TypeAssertExpr: 119 | Walk(v, n.X) 120 | if n.Type != nil { 121 | Walk(v, n.Type) 122 | } 123 | 124 | case *CallExpr: 125 | Walk(v, n.Fun) 126 | walkExprList(v, n.Args) 127 | 128 | case *StarExpr: 129 | Walk(v, n.X) 130 | 131 | case *UnaryExpr: 132 | Walk(v, n.X) 133 | 134 | case *BinaryExpr: 135 | Walk(v, n.X) 136 | Walk(v, n.Y) 137 | 138 | case *KeyValueExpr: 139 | Walk(v, n.Key) 140 | Walk(v, n.Value) 141 | 142 | // Types 143 | case *ArrayType: 144 | if n.Len != nil { 145 | Walk(v, n.Len) 146 | } 147 | Walk(v, n.Elt) 148 | 149 | case *StructType: 150 | Walk(v, n.Fields) 151 | 152 | case *FuncType: 153 | if n.TypeParams != nil { 154 | Walk(v, n.TypeParams) 155 | } 156 | if n.Params != nil { 157 | Walk(v, n.Params) 158 | } 159 | if n.Results != nil { 160 | Walk(v, n.Results) 161 | } 162 | 163 | case *InterfaceType: 164 | Walk(v, n.Methods) 165 | 166 | case *MapType: 167 | Walk(v, n.Key) 168 | Walk(v, n.Value) 169 | 170 | case *ChanType: 171 | Walk(v, n.Value) 172 | 173 | // Statements 174 | case *BadStmt: 175 | // nothing to do 176 | 177 | case *DeclStmt: 178 | Walk(v, n.Decl) 179 | 180 | case *EmptyStmt: 181 | // nothing to do 182 | 183 | case *LabeledStmt: 184 | Walk(v, n.Label) 185 | Walk(v, n.Stmt) 186 | 187 | case *ExprStmt: 188 | Walk(v, n.X) 189 | 190 | case *SendStmt: 191 | Walk(v, n.Chan) 192 | Walk(v, n.Value) 193 | 194 | case *IncDecStmt: 195 | Walk(v, n.X) 196 | 197 | case *AssignStmt: 198 | walkExprList(v, n.Lhs) 199 | walkExprList(v, n.Rhs) 200 | 201 | case *GoStmt: 202 | Walk(v, n.Call) 203 | 204 | case *DeferStmt: 205 | Walk(v, n.Call) 206 | 207 | case *ReturnStmt: 208 | walkExprList(v, n.Results) 209 | 210 | case *BranchStmt: 211 | if n.Label != nil { 212 | Walk(v, n.Label) 213 | } 214 | 215 | case *BlockStmt: 216 | walkStmtList(v, n.List) 217 | 218 | case *IfStmt: 219 | if n.Init != nil { 220 | Walk(v, n.Init) 221 | } 222 | Walk(v, n.Cond) 223 | Walk(v, n.Body) 224 | if n.Else != nil { 225 | Walk(v, n.Else) 226 | } 227 | 228 | case *CaseClause: 229 | walkExprList(v, n.List) 230 | walkStmtList(v, n.Body) 231 | 232 | case *SwitchStmt: 233 | if n.Init != nil { 234 | Walk(v, n.Init) 235 | } 236 | if n.Tag != nil { 237 | Walk(v, n.Tag) 238 | } 239 | Walk(v, n.Body) 240 | 241 | case *TypeSwitchStmt: 242 | if n.Init != nil { 243 | Walk(v, n.Init) 244 | } 245 | Walk(v, n.Assign) 246 | Walk(v, n.Body) 247 | 248 | case *CommClause: 249 | if n.Comm != nil { 250 | Walk(v, n.Comm) 251 | } 252 | walkStmtList(v, n.Body) 253 | 254 | case *SelectStmt: 255 | Walk(v, n.Body) 256 | 257 | case *ForStmt: 258 | if n.Init != nil { 259 | Walk(v, n.Init) 260 | } 261 | if n.Cond != nil { 262 | Walk(v, n.Cond) 263 | } 264 | if n.Post != nil { 265 | Walk(v, n.Post) 266 | } 267 | Walk(v, n.Body) 268 | 269 | case *RangeStmt: 270 | if n.Key != nil { 271 | Walk(v, n.Key) 272 | } 273 | if n.Value != nil { 274 | Walk(v, n.Value) 275 | } 276 | Walk(v, n.X) 277 | Walk(v, n.Body) 278 | 279 | // Declarations 280 | case *ImportSpec: 281 | if n.Name != nil { 282 | Walk(v, n.Name) 283 | } 284 | Walk(v, n.Path) 285 | 286 | case *ValueSpec: 287 | walkIdentList(v, n.Names) 288 | if n.Type != nil { 289 | Walk(v, n.Type) 290 | } 291 | walkExprList(v, n.Values) 292 | 293 | case *TypeSpec: 294 | Walk(v, n.Name) 295 | if n.TypeParams != nil { 296 | Walk(v, n.TypeParams) 297 | } 298 | Walk(v, n.Type) 299 | 300 | case *BadDecl: 301 | // nothing to do 302 | 303 | case *GenDecl: 304 | for _, s := range n.Specs { 305 | Walk(v, s) 306 | } 307 | 308 | case *FuncDecl: 309 | if n.Recv != nil { 310 | Walk(v, n.Recv) 311 | } 312 | Walk(v, n.Name) 313 | Walk(v, n.Type) 314 | if n.Body != nil { 315 | Walk(v, n.Body) 316 | } 317 | 318 | // Files and packages 319 | case *File: 320 | Walk(v, n.Name) 321 | walkDeclList(v, n.Decls) 322 | // don't walk n.Comments - they have been 323 | // visited already through the individual 324 | // nodes 325 | 326 | case *Package: 327 | for _, f := range n.Files { 328 | Walk(v, f) 329 | } 330 | 331 | default: 332 | panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) 333 | } 334 | 335 | v.Visit(nil) 336 | } 337 | 338 | type inspector func(Node) bool 339 | 340 | func (f inspector) Visit(node Node) Visitor { 341 | if f(node) { 342 | return f 343 | } 344 | return nil 345 | } 346 | 347 | // Inspect traverses an AST in depth-first order: It starts by calling 348 | // f(node); node must not be nil. If f returns true, Inspect invokes f 349 | // recursively for each of the non-nil children of node, followed by a 350 | // call of f(nil). 351 | // 352 | func Inspect(node Node, f func(Node) bool) { 353 | Walk(inspector(f), node) 354 | } 355 | --------------------------------------------------------------------------------