8
8
"go/ast"
9
9
"go/token"
10
10
"go/types"
11
+ "slices"
11
12
"strconv"
12
13
"strings"
13
14
@@ -20,15 +21,16 @@ import (
20
21
21
22
// Options are options for the sloglint analyzer.
22
23
type Options struct {
23
- NoMixedArgs bool // Enforce not mixing key-value pairs and attributes (default true).
24
- KVOnly bool // Enforce using key-value pairs only (overrides NoMixedArgs, incompatible with AttrOnly).
25
- AttrOnly bool // Enforce using attributes only (overrides NoMixedArgs, incompatible with KVOnly).
26
- NoGlobal string // Enforce not using global loggers ("all" or "default").
27
- ContextOnly string // Enforce using methods that accept a context ("all" or "scope").
28
- StaticMsg bool // Enforce using static log messages.
29
- NoRawKeys bool // Enforce using constants instead of raw keys.
30
- KeyNamingCase string // Enforce a single key naming convention ("snake", "kebab", "camel", or "pascal").
31
- ArgsOnSepLines bool // Enforce putting arguments on separate lines.
24
+ NoMixedArgs bool // Enforce not mixing key-value pairs and attributes (default true).
25
+ KVOnly bool // Enforce using key-value pairs only (overrides NoMixedArgs, incompatible with AttrOnly).
26
+ AttrOnly bool // Enforce using attributes only (overrides NoMixedArgs, incompatible with KVOnly).
27
+ NoGlobal string // Enforce not using global loggers ("all" or "default").
28
+ ContextOnly string // Enforce using methods that accept a context ("all" or "scope").
29
+ StaticMsg bool // Enforce using static log messages.
30
+ NoRawKeys bool // Enforce using constants instead of raw keys.
31
+ KeyNamingCase string // Enforce a single key naming convention ("snake", "kebab", "camel", or "pascal").
32
+ ForbiddenKeys []string // Enforce not using specific keys.
33
+ ArgsOnSepLines bool // Enforce putting arguments on separate lines.
32
34
}
33
35
34
36
// New creates a new sloglint analyzer.
@@ -104,6 +106,11 @@ func flags(opts *Options) flag.FlagSet {
104
106
strVar (& opts .KeyNamingCase , "key-naming-case" , "enforce a single key naming convention (snake|kebab|camel|pascal)" )
105
107
boolVar (& opts .ArgsOnSepLines , "args-on-sep-lines" , "enforce putting arguments on separate lines" )
106
108
109
+ fset .Func ("forbidden-keys" , "enforce not using specific keys (comma-separated)" , func (s string ) error {
110
+ opts .ForbiddenKeys = append (opts .ForbiddenKeys , strings .Split (s , "," )... )
111
+ return nil
112
+ })
113
+
107
114
return * fset
108
115
}
109
116
@@ -249,15 +256,41 @@ func visit(pass *analysis.Pass, opts *Options, node ast.Node, stack []ast.Node)
249
256
pass .Reportf (call .Pos (), "arguments should be put on separate lines" )
250
257
}
251
258
259
+ if len (opts .ForbiddenKeys ) > 0 {
260
+ if name , found := badKeyNames (pass .TypesInfo , isForbiddenKey (opts .ForbiddenKeys ), keys , attrs ); found {
261
+ pass .Reportf (call .Pos (), "%q key is forbidden and should not be used" , name )
262
+ }
263
+ }
264
+
252
265
switch {
253
- case opts .KeyNamingCase == snakeCase && badKeyNames (pass .TypesInfo , strcase .ToSnake , keys , attrs ):
254
- pass .Reportf (call .Pos (), "keys should be written in snake_case" )
255
- case opts .KeyNamingCase == kebabCase && badKeyNames (pass .TypesInfo , strcase .ToKebab , keys , attrs ):
256
- pass .Reportf (call .Pos (), "keys should be written in kebab-case" )
257
- case opts .KeyNamingCase == camelCase && badKeyNames (pass .TypesInfo , strcase .ToCamel , keys , attrs ):
258
- pass .Reportf (call .Pos (), "keys should be written in camelCase" )
259
- case opts .KeyNamingCase == pascalCase && badKeyNames (pass .TypesInfo , strcase .ToPascal , keys , attrs ):
260
- pass .Reportf (call .Pos (), "keys should be written in PascalCase" )
266
+ case opts .KeyNamingCase == snakeCase :
267
+ if _ , found := badKeyNames (pass .TypesInfo , valueChanged (strcase .ToSnake ), keys , attrs ); found {
268
+ pass .Reportf (call .Pos (), "keys should be written in snake_case" )
269
+ }
270
+ case opts .KeyNamingCase == kebabCase :
271
+ if _ , found := badKeyNames (pass .TypesInfo , valueChanged (strcase .ToKebab ), keys , attrs ); found {
272
+ pass .Reportf (call .Pos (), "keys should be written in kebab-case" )
273
+ }
274
+ case opts .KeyNamingCase == camelCase :
275
+ if _ , found := badKeyNames (pass .TypesInfo , valueChanged (strcase .ToCamel ), keys , attrs ); found {
276
+ pass .Reportf (call .Pos (), "keys should be written in camelCase" )
277
+ }
278
+ case opts .KeyNamingCase == pascalCase :
279
+ if _ , found := badKeyNames (pass .TypesInfo , valueChanged (strcase .ToPascal ), keys , attrs ); found {
280
+ pass .Reportf (call .Pos (), "keys should be written in PascalCase" )
281
+ }
282
+ }
283
+ }
284
+
285
+ func isForbiddenKey (forbiddenKeys []string ) func (string ) bool {
286
+ return func (name string ) bool {
287
+ return slices .Contains (forbiddenKeys , name )
288
+ }
289
+ }
290
+
291
+ func valueChanged (handler func (string ) string ) func (string ) bool {
292
+ return func (name string ) bool {
293
+ return handler (name ) != name
261
294
}
262
295
}
263
296
@@ -351,10 +384,10 @@ func rawKeysUsed(info *types.Info, keys, attrs []ast.Expr) bool {
351
384
return false
352
385
}
353
386
354
- func badKeyNames (info * types.Info , caseFn func (string ) string , keys , attrs []ast.Expr ) bool {
387
+ func badKeyNames (info * types.Info , validationFn func (string ) bool , keys , attrs []ast.Expr ) ( string , bool ) {
355
388
for _ , key := range keys {
356
- if name , ok := getKeyName (key ); ok && name != caseFn (name ) {
357
- return true
389
+ if name , ok := getKeyName (key ); ok && validationFn (name ) {
390
+ return name , true
358
391
}
359
392
}
360
393
@@ -389,12 +422,12 @@ func badKeyNames(info *types.Info, caseFn func(string) string, keys, attrs []ast
389
422
}
390
423
}
391
424
392
- if name , ok := getKeyName (expr ); ok && name != caseFn (name ) {
393
- return true
425
+ if name , ok := getKeyName (expr ); ok && validationFn (name ) {
426
+ return name , true
394
427
}
395
428
}
396
429
397
- return false
430
+ return "" , false
398
431
}
399
432
400
433
func getKeyName (expr ast.Expr ) (string , bool ) {
@@ -411,7 +444,12 @@ func getKeyName(expr ast.Expr) (string, bool) {
411
444
}
412
445
}
413
446
if lit , ok := expr .(* ast.BasicLit ); ok && lit .Kind == token .STRING {
414
- return lit .Value , true
447
+ // string literals are always quoted.
448
+ value , err := strconv .Unquote (lit .Value )
449
+ if err != nil {
450
+ panic ("unreachable" )
451
+ }
452
+ return value , true
415
453
}
416
454
return "" , false
417
455
}
0 commit comments