99 "go/token"
1010 "go/types"
1111 "strconv"
12+ "strings"
1213
1314 "github.com/ettle/strcase"
1415 "golang.org/x/tools/go/analysis"
@@ -22,6 +23,7 @@ type Options struct {
2223 NoMixedArgs bool // Enforce not mixing key-value pairs and attributes (default true).
2324 KVOnly bool // Enforce using key-value pairs only (overrides NoMixedArgs, incompatible with AttrOnly).
2425 AttrOnly bool // Enforce using attributes only (overrides NoMixedArgs, incompatible with KVOnly).
26+ NoGlobal string // Enforce not using global loggers ("all" or "default").
2527 ContextOnly bool // Enforce using methods that accept a context.
2628 StaticMsg bool // Enforce using static log messages.
2729 NoRawKeys bool // Enforce using constants instead of raw keys.
@@ -43,11 +45,19 @@ func New(opts *Options) *analysis.Analyzer {
4345 if opts .KVOnly && opts .AttrOnly {
4446 return nil , fmt .Errorf ("sloglint: Options.KVOnly and Options.AttrOnly: %w" , errIncompatible )
4547 }
48+
49+ switch opts .NoGlobal {
50+ case "" , "all" , "default" :
51+ default :
52+ return nil , fmt .Errorf ("sloglint: Options.NoGlobal=%s: %w" , opts .NoGlobal , errInvalidValue )
53+ }
54+
4655 switch opts .KeyNamingCase {
4756 case "" , snakeCase , kebabCase , camelCase , pascalCase :
4857 default :
4958 return nil , fmt .Errorf ("sloglint: Options.KeyNamingCase=%s: %w" , opts .KeyNamingCase , errInvalidValue )
5059 }
60+
5161 run (pass , opts )
5262 return nil , nil
5363 },
@@ -60,30 +70,34 @@ var (
6070)
6171
6272func flags (opts * Options ) flag.FlagSet {
63- fs := flag .NewFlagSet ("sloglint" , flag .ContinueOnError )
73+ fset := flag .NewFlagSet ("sloglint" , flag .ContinueOnError )
6474
6575 boolVar := func (value * bool , name , usage string ) {
66- fs .Func (name , usage , func (s string ) error {
76+ fset .Func (name , usage , func (s string ) error {
6777 v , err := strconv .ParseBool (s )
6878 * value = v
6979 return err
7080 })
7181 }
7282
83+ strVar := func (value * string , name , usage string ) {
84+ fset .Func (name , usage , func (s string ) error {
85+ * value = s
86+ return nil
87+ })
88+ }
89+
7390 boolVar (& opts .NoMixedArgs , "no-mixed-args" , "enforce not mixing key-value pairs and attributes (default true)" )
7491 boolVar (& opts .KVOnly , "kv-only" , "enforce using key-value pairs only (overrides -no-mixed-args, incompatible with -attr-only)" )
7592 boolVar (& opts .AttrOnly , "attr-only" , "enforce using attributes only (overrides -no-mixed-args, incompatible with -kv-only)" )
93+ strVar (& opts .NoGlobal , "no-global" , "enforce not using global loggers (all|default)" )
7694 boolVar (& opts .ContextOnly , "context-only" , "enforce using methods that accept a context" )
7795 boolVar (& opts .StaticMsg , "static-msg" , "enforce using static log messages" )
7896 boolVar (& opts .NoRawKeys , "no-raw-keys" , "enforce using constants instead of raw keys" )
97+ strVar (& opts .KeyNamingCase , "key-naming-case" , "enforce a single key naming convention (snake|kebab|camel|pascal)" )
7998 boolVar (& opts .ArgsOnSepLines , "args-on-sep-lines" , "enforce putting arguments on separate lines" )
8099
81- fs .Func ("key-naming-case" , "enforce a single key naming convention (snake|kebab|camel|pascal)" , func (s string ) error {
82- opts .KeyNamingCase = s
83- return nil
84- })
85-
86- return * fs
100+ return * fset
87101}
88102
89103var slogFuncs = map [string ]int { // funcName:argsPos
@@ -139,17 +153,30 @@ func run(pass *analysis.Pass, opts *Options) {
139153 return
140154 }
141155
142- argsPos , ok := slogFuncs [fn .FullName ()]
156+ name := fn .FullName ()
157+ argsPos , ok := slogFuncs [name ]
143158 if ! ok {
144159 return
145160 }
146161
162+ switch opts .NoGlobal {
163+ case "all" :
164+ if strings .HasPrefix (name , "log/slog." ) || globalLoggerUsed (pass .TypesInfo , call .Fun ) {
165+ pass .Reportf (call .Pos (), "global logger should not be used" )
166+ }
167+ case "default" :
168+ if strings .HasPrefix (name , "log/slog." ) {
169+ pass .Reportf (call .Pos (), "default logger should not be used" )
170+ }
171+ }
172+
147173 if opts .ContextOnly {
148174 typ := pass .TypesInfo .TypeOf (call .Args [0 ])
149175 if typ != nil && typ .String () != "context.Context" {
150176 pass .Reportf (call .Pos (), "methods without a context should not be used" )
151177 }
152178 }
179+
153180 if opts .StaticMsg && ! staticMsg (call .Args [argsPos - 1 ]) {
154181 pass .Reportf (call .Pos (), "message should be a string literal or a constant" )
155182 }
@@ -189,6 +216,7 @@ func run(pass *analysis.Pass, opts *Options) {
189216 if opts .NoRawKeys && rawKeysUsed (pass .TypesInfo , keys , attrs ) {
190217 pass .Reportf (call .Pos (), "raw keys should not be used" )
191218 }
219+
192220 if opts .ArgsOnSepLines && argsOnSameLine (pass .Fset , call , keys , attrs ) {
193221 pass .Reportf (call .Pos (), "arguments should be put on separate lines" )
194222 }
@@ -206,6 +234,19 @@ func run(pass *analysis.Pass, opts *Options) {
206234 })
207235}
208236
237+ func globalLoggerUsed (info * types.Info , expr ast.Expr ) bool {
238+ selector , ok := expr .(* ast.SelectorExpr )
239+ if ! ok {
240+ return false
241+ }
242+ ident , ok := selector .X .(* ast.Ident )
243+ if ! ok {
244+ return false
245+ }
246+ obj := info .ObjectOf (ident )
247+ return obj .Parent () == obj .Pkg ().Scope ()
248+ }
249+
209250func staticMsg (expr ast.Expr ) bool {
210251 switch msg := expr .(type ) {
211252 case * ast.BasicLit : // e.g. slog.Info("msg")
0 commit comments