diff --git a/sloglint.go b/sloglint.go index 90067b1..d4dc964 100644 --- a/sloglint.go +++ b/sloglint.go @@ -239,15 +239,19 @@ func visit(pass *analysis.Pass, opts *Options, node ast.Node, stack []ast.Node) } msgPos := funcInfo.argsPos - 1 + // NOTE: "With" functions have no message argument and must be skipped. if opts.StaticMsg && msgPos >= 0 && !isStaticMsg(call.Args[msgPos]) { pass.Reportf(call.Pos(), "message should be a string literal or a constant") } if opts.MsgStyle != "" && msgPos >= 0 { - if msg, ok := call.Args[msgPos].(*ast.BasicLit); ok && msg.Kind == token.STRING { - msg.Value = msg.Value[1 : len(msg.Value)-1] // trim quotes/backticks. - if ok := isValidMsgStyle(msg.Value, opts.MsgStyle); !ok { + if lit, ok := call.Args[msgPos].(*ast.BasicLit); ok && lit.Kind == token.STRING { + value, err := strconv.Unquote(lit.Value) + if err != nil { + panic("unreachable") // string literals are always quoted. + } + if ok := isValidMsgStyle(value, opts.MsgStyle); !ok { pass.Reportf(call.Pos(), "message should be %s", opts.MsgStyle) } } @@ -267,7 +271,6 @@ func visit(pass *analysis.Pass, opts *Options, node ast.Node, stack []ast.Node) if typ == nil { continue } - switch typ.String() { case "string": keys = append(keys, args[i]) @@ -376,6 +379,11 @@ func isStaticMsg(msg ast.Expr) bool { return msg.Kind == token.STRING case *ast.Ident: // e.g. const msg = "msg"; slog.Info(msg) return msg.Obj != nil && msg.Obj.Kind == ast.Con + case *ast.BinaryExpr: // e.g. slog.Info("x" + "y") + if msg.Op != token.ADD { + panic("unreachable") // only + can be applied to strings. + } + return isStaticMsg(msg.X) && isStaticMsg(msg.Y) default: return false } @@ -455,10 +463,9 @@ func getKeyName(key ast.Expr) (string, bool) { } } if lit, ok := key.(*ast.BasicLit); ok && lit.Kind == token.STRING { - // string literals are always quoted. value, err := strconv.Unquote(lit.Value) if err != nil { - panic("unreachable") + panic("unreachable") // string literals are always quoted. } return value, true } diff --git a/testdata/src/static_msg/static_msg.go b/testdata/src/static_msg/static_msg.go index e9e8e96..9d2ac10 100644 --- a/testdata/src/static_msg/static_msg.go +++ b/testdata/src/static_msg/static_msg.go @@ -23,13 +23,21 @@ func tests() { slog.Log(ctx, slog.LevelInfo, constMsg) slog.With("key", "value").Info(constMsg) + slog.Info(varMsg) // want `message should be a string literal or a constant` + slog.InfoContext(ctx, varMsg) // want `message should be a string literal or a constant` + slog.Log(ctx, slog.LevelInfo, varMsg) // want `message should be a string literal or a constant` + slog.With("key", "value").Info(varMsg) // want `message should be a string literal or a constant` + slog.Info(fmt.Sprintf("msg")) // want `message should be a string literal or a constant` slog.InfoContext(ctx, fmt.Sprintf("msg")) // want `message should be a string literal or a constant` slog.Log(ctx, slog.LevelInfo, fmt.Sprintf("msg")) // want `message should be a string literal or a constant` slog.With("key", "value").Info(fmt.Sprintf("msg")) // want `message should be a string literal or a constant` - slog.Info(varMsg) // want `message should be a string literal or a constant` - slog.InfoContext(ctx, varMsg) // want `message should be a string literal or a constant` - slog.Log(ctx, slog.LevelInfo, varMsg) // want `message should be a string literal or a constant` - slog.With("key", "value").Info(varMsg) // want `message should be a string literal or a constant` + // binary expressions: + slog.Info("msg" + "msg") + slog.Info("msg" + "msg" + "msg") + slog.Info("msg" + constMsg) + slog.Info("msg" + varMsg) // want `message should be a string literal or a constant` + slog.Info("msg" + fmt.Sprintf("msg")) // want `message should be a string literal or a constant` + slog.Info("msg" + constMsg + varMsg + fmt.Sprintf("msg")) // want `message should be a string literal or a constant` }