github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/config/reader.go (about) 1 package config 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/mitchellh/go-homedir" 11 "github.com/spf13/viper" 12 13 "github.com/elek/golangci-lint/pkg/fsutils" 14 "github.com/elek/golangci-lint/pkg/logutils" 15 "github.com/elek/golangci-lint/pkg/sliceutil" 16 ) 17 18 type FileReader struct { 19 log logutils.Log 20 cfg *Config 21 commandLineCfg *Config 22 } 23 24 func NewFileReader(toCfg, commandLineCfg *Config, log logutils.Log) *FileReader { 25 return &FileReader{ 26 log: log, 27 cfg: toCfg, 28 commandLineCfg: commandLineCfg, 29 } 30 } 31 32 func (r *FileReader) Read() error { 33 // XXX: hack with double parsing for 2 purposes: 34 // 1. to access "config" option here. 35 // 2. to give config less priority than command line. 36 37 configFile, err := r.parseConfigOption() 38 if err != nil { 39 if err == errConfigDisabled { 40 return nil 41 } 42 43 return fmt.Errorf("can't parse --config option: %s", err) 44 } 45 46 if configFile != "" { 47 viper.SetConfigFile(configFile) 48 } else { 49 r.setupConfigFileSearch() 50 } 51 52 return r.parseConfig() 53 } 54 55 func (r *FileReader) parseConfig() error { 56 if err := viper.ReadInConfig(); err != nil { 57 if _, ok := err.(viper.ConfigFileNotFoundError); ok { 58 return nil 59 } 60 61 return fmt.Errorf("can't read viper config: %s", err) 62 } 63 64 usedConfigFile := viper.ConfigFileUsed() 65 if usedConfigFile == "" { 66 return nil 67 } 68 69 usedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "") 70 if err != nil { 71 r.log.Warnf("Can't pretty print config file path: %s", err) 72 } 73 r.log.Infof("Used config file %s", usedConfigFile) 74 75 if err := viper.Unmarshal(r.cfg); err != nil { 76 return fmt.Errorf("can't unmarshal config by viper: %s", err) 77 } 78 79 if err := r.validateConfig(); err != nil { 80 return fmt.Errorf("can't validate config: %s", err) 81 } 82 83 if r.cfg.InternalTest { // just for testing purposes: to detect config file usage 84 fmt.Fprintln(logutils.StdOut, "test") 85 os.Exit(0) 86 } 87 88 return nil 89 } 90 91 func (r *FileReader) validateConfig() error { 92 c := r.cfg 93 if len(c.Run.Args) != 0 { 94 return errors.New("option run.args in config isn't supported now") 95 } 96 97 if c.Run.CPUProfilePath != "" { 98 return errors.New("option run.cpuprofilepath in config isn't allowed") 99 } 100 101 if c.Run.MemProfilePath != "" { 102 return errors.New("option run.memprofilepath in config isn't allowed") 103 } 104 105 if c.Run.TracePath != "" { 106 return errors.New("option run.tracepath in config isn't allowed") 107 } 108 109 if c.Run.IsVerbose { 110 return errors.New("can't set run.verbose option with config: only on command-line") 111 } 112 for i, rule := range c.Issues.ExcludeRules { 113 if err := rule.Validate(); err != nil { 114 return fmt.Errorf("error in exclude rule #%d: %v", i, err) 115 } 116 } 117 if len(c.Severity.Rules) > 0 && c.Severity.Default == "" { 118 return errors.New("can't set severity rule option: no default severity defined") 119 } 120 for i, rule := range c.Severity.Rules { 121 if err := rule.Validate(); err != nil { 122 return fmt.Errorf("error in severity rule #%d: %v", i, err) 123 } 124 } 125 if err := c.LintersSettings.Govet.Validate(); err != nil { 126 return fmt.Errorf("error in govet config: %v", err) 127 } 128 return nil 129 } 130 131 func getFirstPathArg() string { 132 args := os.Args 133 134 // skip all args ([golangci-lint, run/linters]) before files/dirs list 135 for len(args) != 0 { 136 if args[0] == "run" { 137 args = args[1:] 138 break 139 } 140 141 args = args[1:] 142 } 143 144 // find first file/dir arg 145 firstArg := "./..." 146 for _, arg := range args { 147 if !strings.HasPrefix(arg, "-") { 148 firstArg = arg 149 break 150 } 151 } 152 153 return firstArg 154 } 155 156 func (r *FileReader) setupConfigFileSearch() { 157 firstArg := getFirstPathArg() 158 absStartPath, err := filepath.Abs(firstArg) 159 if err != nil { 160 r.log.Warnf("Can't make abs path for %q: %s", firstArg, err) 161 absStartPath = filepath.Clean(firstArg) 162 } 163 164 // start from it 165 var curDir string 166 if fsutils.IsDir(absStartPath) { 167 curDir = absStartPath 168 } else { 169 curDir = filepath.Dir(absStartPath) 170 } 171 172 // find all dirs from it up to the root 173 configSearchPaths := []string{"./"} 174 175 for { 176 configSearchPaths = append(configSearchPaths, curDir) 177 newCurDir := filepath.Dir(curDir) 178 if curDir == newCurDir || newCurDir == "" { 179 break 180 } 181 curDir = newCurDir 182 } 183 184 // find home directory for global config 185 if home, err := homedir.Dir(); err != nil { 186 r.log.Warnf("Can't get user's home directory: %s", err.Error()) 187 } else if !sliceutil.Contains(configSearchPaths, home) { 188 configSearchPaths = append(configSearchPaths, home) 189 } 190 191 r.log.Infof("Config search paths: %s", configSearchPaths) 192 viper.SetConfigName(".golangci") 193 for _, p := range configSearchPaths { 194 viper.AddConfigPath(p) 195 } 196 } 197 198 var errConfigDisabled = errors.New("config is disabled by --no-config") 199 200 func (r *FileReader) parseConfigOption() (string, error) { 201 cfg := r.commandLineCfg 202 if cfg == nil { 203 return "", nil 204 } 205 206 configFile := cfg.Run.Config 207 if cfg.Run.NoConfig && configFile != "" { 208 return "", fmt.Errorf("can't combine option --config and --no-config") 209 } 210 211 if cfg.Run.NoConfig { 212 return "", errConfigDisabled 213 } 214 215 configFile, err := homedir.Expand(configFile) 216 if err != nil { 217 return "", fmt.Errorf("failed to expand configuration path") 218 } 219 220 return configFile, nil 221 }