github.com/dvyukov/gometalinter@v2.0.12-0.20181028185006-9777a28a8438+incompatible/config.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"text/template"
     9  	"time"
    10  )
    11  
    12  // Config for gometalinter. This can be loaded from a JSON file with --config.
    13  type Config struct { // nolint: maligned
    14  	// A map from linter name -> <LinterConfig|string>.
    15  	//
    16  	// For backwards compatibility, the value stored in the JSON blob can also
    17  	// be a string of the form "<command>:<pattern>".
    18  	Linters map[string]StringOrLinterConfig
    19  
    20  	// The set of linters that should be enabled.
    21  	Enable  []string
    22  	Disable []string
    23  
    24  	// A map of linter name to message that is displayed. This is useful when linters display text
    25  	// that is useful only in isolation, such as errcheck which just reports the construct.
    26  	MessageOverride map[string]string
    27  	Severity        map[string]string
    28  	VendoredLinters bool
    29  	Format          string
    30  	Fast            bool
    31  	Install         bool
    32  	Update          bool
    33  	Force           bool
    34  	DownloadOnly    bool
    35  	Debug           bool
    36  	Concurrency     int
    37  	Exclude         []string
    38  	Include         []string
    39  	Skip            []string
    40  	Vendor          bool
    41  	Cyclo           int
    42  	LineLength      int
    43  	MisspellLocale  string
    44  	MinConfidence   float64
    45  	MinOccurrences  int
    46  	MinConstLength  int
    47  	DuplThreshold   int
    48  	Sort            []string
    49  	Test            bool
    50  	Deadline        jsonDuration
    51  	Errors          bool
    52  	JSON            bool
    53  	Checkstyle      bool
    54  	EnableGC        bool
    55  	Aggregate       bool
    56  	EnableAll       bool
    57  
    58  	// Warn if a nolint directive was never matched to a linter issue
    59  	WarnUnmatchedDirective bool
    60  
    61  	formatTemplate *template.Template
    62  }
    63  
    64  type StringOrLinterConfig LinterConfig
    65  
    66  func (c *StringOrLinterConfig) UnmarshalJSON(raw []byte) error {
    67  	var linterConfig LinterConfig
    68  	// first try to un-marshall directly into struct
    69  	origErr := json.Unmarshal(raw, &linterConfig)
    70  	if origErr == nil {
    71  		*c = StringOrLinterConfig(linterConfig)
    72  		return nil
    73  	}
    74  
    75  	// i.e. bytes didn't represent the struct, treat them as a string
    76  	var linterSpec string
    77  	if err := json.Unmarshal(raw, &linterSpec); err != nil {
    78  		return origErr
    79  	}
    80  	linter, err := parseLinterConfigSpec("", linterSpec)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	*c = StringOrLinterConfig(linter)
    85  	return nil
    86  }
    87  
    88  type jsonDuration time.Duration
    89  
    90  func (td *jsonDuration) UnmarshalJSON(raw []byte) error {
    91  	var durationAsString string
    92  	if err := json.Unmarshal(raw, &durationAsString); err != nil {
    93  		return err
    94  	}
    95  	duration, err := time.ParseDuration(durationAsString)
    96  	*td = jsonDuration(duration)
    97  	return err
    98  }
    99  
   100  // Duration returns the value as a time.Duration
   101  func (td *jsonDuration) Duration() time.Duration {
   102  	return time.Duration(*td)
   103  }
   104  
   105  var sortKeys = []string{"none", "path", "line", "column", "severity", "message", "linter"}
   106  
   107  // Configuration defaults.
   108  var config = &Config{
   109  	Format: DefaultIssueFormat,
   110  
   111  	Linters: map[string]StringOrLinterConfig{},
   112  	Severity: map[string]string{
   113  		"gotype":  "error",
   114  		"gotypex": "error",
   115  		"test":    "error",
   116  		"testify": "error",
   117  		"vet":     "error",
   118  	},
   119  	MessageOverride: map[string]string{
   120  		"errcheck":    "error return value not checked ({message})",
   121  		"gocyclo":     "cyclomatic complexity {cyclo} of function {function}() is high (> {mincyclo})",
   122  		"gofmt":       "file is not gofmted with -s",
   123  		"goimports":   "file is not goimported",
   124  		"safesql":     "potentially unsafe SQL statement",
   125  		"structcheck": "unused struct field {message}",
   126  		"unparam":     "parameter {message}",
   127  		"varcheck":    "unused variable or constant {message}",
   128  	},
   129  	Enable:          defaultEnabled(),
   130  	VendoredLinters: true,
   131  	Concurrency:     runtime.NumCPU(),
   132  	Cyclo:           10,
   133  	LineLength:      80,
   134  	MisspellLocale:  "",
   135  	MinConfidence:   0.8,
   136  	MinOccurrences:  3,
   137  	MinConstLength:  3,
   138  	DuplThreshold:   50,
   139  	Sort:            []string{"none"},
   140  	Deadline:        jsonDuration(time.Second * 30),
   141  }
   142  
   143  func loadConfigFile(filename string) error {
   144  	r, err := os.Open(filename)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	defer r.Close() // nolint: errcheck
   149  	err = json.NewDecoder(r).Decode(config)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	for _, disable := range config.Disable {
   154  		for i, enable := range config.Enable {
   155  			if enable == disable {
   156  				config.Enable = append(config.Enable[:i], config.Enable[i+1:]...)
   157  				break
   158  			}
   159  		}
   160  	}
   161  	return err
   162  }
   163  
   164  func findDefaultConfigFile() (fullPath string, found bool, err error) {
   165  	prevPath := ""
   166  	dirPath, err := os.Getwd()
   167  	if err != nil {
   168  		return "", false, err
   169  	}
   170  
   171  	for dirPath != prevPath {
   172  		fullPath, found, err = findConfigFileInDir(dirPath)
   173  		if err != nil || found {
   174  			return fullPath, found, err
   175  		}
   176  		prevPath, dirPath = dirPath, filepath.Dir(dirPath)
   177  	}
   178  
   179  	return "", false, nil
   180  }
   181  
   182  func findConfigFileInDir(dirPath string) (fullPath string, found bool, err error) {
   183  	fullPath = filepath.Join(dirPath, defaultConfigPath)
   184  	if _, err := os.Stat(fullPath); err != nil {
   185  		if os.IsNotExist(err) {
   186  			return "", false, nil
   187  		}
   188  		return "", false, err
   189  	}
   190  
   191  	return fullPath, true, nil
   192  }