aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/timonwong
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-02-22 22:16:50 +0100
committerTaras Madan <tarasmadan@google.com>2023-02-24 12:47:23 +0100
commit4165372ec8fd142475a4e35fd0cf4f8042132208 (patch)
tree21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/timonwong
parent2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff)
dependencies: update
set go min requirements to 1.19 update dependencies update vendor
Diffstat (limited to 'vendor/github.com/timonwong')
-rw-r--r--vendor/github.com/timonwong/loggercheck/.codecov.yml16
-rw-r--r--vendor/github.com/timonwong/loggercheck/.gitignore9
-rw-r--r--vendor/github.com/timonwong/loggercheck/.golangci.yml94
-rw-r--r--vendor/github.com/timonwong/loggercheck/.goreleaser.yml59
-rw-r--r--vendor/github.com/timonwong/loggercheck/LICENSE21
-rw-r--r--vendor/github.com/timonwong/loggercheck/Makefile22
-rw-r--r--vendor/github.com/timonwong/loggercheck/README.md103
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/bytebufferpool/pool.go22
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/checkers/checker.go59
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/checkers/common.go49
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/checkers/general.go68
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/checkers/printf/printf.go252
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/checkers/zap.go42
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/rules/rules.go201
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/sets/string.go59
-rw-r--r--vendor/github.com/timonwong/loggercheck/internal/stringutil/is.go15
-rw-r--r--vendor/github.com/timonwong/loggercheck/loggercheck.go196
-rw-r--r--vendor/github.com/timonwong/loggercheck/options.go31
-rw-r--r--vendor/github.com/timonwong/loggercheck/staticrules.go68
19 files changed, 1386 insertions, 0 deletions
diff --git a/vendor/github.com/timonwong/loggercheck/.codecov.yml b/vendor/github.com/timonwong/loggercheck/.codecov.yml
new file mode 100644
index 000000000..ef90457ca
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/.codecov.yml
@@ -0,0 +1,16 @@
+coverage:
+ range: 70..90 # green if 90+, red if 70-
+ status:
+ patch:
+ # coverage status for pull request diff
+ default:
+ threshold: 1% # allow a little drop
+ project:
+ # coverage status for whole project
+ default:
+ target: auto # use coverage of base commit as target
+ threshold: 1% # allow a little drop
+
+ignore:
+ - "plugin/**"
+ - "cmd/**"
diff --git a/vendor/github.com/timonwong/loggercheck/.gitignore b/vendor/github.com/timonwong/loggercheck/.gitignore
new file mode 100644
index 000000000..33df0df91
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/.gitignore
@@ -0,0 +1,9 @@
+.vscode/
+.idea/
+
+bin/
+vendor/
+
+dist/
+
+cover.out
diff --git a/vendor/github.com/timonwong/loggercheck/.golangci.yml b/vendor/github.com/timonwong/loggercheck/.golangci.yml
new file mode 100644
index 000000000..287327893
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/.golangci.yml
@@ -0,0 +1,94 @@
+linters-settings:
+ dupl:
+ threshold: 100
+ funlen:
+ lines: 100
+ statements: 50
+ goconst:
+ min-len: 2
+ min-occurrences: 3
+ gocritic:
+ enabled-tags:
+ - diagnostic
+ - experimental
+ - opinionated
+ - performance
+ - style
+ disabled-checks:
+ - whyNoLint
+ gocyclo:
+ min-complexity: 15
+ goimports:
+ local-prefixes: github.com/timonwong/loggercheck
+ gomnd:
+ # don't include the "operation" and "assign"
+ checks:
+ - argument
+ - case
+ - condition
+ - return
+ ignored-numbers:
+ - '0'
+ - '1'
+ - '2'
+ - '3'
+ ignored-functions:
+ - strings.SplitN
+ - strconv.ParseInt
+ govet:
+ check-shadowing: true
+ lll:
+ line-length: 140
+ misspell:
+ locale: US
+ nolintlint:
+ allow-unused: false # report any unused nolint directives
+ require-explanation: false # don't require an explanation for nolint directives
+ require-specific: false # don't require nolint directives to be specific about which linter is being skipped
+linters:
+ disable-all: true
+ enable:
+ - bodyclose
+ - dogsled
+ - dupl
+ - errcheck
+ - exportloopref
+ - funlen
+ - gochecknoinits
+ - goconst
+ - gocritic
+ - gocyclo
+ - gofumpt
+ - goimports
+ - gomnd
+ - goprintffuncname
+ - gosec
+ - gosimple
+ - govet
+ - ineffassign
+ - lll
+ - misspell
+ - nakedret
+ - noctx
+ - nolintlint
+ - revive
+ - staticcheck
+ - stylecheck
+ - typecheck
+ - unconvert
+ - unparam
+ - unused
+ - whitespace
+
+issues:
+ # Excluding configuration per-path, per-linter, per-text and per-source
+ exclude-rules:
+ - path: _test\.go
+ linters:
+ - gomnd
+
+run:
+ timeout: 5m
+ go: '1.17'
+ skip-dirs:
+ - testdata \ No newline at end of file
diff --git a/vendor/github.com/timonwong/loggercheck/.goreleaser.yml b/vendor/github.com/timonwong/loggercheck/.goreleaser.yml
new file mode 100644
index 000000000..55ffe7ea0
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/.goreleaser.yml
@@ -0,0 +1,59 @@
+---
+project_name: loggercheck
+
+release:
+ github:
+ owner: timonwong
+ name: loggercheck
+
+builds:
+ - binary: loggercheck
+ goos:
+ - darwin
+ - windows
+ - linux
+ goarch:
+ - '386'
+ - amd64
+ - arm
+ - arm64
+ goarm:
+ - '7'
+ env:
+ - CGO_ENABLED=0
+ ignore:
+ - goos: darwin
+ goarch: '386'
+ main: ./cmd/loggercheck/
+ flags:
+ - -trimpath
+ ldflags: -s -w
+
+archives:
+ - name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
+ format_overrides:
+ - goos: windows
+ format: zip
+ files:
+ - LICENSE
+ - README.md
+
+snapshot:
+ name_template: '{{ incpatch .Version }}-next'
+
+checksum:
+ name_template: 'checksums.txt'
+
+changelog:
+ sort: asc
+ filters:
+ exclude:
+ - '(?i)^docs?:'
+ - '(?i)^docs\([^:]+\):'
+ - '(?i)^docs\[[^:]+\]:'
+ - '^tests?:'
+ - '(?i)^dev:'
+ - '^build\(deps\): bump .* in /docs \(#\d+\)'
+ - '^build\(deps\): bump .* in /\.github/peril \(#\d+\)'
+ - Merge pull request
+ - Merge branch
diff --git a/vendor/github.com/timonwong/loggercheck/LICENSE b/vendor/github.com/timonwong/loggercheck/LICENSE
new file mode 100644
index 000000000..fb6531090
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Timon Wong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/timonwong/loggercheck/Makefile b/vendor/github.com/timonwong/loggercheck/Makefile
new file mode 100644
index 000000000..37bf87202
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/Makefile
@@ -0,0 +1,22 @@
+.PHONY: lint
+lint:
+ golangci-lint run ./...
+
+.PHONY: test-deps
+test-deps:
+ cd testdata/src/a && go mod vendor
+
+.PHONY: test
+test: test-deps
+ go test -v -covermode=atomic -coverprofile=cover.out -coverpkg ./... ./...
+
+.PHONY: build
+build:
+ go build -o bin/loggercheck ./cmd/loggercheck
+
+.PHONY: build-plugin
+build-plugin:
+ CGO_ENABLED=1 go build -o bin/loggercheck.so -buildmode=plugin ./plugin
+
+.PHONY: build-all
+build-all: build build-plugin
diff --git a/vendor/github.com/timonwong/loggercheck/README.md b/vendor/github.com/timonwong/loggercheck/README.md
new file mode 100644
index 000000000..14aeca371
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/README.md
@@ -0,0 +1,103 @@
+# loggercheck
+
+## Description
+
+A linter checks the odd number of key and value pairs for common logger libraries:
+- [kitlog](https://github.com/go-kit/log)
+- [klog](https://github.com/kubernetes/klog)
+- [logr](https://github.com/go-logr/logr)
+- [zap](https://github.com/uber-go/zap)
+
+## Badges
+
+![Build Status](https://github.com/timonwong/loggercheck/workflows/CI/badge.svg)
+[![Coverage](https://img.shields.io/codecov/c/github/timonwong/loggercheck?token=Nutf41gwoG)](https://app.codecov.io/gh/timonwong/loggercheck)
+[![License](https://img.shields.io/github/license/timonwong/loggercheck.svg)](/LICENSE)
+[![Release](https://img.shields.io/github/release/timonwong/loggercheck.svg)](https://github.com/timonwong/loggercheck/releases/latest)
+
+## Install
+
+```shel
+go install github.com/timonwong/loggercheck/cmd/loggercheck
+```
+
+## Usage
+
+```
+loggercheck: Checks key value pairs for common logger libraries (kitlog,logr,klog,zap).
+
+Usage: loggercheck [-flag] [package]
+
+
+Flags:
+ -V print version and exit
+ -all
+ no effect (deprecated)
+ -c int
+ display offending line with this many lines of context (default -1)
+ -cpuprofile string
+ write CPU profile to this file
+ -debug string
+ debug flags, any subset of "fpstv"
+ -disable value
+ comma-separated list of disabled logger checker (kitlog,klog,logr,zap) (default kitlog)
+ -fix
+ apply all suggested fixes
+ -flags
+ print analyzer flags in JSON
+ -json
+ emit JSON output
+ -memprofile string
+ write memory profile to this file
+ -noprintflike
+ require printf-like format specifier not present in args
+ -requirestringkey
+ require all logging keys to be inlined constant strings
+ -rulefile string
+ path to a file contains a list of rules
+ -source
+ no effect (deprecated)
+ -tags string
+ no effect (deprecated)
+ -test
+ indicates whether test files should be analyzed, too (default true)
+ -trace string
+ write trace log to this file
+ -v no effect (deprecated)
+```
+
+## Example
+
+```go
+package a
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-logr/logr"
+)
+
+func Example() {
+ log := logr.Discard()
+ log = log.WithValues("key")
+ log.Info("message", "key1", "value1", "key2", "value2", "key3")
+ log.Error(fmt.Errorf("error"), "message", "key1", "value1", "key2")
+ log.Error(fmt.Errorf("error"), "message", "key1", "value1", "key2", "value2")
+
+ var log2 logr.Logger
+ log2 = log
+ log2.Info("message", "key1")
+
+ log3 := logr.FromContextOrDiscard(context.TODO())
+ log3.Error(fmt.Errorf("error"), "message", "key1")
+}
+```
+
+```
+a.go:12:23: odd number of arguments passed as key-value pairs for logging
+a.go:13:22: odd number of arguments passed as key-value pairs for logging
+a.go:14:44: odd number of arguments passed as key-value pairs for logging
+a.go:19:23: odd number of arguments passed as key-value pairs for logging
+a.go:22:45: odd number of arguments passed as key-value pairs for logging
+``` \ No newline at end of file
diff --git a/vendor/github.com/timonwong/loggercheck/internal/bytebufferpool/pool.go b/vendor/github.com/timonwong/loggercheck/internal/bytebufferpool/pool.go
new file mode 100644
index 000000000..9d88d21c4
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/bytebufferpool/pool.go
@@ -0,0 +1,22 @@
+package bytebufferpool
+
+import (
+ "bytes"
+ "sync"
+)
+
+var pool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+}
+
+func Get() *bytes.Buffer {
+ buf := pool.Get().(*bytes.Buffer)
+ buf.Reset()
+ return buf
+}
+
+func Put(buf *bytes.Buffer) {
+ pool.Put(buf)
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/checkers/checker.go b/vendor/github.com/timonwong/loggercheck/internal/checkers/checker.go
new file mode 100644
index 000000000..5615636ef
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/checkers/checker.go
@@ -0,0 +1,59 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+type Config struct {
+ RequireStringKey bool
+ NoPrintfLike bool
+}
+
+type CallContext struct {
+ Expr *ast.CallExpr
+ Func *types.Func
+ Signature *types.Signature
+}
+
+type Checker interface {
+ FilterKeyAndValues(pass *analysis.Pass, keyAndValues []ast.Expr) []ast.Expr
+ CheckLoggingKey(pass *analysis.Pass, keyAndValues []ast.Expr)
+ CheckPrintfLikeSpecifier(pass *analysis.Pass, args []ast.Expr)
+}
+
+func ExecuteChecker(c Checker, pass *analysis.Pass, call CallContext, cfg Config) {
+ params := call.Signature.Params()
+ nparams := params.Len() // variadic => nonzero
+ startIndex := nparams - 1
+
+ lastArg := params.At(nparams - 1)
+ iface, ok := lastArg.Type().(*types.Slice).Elem().(*types.Interface)
+ if !ok || !iface.Empty() {
+ return // final (args) param is not ...interface{}
+ }
+
+ keyValuesArgs := c.FilterKeyAndValues(pass, call.Expr.Args[startIndex:])
+
+ if len(keyValuesArgs)%2 != 0 {
+ firstArg := keyValuesArgs[0]
+ lastArg := keyValuesArgs[len(keyValuesArgs)-1]
+ pass.Report(analysis.Diagnostic{
+ Pos: firstArg.Pos(),
+ End: lastArg.End(),
+ Category: DiagnosticCategory,
+ Message: "odd number of arguments passed as key-value pairs for logging",
+ })
+ }
+
+ if cfg.RequireStringKey {
+ c.CheckLoggingKey(pass, keyValuesArgs)
+ }
+
+ if cfg.NoPrintfLike {
+ // Check all args
+ c.CheckPrintfLikeSpecifier(pass, call.Expr.Args)
+ }
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/checkers/common.go b/vendor/github.com/timonwong/loggercheck/internal/checkers/common.go
new file mode 100644
index 000000000..42cbd0193
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/checkers/common.go
@@ -0,0 +1,49 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/printer"
+ "go/token"
+ "go/types"
+ "unicode/utf8"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/timonwong/loggercheck/internal/bytebufferpool"
+)
+
+const (
+ DiagnosticCategory = "logging"
+)
+
+// extractValueFromStringArg returns true if the argument is a string type (literal or constant).
+func extractValueFromStringArg(pass *analysis.Pass, arg ast.Expr) (value string, ok bool) {
+ if typeAndValue, ok := pass.TypesInfo.Types[arg]; ok {
+ if typ, ok := typeAndValue.Type.(*types.Basic); ok && typ.Kind() == types.String && typeAndValue.Value != nil {
+ return constant.StringVal(typeAndValue.Value), true
+ }
+ }
+
+ return "", false
+}
+
+func renderNodeEllipsis(fset *token.FileSet, v interface{}) string {
+ const maxLen = 20
+
+ buf := bytebufferpool.Get()
+ defer bytebufferpool.Put(buf)
+
+ _ = printer.Fprint(buf, fset, v)
+ s := buf.String()
+ if utf8.RuneCountInString(s) > maxLen {
+ // Copied from go/constant/value.go
+ i := 0
+ for n := 0; n < maxLen-3; n++ {
+ _, size := utf8.DecodeRuneInString(s[i:])
+ i += size
+ }
+ s = s[:i] + "..."
+ }
+ return s
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/checkers/general.go b/vendor/github.com/timonwong/loggercheck/internal/checkers/general.go
new file mode 100644
index 000000000..6512cce30
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/checkers/general.go
@@ -0,0 +1,68 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/timonwong/loggercheck/internal/checkers/printf"
+ "github.com/timonwong/loggercheck/internal/stringutil"
+)
+
+type General struct{}
+
+func (g General) FilterKeyAndValues(_ *analysis.Pass, keyAndValues []ast.Expr) []ast.Expr {
+ return keyAndValues
+}
+
+func (g General) CheckLoggingKey(pass *analysis.Pass, keyAndValues []ast.Expr) {
+ for i := 0; i < len(keyAndValues); i += 2 {
+ arg := keyAndValues[i]
+ if value, ok := extractValueFromStringArg(pass, arg); ok {
+ if stringutil.IsASCII(value) {
+ continue
+ }
+
+ pass.Report(analysis.Diagnostic{
+ Pos: arg.Pos(),
+ End: arg.End(),
+ Category: DiagnosticCategory,
+ Message: fmt.Sprintf(
+ "logging keys are expected to be alphanumeric strings, please remove any non-latin characters from %q",
+ value),
+ })
+ } else {
+ pass.Report(analysis.Diagnostic{
+ Pos: arg.Pos(),
+ End: arg.End(),
+ Category: DiagnosticCategory,
+ Message: fmt.Sprintf(
+ "logging keys are expected to be inlined constant strings, please replace %q provided with string",
+ renderNodeEllipsis(pass.Fset, arg)),
+ })
+ }
+ }
+}
+
+func (g General) CheckPrintfLikeSpecifier(pass *analysis.Pass, args []ast.Expr) {
+ for _, arg := range args {
+ format, ok := extractValueFromStringArg(pass, arg)
+ if !ok {
+ continue
+ }
+
+ if specifier, ok := printf.IsPrintfLike(format); ok {
+ pass.Report(analysis.Diagnostic{
+ Pos: arg.Pos(),
+ End: arg.End(),
+ Category: DiagnosticCategory,
+ Message: fmt.Sprintf("logging message should not use format specifier %q", specifier),
+ })
+
+ return // One error diagnostic is enough
+ }
+ }
+}
+
+var _ Checker = (*General)(nil)
diff --git a/vendor/github.com/timonwong/loggercheck/internal/checkers/printf/printf.go b/vendor/github.com/timonwong/loggercheck/internal/checkers/printf/printf.go
new file mode 100644
index 000000000..b38f46f20
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/checkers/printf/printf.go
@@ -0,0 +1,252 @@
+package printf
+
+import (
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// Copied from golang.org/x/tools
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+type printVerb struct {
+ verb rune // User may provide verb through Formatter; could be a rune.
+ flags string // known flags are all ASCII
+}
+
+// Common flag sets for printf verbs.
+const (
+ noFlag = ""
+ numFlag = " -+.0"
+ sharpNumFlag = " -+.0#"
+ allFlags = " -+.0#"
+)
+
+// printVerbs identifies which flags are known to printf for each verb.
+var printVerbs = []printVerb{
+ // '-' is a width modifier, always valid.
+ // '.' is a precision for float, max width for strings.
+ // '+' is required sign for numbers, Go format for %v.
+ // '#' is alternate format for several verbs.
+ // ' ' is spacer for numbers
+ {'%', noFlag},
+ {'b', sharpNumFlag},
+ {'c', "-"},
+ {'d', numFlag},
+ {'e', sharpNumFlag},
+ {'E', sharpNumFlag},
+ {'f', sharpNumFlag},
+ {'F', sharpNumFlag},
+ {'g', sharpNumFlag},
+ {'G', sharpNumFlag},
+ {'o', sharpNumFlag},
+ {'O', sharpNumFlag},
+ {'p', "-#"},
+ {'q', " -+.0#"},
+ {'s', " -+.0"},
+ {'t', "-"},
+ {'T', "-"},
+ {'U', "-#"},
+ {'v', allFlags},
+ {'w', allFlags},
+ {'x', sharpNumFlag},
+ {'X', sharpNumFlag},
+}
+
+// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
+// It is constructed by parsePrintfVerb.
+type formatState struct {
+ verb rune // the format verb: 'd' for "%d"
+ format string // the full format directive from % through verb, "%.3d".
+ flags []byte // the list of # + etc.
+ // Used only during parse.
+ hasIndex bool // Whether the argument is indexed.
+ indexPending bool // Whether we have an indexed argument that has not resolved.
+ nbytes int // number of bytes of the format string consumed.
+}
+
+// parseFlags accepts any printf flags.
+func (s *formatState) parseFlags() {
+ for s.nbytes < len(s.format) {
+ switch c := s.format[s.nbytes]; c {
+ case '#', '0', '+', '-', ' ':
+ s.flags = append(s.flags, c)
+ s.nbytes++
+ default:
+ return
+ }
+ }
+}
+
+// scanNum advances through a decimal number if present.
+func (s *formatState) scanNum() {
+ for ; s.nbytes < len(s.format); s.nbytes++ {
+ c := s.format[s.nbytes]
+ if c < '0' || '9' < c {
+ return
+ }
+ }
+}
+
+func stringIndexAt(s, substr string, start int) int {
+ idx := strings.Index(s[start:], substr)
+ if idx < 0 {
+ return idx
+ }
+ return idx + start
+}
+
+// parseIndex scans an index expression. It returns false if there is a syntax error.
+func (s *formatState) parseIndex() bool {
+ if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
+ return true
+ }
+ // Argument index present.
+ s.nbytes++ // skip '['
+ start := s.nbytes
+ s.scanNum()
+ ok := true
+ if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
+ ok = false
+ s.nbytes = stringIndexAt(s.format, "]", start)
+ if s.nbytes < 0 {
+ return false
+ }
+ }
+ arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
+ if err != nil || !ok || arg32 <= 0 {
+ return false
+ }
+ s.nbytes++ // skip ']'
+ s.hasIndex = true
+ s.indexPending = true
+ return true
+}
+
+// parseNum scans a width or precision (or *).
+func (s *formatState) parseNum() {
+ if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
+ if s.indexPending { // Absorb it.
+ s.indexPending = false
+ }
+ s.nbytes++
+ } else {
+ s.scanNum()
+ }
+}
+
+// parsePrecision scans for a precision. It returns false if there's a bad index expression.
+func (s *formatState) parsePrecision() bool {
+ // If there's a period, there may be a precision.
+ if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
+ s.flags = append(s.flags, '.') // Treat precision as a flag.
+ s.nbytes++
+ if !s.parseIndex() {
+ return false
+ }
+ s.parseNum()
+ }
+ return true
+}
+
+// parsePrintfVerb looks the formatting directive that begins the format string
+// and returns a formatState that encodes what the directive wants, without looking
+// at the actual arguments present in the call. The result is nil if there is an error.
+func parsePrintfVerb(format string) *formatState {
+ state := &formatState{
+ format: format,
+ flags: make([]byte, 0, 5), //nolint:gomnd
+ nbytes: 1, // There's guaranteed to be a percent sign.
+ }
+
+ // There may be flags.
+ state.parseFlags()
+ // There may be an index.
+ if !state.parseIndex() {
+ return nil
+ }
+ // There may be a width.
+ state.parseNum()
+ // There may be a precision.
+ if !state.parsePrecision() {
+ return nil
+ }
+ // Now a verb, possibly prefixed by an index (which we may already have).
+ if !state.indexPending && !state.parseIndex() {
+ return nil
+ }
+ if state.nbytes == len(state.format) {
+ // missing verb at end of string
+ return nil
+ }
+ verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
+ state.verb = verb
+ state.nbytes += w
+ state.format = state.format[:state.nbytes]
+ return state
+}
+
+func containsAll(s string, pattern []byte) bool {
+ for _, c := range pattern {
+ if !strings.ContainsRune(s, rune(c)) {
+ return false
+ }
+ }
+ return true
+}
+
+func isPrintfArg(state *formatState) bool {
+ var v printVerb
+ found := false
+ // Linear scan is fast enough for a small list.
+ for _, v = range printVerbs {
+ if v.verb == state.verb {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ // unknown verb, just skip
+ return false
+ }
+
+ if !containsAll(v.flags, state.flags) {
+ // unrecognized format flag, just skip
+ return false
+ }
+
+ return true
+}
+
+func IsPrintfLike(format string) (firstSpecifier string, ok bool) {
+ if !strings.Contains(format, "%") {
+ return "", false
+ }
+
+ for i, w := 0, 0; i < len(format); i += w {
+ w = 1
+ if format[i] != '%' {
+ continue
+ }
+
+ state := parsePrintfVerb(format[i:])
+ if state == nil {
+ return "", false
+ }
+
+ w = len(state.format)
+ if !isPrintfArg(state) {
+ return "", false
+ }
+
+ if !ok {
+ firstSpecifier = state.format
+ ok = true
+ }
+ }
+
+ return
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/checkers/zap.go b/vendor/github.com/timonwong/loggercheck/internal/checkers/zap.go
new file mode 100644
index 000000000..2356f8348
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/checkers/zap.go
@@ -0,0 +1,42 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+type Zap struct {
+ General
+}
+
+func (z Zap) FilterKeyAndValues(pass *analysis.Pass, keyAndValues []ast.Expr) []ast.Expr {
+ // Check the argument count
+ filtered := make([]ast.Expr, 0, len(keyAndValues))
+ for _, arg := range keyAndValues {
+ // Skip any zapcore.Field we found
+ switch arg := arg.(type) {
+ case *ast.CallExpr, *ast.Ident:
+ typ := pass.TypesInfo.TypeOf(arg)
+ switch typ := typ.(type) {
+ case *types.Named:
+ obj := typ.Obj()
+ // This is a strongly-typed field. Consume it and move on.
+ // Actually it's go.uber.org/zap/zapcore.Field, however for simplicity
+ // we don't check the import path
+ if obj != nil && obj.Name() == "Field" {
+ continue
+ }
+ default:
+ // pass
+ }
+ }
+
+ filtered = append(filtered, arg)
+ }
+
+ return filtered
+}
+
+var _ Checker = (*Zap)(nil)
diff --git a/vendor/github.com/timonwong/loggercheck/internal/rules/rules.go b/vendor/github.com/timonwong/loggercheck/internal/rules/rules.go
new file mode 100644
index 000000000..27d6ebb27
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/rules/rules.go
@@ -0,0 +1,201 @@
+package rules
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "go/types"
+ "io"
+ "strings"
+
+ "github.com/timonwong/loggercheck/internal/bytebufferpool"
+)
+
+var ErrInvalidRule = errors.New("invalid rule format")
+
+const CustomRulesetName = "custom"
+
+type Ruleset struct {
+ Name string
+ PackageImport string
+ Rules []FuncRule
+
+ ruleIndicesByFuncName map[string][]int
+}
+
+func (rs *Ruleset) Match(fn *types.Func) bool {
+ // PackageImport is already checked (by indices), skip checking it here
+ sig := fn.Type().(*types.Signature) // it's safe since we already checked
+
+ // Fail fast if the function name is not in the rule list.
+ indices, ok := rs.ruleIndicesByFuncName[fn.Name()]
+ if !ok {
+ return false
+ }
+
+ for _, idx := range indices {
+ rule := &rs.Rules[idx]
+ if matchRule(rule, sig) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func receiverTypeOf(recvType types.Type) string {
+ buf := bytebufferpool.Get()
+ defer bytebufferpool.Put(buf)
+
+ var recvNamed *types.Named
+ switch recvType := recvType.(type) {
+ case *types.Pointer:
+ buf.WriteByte('*')
+ if elem, ok := recvType.Elem().(*types.Named); ok {
+ recvNamed = elem
+ }
+ case *types.Named:
+ recvNamed = recvType
+ }
+
+ if recvNamed == nil {
+ // not supported type
+ return ""
+ }
+
+ buf.WriteString(recvNamed.Obj().Name())
+ typeParams := recvNamed.TypeParams()
+ if typeParamsLen := typeParams.Len(); typeParamsLen > 0 {
+ buf.WriteByte('[')
+ for i := 0; i < typeParamsLen; i++ {
+ if i > 0 {
+ // comma as separator
+ buf.WriteByte(',')
+ }
+ p := typeParams.At(i)
+ buf.WriteString(p.Obj().Name())
+ }
+ buf.WriteByte(']')
+ }
+
+ return buf.String()
+}
+
+func matchRule(p *FuncRule, sig *types.Signature) bool {
+ // we do not check package import here since it's already checked in Match()
+ recv := sig.Recv()
+ isReceiver := recv != nil
+ if isReceiver != p.IsReceiver {
+ return false
+ }
+
+ if isReceiver {
+ recvType := recv.Type()
+ receiverType := receiverTypeOf(recvType)
+ if receiverType != p.ReceiverType {
+ return false
+ }
+ }
+
+ return true
+}
+
+type FuncRule struct { // package import should be accessed from Rulset
+ ReceiverType string
+ FuncName string
+ IsReceiver bool
+}
+
+func ParseFuncRule(rule string) (packageImport string, pat FuncRule, err error) {
+ lastDot := strings.LastIndexFunc(rule, func(r rune) bool {
+ return r == '.' || r == '/'
+ })
+ if lastDot == -1 || rule[lastDot] == '/' {
+ return "", pat, ErrInvalidRule
+ }
+
+ importOrReceiver := rule[:lastDot]
+ pat.FuncName = rule[lastDot+1:]
+
+ if strings.HasPrefix(rule, "(") { // package
+ if !strings.HasSuffix(importOrReceiver, ")") {
+ return "", FuncRule{}, ErrInvalidRule
+ }
+
+ var isPointerReceiver bool
+ pat.IsReceiver = true
+ receiver := importOrReceiver[1 : len(importOrReceiver)-1]
+ if strings.HasPrefix(receiver, "*") {
+ isPointerReceiver = true
+ receiver = receiver[1:]
+ }
+
+ typeDotIdx := strings.LastIndexFunc(receiver, func(r rune) bool {
+ return r == '.' || r == '/'
+ })
+ if typeDotIdx == -1 || receiver[typeDotIdx] == '/' {
+ return "", FuncRule{}, ErrInvalidRule
+ }
+ receiverType := receiver[typeDotIdx+1:]
+ if isPointerReceiver {
+ receiverType = "*" + receiverType
+ }
+ pat.ReceiverType = receiverType
+ packageImport = receiver[:typeDotIdx]
+ } else {
+ packageImport = importOrReceiver
+ }
+
+ return packageImport, pat, nil
+}
+
+func ParseRules(lines []string) (result []Ruleset, err error) {
+ rulesByImport := make(map[string][]FuncRule)
+ for i, line := range lines {
+ if line == "" {
+ continue
+ }
+
+ if strings.HasPrefix(line, "#") { // comments
+ continue
+ }
+
+ packageImport, pat, err := ParseFuncRule(line)
+ if err != nil {
+ return nil, fmt.Errorf("error parse rule at line %d: %w", i+1, err)
+ }
+ rulesByImport[packageImport] = append(rulesByImport[packageImport], pat)
+ }
+
+ for packageImport, rules := range rulesByImport {
+ ruleIndicesByFuncName := make(map[string][]int, len(rules))
+ for idx, rule := range rules {
+ fnName := rule.FuncName
+ ruleIndicesByFuncName[fnName] = append(ruleIndicesByFuncName[fnName], idx)
+ }
+
+ result = append(result, Ruleset{
+ Name: CustomRulesetName, // NOTE(timonwong) Always "custom" for custom rule
+ PackageImport: packageImport,
+ Rules: rules,
+ ruleIndicesByFuncName: ruleIndicesByFuncName,
+ })
+ }
+ return result, nil
+}
+
+func ParseRuleFile(r io.Reader) (result []Ruleset, err error) {
+ // Rule files are relatively small, so read it into string slice first.
+ var lines []string
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ lines = append(lines, line)
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return ParseRules(lines)
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/sets/string.go b/vendor/github.com/timonwong/loggercheck/internal/sets/string.go
new file mode 100644
index 000000000..daf8d57fc
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/sets/string.go
@@ -0,0 +1,59 @@
+package sets
+
+import (
+ "sort"
+ "strings"
+)
+
+type Empty struct{}
+
+type StringSet map[string]Empty
+
+func NewString(items ...string) StringSet {
+ s := make(StringSet)
+ s.Insert(items...)
+ return s
+}
+
+func (s StringSet) Insert(items ...string) {
+ for _, item := range items {
+ s[item] = Empty{}
+ }
+}
+
+func (s StringSet) Has(item string) bool {
+ _, contained := s[item]
+ return contained
+}
+
+func (s StringSet) List() []string {
+ if len(s) == 0 {
+ return nil
+ }
+
+ res := make([]string, 0, len(s))
+ for key := range s {
+ res = append(res, key)
+ }
+ sort.Strings(res)
+ return res
+}
+
+// Set implements flag.Value interface.
+func (s *StringSet) Set(v string) error {
+ v = strings.TrimSpace(v)
+ if v == "" {
+ *s = nil
+ return nil
+ }
+
+ parts := strings.Split(v, ",")
+ set := NewString(parts...)
+ *s = set
+ return nil
+}
+
+// String implements flag.Value interface
+func (s StringSet) String() string {
+ return strings.Join(s.List(), ",")
+}
diff --git a/vendor/github.com/timonwong/loggercheck/internal/stringutil/is.go b/vendor/github.com/timonwong/loggercheck/internal/stringutil/is.go
new file mode 100644
index 000000000..a36b742fc
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/internal/stringutil/is.go
@@ -0,0 +1,15 @@
+package stringutil
+
+import "unicode/utf8"
+
+// IsASCII returns true if string are ASCII.
+func IsASCII(s string) bool {
+ for _, r := range s {
+ if r >= utf8.RuneSelf {
+ // Not ASCII.
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/vendor/github.com/timonwong/loggercheck/loggercheck.go b/vendor/github.com/timonwong/loggercheck/loggercheck.go
new file mode 100644
index 000000000..8bd10aee8
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/loggercheck.go
@@ -0,0 +1,196 @@
+package loggercheck
+
+import (
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/types"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/go/types/typeutil"
+
+ "github.com/timonwong/loggercheck/internal/checkers"
+ "github.com/timonwong/loggercheck/internal/rules"
+ "github.com/timonwong/loggercheck/internal/sets"
+)
+
+const Doc = `Checks key value pairs for common logger libraries (kitlog,klog,logr,zap).`
+
+func NewAnalyzer(opts ...Option) *analysis.Analyzer {
+ l := newLoggerCheck(opts...)
+ a := &analysis.Analyzer{
+ Name: "loggercheck",
+ Doc: Doc,
+ Flags: *l.fs,
+ Run: l.run,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ }
+ return a
+}
+
+type loggercheck struct {
+ fs *flag.FlagSet
+
+ disable sets.StringSet // flag -disable
+ ruleFile string // flag -rulefile
+ requireStringKey bool // flag -requirestringkey
+ noPrintfLike bool // flag -noprintflike
+
+ rules []string // used for external integration, for example golangci-lint
+ rulesetList []rules.Ruleset // populate at runtime
+ rulesetIndicesByImport map[string][]int // ruleset index, populate at runtime
+}
+
+func newLoggerCheck(opts ...Option) *loggercheck {
+ fs := flag.NewFlagSet("loggercheck", flag.ExitOnError)
+ l := &loggercheck{
+ fs: fs,
+ disable: sets.NewString("kitlog"),
+ rulesetList: append([]rules.Ruleset{}, staticRuleList...), // ensure we make a clone of static rules first
+ }
+
+ fs.StringVar(&l.ruleFile, "rulefile", "", "path to a file contains a list of rules")
+ fs.Var(&l.disable, "disable", "comma-separated list of disabled logger checker (kitlog,klog,logr,zap)")
+ fs.BoolVar(&l.requireStringKey, "requirestringkey", false, "require all logging keys to be inlined constant strings")
+ fs.BoolVar(&l.noPrintfLike, "noprintflike", false, "require printf-like format specifier not present in args")
+
+ for _, opt := range opts {
+ opt(l)
+ }
+
+ return l
+}
+
+func (l *loggercheck) isCheckerDisabled(name string) bool {
+ return l.disable.Has(name)
+}
+
+// vendorLessPath returns the devendorized version of the import path ipath.
+// For example: "a/vendor/github.com/go-logr/logr" will become "github.com/go-logr/logr".
+func vendorLessPath(ipath string) string {
+ if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
+ return ipath[i+len("/vendor/"):]
+ }
+ return ipath
+}
+
+func (l *loggercheck) getCheckerForFunc(fn *types.Func) checkers.Checker {
+ pkg := fn.Pkg()
+ if pkg == nil {
+ return nil
+ }
+
+ pkgPath := vendorLessPath(pkg.Path())
+ indices := l.rulesetIndicesByImport[pkgPath]
+
+ for _, idx := range indices {
+ rs := &l.rulesetList[idx]
+ if l.isCheckerDisabled(rs.Name) {
+ // Skip ignored logger checker.
+ continue
+ }
+
+ if !rs.Match(fn) {
+ continue
+ }
+
+ checker := checkerByRulesetName[rs.Name]
+ if checker == nil {
+ return checkers.General{}
+ }
+ return checker
+ }
+
+ return nil
+}
+
+func (l *loggercheck) checkLoggerArguments(pass *analysis.Pass, call *ast.CallExpr) {
+ fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
+ if fn == nil {
+ return // function pointer is not supported
+ }
+
+ sig, ok := fn.Type().(*types.Signature)
+ if !ok || !sig.Variadic() {
+ return // not variadic
+ }
+
+ // ellipsis args is hard, just skip
+ if call.Ellipsis.IsValid() {
+ return
+ }
+
+ checker := l.getCheckerForFunc(fn)
+ if checker == nil {
+ return
+ }
+
+ checkers.ExecuteChecker(checker, pass, checkers.CallContext{
+ Expr: call,
+ Func: fn,
+ Signature: sig,
+ }, checkers.Config{
+ RequireStringKey: l.requireStringKey,
+ NoPrintfLike: l.noPrintfLike,
+ })
+}
+
+func (l *loggercheck) processConfig() error {
+ if l.ruleFile != "" { // flags takes precedence over configs
+ f, err := os.Open(l.ruleFile)
+ if err != nil {
+ return fmt.Errorf("failed to open rule file: %w", err)
+ }
+ defer f.Close()
+
+ custom, err := rules.ParseRuleFile(f)
+ if err != nil {
+ return fmt.Errorf("failed to parse rule file: %w", err)
+ }
+ l.rulesetList = append(l.rulesetList, custom...)
+ } else if len(l.rules) > 0 {
+ custom, err := rules.ParseRules(l.rules)
+ if err != nil {
+ return fmt.Errorf("failed to parse rules: %w", err)
+ }
+ l.rulesetList = append(l.rulesetList, custom...)
+ }
+
+ // Build index
+ indices := make(map[string][]int)
+ for i, rs := range l.rulesetList {
+ indices[rs.PackageImport] = append(indices[rs.PackageImport], i)
+ }
+ l.rulesetIndicesByImport = indices
+
+ return nil
+}
+
+func (l *loggercheck) run(pass *analysis.Pass) (interface{}, error) {
+ err := l.processConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ nodeFilter := []ast.Node{
+ (*ast.CallExpr)(nil),
+ }
+ insp.Preorder(nodeFilter, func(node ast.Node) {
+ call := node.(*ast.CallExpr)
+
+ typ := pass.TypesInfo.Types[call.Fun].Type
+ if typ == nil {
+ // Skip checking functions with unknown type.
+ return
+ }
+
+ l.checkLoggerArguments(pass, call)
+ })
+
+ return nil, nil
+}
diff --git a/vendor/github.com/timonwong/loggercheck/options.go b/vendor/github.com/timonwong/loggercheck/options.go
new file mode 100644
index 000000000..6b5f00af1
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/options.go
@@ -0,0 +1,31 @@
+package loggercheck
+
+import (
+ "github.com/timonwong/loggercheck/internal/sets"
+)
+
+type Option func(*loggercheck)
+
+func WithDisable(disable []string) Option {
+ return func(l *loggercheck) {
+ l.disable = sets.NewString(disable...)
+ }
+}
+
+func WithRules(customRules []string) Option {
+ return func(l *loggercheck) {
+ l.rules = customRules
+ }
+}
+
+func WithRequireStringKey(requireStringKey bool) Option {
+ return func(l *loggercheck) {
+ l.requireStringKey = requireStringKey
+ }
+}
+
+func WithNoPrintfLike(noPrintfLike bool) Option {
+ return func(l *loggercheck) {
+ l.noPrintfLike = noPrintfLike
+ }
+}
diff --git a/vendor/github.com/timonwong/loggercheck/staticrules.go b/vendor/github.com/timonwong/loggercheck/staticrules.go
new file mode 100644
index 000000000..f955b3434
--- /dev/null
+++ b/vendor/github.com/timonwong/loggercheck/staticrules.go
@@ -0,0 +1,68 @@
+package loggercheck
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/timonwong/loggercheck/internal/checkers"
+ "github.com/timonwong/loggercheck/internal/rules"
+)
+
+var (
+ staticRuleList = []rules.Ruleset{
+ mustNewStaticRuleSet("logr", []string{
+ "(github.com/go-logr/logr.Logger).Error",
+ "(github.com/go-logr/logr.Logger).Info",
+ "(github.com/go-logr/logr.Logger).WithValues",
+ }),
+ mustNewStaticRuleSet("klog", []string{
+ "k8s.io/klog/v2.InfoS",
+ "k8s.io/klog/v2.InfoSDepth",
+ "k8s.io/klog/v2.ErrorS",
+ "(k8s.io/klog/v2.Verbose).InfoS",
+ "(k8s.io/klog/v2.Verbose).InfoSDepth",
+ "(k8s.io/klog/v2.Verbose).ErrorS",
+ }),
+ mustNewStaticRuleSet("zap", []string{
+ "(*go.uber.org/zap.SugaredLogger).With",
+ "(*go.uber.org/zap.SugaredLogger).Debugw",
+ "(*go.uber.org/zap.SugaredLogger).Infow",
+ "(*go.uber.org/zap.SugaredLogger).Warnw",
+ "(*go.uber.org/zap.SugaredLogger).Errorw",
+ "(*go.uber.org/zap.SugaredLogger).DPanicw",
+ "(*go.uber.org/zap.SugaredLogger).Panicw",
+ "(*go.uber.org/zap.SugaredLogger).Fatalw",
+ }),
+ mustNewStaticRuleSet("kitlog", []string{
+ "github.com/go-kit/log.With",
+ "github.com/go-kit/log.WithPrefix",
+ "github.com/go-kit/log.WithSuffix",
+ "(github.com/go-kit/log.Logger).Log",
+ }),
+ }
+ checkerByRulesetName = map[string]checkers.Checker{
+ // by default, checkers.General will be used.
+ "zap": checkers.Zap{},
+ }
+)
+
+// mustNewStaticRuleSet only called at init, catch errors during development.
+// In production it will not panic.
+func mustNewStaticRuleSet(name string, lines []string) rules.Ruleset {
+ if len(lines) == 0 {
+ panic(errors.New("no rules provided"))
+ }
+
+ rulesetList, err := rules.ParseRules(lines)
+ if err != nil {
+ panic(err)
+ }
+
+ if len(rulesetList) != 1 {
+ panic(fmt.Errorf("expected 1 ruleset, got %d", len(rulesetList)))
+ }
+
+ ruleset := rulesetList[0]
+ ruleset.Name = name
+ return ruleset
+}