github.com/wolfd/bazel-gazelle@v0.14.0/internal/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/internal/rule"
    23  )
    24  
    25  // FixLoads removes loads of unused go rules and adds loads of newly used rules.
    26  // This should be called after FixFile and MergeFile, since symbols
    27  // may be introduced that aren't loaded.
    28  //
    29  // This function calls File.Sync before processing loads.
    30  func FixLoads(f *rule.File, knownLoads []rule.LoadInfo) {
    31  	knownFiles := make(map[string]bool)
    32  	knownKinds := make(map[string]string)
    33  	for _, l := range knownLoads {
    34  		knownFiles[l.Name] = true
    35  		for _, k := range l.Symbols {
    36  			knownKinds[k] = l.Name
    37  		}
    38  	}
    39  
    40  	// Sync the file. We need File.Loads and File.Rules to contain inserted
    41  	// statements and not deleted statements.
    42  	f.Sync()
    43  
    44  	// Scan load statements in the file. Keep track of loads of known files,
    45  	// since these may be changed. Keep track of symbols loaded from unknown
    46  	// files; we will not add loads for these.
    47  	var loads []*rule.Load
    48  	otherLoadedKinds := make(map[string]bool)
    49  	for _, l := range f.Loads {
    50  		if knownFiles[l.Name()] {
    51  			loads = append(loads, l)
    52  			continue
    53  		}
    54  		for _, sym := range l.Symbols() {
    55  			otherLoadedKinds[sym] = true
    56  		}
    57  	}
    58  
    59  	// Make a map of all the symbols from known files used in this file.
    60  	usedKinds := make(map[string]map[string]bool)
    61  	for _, r := range f.Rules {
    62  		kind := r.Kind()
    63  		if file, ok := knownKinds[kind]; ok && !otherLoadedKinds[kind] {
    64  			if usedKinds[file] == nil {
    65  				usedKinds[file] = make(map[string]bool)
    66  			}
    67  			usedKinds[file][kind] = true
    68  		}
    69  	}
    70  
    71  	// Fix the load statements. The order is important, so we iterate over
    72  	// knownLoads instead of knownFiles.
    73  	for _, known := range knownLoads {
    74  		file := known.Name
    75  		first := true
    76  		for _, l := range loads {
    77  			if l.Name() != file {
    78  				continue
    79  			}
    80  			if first {
    81  				fixLoad(l, file, usedKinds[file], knownKinds)
    82  				first = false
    83  			} else {
    84  				fixLoad(l, file, nil, knownKinds)
    85  			}
    86  			if l.IsEmpty() {
    87  				l.Delete()
    88  			}
    89  		}
    90  		if first {
    91  			load := fixLoad(nil, file, usedKinds[file], knownKinds)
    92  			if load != nil {
    93  				index := newLoadIndex(f, known.After)
    94  				load.Insert(f, index)
    95  			}
    96  		}
    97  	}
    98  }
    99  
   100  // fixLoad updates a load statement with the given symbols. If load is nil,
   101  // a new load may be created and returned. Symbols in kinds will be added
   102  // to the load if they're not already present. Known symbols not in kinds
   103  // will be removed if present. Other symbols will be preserved. If load is
   104  // empty, nil is returned.
   105  func fixLoad(load *rule.Load, file string, kinds map[string]bool, knownKinds map[string]string) *rule.Load {
   106  	if load == nil {
   107  		if len(kinds) == 0 {
   108  			return nil
   109  		}
   110  		load = rule.NewLoad(file)
   111  	}
   112  
   113  	for k := range kinds {
   114  		load.Add(k)
   115  	}
   116  	for _, k := range load.Symbols() {
   117  		if knownKinds[k] != "" && !kinds[k] {
   118  			load.Remove(k)
   119  		}
   120  	}
   121  	return load
   122  }
   123  
   124  // newLoadIndex returns the index in stmts where a new load statement should
   125  // be inserted. after is a list of function names that the load should not
   126  // be inserted before.
   127  func newLoadIndex(f *rule.File, after []string) int {
   128  	if len(after) == 0 {
   129  		return 0
   130  	}
   131  	index := 0
   132  	for _, r := range f.Rules {
   133  		for _, a := range after {
   134  			if r.Kind() == a && r.Index() >= index {
   135  				index = r.Index() + 1
   136  			}
   137  		}
   138  	}
   139  	return index
   140  }
   141  
   142  // FixWorkspace updates rules in the WORKSPACE file f that were used with an
   143  // older version of rules_go or gazelle.
   144  func FixWorkspace(f *rule.File) {
   145  	removeLegacyGoRepository(f)
   146  }
   147  
   148  // CheckGazelleLoaded searches the given WORKSPACE file for a repository named
   149  // "bazel_gazelle". If no such repository is found *and* the repo is not
   150  // declared with a directive *and* at least one load statement mentions
   151  // the repository, a descriptive error will be returned.
   152  //
   153  // This should be called after modifications have been made to WORKSPACE
   154  // (i.e., after FixLoads) before writing it to disk.
   155  func CheckGazelleLoaded(f *rule.File) error {
   156  	needGazelle := false
   157  	for _, l := range f.Loads {
   158  		if strings.HasPrefix(l.Name(), "@bazel_gazelle//") {
   159  			needGazelle = true
   160  		}
   161  	}
   162  	if !needGazelle {
   163  		return nil
   164  	}
   165  	for _, r := range f.Rules {
   166  		if r.Name() == "bazel_gazelle" {
   167  			return nil
   168  		}
   169  	}
   170  	for _, d := range f.Directives {
   171  		if d.Key != "repo" {
   172  			continue
   173  		}
   174  		if fs := strings.Fields(d.Value); len(fs) > 0 && fs[0] == "bazel_gazelle" {
   175  			return nil
   176  		}
   177  	}
   178  	return fmt.Errorf(`%s: error: bazel_gazelle is not declared in WORKSPACE.
   179  Without this repository, Gazelle cannot safely modify the WORKSPACE file.
   180  See the instructions at https://github.com/bazelbuild/bazel-gazelle.
   181  If the bazel_gazelle is declared inside a macro, you can suppress this error
   182  by adding a comment like this to WORKSPACE:
   183      # gazelle:repo bazel_gazelle
   184  `, f.Path)
   185  }
   186  
   187  // removeLegacyGoRepository removes loads of go_repository from
   188  // @io_bazel_rules_go. FixLoads should be called after this; it will load from
   189  // @bazel_gazelle.
   190  func removeLegacyGoRepository(f *rule.File) {
   191  	for _, l := range f.Loads {
   192  		if l.Name() == "@io_bazel_rules_go//go:def.bzl" {
   193  			l.Remove("go_repository")
   194  			if l.IsEmpty() {
   195  				l.Delete()
   196  			}
   197  		}
   198  	}
   199  }