Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement -args-on-sep-lines #6

Merged
merged 1 commit into from
Oct 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -12,4 +12,4 @@ Ensure consistent code-style when using `log/slog`.
* Forbid mixing key-value pairs and attributes in a single function call (default)
* Enforce using either key-value pairs or attributes for the entire project (optional)
* Enforce using constants (or custom `slog.Attr` constructors) instead of raw keys (optional)
* [WIP] Enforce putting arguments on separate lines (optional)
* Enforce putting arguments on separate lines (optional)
39 changes: 34 additions & 5 deletions sloglint.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"errors"
"flag"
"go/ast"
"go/token"
"go/types"
"strconv"

@@ -15,9 +16,10 @@ import (
)

type Options struct {
KVOnly bool
AttrOnly bool
NoRawKeys bool
KVOnly bool
AttrOnly bool
NoRawKeys bool
ArgsOnSepLines bool
}

// New creates a new sloglint analyzer.
@@ -54,6 +56,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.NoRawKeys, "no-raw-keys", "forbid using raw keys")
boolVar(&opts.ArgsOnSepLines, "args-on-sep-lines", "enforce putting arguments on separate lines")

return *fs
}
@@ -125,13 +128,16 @@ func run(pass *analysis.Pass, opts *Options) {
pass.Reportf(call.Pos(), "key-value pairs and attributes should not be mixed")
}

if opts.NoRawKeys && rawKeysUsed(keys, attrs, pass.TypesInfo) {
if opts.NoRawKeys && rawKeysUsed(pass.TypesInfo, keys, attrs) {
pass.Reportf(call.Pos(), "raw keys should not be used")
}
if opts.ArgsOnSepLines && argsOnSameLine(pass.Fset, call, keys, attrs) {
pass.Reportf(call.Pos(), "arguments should be put on separate lines")
}
})
}

func rawKeysUsed(keys, attrs []ast.Expr, info *types.Info) bool {
func rawKeysUsed(info *types.Info, keys, attrs []ast.Expr) bool {
isConst := func(expr ast.Expr) bool {
ident, ok := expr.(*ast.Ident)
return ok && ident.Obj != nil && ident.Obj.Kind == ast.Con
@@ -190,3 +196,26 @@ func rawKeysUsed(keys, attrs []ast.Expr, info *types.Info) bool {

return false
}

func argsOnSameLine(fset *token.FileSet, call ast.Expr, keys, attrs []ast.Expr) bool {
if len(keys)+len(attrs) <= 1 {
return false // special case: slog.Info("msg", "key", "value") is ok.
}

l := len(keys) + len(attrs) + 1
args := make([]ast.Expr, 0, l)
args = append(args, call)
args = append(args, keys...)
args = append(args, attrs...)

lines := make(map[int]struct{}, l)
for _, arg := range args {
line := fset.Position(arg.Pos()).Line
if _, ok := lines[line]; ok {
return true
}
lines[line] = struct{}{}
}

return false
}
5 changes: 5 additions & 0 deletions sloglint_test.go
Original file line number Diff line number Diff line change
@@ -29,4 +29,9 @@ func TestAnalyzer(t *testing.T) {
analyzer := sloglint.New(&sloglint.Options{NoRawKeys: true})
analysistest.Run(t, testdata, analyzer, "no_raw_keys")
})

t.Run("arguments on separate lines", func(t *testing.T) {
analyzer := sloglint.New(&sloglint.Options{ArgsOnSepLines: true})
analysistest.Run(t, testdata, analyzer, "args_on_sep_lines")
})
}
19 changes: 19 additions & 0 deletions testdata/src/args_on_sep_lines/args_on_sep_lines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package args_on_sep_lines

import "log/slog"

func tests() {
slog.Info("msg", "foo", 1)
slog.Info("msg",
"foo", 1,
"bar", 2,
)

slog.Info("msg", "foo", 1, "bar", 2) // want `arguments should be put on separate lines`
slog.Info("msg", "foo", 1, // want `arguments should be put on separate lines`
"bar", 2,
)
slog.Info("msg", // want `arguments should be put on separate lines`
"foo", 1, "bar", 2,
)
}