github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/imports.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found at 4 // 5 // https://github.com/golang/tools/blob/master/LICENSE 6 7 package main 8 9 import ( 10 "go/ast" 11 "go/token" 12 "strconv" 13 "strings" 14 ) 15 16 // Adapted from golang.org/x/tools/go/ast/astutil.AddNamedImport 17 func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) bool { 18 newImport := &ast.ImportSpec{ 19 Path: &ast.BasicLit{ 20 Kind: token.STRING, 21 Value: strconv.Quote(path), 22 }, 23 } 24 if name != "" { 25 newImport.Name = &ast.Ident{Name: name} 26 } 27 28 // Find an import decl to add to. 29 // The goal is to find an existing import 30 // whose import path has the longest shared 31 // prefix with path. 32 var ( 33 bestMatch = -1 // length of longest shared prefix 34 lastImport = -1 // index in f.Decls of the file's final import decl 35 impDecl *ast.GenDecl // import decl containing the best match 36 impIndex = -1 // spec index in impDecl containing the best match 37 38 isThirdPartyPath = isThirdParty(path) 39 ) 40 for i, decl := range f.Decls { 41 gen, ok := decl.(*ast.GenDecl) 42 if ok && gen.Tok == token.IMPORT { 43 lastImport = i 44 // Do not add to import "C", to avoid disrupting the 45 // association with its doc comment, breaking cgo. 46 if declImports(gen, "C") { 47 continue 48 } 49 50 // Match an empty import decl if that's all that is available. 51 if len(gen.Specs) == 0 && bestMatch == -1 { 52 impDecl = gen 53 } 54 55 // Compute longest shared prefix with imports in this group and find best 56 // matched import spec. 57 // 1. Always prefer import spec with longest shared prefix. 58 // 2. While match length is 0, 59 // - for stdlib package: prefer first import spec. 60 // - for third party package: prefer first third party import spec. 61 // We cannot use last import spec as best match for third party package 62 // because grouped imports are usually placed last by goimports -local 63 // flag. 64 // See issue #19190. 65 seenAnyThirdParty := false 66 for j, spec := range gen.Specs { 67 impspec := spec.(*ast.ImportSpec) 68 p := importPath(impspec) 69 n := matchLen(p, path) 70 if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { 71 bestMatch = n 72 impDecl = gen 73 impIndex = j 74 } 75 seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p) 76 } 77 } 78 } 79 80 // If no import decl found, add one after the last import. 81 if impDecl == nil { 82 impDecl = &ast.GenDecl{ 83 Tok: token.IMPORT, 84 } 85 if lastImport >= 0 { 86 impDecl.TokPos = f.Decls[lastImport].End() 87 } else { 88 // There are no existing imports. 89 // Our new import, preceded by a blank line, goes after the package declaration 90 // and after the comment, if any, that starts on the same line as the 91 // package declaration. 92 impDecl.TokPos = f.Package 93 94 file := fset.File(f.Package) 95 pkgLine := file.Line(f.Package) 96 for _, c := range f.Comments { 97 if file.Line(c.Pos()) > pkgLine { 98 break 99 } 100 // +2 for a blank line 101 impDecl.TokPos = c.End() + 2 102 } 103 } 104 f.Decls = append(f.Decls, nil) 105 copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) 106 f.Decls[lastImport+1] = impDecl 107 } 108 109 // Insert new import at insertAt. 110 insertAt := 0 111 if impIndex >= 0 { 112 // insert after the found import 113 insertAt = impIndex + 1 114 } 115 impDecl.Specs = append(impDecl.Specs, nil) 116 copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) 117 impDecl.Specs[insertAt] = newImport 118 pos := impDecl.Pos() 119 if insertAt > 0 { 120 // If there is a comment after an existing import, preserve the comment 121 // position by adding the new import after the comment. 122 if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { 123 pos = spec.Comment.End() 124 } else { 125 // Assign same position as the previous import, 126 // so that the sorter sees it as being in the same block. 127 pos = impDecl.Specs[insertAt-1].Pos() 128 } 129 } 130 if newImport.Name != nil { 131 newImport.Name.NamePos = pos 132 } 133 newImport.Path.ValuePos = pos 134 newImport.EndPos = pos 135 136 // Clean up parens. impDecl contains at least one spec. 137 if len(impDecl.Specs) == 1 { 138 // Remove unneeded parens. 139 impDecl.Lparen = token.NoPos 140 } else if !impDecl.Lparen.IsValid() { 141 // impDecl needs parens added. 142 impDecl.Lparen = impDecl.Specs[0].Pos() 143 } 144 145 f.Imports = append(f.Imports, newImport) 146 147 if len(f.Decls) <= 1 { 148 return true 149 } 150 151 // Merge all the import declarations into the first one. 152 var first *ast.GenDecl 153 for i := 0; i < len(f.Decls); i++ { 154 decl := f.Decls[i] 155 gen, ok := decl.(*ast.GenDecl) 156 if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { 157 continue 158 } 159 if first == nil { 160 first = gen 161 continue // Don't touch the first one. 162 } 163 // We now know there is more than one package in this import 164 // declaration. Ensure that it ends up parenthesized. 165 first.Lparen = first.Pos() 166 // Move the imports of the other import declaration to the first one. 167 for _, spec := range gen.Specs { 168 spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() 169 first.Specs = append(first.Specs, spec) 170 } 171 f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) 172 i-- 173 } 174 175 return true 176 } 177 178 // This function is copied from golang.org/x/tools/go/ast/astutil.isThirdParty 179 func isThirdParty(importPath string) bool { 180 // Third party package import path usually contains "." (".com", ".org", ...) 181 // This logic is taken from golang.org/x/tools/imports package. 182 return strings.Contains(importPath, ".") 183 } 184 185 // importPath returns the unquoted import path of s, 186 // or "" if the path is not properly quoted. 187 // This function is copied from golang.org/x/tools/go/ast/astutil.importPath 188 func importPath(s *ast.ImportSpec) string { 189 t, err := strconv.Unquote(s.Path.Value) 190 if err != nil { 191 return "" 192 } 193 return t 194 } 195 196 // declImports reports whether gen contains an import of path. 197 // This function is copied from golang.org/x/tools/go/ast/astutil.declImports 198 func declImports(gen *ast.GenDecl, path string) bool { 199 if gen.Tok != token.IMPORT { 200 return false 201 } 202 for _, spec := range gen.Specs { 203 impspec := spec.(*ast.ImportSpec) 204 if importPath(impspec) == path { 205 return true 206 } 207 } 208 return false 209 } 210 211 // matchLen returns the length of the longest path segment prefix shared by x and y. 212 // This function is copied from golang.org/x/tools/go/ast/astutil.matchLen 213 func matchLen(x, y string) int { 214 n := 0 215 for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { 216 if x[i] == '/' { 217 n++ 218 } 219 } 220 return n 221 }