github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/config/directives.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 config
    17  
    18  import (
    19  	"log"
    20  	"path"
    21  	"regexp"
    22  	"strings"
    23  
    24  	bf "github.com/bazelbuild/buildtools/build"
    25  )
    26  
    27  // Directive is a key-value pair extracted from a top-level comment in
    28  // a build file. Directives have the following format:
    29  //
    30  //     # gazelle:key value
    31  //
    32  // Keys may not contain spaces. Values may be empty and may contain spaces,
    33  // but surrounding space is trimmed.
    34  type Directive struct {
    35  	Key, Value string
    36  }
    37  
    38  // Top-level directives apply to the whole package or build file. They must
    39  // appear before the first statement.
    40  var knownTopLevelDirectives = map[string]bool{
    41  	"build_file_name": true,
    42  	"build_tags":      true,
    43  	"exclude":         true,
    44  	"prefix":          true,
    45  	"ignore":          true,
    46  	"proto":           true,
    47  }
    48  
    49  // TODO(jayconrod): annotation directives will apply to an individual rule.
    50  // They must appear in the block of comments above that rule.
    51  
    52  // ParseDirectives scans f for Gazelle directives. The full list of directives
    53  // is returned. Errors are reported for unrecognized directives and directives
    54  // out of place (after the first statement).
    55  func ParseDirectives(f *bf.File) []Directive {
    56  	var directives []Directive
    57  	parseComment := func(com bf.Comment) {
    58  		match := directiveRe.FindStringSubmatch(com.Token)
    59  		if match == nil {
    60  			return
    61  		}
    62  		key, value := match[1], match[2]
    63  		if _, ok := knownTopLevelDirectives[key]; !ok {
    64  			log.Printf("%s:%d: unknown directive: %s", f.Path, com.Start.Line, com.Token)
    65  			return
    66  		}
    67  		directives = append(directives, Directive{key, value})
    68  	}
    69  
    70  	for _, s := range f.Stmt {
    71  		coms := s.Comment()
    72  		for _, com := range coms.Before {
    73  			parseComment(com)
    74  		}
    75  		for _, com := range coms.After {
    76  			parseComment(com)
    77  		}
    78  	}
    79  	return directives
    80  }
    81  
    82  var directiveRe = regexp.MustCompile(`^#\s*gazelle:(\w+)\s*(.*?)\s*$`)
    83  
    84  // ApplyDirectives applies directives that modify the configuration to a copy of
    85  // c, which is returned. If there are no configuration directives, c is returned
    86  // unmodified.
    87  func ApplyDirectives(c *Config, directives []Directive, rel string) *Config {
    88  	modified := *c
    89  	didModify := false
    90  	for _, d := range directives {
    91  		switch d.Key {
    92  		case "build_tags":
    93  			if err := modified.SetBuildTags(d.Value); err != nil {
    94  				log.Print(err)
    95  				modified.GenericTags = c.GenericTags
    96  			} else {
    97  				modified.PreprocessTags()
    98  				didModify = true
    99  			}
   100  		case "build_file_name":
   101  			modified.ValidBuildFileNames = strings.Split(d.Value, ",")
   102  			didModify = true
   103  		case "prefix":
   104  			if err := CheckPrefix(d.Value); err != nil {
   105  				log.Print(err)
   106  				continue
   107  			}
   108  			modified.GoPrefix = d.Value
   109  			modified.GoPrefixRel = rel
   110  			didModify = true
   111  		case "proto":
   112  			protoMode, err := ProtoModeFromString(d.Value)
   113  			if err != nil {
   114  				log.Print(err)
   115  				continue
   116  			}
   117  			modified.ProtoMode = protoMode
   118  			modified.ProtoModeExplicit = true
   119  			didModify = true
   120  		}
   121  	}
   122  	if !didModify {
   123  		return c
   124  	}
   125  	return &modified
   126  }
   127  
   128  // InferProtoMode sets Config.ProtoMode, based on the contents of f.  If the
   129  // proto mode is already set to something other than the default, or if the mode
   130  // is set explicitly in directives, this function does not change it. If the
   131  // legacy go_proto_library.bzl is loaded, or if this is the Well Known Types
   132  // repository, legacy mode is used. If go_proto_library is loaded from another
   133  // file, proto rule generation is disabled.
   134  func InferProtoMode(c *Config, rel string, f *bf.File, directives []Directive) *Config {
   135  	if c.ProtoMode != DefaultProtoMode || c.ProtoModeExplicit {
   136  		return c
   137  	}
   138  	for _, d := range directives {
   139  		if d.Key == "proto" {
   140  			return c
   141  		}
   142  	}
   143  	if c.GoPrefix == WellKnownTypesGoPrefix {
   144  		// Use legacy mode in this repo. We don't need proto_library or
   145  		// go_proto_library, since we get that from @com_google_protobuf.
   146  		// Legacy rules still refer to .proto files in here, which need are
   147  		// exposed by filegroup. go_library rules from .pb.go files will be
   148  		// generated, which are depended upon by the new rules.
   149  		modified := *c
   150  		modified.ProtoMode = LegacyProtoMode
   151  		return &modified
   152  	}
   153  	if path.Base(rel) == "vendor" {
   154  		modified := *c
   155  		modified.ProtoMode = DisableProtoMode
   156  		return &modified
   157  	}
   158  	if f == nil {
   159  		return c
   160  	}
   161  	mode := DefaultProtoMode
   162  	for _, stmt := range f.Stmt {
   163  		c, ok := stmt.(*bf.CallExpr)
   164  		if !ok {
   165  			continue
   166  		}
   167  		x, ok := c.X.(*bf.LiteralExpr)
   168  		if !ok || x.Token != "load" || len(c.List) == 0 {
   169  			continue
   170  		}
   171  		name, ok := c.List[0].(*bf.StringExpr)
   172  		if !ok {
   173  			continue
   174  		}
   175  		if name.Value == "@io_bazel_rules_go//proto:def.bzl" {
   176  			break
   177  		}
   178  		if name.Value == "@io_bazel_rules_go//proto:go_proto_library.bzl" {
   179  			mode = LegacyProtoMode
   180  			break
   181  		}
   182  		for _, arg := range c.List[1:] {
   183  			if sym, ok := arg.(*bf.StringExpr); ok && sym.Value == "go_proto_library" {
   184  				mode = DisableProtoMode
   185  				break
   186  			}
   187  			kwarg, ok := arg.(*bf.BinaryExpr)
   188  			if !ok || kwarg.Op != "=" {
   189  				continue
   190  			}
   191  			if key, ok := kwarg.X.(*bf.LiteralExpr); ok && key.Token == "go_proto_library" {
   192  				mode = DisableProtoMode
   193  				break
   194  			}
   195  		}
   196  	}
   197  	if mode == DefaultProtoMode || c.ProtoMode == mode || c.ShouldFix && mode == LegacyProtoMode {
   198  		return c
   199  	}
   200  	modified := *c
   201  	modified.ProtoMode = mode
   202  	return &modified
   203  }