github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/merger/fix.go (about) 1 /* Copyright 2017 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package merger 17 18 import ( 19 "fmt" 20 "strings" 21 22 "github.com/bazelbuild/bazel-gazelle/rule" 23 bzl "github.com/bazelbuild/buildtools/build" 24 ) 25 26 // FixLoads removes loads of unused go rules and adds loads of newly used rules. 27 // This should be called after FixFile and MergeFile, since symbols 28 // may be introduced that aren't loaded. 29 // 30 // This function calls File.Sync before processing loads. 31 func FixLoads(f *rule.File, knownLoads []rule.LoadInfo) { 32 knownFiles := make(map[string]bool) 33 knownSymbols := make(map[string]string) 34 for _, l := range knownLoads { 35 knownFiles[l.Name] = true 36 for _, k := range l.Symbols { 37 knownSymbols[k] = l.Name 38 } 39 } 40 41 // Sync the file. We need File.Loads and File.Rules to contain inserted 42 // statements and not deleted statements. 43 f.Sync() 44 45 // Scan load statements in the file. Keep track of loads of known files, 46 // since these may be changed. Keep track of symbols loaded from unknown 47 // files; we will not add loads for these. 48 var loads []*rule.Load 49 otherLoadedKinds := make(map[string]bool) 50 for _, l := range f.Loads { 51 if knownFiles[l.Name()] { 52 loads = append(loads, l) 53 continue 54 } 55 for _, sym := range l.Symbols() { 56 otherLoadedKinds[sym] = true 57 } 58 } 59 60 // Make a map of all the symbols from known files used in this file. 61 usedSymbols := make(map[string]map[string]bool) 62 bzl.Walk(f.File, func(x bzl.Expr, stk []bzl.Expr) { 63 ce, ok := x.(*bzl.CallExpr) 64 if !ok { 65 return 66 } 67 68 var functionIdent *bzl.Ident 69 70 d, ok := ce.X.(*bzl.DotExpr) 71 if ok { 72 functionIdent, ok = d.X.(*bzl.Ident) 73 } else { 74 functionIdent, ok = ce.X.(*bzl.Ident) 75 } 76 77 if !ok { 78 return 79 } 80 81 idents := []*bzl.Ident{functionIdent} 82 83 for _, arg := range ce.List { 84 if argIdent, ok := arg.(*bzl.Ident); ok { 85 idents = append(idents, argIdent) 86 } 87 } 88 89 for _, id := range idents { 90 file, ok := knownSymbols[id.Name] 91 if !ok || otherLoadedKinds[id.Name] { 92 continue 93 } 94 95 if usedSymbols[file] == nil { 96 usedSymbols[file] = make(map[string]bool) 97 } 98 usedSymbols[file][id.Name] = true 99 } 100 }) 101 102 // Fix the load statements. The order is important, so we iterate over 103 // knownLoads instead of knownFiles. 104 for _, known := range knownLoads { 105 file := known.Name 106 first := true 107 for _, l := range loads { 108 if l.Name() != file { 109 continue 110 } 111 if first { 112 fixLoad(l, file, usedSymbols[file], knownSymbols) 113 first = false 114 } else { 115 fixLoad(l, file, nil, knownSymbols) 116 } 117 if l.IsEmpty() { 118 l.Delete() 119 } 120 } 121 if first { 122 load := fixLoad(nil, file, usedSymbols[file], knownSymbols) 123 if load != nil { 124 index := newLoadIndex(f, known.After) 125 load.Insert(f, index) 126 } 127 } 128 } 129 } 130 131 // fixLoad updates a load statement with the given symbols. If load is nil, 132 // a new load may be created and returned. Symbols in symbols will be added 133 // to the load if they're not already present. Known symbols not in symbols 134 // will be removed if present. Other symbols will be preserved. If load is 135 // empty, nil is returned. 136 func fixLoad(load *rule.Load, file string, symbols map[string]bool, knownSymbols map[string]string) *rule.Load { 137 if load == nil { 138 if len(symbols) == 0 { 139 return nil 140 } 141 load = rule.NewLoad(file) 142 } 143 144 for k := range symbols { 145 load.Add(k) 146 } 147 for _, k := range load.Symbols() { 148 if knownSymbols[k] != "" && !symbols[k] { 149 load.Remove(k) 150 } 151 } 152 153 return load 154 } 155 156 // newLoadIndex returns the index in stmts where a new load statement should 157 // be inserted. after is a list of function names that the load should not 158 // be inserted before. 159 func newLoadIndex(f *rule.File, after []string) int { 160 if len(after) == 0 { 161 return 0 162 } 163 index := 0 164 for _, r := range f.Rules { 165 for _, a := range after { 166 if r.Kind() == a && r.Index() >= index { 167 index = r.Index() + 1 168 } 169 } 170 } 171 return index 172 } 173 174 // CheckGazelleLoaded searches the given WORKSPACE file for a repository named 175 // "bazel_gazelle". If no such repository is found *and* the repo is not 176 // declared with a directive *and* at least one load statement mentions 177 // the repository, a descriptive error will be returned. 178 // 179 // This should be called after modifications have been made to WORKSPACE 180 // (i.e., after FixLoads) before writing it to disk. 181 func CheckGazelleLoaded(f *rule.File) error { 182 needGazelle := false 183 for _, l := range f.Loads { 184 if strings.HasPrefix(l.Name(), "@bazel_gazelle//") { 185 needGazelle = true 186 } 187 } 188 if !needGazelle { 189 return nil 190 } 191 for _, r := range f.Rules { 192 if r.Name() == "bazel_gazelle" { 193 return nil 194 } 195 } 196 for _, d := range f.Directives { 197 if d.Key != "repo" { 198 continue 199 } 200 if fs := strings.Fields(d.Value); len(fs) > 0 && fs[0] == "bazel_gazelle" { 201 return nil 202 } 203 } 204 return fmt.Errorf(`%s: error: bazel_gazelle is not declared in WORKSPACE. 205 Without this repository, Gazelle cannot safely modify the WORKSPACE file. 206 See the instructions at https://github.com/bazelbuild/bazel-gazelle. 207 If the bazel_gazelle is declared inside a macro, you can suppress this error 208 by adding a comment like this to WORKSPACE: 209 # gazelle:repo bazel_gazelle 210 `, f.Path) 211 }