...

Source file src/github.com/bazelbuild/buildtools/buildifier/config/config.go

Documentation: github.com/bazelbuild/buildtools/buildifier/config

     1  /*
     2  Copyright 2022 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package config provides configuration objects for buildifier
    18  package config
    19  
    20  import (
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"log"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  
    31  	"github.com/bazelbuild/buildtools/tables"
    32  	"github.com/bazelbuild/buildtools/warn"
    33  	"github.com/bazelbuild/buildtools/wspace"
    34  )
    35  
    36  const buildifierJSONFilename = ".buildifier.json"
    37  
    38  // New constructs a Config with default values.
    39  func New() *Config {
    40  	return &Config{
    41  		InputType: "auto",
    42  	}
    43  }
    44  
    45  // FindConfigPath locates the nearest buildifier configuration file.  First
    46  // tries the value of the BUILDIFIER_CONFIG environment variable.  If no
    47  // environment variable is defined, The configuration file will be resolved
    48  // starting from the process cwd and searching up the file tree until a config
    49  // file is (or isn't) found.
    50  func FindConfigPath(rootDir string) string {
    51  	if filename, ok := os.LookupEnv("BUILDIFIER_CONFIG"); ok {
    52  		return filename
    53  	}
    54  	if rootDir == "" {
    55  		rootDir, _ = os.Getwd() // best-effort, ignore error
    56  	}
    57  	dirname, err := wspace.Find(
    58  		rootDir,
    59  		map[string]func(os.FileInfo) bool{
    60  			buildifierJSONFilename: func(fi os.FileInfo) bool {
    61  				return fi.Mode()&os.ModeType == 0
    62  			},
    63  		},
    64  	)
    65  	if err != nil {
    66  		return ""
    67  	}
    68  	return filepath.Join(dirname, buildifierJSONFilename)
    69  }
    70  
    71  // Config is used to configure buildifier
    72  type Config struct {
    73  	// InputType determines the input file type: build (for BUILD files), bzl
    74  	// (for .bzl files), workspace (for WORKSPACE files), default (for generic
    75  	// Starlark files), module (for MODULE.bazel files)
    76  	// or auto (default, based on the filename)
    77  	InputType string `json:"type,omitempty"`
    78  	// Format sets the diagnostics format: text or json (default text)
    79  	Format string `json:"format,omitempty"`
    80  	// Mode determines the formatting mode: check, diff, or fix (default fix)
    81  	Mode string `json:"mode,omitempty"`
    82  	// DiffMode is an alias for
    83  	DiffMode bool `json:"diffMode,omitempty"`
    84  	// Lint determines the lint mode: off, warn, or fix (default off)
    85  	Lint string `json:"lint,omitempty"`
    86  	// Warnings is a comma-separated list of warning identifiers used in the lint mode or "all"
    87  	Warnings string `json:"warnings,omitempty"`
    88  	// WarningsList is a list of warnings (alternative to comma-separated warnings string)
    89  	WarningsList []string `json:"warningsList,omitempty"`
    90  	// Recursive instructs buildifier to find starlark files recursively
    91  	Recursive bool `json:"recursive,omitempty"`
    92  	// Verbose instructs buildifier to output verbose diagnostics
    93  	Verbose bool `json:"verbose,omitempty"`
    94  	// DiffCommand is the command to run when the formatting mode is diff
    95  	// (default uses the BUILDIFIER_DIFF, BUILDIFIER_MULTIDIFF, and DISPLAY
    96  	// environment variables to create the diff command)
    97  	DiffCommand string `json:"diffCommand,omitempty"`
    98  	// MultiDiff means the command specified by the -diff_command flag can diff
    99  	// multiple files in the style of tkdiff (default false)
   100  	MultiDiff bool `json:"multiDiff,omitempty"`
   101  	// TablesPath is the path to JSON file with custom table definitions that
   102  	// will replace the built-in tables
   103  	TablesPath string `json:"tables,omitempty"`
   104  	// AddTablesPath path to JSON file with custom table definitions which will be merged with the built-in tables
   105  	AddTablesPath string `json:"addTables,omitempty"`
   106  	// WorkspaceRelativePath - assume BUILD file has this path relative to the workspace directory
   107  	WorkspaceRelativePath string `json:"path,omitempty"`
   108  	// DisableRewrites configures the list of buildifier rewrites to disable
   109  	DisableRewrites ArrayFlags `json:"buildifier_disable,omitempty"`
   110  	// AllowSort specifies additional sort contexts to treat as safe
   111  	AllowSort ArrayFlags `json:"allowsort,omitempty"`
   112  
   113  	// Help is true if the -h flag is set
   114  	Help bool `json:"-"`
   115  	// Version is true if the -v flag is set
   116  	Version bool `json:"-"`
   117  	// ConfigPath is the path to this config
   118  	ConfigPath string `json:"-"`
   119  	// LintWarnings is the final validated list of Lint/Fix warnings
   120  	LintWarnings []string `json:"-"`
   121  }
   122  
   123  // LoadFile unmarshals JSON file from the ConfigPath field.
   124  func (c *Config) LoadFile() error {
   125  	file, err := os.Open(c.ConfigPath)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer file.Close()
   130  	return c.LoadReader(file)
   131  }
   132  
   133  // LoadReader unmarshals JSON data from the given reader.
   134  func (c *Config) LoadReader(in io.Reader) error {
   135  	data, err := ioutil.ReadAll(in)
   136  	if err != nil {
   137  		return fmt.Errorf("reading config: %w", err)
   138  	}
   139  	if err := json.Unmarshal(data, c); err != nil {
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  // FlagSet returns a flag.FlagSet that can be used to override the config.
   146  func (c *Config) FlagSet(name string, errorHandling flag.ErrorHandling) *flag.FlagSet {
   147  	flags := flag.NewFlagSet(name, errorHandling)
   148  
   149  	flags.BoolVar(&c.Help, "help", false, "print usage information")
   150  	flags.BoolVar(&c.Version, "version", false, "print the version of buildifier")
   151  	flags.BoolVar(&c.Verbose, "v", c.Verbose, "print verbose information to standard error")
   152  	flags.BoolVar(&c.DiffMode, "d", c.DiffMode, "alias for -mode=diff")
   153  	flags.BoolVar(&c.Recursive, "r", c.Recursive, "find starlark files recursively")
   154  	flags.BoolVar(&c.MultiDiff, "multi_diff", c.MultiDiff, "the command specified by the -diff_command flag can diff multiple files in the style of tkdiff (default false)")
   155  	flags.StringVar(&c.Mode, "mode", c.Mode, "formatting mode: check, diff, or fix (default fix)")
   156  	flags.StringVar(&c.Format, "format", c.Format, "diagnostics format: text or json (default text)")
   157  	flags.StringVar(&c.DiffCommand, "diff_command", c.DiffCommand, "command to run when the formatting mode is diff (default uses the BUILDIFIER_DIFF, BUILDIFIER_MULTIDIFF, and DISPLAY environment variables to create the diff command)")
   158  	flags.StringVar(&c.Lint, "lint", c.Lint, "lint mode: off, warn, or fix (default off)")
   159  	flags.StringVar(&c.Warnings, "warnings", c.Warnings, "comma-separated warnings used in the lint mode or \"all\"")
   160  	flags.StringVar(&c.WorkspaceRelativePath, "path", c.WorkspaceRelativePath, "assume BUILD file has this path relative to the workspace directory")
   161  	flags.StringVar(&c.TablesPath, "tables", c.TablesPath, "path to JSON file with custom table definitions which will replace the built-in tables")
   162  	flags.StringVar(&c.AddTablesPath, "add_tables", c.AddTablesPath, "path to JSON file with custom table definitions which will be merged with the built-in tables")
   163  	flags.StringVar(&c.InputType, "type", c.InputType, "Input file type: build (for BUILD files), bzl (for .bzl files), workspace (for WORKSPACE files), module (for MODULE.bazel files), default (for generic Starlark files) or auto (default, based on the filename)")
   164  	flags.StringVar(&c.ConfigPath, "config", "", "path to .buildifier.json config file")
   165  	flags.Var(&c.AllowSort, "allowsort", "additional sort contexts to treat as safe")
   166  	flags.Var(&c.DisableRewrites, "buildifier_disable", "list of buildifier rewrites to disable")
   167  
   168  	return flags
   169  }
   170  
   171  // Validate checks that the input type, format, and lint modes are correctly
   172  // set.  It computes the final set of warnings used for linting.  The tables
   173  // package is configured as a side-effect.
   174  func (c *Config) Validate(args []string) error {
   175  	if err := ValidateInputType(&c.InputType); err != nil {
   176  		return err
   177  	}
   178  
   179  	if err := ValidateFormat(&c.Format, &c.Mode); err != nil {
   180  		return err
   181  	}
   182  
   183  	if err := ValidateModes(&c.Mode, &c.Lint, &c.DiffMode); err != nil {
   184  		return err
   185  	}
   186  
   187  	// If the path flag is set, must only be formatting a single file.
   188  	// It doesn't make sense for multiple files to have the same path.
   189  	if (c.WorkspaceRelativePath != "" || c.Mode == "print_if_changed") && len(args) > 1 {
   190  		return fmt.Errorf("can only format one file when using -path flag or -mode=print_if_changed")
   191  	}
   192  
   193  	if c.TablesPath != "" {
   194  		if err := tables.ParseAndUpdateJSONDefinitions(c.TablesPath, false); err != nil {
   195  			return fmt.Errorf("failed to parse %s for -tables: %w", c.TablesPath, err)
   196  		}
   197  	}
   198  
   199  	if c.AddTablesPath != "" {
   200  		if err := tables.ParseAndUpdateJSONDefinitions(c.AddTablesPath, true); err != nil {
   201  			return fmt.Errorf("failed to parse %s for -add_tables: %w", c.AddTablesPath, err)
   202  		}
   203  	}
   204  
   205  	warningsList := c.WarningsList
   206  	if c.Warnings != "" {
   207  		warningsList = append(warningsList, c.Warnings)
   208  	}
   209  	warnings := strings.Join(warningsList, ",")
   210  	lintWarnings, err := ValidateWarnings(&warnings, &warn.AllWarnings, &warn.DefaultWarnings)
   211  	if err != nil {
   212  		return err // TODO(pcj) return nil?
   213  	}
   214  	c.LintWarnings = lintWarnings
   215  
   216  	return nil
   217  }
   218  
   219  // String renders the config as a formatted JSON string and satisfies the
   220  // Stringer interface.
   221  func (c *Config) String() string {
   222  	data, err := json.MarshalIndent(c, "", "  ")
   223  	if err != nil {
   224  		log.Panicf("config marshal json: %v", err)
   225  	}
   226  	return string(data)
   227  }
   228  
   229  // ArrayFlags is a string slice that satisfies the flag.Value interface
   230  type ArrayFlags []string
   231  
   232  // String implements part of the flag.Value interface
   233  func (i *ArrayFlags) String() string {
   234  	return strings.Join(*i, ",")
   235  }
   236  
   237  // Set implements part of the flag.Value interface
   238  func (i *ArrayFlags) Set(value string) error {
   239  	*i = append(*i, value)
   240  	return nil
   241  }
   242  
   243  // Example creates a sample configuration file for the -config=example flag.
   244  func Example() *Config {
   245  	c := New()
   246  	c.InputType = "auto"
   247  	c.Mode = "fix"
   248  	c.Lint = "fix"
   249  	c.WarningsList = warn.AllWarnings
   250  	return c
   251  }
   252  

View as plain text