Skip to content

Commit 39e3d06

Browse files
authored
Merge pull request #217 from shiftstack/config-for-disabled-linter
Allow configuration for disabled linters
2 parents d65d24a + b7319a9 commit 39e3d06

2 files changed

Lines changed: 81 additions & 6 deletions

File tree

pkg/analysis/registry/registry.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func (r *registry) AllLinters() sets.Set[string] {
100100
r.lock.RLock()
101101
defer r.lock.RUnlock()
102102

103+
return r.allLinters()
104+
}
105+
106+
// allLinters returns the list of all known linters without acquiring the lock.
107+
// Callers must hold the lock.
108+
func (r *registry) allLinters() sets.Set[string] {
103109
linters := sets.New[string]()
104110

105111
for _, initializer := range r.initializers {
@@ -165,11 +171,24 @@ func (r *registry) validateLintersConfig(cfg config.Linters, lintersCfg config.L
165171
}
166172
}
167173

168-
fieldErrors = append(fieldErrors, validateUnusedLinters(lintersCfg, validatedLinters, fieldPath)...)
174+
fieldErrors = append(fieldErrors, validateUnusedLinters(lintersCfg, validatedLinters, r.allConfigurableLinters(), r.allLinters(), fieldPath)...)
169175

170176
return fieldErrors
171177
}
172178

179+
// allConfigurableLinters returns the names of all linters that are configurable.
180+
func (r *registry) allConfigurableLinters() sets.Set[string] {
181+
configurableLinters := sets.New[string]()
182+
183+
for _, init := range r.initializers {
184+
if _, ok := isConfigurable(init); ok {
185+
configurableLinters.Insert(init.Name())
186+
}
187+
}
188+
189+
return configurableLinters
190+
}
191+
173192
// getEnabledInitializers returns the initializers that are enabled by the config.
174193
// It returns a list of initializers that are enabled by the config.
175194
func (r *registry) getEnabledInitializers(cfg config.Linters) []initializer.AnalyzerInitializer {
@@ -234,18 +253,34 @@ func getConfigByName(name string, lintersCfg config.LintersConfig) (any, bool) {
234253
return nil, false
235254
}
236255

237-
// validateUnusedLinters validates that all linters in the config are enabled.
238-
// It returns a list of errors for each linter that is not enabled.
239-
func validateUnusedLinters(lintersCfg config.LintersConfig, validatedLinters sets.Set[string], fieldPath *field.Path) field.ErrorList {
256+
// validateUnusedLinters validates that all linters in the config exist as configurable linters.
257+
// It returns a list of errors for each linter config that does not correspond to a known configurable linter.
258+
// It does NOT error for disabled linters that have configuration - this allows users to keep
259+
// configuration for linters they have temporarily disabled.
260+
func validateUnusedLinters(lintersCfg config.LintersConfig, validatedLinters, configurableLinters, allLinters sets.Set[string], fieldPath *field.Path) field.ErrorList {
240261
fieldErrors := field.ErrorList{}
241262

242263
for name := range lintersCfg {
243264
// Hack to allow backwards compatibility with early configuration.
244265
// We use to have camelCased config names, but now it is all lowercase matched on the linter name.
245266
// TODO(@JoelSpeed): Remove the strings.ToLower in a future release with a release note about the change.
246-
if !validatedLinters.Has(strings.ToLower(name)) {
247-
fieldErrors = append(fieldErrors, field.Invalid(fieldPath.Child(name), nil, "linter is not enabled"))
267+
lowerName := strings.ToLower(name)
268+
269+
if validatedLinters.Has(lowerName) {
270+
continue
271+
}
272+
273+
// If the linter is configurable, it's OK to have config for it even if disabled
274+
if configurableLinters.Has(lowerName) {
275+
continue
248276
}
277+
278+
if allLinters.Has(lowerName) {
279+
fieldErrors = append(fieldErrors, field.Invalid(fieldPath.Child(name), name, "linter is not configurable"))
280+
continue
281+
}
282+
283+
fieldErrors = append(fieldErrors, field.Invalid(fieldPath.Child(name), name, "unknown linter"))
249284
}
250285

251286
return fieldErrors

pkg/analysis/registry/registry_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,46 @@ var _ = Describe("Registry", func() {
248248
},
249249
expectedErr: "error validating linters config: lintersConfig.optionalorrequired.preferredRequiredMarker: Invalid value: \"invalid\": invalid value, must be one of \"required\", \"kubebuilder:validation:Required\" or omitted",
250250
}),
251+
252+
// Tests for disabled linters with configuration
253+
Entry("With config for explicitly disabled linter should not error", validateLintersConfigTableInput{
254+
linters: config.Linters{
255+
Disable: []string{"jsontags"},
256+
},
257+
config: config.LintersConfig{
258+
"jsontags": jsontags.JSONTagsConfig{
259+
JSONTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$",
260+
},
261+
},
262+
expectedErr: "",
263+
}),
264+
Entry("With config for disabled linter when all disabled with wildcard should not error", validateLintersConfigTableInput{
265+
linters: config.Linters{
266+
Disable: []string{config.Wildcard},
267+
},
268+
config: config.LintersConfig{
269+
"jsontags": jsontags.JSONTagsConfig{
270+
JSONTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$",
271+
},
272+
},
273+
expectedErr: "",
274+
}),
275+
Entry("With config for non-existent linter should error", validateLintersConfigTableInput{
276+
config: config.LintersConfig{
277+
"nonexistent": map[string]any{
278+
"someOption": "value",
279+
},
280+
},
281+
expectedErr: "error validating linters config: lintersConfig.nonexistent: Invalid value: \"nonexistent\": unknown linter",
282+
}),
283+
Entry("With config for non-configurable linter should error", validateLintersConfigTableInput{
284+
config: config.LintersConfig{
285+
"nobools": map[string]any{
286+
"someOption": "value",
287+
},
288+
},
289+
expectedErr: "error validating linters config: lintersConfig.nobools: Invalid value: \"nobools\": linter is not configurable",
290+
}),
251291
)
252292
})
253293
})

0 commit comments

Comments
 (0)