From 1301dc655f75647146fd696925b83343a13ac40a Mon Sep 17 00:00:00 2001 From: Matthew Dowdell Date: Sun, 22 Oct 2023 22:39:00 +0100 Subject: [PATCH] feat: implement `-context-only` (#14) --- README.md | 17 ++++++++++++ sloglint.go | 9 +++++++ sloglint_test.go | 5 ++++ testdata/src/context_only/context_only.go | 33 +++++++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 testdata/src/context_only/context_only.go diff --git a/README.md b/README.md index 8f26f3a..a839741 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The linter has several options, so you can adjust it to your own code style. * Forbid mixing key-value pairs and attributes within a single function call (default) * Enforce using either key-value pairs or attributes for the entire project (optional) +* Enforce using methods that accept a context (optional) * Enforce using constants instead of raw keys (optional) * Enforce putting arguments on separate lines (optional) @@ -53,6 +54,22 @@ In contrast, the `-attr-only` flag causes `sloglint` to report any use of key-va slog.Info("a user has logged in", "user_id", 42) // sloglint: key-value pairs should not be used ``` +### Context only + +Some `slog.Handler` implementations make use of the given `context.Context` (e.g. to access context values). +For them to work properly, you need to pass a context to all logger calls. +The `-context-only` flag causes `sloglint` to report the use of methods without a context. + +```go +slog.Info("a user has logged in") // sloglint: methods without a context should not be used +``` + +This report can be fixed by using the equivalent method with the `Context` suffix. + +```go +slog.InfoContext(ctx, "a user has logged in") +``` + ### No raw keys To prevent typos, you may want to forbid the use of raw keys altogether. diff --git a/sloglint.go b/sloglint.go index 4e45206..2e0bbfc 100644 --- a/sloglint.go +++ b/sloglint.go @@ -19,6 +19,7 @@ import ( type Options struct { KVOnly bool // Enforce using key-value pairs only (incompatible with AttrOnly). AttrOnly bool // Enforce using attributes only (incompatible with KVOnly). + ContextOnly bool // Enforce using methods that accept a context. NoRawKeys bool // Enforce using constants instead of raw keys. ArgsOnSepLines bool // Enforce putting arguments on separate lines. } @@ -56,6 +57,7 @@ func flags(opts *Options) flag.FlagSet { boolVar(&opts.KVOnly, "kv-only", "enforce using key-value pairs only (incompatible with -attr-only)") boolVar(&opts.AttrOnly, "attr-only", "enforce using attributes only (incompatible with -kv-only)") + boolVar(&opts.ContextOnly, "context-only", "enforce using methods that accept a context") boolVar(&opts.NoRawKeys, "no-raw-keys", "enforce using constants instead of raw keys") boolVar(&opts.ArgsOnSepLines, "args-on-sep-lines", "enforce putting arguments on separate lines") @@ -113,6 +115,13 @@ func run(pass *analysis.Pass, opts *Options) { return } + if opts.ContextOnly { + typ := pass.TypesInfo.TypeOf(call.Args[0]) + if typ != nil && typ.String() != "context.Context" { + pass.Reportf(call.Pos(), "methods without a context should not be used") + } + } + // NOTE: we assume that the arguments have already been validated by govet. args := call.Args[argsPos:] if len(args) == 0 { diff --git a/sloglint_test.go b/sloglint_test.go index 8d9aa20..9c40464 100644 --- a/sloglint_test.go +++ b/sloglint_test.go @@ -25,6 +25,11 @@ func TestAnalyzer(t *testing.T) { analysistest.Run(t, testdata, analyzer, "attr_only") }) + t.Run("context only", func(t *testing.T) { + analyzer := sloglint.New(&sloglint.Options{ContextOnly: true}) + analysistest.Run(t, testdata, analyzer, "context_only") + }) + t.Run("no raw keys", func(t *testing.T) { analyzer := sloglint.New(&sloglint.Options{NoRawKeys: true}) analysistest.Run(t, testdata, analyzer, "no_raw_keys") diff --git a/testdata/src/context_only/context_only.go b/testdata/src/context_only/context_only.go new file mode 100644 index 0000000..1b88568 --- /dev/null +++ b/testdata/src/context_only/context_only.go @@ -0,0 +1,33 @@ +package context_only + +import ( + "context" + "log/slog" +) + +func tests() { + logger := slog.New(nil) + ctx := context.Background() + + slog.Log(ctx, slog.LevelInfo, "msg") + slog.DebugContext(ctx, "msg") + slog.InfoContext(ctx, "msg") + slog.WarnContext(ctx, "msg") + slog.ErrorContext(ctx, "msg") + + logger.Log(ctx, slog.LevelInfo, "msg") + logger.DebugContext(ctx, "msg") + logger.InfoContext(ctx, "msg") + logger.WarnContext(ctx, "msg") + logger.ErrorContext(ctx, "msg") + + slog.Debug("msg") // want `methods without a context should not be used` + slog.Info("msg") // want `methods without a context should not be used` + slog.Warn("msg") // want `methods without a context should not be used` + slog.Error("msg") // want `methods without a context should not be used` + + logger.Debug("msg") // want `methods without a context should not be used` + logger.Info("msg") // want `methods without a context should not be used` + logger.Warn("msg") // want `methods without a context should not be used` + logger.Error("msg") // want `methods without a context should not be used` +}