From a675c6b3e2424efd893bdaa3281cf39f0d9e5f64 Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Mon, 5 Jan 2026 01:54:29 +1100 Subject: [PATCH] Upgrade to golangci-lint v2 (#1008) --- .github/workflows/lint.yml | 20 +++ .golangci.toml | 74 ++++---- benchmark/bench_datasets_test.go | 8 +- benchmark/benchmark_test.go | 32 ++-- cmd/gotoml-test-decoder/main.go | 1 + .../{gotoml-test-encoder.go => main.go} | 3 +- cmd/jsontoml/main.go | 8 +- cmd/jsontoml/main_test.go | 6 +- cmd/tomljson/main_test.go | 4 +- cmd/tomltestgen/main.go | 39 +++-- decode.go | 1 - errors.go | 9 +- errors_test.go | 10 +- fuzz_test.go | 2 +- internal/assert/assertions.go | 56 +++--- internal/assert/assertions_test.go | 165 +++++++++++------- internal/characters/ascii.go | 6 +- internal/characters/utf8.go | 68 +++----- internal/cli/cli.go | 16 +- internal/cli/cli_test.go | 36 ++-- internal/danger/danger.go | 41 +++-- internal/danger/typeid.go | 4 +- .../imported_tests/marshal_imported_test.go | 11 +- .../imported_tests/unmarshal_imported_test.go | 104 +++++------ internal/testsuite/add.go | 15 +- internal/testsuite/json.go | 20 +-- internal/testsuite/parser.go | 69 -------- internal/testsuite/rm.go | 21 +-- internal/testsuite/testsuite.go | 8 +- internal/tracker/key.go | 2 +- internal/tracker/seen.go | 13 +- internal/tracker/tracker.go | 1 + marshaler.go | 90 +++++----- marshaler_test.go | 27 +-- ossfuzz/fuzz.go | 2 + toml_testgen_support_test.go | 5 +- toml_testgen_test.go | 2 +- types.go | 14 +- unmarshaler.go | 40 +++-- unmarshaler_test.go | 63 ++----- unstable/ast.go | 8 +- unstable/benchmark_test.go | 30 ++-- unstable/kind.go | 20 ++- unstable/parser.go | 36 ++-- unstable/parser_test.go | 7 +- 45 files changed, 568 insertions(+), 649 deletions(-) create mode 100644 .github/workflows/lint.yml rename cmd/gotoml-test-encoder/{gotoml-test-encoder.go => main.go} (64%) delete mode 100644 internal/testsuite/parser.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..01822ea --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +name: lint +on: + pull_request: + branches: + - v2 + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v8 diff --git a/.golangci.toml b/.golangci.toml index 067db55..7d2e5b0 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -1,84 +1,76 @@ -[service] -golangci-lint-version = "1.39.0" - -[linters-settings.wsl] -allow-assign-and-anything = true - -[linters-settings.exhaustive] -default-signifies-exhaustive = true +version = "2" [linters] -disable-all = true +default = "none" enable = [ "asciicheck", "bodyclose", - "cyclop", - "deadcode", - "depguard", "dogsled", "dupl", "durationcheck", "errcheck", "errorlint", "exhaustive", - # "exhaustivestruct", - "exportloopref", "forbidigo", - # "forcetypeassert", - "funlen", - "gci", - # "gochecknoglobals", "gochecknoinits", - "gocognit", "goconst", "gocritic", - "gocyclo", - "godot", - "godox", - # "goerr113", - "gofmt", - "gofumpt", + "godoclint", "goheader", - "goimports", - "golint", - "gomnd", - # "gomoddirectives", "gomodguard", "goprintffuncname", "gosec", - "gosimple", "govet", - # "ifshort", "importas", "ineffassign", "lll", "makezero", + "mirror", "misspell", "nakedret", - "nestif", "nilerr", - # "nlreturn", "noctx", "nolintlint", - #"paralleltest", + "perfsprint", "prealloc", "predeclared", "revive", "rowserrcheck", "sqlclosecheck", "staticcheck", - "structcheck", - "stylecheck", - # "testpackage", "thelper", "tparallel", - "typecheck", "unconvert", "unparam", "unused", - "varcheck", + "usetesting", "wastedassign", "whitespace", - # "wrapcheck", - # "wsl" +] + +[linters.settings.exhaustive] +default-signifies-exhaustive = true + +[linters.settings.lll] +line-length = 150 + +[[linters.exclusions.rules]] +path = ".test.go" +linters = ["goconst", "gosec"] + +[[linters.exclusions.rules]] +path = "main.go" +linters = ["forbidigo"] + +[[linters.exclusions.rules]] +path = "internal" +linters = ["revive"] +text = "(exported|indent-error-flow): " + +[formatters] +enable = [ + "gci", + "gofmt", + "gofumpt", + "goimports", ] diff --git a/benchmark/bench_datasets_test.go b/benchmark/bench_datasets_test.go index ee88f03..91869df 100644 --- a/benchmark/bench_datasets_test.go +++ b/benchmark/bench_datasets_test.go @@ -12,7 +12,7 @@ import ( "github.com/pelletier/go-toml/v2/internal/assert" ) -var bench_inputs = []struct { +var benchInputs = []struct { name string jsonLen int }{ @@ -30,7 +30,7 @@ var bench_inputs = []struct { } func TestUnmarshalDatasetCode(t *testing.T) { - for _, tc := range bench_inputs { + for _, tc := range benchInputs { t.Run(tc.name, func(t *testing.T) { buf := fixture(t, tc.name) @@ -45,7 +45,7 @@ func TestUnmarshalDatasetCode(t *testing.T) { } func BenchmarkUnmarshalDataset(b *testing.B) { - for _, tc := range bench_inputs { + for _, tc := range benchInputs { b.Run(tc.name, func(b *testing.B) { buf := fixture(b, tc.name) b.SetBytes(int64(len(buf))) @@ -69,7 +69,7 @@ func fixture(tb testing.TB, path string) []byte { tb.Skip("benchmark fixture not found:", file) } assert.NoError(tb, err) - defer f.Close() + defer func() { _ = f.Close() }() gz, err := gzip.NewReader(f) assert.NoError(tb, err) diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 9cc2c20..7d9b136 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -18,7 +18,7 @@ func TestUnmarshalSimple(t *testing.T) { err := toml.Unmarshal(doc, &d) if err != nil { - panic(err) + t.Error(err) } } @@ -38,7 +38,7 @@ func BenchmarkUnmarshal(b *testing.B) { err := toml.Unmarshal(doc, &d) if err != nil { - panic(err) + b.Error(err) } } }) @@ -52,7 +52,7 @@ func BenchmarkUnmarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(doc, &d) if err != nil { - panic(err) + b.Error(err) } } }) @@ -72,7 +72,7 @@ func BenchmarkUnmarshal(b *testing.B) { d := benchmarkDoc{} err := toml.Unmarshal(bytes, &d) if err != nil { - panic(err) + b.Error(err) } } }) @@ -85,7 +85,7 @@ func BenchmarkUnmarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(bytes, &d) if err != nil { - panic(err) + b.Error(err) } } }) @@ -99,7 +99,7 @@ func BenchmarkUnmarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(hugoFrontMatterbytes, &d) if err != nil { - panic(err) + b.Error(err) } } }) @@ -123,7 +123,7 @@ func BenchmarkMarshal(b *testing.B) { err := toml.Unmarshal(doc, &d) if err != nil { - panic(err) + b.Error(err) } b.ReportAllocs() @@ -134,7 +134,7 @@ func BenchmarkMarshal(b *testing.B) { for i := 0; i < b.N; i++ { out, err = marshal(d) if err != nil { - panic(err) + b.Error(err) } } @@ -145,7 +145,7 @@ func BenchmarkMarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(doc, &d) if err != nil { - panic(err) + b.Error(err) } b.ReportAllocs() @@ -156,7 +156,7 @@ func BenchmarkMarshal(b *testing.B) { for i := 0; i < b.N; i++ { out, err = marshal(d) if err != nil { - panic(err) + b.Error(err) } } @@ -174,7 +174,7 @@ func BenchmarkMarshal(b *testing.B) { d := benchmarkDoc{} err := toml.Unmarshal(bytes, &d) if err != nil { - panic(err) + b.Error(err) } b.ReportAllocs() b.ResetTimer() @@ -184,7 +184,7 @@ func BenchmarkMarshal(b *testing.B) { for i := 0; i < b.N; i++ { out, err = marshal(d) if err != nil { - panic(err) + b.Error(err) } } @@ -195,7 +195,7 @@ func BenchmarkMarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(bytes, &d) if err != nil { - panic(err) + b.Error(err) } b.ReportAllocs() @@ -205,7 +205,7 @@ func BenchmarkMarshal(b *testing.B) { for i := 0; i < b.N; i++ { out, err = marshal(d) if err != nil { - panic(err) + b.Error(err) } } @@ -217,7 +217,7 @@ func BenchmarkMarshal(b *testing.B) { d := map[string]interface{}{} err := toml.Unmarshal(hugoFrontMatterbytes, &d) if err != nil { - panic(err) + b.Error(err) } b.ReportAllocs() @@ -228,7 +228,7 @@ func BenchmarkMarshal(b *testing.B) { for i := 0; i < b.N; i++ { out, err = marshal(d) if err != nil { - panic(err) + b.Error(err) } } diff --git a/cmd/gotoml-test-decoder/main.go b/cmd/gotoml-test-decoder/main.go index 2a1a458..e4865de 100644 --- a/cmd/gotoml-test-decoder/main.go +++ b/cmd/gotoml-test-decoder/main.go @@ -1,3 +1,4 @@ +// Package gotoml-test-decoder is a minimal decoder program used to compare this library with other TOML implementations. package main import ( diff --git a/cmd/gotoml-test-encoder/gotoml-test-encoder.go b/cmd/gotoml-test-encoder/main.go similarity index 64% rename from cmd/gotoml-test-encoder/gotoml-test-encoder.go rename to cmd/gotoml-test-encoder/main.go index 4305c00..e33cc3e 100644 --- a/cmd/gotoml-test-encoder/gotoml-test-encoder.go +++ b/cmd/gotoml-test-encoder/main.go @@ -1,3 +1,4 @@ +// Package gotoml-test-encoder is a minimal encoder program used to compare this library with other TOML implementations. package main import ( @@ -24,7 +25,7 @@ func main() { } func usage() { - log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0])) + log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0])) flag.PrintDefaults() os.Exit(1) } diff --git a/cmd/jsontoml/main.go b/cmd/jsontoml/main.go index a7bc709..a0384ad 100644 --- a/cmd/jsontoml/main.go +++ b/cmd/jsontoml/main.go @@ -34,10 +34,10 @@ Reading from a file: jsontoml file.json > file.toml ` -var useJsonNumber bool +var useJSONNumber bool func main() { - flag.BoolVar(&useJsonNumber, "use-json-number", false, "unmarshal numbers into `json.Number` type instead of as `float64`") + flag.BoolVar(&useJSONNumber, "use-json-number", false, "unmarshal numbers into `json.Number` type instead of as `float64`") p := cli.Program{ Usage: usage, @@ -52,9 +52,9 @@ func convert(r io.Reader, w io.Writer) error { d := json.NewDecoder(r) e := toml.NewEncoder(w) - if useJsonNumber { + if useJSONNumber { d.UseNumber() - e.SetMarshalJsonNumbers(true) + e.SetMarshalJSONNumbers(true) } err := d.Decode(&v) diff --git a/cmd/jsontoml/main_test.go b/cmd/jsontoml/main_test.go index 1e143bb..8b188a7 100644 --- a/cmd/jsontoml/main_test.go +++ b/cmd/jsontoml/main_test.go @@ -14,7 +14,7 @@ func TestConvert(t *testing.T) { input string expected string errors bool - useJsonNumber bool + useJSONNumber bool }{ { name: "valid json", @@ -30,7 +30,7 @@ a = 42.0 }, { name: "use json number", - useJsonNumber: true, + useJSONNumber: true, input: ` { "mytoml": { @@ -50,7 +50,7 @@ a = 42 for _, e := range examples { b := new(bytes.Buffer) - useJsonNumber = e.useJsonNumber + useJSONNumber = e.useJSONNumber err := convert(strings.NewReader(e.input), b) if e.errors { assert.Error(t, err) diff --git a/cmd/tomljson/main_test.go b/cmd/tomljson/main_test.go index 4b056f8..7fa7a77 100644 --- a/cmd/tomljson/main_test.go +++ b/cmd/tomljson/main_test.go @@ -2,7 +2,7 @@ package main import ( "bytes" - "fmt" + "errors" "io" "strings" "testing" @@ -56,5 +56,5 @@ a = 42`), type badReader struct{} func (r *badReader) Read([]byte) (int, error) { - return 0, fmt.Errorf("reader failed on purpose") + return 0, errors.New("reader failed on purpose") } diff --git a/cmd/tomltestgen/main.go b/cmd/tomltestgen/main.go index 337e3e2..c9102a0 100644 --- a/cmd/tomltestgen/main.go +++ b/cmd/tomltestgen/main.go @@ -18,6 +18,7 @@ import ( "strings" "text/template" "time" + "unicode" ) type invalid struct { @@ -28,7 +29,7 @@ type invalid struct { type valid struct { Name string Input string - JsonRef string + JSONRef string } type testsCollection struct { @@ -39,12 +40,11 @@ type testsCollection struct { Count int } -const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" + +const srcTemplate = "// Code generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}. DO NOT EDIT.\n" + "package toml_test\n" + " import (\n" + " \"testing\"\n" + ")\n" + - "{{range .Invalid}}\n" + "func TestTOMLTest_Invalid_{{.Name}}(t *testing.T) {\n" + " input := {{.Input|gostr}}\n" + @@ -55,28 +55,31 @@ const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on { "{{range .Valid}}\n" + "func TestTOMLTest_Valid_{{.Name}}(t *testing.T) {\n" + " input := {{.Input|gostr}}\n" + - " jsonRef := {{.JsonRef|gostr}}\n" + + " jsonRef := {{.JSONRef|gostr}}\n" + " testgenValid(t, input, jsonRef)\n" + "}\n" + "{{end}}\n" func kebabToCamel(kebab string) string { - camel := "" + var buf strings.Builder nextUpper := true for _, c := range kebab { if nextUpper { - camel += strings.ToUpper(string(c)) + buf.WriteRune(unicode.ToUpper(c)) nextUpper = false - } else if c == '-' { - nextUpper = true - } else if c == '/' { - nextUpper = true - camel += "_" } else { - camel += string(c) + switch c { + case '-': + nextUpper = true + case '/': + nextUpper = true + buf.WriteByte('_') + default: + buf.WriteRune(c) + } } } - return camel + return buf.String() } func templateGoStr(input string) string { @@ -110,7 +113,7 @@ func main() { log.Printf("> [%s] %s\n", "invalid", name) - tomlContent, err := os.ReadFile(f) + tomlContent, err := os.ReadFile(f) // #nosec G304 if err != nil { fmt.Printf("failed to read test file: %s\n", err) os.Exit(1) @@ -131,14 +134,14 @@ func main() { log.Printf("> [%s] %s\n", "valid", name) - tomlContent, err := os.ReadFile(f) + tomlContent, err := os.ReadFile(f) // #nosec G304 if err != nil { fmt.Printf("failed reading test file: %s\n", err) os.Exit(1) } filename = strings.TrimSuffix(f, ".toml") - jsonContent, err := os.ReadFile(filename + ".json") + jsonContent, err := os.ReadFile(filename + ".json") // #nosec G304 if err != nil { fmt.Printf("failed reading validation json: %s\n", err) os.Exit(1) @@ -147,7 +150,7 @@ func main() { collection.Valid = append(collection.Valid, valid{ Name: name, Input: string(tomlContent), - JsonRef: string(jsonContent), + JSONRef: string(jsonContent), }) collection.Count++ } @@ -173,7 +176,7 @@ func main() { return } - err = os.WriteFile(*out, outputBytes, 0o644) + err = os.WriteFile(*out, outputBytes, 0o600) if err != nil { panic(err) } diff --git a/decode.go b/decode.go index f0ec3b1..4092068 100644 --- a/decode.go +++ b/decode.go @@ -279,7 +279,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, b, nil } -//nolint:cyclop func parseFloat(b []byte) (float64, error) { if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { return math.NaN(), nil diff --git a/errors.go b/errors.go index e910a5c..fbb68e8 100644 --- a/errors.go +++ b/errors.go @@ -58,13 +58,14 @@ func (s *StrictMissingError) String() string { // // Implements errors.Join() interface. func (s *StrictMissingError) Unwrap() []error { - var errs []error + errs := make([]error, 0, len(s.Errors)) for i := range s.Errors { errs = append(errs, &s.Errors[i]) } return errs } +// Key is a slice of strings that represents a path to a value in a TOML document. type Key []string // Error returns the error message contained in the DecodeError. @@ -92,12 +93,10 @@ func (e *DecodeError) Key() Key { // wrapDecodeError creates a DecodeError referencing a highlighted // range of bytes from document. // -// highlight needs to be a sub-slice of document, or this function panics. +// Highlight needs to be a sub-slice of document, or this function panics. // // The function copies all bytes used in DecodeError, so that document and // highlight can be freely deallocated. -// -//nolint:funlen func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError { offset := danger.SubsliceOffset(document, de.Highlight) @@ -259,5 +258,5 @@ func positionAtEnd(b []byte) (row int, column int) { } } - return + return row, column } diff --git a/errors_test.go b/errors_test.go index 5b4e8c4..213e494 100644 --- a/errors_test.go +++ b/errors_test.go @@ -11,9 +11,7 @@ import ( "github.com/pelletier/go-toml/v2/unstable" ) -//nolint:funlen func TestDecodeError(t *testing.T) { - examples := []struct { desc string doc [3]string @@ -161,13 +159,12 @@ line 5`, for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - b := bytes.Buffer{} - b.Write([]byte(e.doc[0])) + b.WriteString(e.doc[0]) start := b.Len() - b.Write([]byte(e.doc[1])) + b.WriteString(e.doc[1]) end := b.Len() - b.Write([]byte(e.doc[2])) + b.WriteString(e.doc[2]) doc := b.Bytes() hl := doc[start:end] @@ -189,7 +186,6 @@ line 5`, } func TestDecodeError_Accessors(t *testing.T) { - e := DecodeError{ message: "foo", line: 1, diff --git a/fuzz_test.go b/fuzz_test.go index 465f76b..a28b720 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -12,7 +12,7 @@ import ( func FuzzUnmarshal(f *testing.F) { file, err := os.ReadFile("benchmark/benchmark.toml") if err != nil { - panic(err) + f.Error(err) } f.Add(file) diff --git a/internal/assert/assertions.go b/internal/assert/assertions.go index 2f7e453..12821a2 100644 --- a/internal/assert/assertions.go +++ b/internal/assert/assertions.go @@ -1,3 +1,4 @@ +// Package assert provides assertion functions for unit testing. package assert import ( @@ -9,66 +10,67 @@ import ( ) // True asserts that an expression is true. -func True(t testing.TB, ok bool, msgAndArgs ...any) { +func True(tb testing.TB, ok bool, msgAndArgs ...any) { + tb.Helper() if ok { return } - t.Helper() - t.Fatal(formatMsgAndArgs("Expected expression to be true", msgAndArgs...)) + tb.Fatal(formatMsgAndArgs("Expected expression to be true", msgAndArgs...)) } // False asserts that an expression is false. -func False(t testing.TB, ok bool, msgAndArgs ...any) { +func False(tb testing.TB, ok bool, msgAndArgs ...any) { + tb.Helper() if !ok { return } - t.Helper() - t.Fatal(formatMsgAndArgs("Expected expression to be false", msgAndArgs...)) + tb.Fatal(formatMsgAndArgs("Expected expression to be false", msgAndArgs...)) } // Equal asserts that "expected" and "actual" are equal. -func Equal[T any](t testing.TB, expected, actual T, msgAndArgs ...any) { +func Equal[T any](tb testing.TB, expected, actual T, msgAndArgs ...any) { + tb.Helper() if objectsAreEqual(expected, actual) { return } - t.Helper() msg := formatMsgAndArgs("Expected values to be equal:", msgAndArgs...) - t.Fatalf("%s\n%s", msg, diff(expected, actual)) + tb.Fatalf("%s\n%s", msg, diff(expected, actual)) } // Error asserts that an error is not nil. -func Error(t testing.TB, err error, msgAndArgs ...any) { +func Error(tb testing.TB, err error, msgAndArgs ...any) { + tb.Helper() if err != nil { return } - t.Helper() - t.Fatal(formatMsgAndArgs("Expected an error", msgAndArgs...)) + tb.Fatal(formatMsgAndArgs("Expected an error", msgAndArgs...)) } // NoError asserts that an error is nil. -func NoError(t testing.TB, err error, msgAndArgs ...any) { +func NoError(tb testing.TB, err error, msgAndArgs ...any) { + tb.Helper() if err == nil { return } - t.Helper() msg := formatMsgAndArgs("Unexpected error:", msgAndArgs...) - t.Fatalf("%s\n%+v", msg, err) + tb.Fatalf("%s\n%+v", msg, err) } // Panics asserts that the given function panics. -func Panics(t testing.TB, fn func(), msgAndArgs ...any) { - t.Helper() +func Panics(tb testing.TB, fn func(), msgAndArgs ...any) { + tb.Helper() defer func() { if recover() == nil { msg := formatMsgAndArgs("Expected function to panic", msgAndArgs...) - t.Fatal(msg) + tb.Fatal(msg) } }() fn() } // Zero asserts that a value is its zero value. -func Zero[T any](t testing.TB, value T, msgAndArgs ...any) { +func Zero[T any](tb testing.TB, value T, msgAndArgs ...any) { + tb.Helper() var zero T if objectsAreEqual(value, zero) { return @@ -77,22 +79,26 @@ func Zero[T any](t testing.TB, value T, msgAndArgs ...any) { if (val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0 { return } - t.Helper() msg := formatMsgAndArgs("Expected zero value but got:", msgAndArgs...) - t.Fatalf("%s\n%v", msg, value) + tb.Fatalf("%s\n%v", msg, value) } -func NotZero[T any](t testing.TB, value T, msgAndArgs ...any) { +func NotZero[T any](tb testing.TB, value T, msgAndArgs ...any) { + tb.Helper() var zero T if !objectsAreEqual(value, zero) { val := reflect.ValueOf(value) - if !((val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0) { + switch val.Kind() { + case reflect.Slice, reflect.Map, reflect.Array: + if val.Len() > 0 { + return + } + default: return } } - t.Helper() msg := formatMsgAndArgs("Unexpected zero value:", msgAndArgs...) - t.Fatalf("%s\n%v", msg, value) + tb.Fatalf("%s\n%v", msg, value) } func formatMsgAndArgs(msg string, args ...any) string { diff --git a/internal/assert/assertions_test.go b/internal/assert/assertions_test.go index aefe433..fdd875d 100644 --- a/internal/assert/assertions_test.go +++ b/internal/assert/assertions_test.go @@ -1,6 +1,7 @@ package assert import ( + "errors" "fmt" "testing" ) @@ -12,135 +13,167 @@ type Data struct { func TestBadMessage(t *testing.T) { invalidMessage := func() { True(t, false, 1234) } - assertOk(t, "Non-fmt message value", func(t testing.TB) { - Panics(t, invalidMessage) + assertOk(t, "Non-fmt message value", func(tb testing.TB) { + tb.Helper() + Panics(tb, invalidMessage) }) - assertFail(t, "Non-fmt message value", func(t testing.TB) { - True(t, false, "example %s", "message") + assertFail(t, "Non-fmt message value", func(tb testing.TB) { + tb.Helper() + True(tb, false, "example %s", "message") }) } func TestTrue(t *testing.T) { - assertOk(t, "Succeed", func(t testing.TB) { - True(t, 1 > 0) + assertOk(t, "Succeed", func(tb testing.TB) { + tb.Helper() + True(tb, 1 > 0) }) - assertFail(t, "Fail", func(t testing.TB) { - True(t, 1 < 0) + assertFail(t, "Fail", func(tb testing.TB) { + tb.Helper() + True(tb, 1 < 0) }) } func TestFalse(t *testing.T) { - assertOk(t, "Succeed", func(t testing.TB) { - False(t, 1 < 0) + assertOk(t, "Succeed", func(tb testing.TB) { + tb.Helper() + False(tb, 1 < 0) }) - assertFail(t, "Fail", func(t testing.TB) { - False(t, 1 > 0) + assertFail(t, "Fail", func(tb testing.TB) { + tb.Helper() + False(tb, 1 > 0) }) } func TestEqual(t *testing.T) { - assertOk(t, "Nil", func(t testing.TB) { - Equal(t, interface{}(nil), interface{}(nil)) + assertOk(t, "Nil", func(tb testing.TB) { + tb.Helper() + Equal(tb, interface{}(nil), interface{}(nil)) }) - assertOk(t, "Identical structs", func(t testing.TB) { - Equal(t, Data{"expected", 1234}, Data{"expected", 1234}) + assertOk(t, "Identical structs", func(tb testing.TB) { + tb.Helper() + Equal(tb, Data{"expected", 1234}, Data{"expected", 1234}) }) - assertFail(t, "Different structs", func(t testing.TB) { - Equal(t, Data{"expected", 1234}, Data{"actual", 1234}) + assertFail(t, "Different structs", func(tb testing.TB) { + tb.Helper() + Equal(tb, Data{"expected", 1234}, Data{"actual", 1234}) }) - assertOk(t, "Identical numbers", func(t testing.TB) { - Equal(t, 1234, 1234) + assertOk(t, "Identical numbers", func(tb testing.TB) { + tb.Helper() + Equal(tb, 1234, 1234) }) - assertFail(t, "Identical numbers", func(t testing.TB) { - Equal(t, 1234, 1324) + assertFail(t, "Identical numbers", func(tb testing.TB) { + tb.Helper() + Equal(tb, 1234, 1324) }) - assertOk(t, "Zero-length byte arrays", func(t testing.TB) { - Equal(t, []byte(nil), []byte("")) + assertOk(t, "Zero-length byte arrays", func(tb testing.TB) { + tb.Helper() + Equal(tb, []byte(nil), []byte("")) }) - assertOk(t, "Identical byte arrays", func(t testing.TB) { - Equal(t, []byte{1, 2, 3, 4}, []byte{1, 2, 3, 4}) + assertOk(t, "Identical byte arrays", func(tb testing.TB) { + tb.Helper() + Equal(tb, []byte{1, 2, 3, 4}, []byte{1, 2, 3, 4}) }) - assertFail(t, "Different byte arrays", func(t testing.TB) { - Equal(t, []byte{1, 2, 3, 4}, []byte{1, 3, 2, 4}) + assertFail(t, "Different byte arrays", func(tb testing.TB) { + tb.Helper() + Equal(tb, []byte{1, 2, 3, 4}, []byte{1, 3, 2, 4}) }) - assertOk(t, "Identical strings", func(t testing.TB) { - Equal(t, "example", "example") + assertOk(t, "Identical strings", func(tb testing.TB) { + tb.Helper() + Equal(tb, "example", "example") }) - assertFail(t, "Identical strings", func(t testing.TB) { - Equal(t, "example", "elpmaxe") + assertFail(t, "Identical strings", func(tb testing.TB) { + tb.Helper() + Equal(tb, "example", "elpmaxe") }) } func TestError(t *testing.T) { - assertOk(t, "Error", func(t testing.TB) { - Error(t, fmt.Errorf("example")) + assertOk(t, "Error", func(tb testing.TB) { + tb.Helper() + Error(tb, errors.New("example")) }) - assertFail(t, "Nil", func(t testing.TB) { - Error(t, nil) + assertFail(t, "Nil", func(tb testing.TB) { + tb.Helper() + Error(tb, nil) }) } func TestNoError(t *testing.T) { - assertFail(t, "Error", func(t testing.TB) { - NoError(t, fmt.Errorf("example")) + assertFail(t, "Error", func(tb testing.TB) { + tb.Helper() + NoError(tb, errors.New("example")) }) - assertOk(t, "Nil", func(t testing.TB) { - NoError(t, nil) + assertOk(t, "Nil", func(tb testing.TB) { + tb.Helper() + NoError(tb, nil) }) } func TestPanics(t *testing.T) { willPanic := func() { panic("example") } wontPanic := func() {} - assertOk(t, "Will panic", func(t testing.TB) { - Panics(t, willPanic) + assertOk(t, "Will panic", func(tb testing.TB) { + tb.Helper() + Panics(tb, willPanic) }) - assertFail(t, "Won't panic", func(t testing.TB) { - Panics(t, wontPanic) + assertFail(t, "Won't panic", func(tb testing.TB) { + tb.Helper() + Panics(tb, wontPanic) }) } func TestZero(t *testing.T) { - assertOk(t, "Empty struct", func(t testing.TB) { - Zero(t, Data{}) + assertOk(t, "Empty struct", func(tb testing.TB) { + tb.Helper() + Zero(tb, Data{}) }) - assertFail(t, "Non-empty struct", func(t testing.TB) { - Zero(t, Data{Label: "example"}) + assertFail(t, "Non-empty struct", func(tb testing.TB) { + tb.Helper() + Zero(tb, Data{Label: "example"}) }) - assertOk(t, "Nil slice", func(t testing.TB) { + assertOk(t, "Nil slice", func(tb testing.TB) { + tb.Helper() var slice []int - Zero(t, slice) + Zero(tb, slice) }) - assertFail(t, "Non-empty slice", func(t testing.TB) { + assertFail(t, "Non-empty slice", func(tb testing.TB) { + tb.Helper() slice := []int{1, 2, 3, 4} - Zero(t, slice) + Zero(tb, slice) }) - assertOk(t, "Zero-length slice", func(t testing.TB) { + assertOk(t, "Zero-length slice", func(tb testing.TB) { + tb.Helper() slice := []int{} - Zero(t, slice) + Zero(tb, slice) }) } func TestNotZero(t *testing.T) { - assertFail(t, "Empty struct", func(t testing.TB) { + assertFail(t, "Empty struct", func(tb testing.TB) { + tb.Helper() zero := Data{} - NotZero(t, zero) + NotZero(tb, zero) }) - assertOk(t, "Non-empty struct", func(t testing.TB) { + assertOk(t, "Non-empty struct", func(tb testing.TB) { + tb.Helper() notZero := Data{Label: "example"} - NotZero(t, notZero) + NotZero(tb, notZero) }) - assertFail(t, "Nil slice", func(t testing.TB) { + assertFail(t, "Nil slice", func(tb testing.TB) { + tb.Helper() var slice []int - NotZero(t, slice) + NotZero(tb, slice) }) - assertFail(t, "Zero-length slice", func(t testing.TB) { + assertFail(t, "Zero-length slice", func(tb testing.TB) { + tb.Helper() slice := []int{} - NotZero(t, slice) + NotZero(tb, slice) }) - assertOk(t, "Non-empty slice", func(t testing.TB) { + assertOk(t, "Non-empty slice", func(tb testing.TB) { + tb.Helper() slice := []int{1, 2, 3, 4} - NotZero(t, slice) + NotZero(tb, slice) }) } @@ -157,7 +190,7 @@ func (t *testCase) Fatalf(message string, args ...interface{}) { t.failed = fmt.Sprintf(message, args...) } -func assertFail(t *testing.T, name string, fn func(t testing.TB)) { +func assertFail(t *testing.T, name string, fn func(testing.TB)) { t.Helper() t.Run(name, func(t *testing.T) { t.Helper() @@ -171,7 +204,7 @@ func assertFail(t *testing.T, name string, fn func(t testing.TB)) { }) } -func assertOk(t *testing.T, name string, fn func(t testing.TB)) { +func assertOk(t *testing.T, name string, fn func(testing.TB)) { t.Helper() t.Run(name, func(t *testing.T) { t.Helper() diff --git a/internal/characters/ascii.go b/internal/characters/ascii.go index 80f698d..50a6d17 100644 --- a/internal/characters/ascii.go +++ b/internal/characters/ascii.go @@ -1,6 +1,6 @@ package characters -var invalidAsciiTable = [256]bool{ +var invalidASCIITable = [256]bool{ 0x00: true, 0x01: true, 0x02: true, @@ -37,6 +37,6 @@ var invalidAsciiTable = [256]bool{ 0x7F: true, } -func InvalidAscii(b byte) bool { - return invalidAsciiTable[b] +func InvalidASCII(b byte) bool { + return invalidASCIITable[b] } diff --git a/internal/characters/utf8.go b/internal/characters/utf8.go index db4f45a..7c5cb55 100644 --- a/internal/characters/utf8.go +++ b/internal/characters/utf8.go @@ -1,20 +1,12 @@ +// Package characters provides functions for working with string encodings. package characters import ( "unicode/utf8" ) -type utf8Err struct { - Index int - Size int -} - -func (u utf8Err) Zero() bool { - return u.Size == 0 -} - -// Verified that a given string is only made of valid UTF-8 characters allowed -// by the TOML spec: +// Utf8TomlValidAlreadyEscaped verifies that a given string is only made of +// valid UTF-8 characters allowed by the TOML spec: // // Any Unicode character may be used except those that must be escaped: // quotation mark, backslash, and the control characters other than tab (U+0000 @@ -23,8 +15,8 @@ func (u utf8Err) Zero() bool { // It is a copy of the Go 1.17 utf8.Valid implementation, tweaked to exit early // when a character is not allowed. // -// The returned utf8Err is Zero() if the string is valid, or contains the byte -// index and size of the invalid character. +// The returned slice is empty if the string is valid, or contains the bytes +// of the invalid character. // // quotation mark => already checked // backslash => already checked @@ -32,9 +24,8 @@ func (u utf8Err) Zero() bool { // 0x9 => tab, ok // 0xA - 0x1F => invalid // 0x7F => invalid -func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { +func Utf8TomlValidAlreadyEscaped(p []byte) []byte { // Fast path. Check for and skip 8 bytes of ASCII characters per iteration. - offset := 0 for len(p) >= 8 { // Combining two 32 bit loads allows the same code to be used // for 32 and 64 bit platforms. @@ -48,24 +39,19 @@ func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { } for i, b := range p[:8] { - if InvalidAscii(b) { - err.Index = offset + i - err.Size = 1 - return + if InvalidASCII(b) { + return p[i : i+1] } } p = p[8:] - offset += 8 } n := len(p) for i := 0; i < n; { pi := p[i] if pi < utf8.RuneSelf { - if InvalidAscii(pi) { - err.Index = offset + i - err.Size = 1 - return + if InvalidASCII(pi) { + return p[i : i+1] } i++ continue @@ -73,44 +59,34 @@ func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { x := first[pi] if x == xx { // Illegal starter byte. - err.Index = offset + i - err.Size = 1 - return + return p[i : i+1] } size := int(x & 7) if i+size > n { // Short or invalid. - err.Index = offset + i - err.Size = n - i - return + return p[i:n] } accept := acceptRanges[x>>4] if c := p[i+1]; c < accept.lo || accept.hi < c { - err.Index = offset + i - err.Size = 2 - return - } else if size == 2 { + return p[i : i+2] + } else if size == 2 { //revive:disable:empty-block } else if c := p[i+2]; c < locb || hicb < c { - err.Index = offset + i - err.Size = 3 - return - } else if size == 3 { + return p[i : i+3] + } else if size == 3 { //revive:disable:empty-block } else if c := p[i+3]; c < locb || hicb < c { - err.Index = offset + i - err.Size = 4 - return + return p[i : i+4] } i += size } - return + return nil } -// Return the size of the next rune if valid, 0 otherwise. +// Utf8ValidNext returns the size of the next rune if valid, 0 otherwise. func Utf8ValidNext(p []byte) int { c := p[0] if c < utf8.RuneSelf { - if InvalidAscii(c) { + if InvalidASCII(c) { return 0 } return 1 @@ -129,10 +105,10 @@ func Utf8ValidNext(p []byte) int { accept := acceptRanges[x>>4] if c := p[1]; c < accept.lo || accept.hi < c { return 0 - } else if size == 2 { + } else if size == 2 { //nolint:revive } else if c := p[2]; c < locb || hicb < c { return 0 - } else if size == 3 { + } else if size == 3 { //nolint:revive } else if c := p[3]; c < locb || hicb < c { return 0 } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index c0c5089..a1a168d 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -1,3 +1,4 @@ +// Package cli provides common functions for command-line programs. package cli import ( @@ -27,17 +28,16 @@ func (p *Program) Execute() { os.Exit(p.main(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) } -func (p *Program) main(files []string, input io.Reader, output, error io.Writer) int { +func (p *Program) main(files []string, input io.Reader, output, stderr io.Writer) int { err := p.run(files, input, output) if err != nil { - var derr *toml.DecodeError if errors.As(err, &derr) { - fmt.Fprintln(error, derr.String()) + _, _ = fmt.Fprintln(stderr, derr.String()) row, col := derr.Position() - fmt.Fprintln(error, "error occurred at row", row, "column", col) + _, _ = fmt.Fprintln(stderr, "error occurred at row", row, "column", col) } else { - fmt.Fprintln(error, err.Error()) + _, _ = fmt.Fprintln(stderr, err.Error()) } return -1 @@ -54,7 +54,7 @@ func (p *Program) run(files []string, input io.Reader, output io.Writer) error { if err != nil { return err } - defer f.Close() + defer func() { _ = f.Close() }() input = f } return p.Fn(input, output) @@ -71,7 +71,7 @@ func (p *Program) runAllFilesInPlace(files []string) error { } func (p *Program) runFileInPlace(path string) error { - in, err := os.ReadFile(path) + in, err := os.ReadFile(path) // #nosec G304 if err != nil { return err } @@ -83,5 +83,5 @@ func (p *Program) runFileInPlace(path string) error { return err } - return os.WriteFile(path, out.Bytes(), 0600) + return os.WriteFile(path, out.Bytes(), 0o600) } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index ff8e1ea..619cb0c 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -2,7 +2,7 @@ package cli import ( "bytes" - "fmt" + "errors" "io" "os" "path" @@ -23,7 +23,7 @@ func TestProcessMainStdin(t *testing.T) { stderr := new(bytes.Buffer) input := strings.NewReader("this is the input") - exit := processMain([]string{}, input, stdout, stderr, func(r io.Reader, w io.Writer) error { + exit := processMain([]string{}, input, stdout, stderr, func(io.Reader, io.Writer) error { return nil }) @@ -37,8 +37,8 @@ func TestProcessMainStdinErr(t *testing.T) { stderr := new(bytes.Buffer) input := strings.NewReader("this is the input") - exit := processMain([]string{}, input, stdout, stderr, func(r io.Reader, w io.Writer) error { - return fmt.Errorf("something bad") + exit := processMain([]string{}, input, stdout, stderr, func(io.Reader, io.Writer) error { + return errors.New("something bad") }) assert.Equal(t, -1, exit) @@ -51,7 +51,7 @@ func TestProcessMainStdinDecodeErr(t *testing.T) { stderr := new(bytes.Buffer) input := strings.NewReader("this is the input") - exit := processMain([]string{}, input, stdout, stderr, func(r io.Reader, w io.Writer) error { + exit := processMain([]string{}, input, stdout, stderr, func(io.Reader, io.Writer) error { var v interface{} return toml.Unmarshal([]byte(`qwe = 001`), &v) }) @@ -62,16 +62,16 @@ func TestProcessMainStdinDecodeErr(t *testing.T) { } func TestProcessMainFileExists(t *testing.T) { - tmpfile, err := os.CreateTemp("", "example") + tmpfile, err := os.CreateTemp(t.TempDir(), "example") assert.NoError(t, err) - defer os.Remove(tmpfile.Name()) - _, err = tmpfile.Write([]byte(`some data`)) + _, err = tmpfile.WriteString(`some data`) assert.NoError(t, err) + assert.NoError(t, tmpfile.Close()) stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - exit := processMain([]string{tmpfile.Name()}, nil, stdout, stderr, func(r io.Reader, w io.Writer) error { + exit := processMain([]string{tmpfile.Name()}, nil, stdout, stderr, func(io.Reader, io.Writer) error { return nil }) @@ -84,7 +84,7 @@ func TestProcessMainFileDoesNotExist(t *testing.T) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - exit := processMain([]string{"/lets/hope/this/does/not/exist"}, nil, stdout, stderr, func(r io.Reader, w io.Writer) error { + exit := processMain([]string{"/lets/hope/this/does/not/exist"}, nil, stdout, stderr, func(io.Reader, io.Writer) error { return nil }) @@ -94,16 +94,14 @@ func TestProcessMainFileDoesNotExist(t *testing.T) { } func TestProcessMainFilesInPlace(t *testing.T) { - dir, err := os.MkdirTemp("", "") - assert.NoError(t, err) - defer os.RemoveAll(dir) + dir := t.TempDir() path1 := path.Join(dir, "file1") path2 := path.Join(dir, "file2") - err = os.WriteFile(path1, []byte("content 1"), 0600) + err := os.WriteFile(path1, []byte("content 1"), 0o600) assert.NoError(t, err) - err = os.WriteFile(path2, []byte("content 2"), 0600) + err = os.WriteFile(path2, []byte("content 2"), 0o600) assert.NoError(t, err) p := Program{ @@ -136,17 +134,15 @@ func TestProcessMainFilesInPlaceErrRead(t *testing.T) { } func TestProcessMainFilesInPlaceFailFn(t *testing.T) { - dir, err := os.MkdirTemp("", "") - assert.NoError(t, err) - defer os.RemoveAll(dir) + dir := t.TempDir() path1 := path.Join(dir, "file1") - err = os.WriteFile(path1, []byte("content 1"), 0600) + err := os.WriteFile(path1, []byte("content 1"), 0o600) assert.NoError(t, err) p := Program{ - Fn: func(io.Reader, io.Writer) error { return fmt.Errorf("oh no") }, + Fn: func(io.Reader, io.Writer) error { return errors.New("oh no") }, Inplace: true, } diff --git a/internal/danger/danger.go b/internal/danger/danger.go index e38e113..c7ac844 100644 --- a/internal/danger/danger.go +++ b/internal/danger/danger.go @@ -1,21 +1,21 @@ +// Package danger provides optimized unsafe functions. package danger import ( "fmt" - "reflect" "unsafe" ) const maxInt = uintptr(int(^uint(0) >> 1)) func SubsliceOffset(data []byte, subslice []byte) int { - datap := (*reflect.SliceHeader)(unsafe.Pointer(&data)) - hlp := (*reflect.SliceHeader)(unsafe.Pointer(&subslice)) + datap := uintptr(unsafe.Pointer(unsafe.SliceData(data))) // #nosec G103 + hlp := uintptr(unsafe.Pointer(unsafe.SliceData(subslice))) // #nosec G103 - if hlp.Data < datap.Data { - panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp.Data, datap.Data)) + if hlp < datap { + panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp, datap)) } - offset := hlp.Data - datap.Data + offset := hlp - datap if offset > maxInt { panic(fmt.Errorf("slice offset larger than int (%d)", offset)) @@ -23,12 +23,12 @@ func SubsliceOffset(data []byte, subslice []byte) int { intoffset := int(offset) - if intoffset > datap.Len { - panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len)) + if intoffset > len(data) { + panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, len(data))) } - if intoffset+hlp.Len > datap.Len { - panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, hlp.Len, datap.Len)) + if intoffset+len(subslice) > len(data) { + panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, len(subslice), len(data))) } return intoffset @@ -38,28 +38,27 @@ func BytesRange(start []byte, end []byte) []byte { if start == nil || end == nil { panic("cannot call BytesRange with nil") } - startp := (*reflect.SliceHeader)(unsafe.Pointer(&start)) - endp := (*reflect.SliceHeader)(unsafe.Pointer(&end)) - if startp.Data > endp.Data { - panic(fmt.Errorf("start pointer address (%d) is after end pointer address (%d)", startp.Data, endp.Data)) + startp := uintptr(unsafe.Pointer(unsafe.SliceData(start))) // #nosec G103 + endp := uintptr(unsafe.Pointer(unsafe.SliceData(end))) // #nosec G103 + + if startp > endp { + panic(fmt.Errorf("start pointer address (%d) is after end pointer address (%d)", startp, endp)) } - l := startp.Len - endLen := int(endp.Data-startp.Data) + endp.Len + l := len(start) + endLen := int(endp-startp) + len(end) if endLen > l { l = endLen } - if l > startp.Cap { - panic(fmt.Errorf("range length is larger than capacity")) + if l > cap(start) { + panic("range length is larger than capacity") } return start[:l] } func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer { - // TODO: replace with unsafe.Add when Go 1.17 is released - // https://github.com/golang/go/issues/40481 - return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) + return unsafe.Add(ptr, size*uintptr(offset)) } diff --git a/internal/danger/typeid.go b/internal/danger/typeid.go index 9d41c28..4de065f 100644 --- a/internal/danger/typeid.go +++ b/internal/danger/typeid.go @@ -5,7 +5,7 @@ import ( "unsafe" ) -// typeID is used as key in encoder and decoder caches to enable using +// TypeID is used as key in encoder and decoder caches to enable using // the optimize runtime.mapaccess2_fast64 function instead of the more // expensive lookup if we were to use reflect.Type as map key. // @@ -19,5 +19,5 @@ func MakeTypeID(t reflect.Type) TypeID { // reflect.Type has the fields: // typ unsafe.Pointer // ptr unsafe.Pointer - return TypeID((*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1]) + return TypeID((*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1]) // #nosec G103 } diff --git a/internal/imported_tests/marshal_imported_test.go b/internal/imported_tests/marshal_imported_test.go index b744ded..6c35a4f 100644 --- a/internal/imported_tests/marshal_imported_test.go +++ b/internal/imported_tests/marshal_imported_test.go @@ -1,4 +1,4 @@ -package imported_tests +package imported_tests //revive:disable:var-naming // Those tests have been imported from v1, but adjust to match the new // defaults of v2. @@ -21,12 +21,12 @@ func TestDocMarshal(t *testing.T) { Subdocs testDocSubs `toml:"subdoc"` Basics testDocBasics `toml:"basic"` SubDocList []testSubDoc `toml:"subdoclist"` - err int `toml:"shouldntBeHere"` + err int `toml:"shouldntBeHere"` //nolint:unused unexported int `toml:"shouldntBeHere"` Unexported2 int `toml:"-"` } - var docData = testDoc{ + docData := testDoc{ Title: "TOML Marshal Testing", unexported: 0, Unexported2: 0, @@ -128,8 +128,7 @@ String2 = 'Two' String2 = 'Three' ` - assert.Equal(t, string(expected), string(result)) - + assert.Equal(t, expected, string(result)) } func TestEmptyMarshal(t *testing.T) { @@ -164,7 +163,7 @@ stringlist = [] [map] ` - assert.Equal(t, string(expected), string(result)) + assert.Equal(t, expected, string(result)) } type textMarshaler struct { diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 1eb7ecc..91c69fc 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1,4 +1,4 @@ -package imported_tests +package imported_tests //revive:disable:var-naming // Those tests were imported directly from go-toml v1 // https://raw.githubusercontent.com/pelletier/go-toml/a2e52561804c6cd9392ebf0048ca64fe4af67a43/marshal_test.go @@ -149,9 +149,6 @@ type quotedKeyMarshalTestStruct struct { SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` } -// TODO: Remove nolint once var is used by a test -// -//nolint:deadcode,unused,varcheck var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ String: "Hello", Float: 3.5, @@ -161,7 +158,7 @@ var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 "Z.string-àéù" = "Hello" @@ -183,11 +180,12 @@ type testDoc struct { Subdocs testDocSubs `toml:"subdoc"` Basics testDocBasics `toml:"basic"` SubDocList []testSubDoc `toml:"subdoclist"` - err int `toml:"shouldntBeHere"` // nolint:structcheck,unused + err int `toml:"shouldntBeHere"` //nolint:unused unexported int `toml:"shouldntBeHere"` Unexported2 int `toml:"-"` } +//nolint:unused type testMapDoc struct { Title string `toml:"title"` BasicMap map[string]string `toml:"basic_map"` @@ -274,7 +272,7 @@ var docData = testDoc{ // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var mapTestDoc = testMapDoc{ Title: "TOML Marshal Testing", BasicMap: map[string]string{ @@ -543,31 +541,35 @@ func TestNestedUnmarshal(t *testing.T) { assert.Equal(t, nestedTestData, result) } +//nolint:unused type customMarshalerParent struct { Self customMarshaler `toml:"me"` Friends []customMarshaler `toml:"friends"` } +//nolint:unused type customMarshaler struct { FirstName string LastName string } +//nolint:unused func (c customMarshaler) MarshalTOML() ([]byte, error) { fullName := fmt.Sprintf("%s %s", c.FirstName, c.LastName) return []byte(fullName), nil } +//nolint:unused var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var customMarshalerToml = []byte(`Sally Fields`) // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var nestedCustomMarshalerData = customMarshalerParent{ Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Friends: []customMarshaler{customMarshalerData}, @@ -575,7 +577,7 @@ var nestedCustomMarshalerData = customMarshalerParent{ // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] me = "Maiku Suteda" `) @@ -590,7 +592,7 @@ func (x *IntOrString) MarshalTOML() ([]byte, error) { s := *(*string)(x) _, err := strconv.Atoi(s) if err != nil { - return []byte(fmt.Sprintf(`"%s"`, s)), nil + return []byte(fmt.Sprintf(`"%s"`, s)), nil //nolint:nilerr } return []byte(s), nil } @@ -662,7 +664,7 @@ func (m *textPointerMarshaler) MarshalText() ([]byte, error) { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var commentTestToml = []byte(` # it's a comment on type [postgres] @@ -687,6 +689,7 @@ var commentTestToml = []byte(` My = "Baar" `) +//nolint:unused type mapsTestStruct struct { Simple map[string]string Paths map[string]string @@ -700,7 +703,7 @@ type mapsTestStruct struct { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var mapsTestData = mapsTestStruct{ Simple: map[string]string{ "one plus one": "two", @@ -724,7 +727,7 @@ var mapsTestData = mapsTestStruct{ // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var mapsTestToml = []byte(` [Other] "testing" = 3.9999 @@ -747,7 +750,7 @@ var mapsTestToml = []byte(` // TODO: Remove nolint once type is used by a test // -//nolint:deadcode,unused +//nolint:unused type structArrayNoTag struct { A struct { B []int64 @@ -757,7 +760,7 @@ type structArrayNoTag struct { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var customTagTestToml = []byte(` [postgres] password = "bvalue" @@ -772,7 +775,7 @@ var customTagTestToml = []byte(` // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var customCommentTagTestToml = []byte(` # db connection [postgres] @@ -786,7 +789,7 @@ var customCommentTagTestToml = []byte(` // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var customCommentedTagTestToml = []byte(` [postgres] # password = "bvalue" @@ -841,7 +844,7 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var customMultilineTagTestToml = []byte(`int_slice = [ 1, 2, @@ -851,7 +854,7 @@ var customMultilineTagTestToml = []byte(`int_slice = [ // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var testDocBasicToml = []byte(` [document] bool_val = true @@ -862,16 +865,12 @@ var testDocBasicToml = []byte(` uint_val = 5001 `) -// TODO: Remove nolint once type is used by a test -// -//nolint:deadcode +//nolint:unused type testDocCustomTag struct { Doc testDocBasicsCustomTag `file:"document"` } -// TODO: Remove nolint once type is used by a test -// -//nolint:deadcode +//nolint:unused type testDocBasicsCustomTag struct { Bool bool `file:"bool_val"` Date time.Time `file:"date_val"` @@ -882,9 +881,7 @@ type testDocBasicsCustomTag struct { unexported int `file:"shouldntBeHere"` } -// TODO: Remove nolint once var is used by a test -// -//nolint:deadcode,varcheck +//nolint:unused var testDocCustomTagData = testDocCustomTag{ Doc: testDocBasicsCustomTag{ Bool: true, @@ -987,13 +984,13 @@ func TestUnmarshalInvalidPointerKind(t *testing.T) { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused +//nolint:unused type testDuration struct { Nanosec time.Duration `toml:"nanosec"` Microsec1 time.Duration `toml:"microsec1"` Microsec2 *time.Duration `toml:"microsec2"` Millisec time.Duration `toml:"millisec"` - Sec time.Duration `toml:"sec"` + Sec time.Duration `toml:"sec"` //nolint:staticcheck Min time.Duration `toml:"min"` Hour time.Duration `toml:"hour"` Mixed time.Duration `toml:"mixed"` @@ -1002,7 +999,7 @@ type testDuration struct { // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var testDurationToml = []byte(` nanosec = "1ns" microsec1 = "1us" @@ -1017,7 +1014,7 @@ a_string = "15s" // TODO: Remove nolint once var is used by a test // -//nolint:deadcode,unused,varcheck +//nolint:unused var testDurationToml2 = []byte(`a_string = "15s" hour = "1h0m0s" microsec1 = "1µs" @@ -1031,15 +1028,14 @@ sec = "1s" // TODO: Remove nolint once type is used by a test // -//nolint:deadcode,unused +//nolint:unused type testBadDuration struct { Val time.Duration `toml:"val"` } // TODO: add back camelCase test -var testCamelCaseKeyToml = []byte(`fooBar = 10`) //nolint:unused +var testCamelCaseKeyToml = []byte(`fooBar = 10`) -//nolint:unused func TestUnmarshalCamelCaseKey(t *testing.T) { t.Skipf("don't know if it is a good idea to automatically convert like that yet") var x struct { @@ -1058,7 +1054,7 @@ func TestUnmarshalCamelCaseKey(t *testing.T) { func TestUnmarshalNegativeUint(t *testing.T) { t.Skipf("not sure if we this should always error") - type check struct{ U uint } // nolint:unused + type check struct{ U uint } err := toml.Unmarshal([]byte("U = -1"), &check{}) assert.Error(t, err) } @@ -1535,7 +1531,7 @@ func TestUnmarshalLocalDateTime(t *testing.T) { } for i, example := range examples { - doc := fmt.Sprintf(`date = %s`, example.in) + doc := "date = " + example.in t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct { @@ -1621,7 +1617,7 @@ func TestUnmarshalLocalTime(t *testing.T) { } for i, example := range examples { - doc := fmt.Sprintf(`Time = %s`, example.in) + doc := "Time = " + example.in t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct { @@ -1906,19 +1902,12 @@ func TestUnmarshalMixedTypeSlice(t *testing.T) { ArrayField []interface{} } - //doc := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] - //`) - doc := []byte(`ArrayField = [{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] `) actual := TestStruct{} expected := TestStruct{ ArrayField: []interface{}{ - //3.14, - //int64(100), - //true, - //"hello world", map[string]interface{}{ "Field": "inner1", }, @@ -2004,7 +1993,8 @@ func TestDecoderStrict(t *testing.T) { "Expected a *toml.StrictMissingError, got: %v", reflect.TypeOf(err), ) - se := err.(*toml.StrictMissingError) + var se *toml.StrictMissingError + assert.True(t, errors.As(err, &se)) keys := []toml.Key{} @@ -2026,6 +2016,7 @@ func TestDecoderStrict(t *testing.T) { var m map[string]interface{} err = decoder(input).Decode(&m) + assert.NoError(t, err) } func TestDecoderStrictValid(t *testing.T) { @@ -2062,19 +2053,6 @@ func (d *docUnmarshalTOML) UnmarshalTOML(i interface{}) error { return nil } -func TestDecoderStrictCustomUnmarshal(t *testing.T) { - t.Skip() - //input := `key = "ok"` - //var doc docUnmarshalTOML - //err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - //if err != nil { - // t.Fatal("unexpected error:", err) - //} - //if doc.Decoded.Key != "ok" { - // t.Errorf("Bad unmarshal: expected ok, got %v", doc.Decoded.Key) - //} -} - type parent struct { Doc docUnmarshalTOML DocPointer *docUnmarshalTOML @@ -2278,7 +2256,7 @@ type Custom struct { v string } -func (c *Custom) UnmarshalTOML(v interface{}) error { +func (c *Custom) UnmarshalTOML(interface{}) error { c.v = "called" return nil } @@ -2303,14 +2281,14 @@ type durationString struct { time.Duration } -func (d *durationString) UnmarshalTOML(v interface{}) error { +func (d *durationString) UnmarshalTOML(interface{}) error { d.Duration = 10 * time.Second return nil } type config437Error struct{} -func (e *config437Error) UnmarshalTOML(v interface{}) error { +func (e *config437Error) UnmarshalTOML(interface{}) error { return errors.New("expected") } diff --git a/internal/testsuite/add.go b/internal/testsuite/add.go index 3f9bea9..ee23b3c 100644 --- a/internal/testsuite/add.go +++ b/internal/testsuite/add.go @@ -3,17 +3,18 @@ package testsuite import ( "fmt" "math" + "strconv" "time" "github.com/pelletier/go-toml/v2" ) // addTag adds JSON tags to a data structure as expected by toml-test. -func addTag(key string, tomlData interface{}) interface{} { +func addTag(tomlData interface{}) interface{} { // Switch on the data type. switch orig := tomlData.(type) { default: - //return map[string]interface{}{} + // return map[string]interface{}{} panic(fmt.Sprintf("Unknown type: %T", tomlData)) // A table: we don't need to add any tags, just recurse for every table @@ -21,7 +22,7 @@ func addTag(key string, tomlData interface{}) interface{} { case map[string]interface{}: typed := make(map[string]interface{}, len(orig)) for k, v := range orig { - typed[k] = addTag(k, v) + typed[k] = addTag(v) } return typed @@ -30,13 +31,13 @@ func addTag(key string, tomlData interface{}) interface{} { case []map[string]interface{}: typed := make([]map[string]interface{}, len(orig)) for i, v := range orig { - typed[i] = addTag("", v).(map[string]interface{}) + typed[i] = addTag(v).(map[string]interface{}) } return typed case []interface{}: typed := make([]interface{}, len(orig)) for i, v := range orig { - typed[i] = addTag("", v) + typed[i] = addTag(v) } return typed @@ -52,11 +53,11 @@ func addTag(key string, tomlData interface{}) interface{} { // Tag primitive values: bool, string, int, and float64. case bool: - return tag("bool", fmt.Sprintf("%v", orig)) + return tag("bool", strconv.FormatBool(orig)) case string: return tag("string", orig) case int64: - return tag("integer", fmt.Sprintf("%d", orig)) + return tag("integer", strconv.FormatInt(orig, 10)) case float64: // Special case for nan since NaN == NaN is false. if math.IsNaN(orig) { diff --git a/internal/testsuite/json.go b/internal/testsuite/json.go index 9bb7988..c3e7551 100644 --- a/internal/testsuite/json.go +++ b/internal/testsuite/json.go @@ -9,6 +9,7 @@ import ( ) func CmpJSON(t *testing.T, key string, want, have interface{}) { + t.Helper() switch w := want.(type) { case map[string]interface{}: cmpJSONMaps(t, key, w, have) @@ -22,6 +23,7 @@ func CmpJSON(t *testing.T, key string, want, have interface{}) { } func cmpJSONMaps(t *testing.T, key string, want map[string]interface{}, have interface{}) { + t.Helper() haveMap, ok := have.(map[string]interface{}) if !ok { mismatch(t, key, "table", want, haveMap) @@ -61,6 +63,7 @@ func cmpJSONMaps(t *testing.T, key string, want map[string]interface{}, have int } func cmpJSONArrays(t *testing.T, key string, want, have interface{}) { + t.Helper() wantSlice, ok := want.([]interface{}) if !ok { panic(fmt.Sprintf("'value' should be a JSON array when 'type=array', but it is a %T", want)) @@ -83,6 +86,7 @@ func cmpJSONArrays(t *testing.T, key string, want, have interface{}) { } func cmpJSONValues(t *testing.T, key string, want, have map[string]interface{}) { + t.Helper() wantType, ok := want["type"].(string) if !ok { panic(fmt.Sprintf("'type' should be a string, but it is a %T", want["type"])) @@ -126,6 +130,7 @@ func cmpJSONValues(t *testing.T, key string, want, have map[string]interface{}) } func cmpAsStrings(t *testing.T, key string, want, have string) { + t.Helper() if want != have { t.Fatalf("Values for key '%s' don't match:\n"+ " Expected: %s\n"+ @@ -135,6 +140,7 @@ func cmpAsStrings(t *testing.T, key string, want, have string) { } func cmpFloats(t *testing.T, key string, want, have string) { + t.Helper() // Special case for NaN, since NaN != NaN. if strings.HasSuffix(want, "nan") || strings.HasSuffix(have, "nan") { if want != have { @@ -177,6 +183,7 @@ var layouts = map[string]string{ } func cmpAsDatetimes(t *testing.T, key string, kind, want, have string) { + t.Helper() layout, ok := layouts[kind] if !ok { panic("should never happen") @@ -200,15 +207,6 @@ func cmpAsDatetimes(t *testing.T, key string, kind, want, have string) { } } -func cmpAsDatetimesLocal(t *testing.T, key string, want, have string) { - if datetimeRepl.Replace(want) != datetimeRepl.Replace(have) { - t.Fatalf("Values for key '%s' don't match:\n"+ - " Expected: %v\n"+ - " Your encoder: %v", - key, want, have) - } -} - func kjoin(old, key string) string { if len(old) == 0 { return key @@ -230,6 +228,7 @@ func isValue(m map[string]interface{}) bool { } func mismatch(t *testing.T, key string, wantType string, want, have interface{}) { + t.Helper() t.Fatalf("Key '%s' is not an %s but %[4]T:\n"+ " Expected: %#[3]v\n"+ " Your encoder: %#[4]v", @@ -237,8 +236,9 @@ func mismatch(t *testing.T, key string, wantType string, want, have interface{}) } func valMismatch(t *testing.T, key string, wantType, haveType string, want, have interface{}) { + t.Helper() t.Fatalf("Key '%s' is not an %s but %s:\n"+ " Expected: %#[3]v\n"+ " Your encoder: %#[4]v", - key, wantType, want, have) + key, wantType, haveType, want, have) } diff --git a/internal/testsuite/parser.go b/internal/testsuite/parser.go deleted file mode 100644 index afb0005..0000000 --- a/internal/testsuite/parser.go +++ /dev/null @@ -1,69 +0,0 @@ -package testsuite - -import ( - "bytes" - "encoding/json" - "fmt" - - "github.com/pelletier/go-toml/v2" -) - -type parser struct{} - -func (p parser) Decode(input string) (output string, outputIsError bool, retErr error) { - defer func() { - if r := recover(); r != nil { - switch rr := r.(type) { - case error: - retErr = rr - default: - retErr = fmt.Errorf("%s", rr) - } - } - }() - - var v interface{} - - if err := toml.Unmarshal([]byte(input), &v); err != nil { - return err.Error(), true, nil - } - - j, err := json.MarshalIndent(addTag("", v), "", " ") - if err != nil { - return "", false, retErr - } - - return string(j), false, retErr -} - -func (p parser) Encode(input string) (output string, outputIsError bool, retErr error) { - defer func() { - if r := recover(); r != nil { - switch rr := r.(type) { - case error: - retErr = rr - default: - retErr = fmt.Errorf("%s", rr) - } - } - }() - - var tmp interface{} - err := json.Unmarshal([]byte(input), &tmp) - if err != nil { - return "", false, err - } - - rm, err := rmTag(tmp) - if err != nil { - return err.Error(), true, retErr - } - - buf := new(bytes.Buffer) - err = toml.NewEncoder(buf).Encode(rm) - if err != nil { - return err.Error(), true, retErr - } - - return buf.String(), false, retErr -} diff --git a/internal/testsuite/rm.go b/internal/testsuite/rm.go index 09b3ffa..0e3ee8f 100644 --- a/internal/testsuite/rm.go +++ b/internal/testsuite/rm.go @@ -9,7 +9,7 @@ import ( ) // Remove JSON tags to a data structure as returned by toml-test. -func rmTag(typedJson interface{}) (interface{}, error) { +func rmTag(typedJSON interface{}) (interface{}, error) { // Check if key is in the table m. in := func(key string, m map[string]interface{}) bool { _, ok := m[key] @@ -17,8 +17,7 @@ func rmTag(typedJson interface{}) (interface{}, error) { } // Switch on the data type. - switch v := typedJson.(type) { - + switch v := typedJSON.(type) { // Object: this can either be a TOML table or a primitive with tags. case map[string]interface{}: // This value represents a primitive: remove the tags and return just @@ -56,7 +55,7 @@ func rmTag(typedJson interface{}) (interface{}, error) { } // The top level must be an object or array. - return nil, fmt.Errorf("unrecognized JSON format '%T'", typedJson) + return nil, fmt.Errorf("unrecognized JSON format '%T'", typedJSON) } // Return a primitive: read the "type" and convert the "value" to that. @@ -79,7 +78,7 @@ func untag(typed map[string]interface{}) (interface{}, error) { } return f, nil - //toml.LocalDate{Year:2020, Month:12, Day:12} + // toml.LocalDate{Year:2020, Month:12, Day:12} case "datetime": return time.Parse("2006-01-02T15:04:05.999999999Z07:00", v) case "datetime-local": @@ -115,15 +114,3 @@ func untag(typed map[string]interface{}) (interface{}, error) { return nil, fmt.Errorf("untag: unrecognized tag type %q", t) } - -func parseTime(v, format string, local bool) (t time.Time, err error) { - if local { - t, err = time.ParseInLocation(format, v, time.Local) - } else { - t, err = time.Parse(format, v) - } - if err != nil { - return time.Time{}, fmt.Errorf("Could not parse %q as a datetime: %w", v, err) - } - return t, nil -} diff --git a/internal/testsuite/testsuite.go b/internal/testsuite/testsuite.go index 720cb8b..812e686 100644 --- a/internal/testsuite/testsuite.go +++ b/internal/testsuite/testsuite.go @@ -27,7 +27,7 @@ func Unmarshal(data []byte, v interface{}) error { // ValueToTaggedJSON takes a data structure and returns the tagged JSON // representation. func ValueToTaggedJSON(doc interface{}) ([]byte, error) { - return json.MarshalIndent(addTag("", doc), "", " ") + return json.MarshalIndent(addTag(doc), "", " ") } // DecodeStdin is a helper function for the toml-test binary interface. TOML input @@ -37,13 +37,13 @@ func DecodeStdin() error { var decoded map[string]interface{} if err := toml.NewDecoder(os.Stdin).Decode(&decoded); err != nil { - return fmt.Errorf("Error decoding TOML: %s", err) + return fmt.Errorf("error decoding TOML: %w", err) } j := json.NewEncoder(os.Stdout) j.SetIndent("", " ") - if err := j.Encode(addTag("", decoded)); err != nil { - return fmt.Errorf("Error encoding JSON: %s", err) + if err := j.Encode(addTag(decoded)); err != nil { + return fmt.Errorf("error encoding JSON: %w", err) } return nil diff --git a/internal/tracker/key.go b/internal/tracker/key.go index 149b17f..6344fd0 100644 --- a/internal/tracker/key.go +++ b/internal/tracker/key.go @@ -36,7 +36,7 @@ func (t *KeyTracker) Pop(node *unstable.Node) { } } -// Key returns the current key +// Key returns the current key. func (t *KeyTracker) Key() []string { k := make([]string, len(t.k)) copy(k, t.k) diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go index 76df2d5..2062358 100644 --- a/internal/tracker/seen.go +++ b/internal/tracker/seen.go @@ -288,11 +288,12 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) { idx = s.create(parentIdx, k, tableKind, false, true) } else { entry := s.entries[idx] - if it.IsLast() { + switch { + case it.IsLast(): return false, fmt.Errorf("toml: key %s is already defined", string(k)) - } else if entry.kind != tableKind { + case entry.kind != tableKind: return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) - } else if entry.explicit { + case entry.explicit: return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k)) } } @@ -309,16 +310,16 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) { return s.checkInlineTable(value) case unstable.Array: return s.checkArray(value) + default: + return false, nil } - - return false, nil } func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) { it := node.Children() for it.Next() { n := it.Node() - switch n.Kind { + switch n.Kind { //nolint:exhaustive case unstable.InlineTable: first, err = s.checkInlineTable(n) if err != nil { diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index bf03173..ed51038 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -1 +1,2 @@ +// Package tracker provides functions for keeping track of AST nodes. package tracker diff --git a/marshaler.go b/marshaler.go index 13193ed..ddea2e5 100644 --- a/marshaler.go +++ b/marshaler.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding" "encoding/json" + "errors" "fmt" "io" "math" @@ -42,7 +43,7 @@ type Encoder struct { arraysMultiline bool indentSymbol string indentTables bool - marshalJsonNumbers bool + marshalJSONNumbers bool } // NewEncoder returns a new Encoder that writes to w. @@ -89,14 +90,14 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder { return enc } -// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a +// SetMarshalJSONNumbers forces the encoder to serialize `json.Number` as a // float or integer instead of relying on TextMarshaler to emit a string. // // *Unstable:* This method does not follow the compatibility guarantees of // semver. It can be changed or removed without a new major version being // issued. -func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder { - enc.marshalJsonNumbers = indent +func (enc *Encoder) SetMarshalJSONNumbers(indent bool) *Encoder { + enc.marshalJSONNumbers = indent return enc } @@ -179,7 +180,7 @@ func (enc *Encoder) Encode(v interface{}) error { ctx.inline = enc.tablesInline if v == nil { - return fmt.Errorf("toml: cannot encode a nil interface") + return errors.New("toml: cannot encode a nil interface") } b, err := enc.encode(b, ctx, reflect.ValueOf(v)) @@ -269,16 +270,15 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e case LocalDateTime: return append(b, x.String()...), nil case json.Number: - if enc.marshalJsonNumbers { + if enc.marshalJSONNumbers { if x == "" { /// Useful zero value. return append(b, "0"...), nil } else if v, err := x.Int64(); err == nil { return enc.encode(b, ctx, reflect.ValueOf(v)) } else if f, err := x.Float64(); err == nil { return enc.encode(b, ctx, reflect.ValueOf(f)) - } else { - return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x) } + return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x) } } @@ -312,7 +312,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e return enc.encodeSlice(b, ctx, v) case reflect.Interface: if v.IsNil() { - return nil, fmt.Errorf("toml: encoding a nil interface is not supported") + return nil, errors.New("toml: encoding a nil interface is not supported") } return enc.encode(b, ctx, v.Elem()) @@ -329,28 +329,30 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e case reflect.Float32: f := v.Float() - if math.IsNaN(f) { + switch { + case math.IsNaN(f): b = append(b, "nan"...) - } else if f > math.MaxFloat32 { + case f > math.MaxFloat32: b = append(b, "inf"...) - } else if f < -math.MaxFloat32 { + case f < -math.MaxFloat32: b = append(b, "-inf"...) - } else if math.Trunc(f) == f { + case math.Trunc(f) == f: b = strconv.AppendFloat(b, f, 'f', 1, 32) - } else { + default: b = strconv.AppendFloat(b, f, 'f', -1, 32) } case reflect.Float64: f := v.Float() - if math.IsNaN(f) { + switch { + case math.IsNaN(f): b = append(b, "nan"...) - } else if f > math.MaxFloat64 { + case f > math.MaxFloat64: b = append(b, "inf"...) - } else if f < -math.MaxFloat64 { + case f < -math.MaxFloat64: b = append(b, "-inf"...) - } else if math.Trunc(f) == f { + case math.Trunc(f) == f: b = strconv.AppendFloat(b, f, 'f', 1, 64) - } else { + default: b = strconv.AppendFloat(b, f, 'f', -1, 64) } case reflect.Bool: @@ -441,8 +443,9 @@ func isEmptyValue(v reflect.Value) bool { return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() + default: + return false } - return false } func isEmptyStruct(v reflect.Value) bool { @@ -486,7 +489,7 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt func needsQuoting(v string) bool { // TODO: vectorize for _, b := range []byte(v) { - if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) { + if b == '\'' || b == '\r' || b == '\n' || characters.InvalidASCII(b) { return true } } @@ -580,9 +583,9 @@ func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { return append(b, v...) } -func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) { +func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) []byte { if len(ctx.parentKey) == 0 { - return b, nil + return b } b = enc.encodeComment(ctx.indent, ctx.options.comment, b) @@ -602,10 +605,9 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) b = append(b, "]\n"...) - return b, nil + return b } -//nolint:cyclop func (enc *Encoder) encodeKey(b []byte, k string) []byte { needsQuotation := false cannotUseLiteral := false @@ -642,30 +644,33 @@ func (enc *Encoder) encodeKey(b []byte, k string) []byte { func (enc *Encoder) keyToString(k reflect.Value) (string, error) { keyType := k.Type() - switch { - case keyType.Kind() == reflect.String: - return k.String(), nil - - case keyType.Implements(textMarshalerType): + if keyType.Implements(textMarshalerType) { keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText() if err != nil { return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err) } return string(keyB), nil + } - case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64: + switch keyType.Kind() { + case reflect.String: + return k.String(), nil + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(k.Int(), 10), nil - case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return strconv.FormatUint(k.Uint(), 10), nil - case keyType.Kind() == reflect.Float32: + case reflect.Float32: return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil - case keyType.Kind() == reflect.Float64: + case reflect.Float64: return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil + + default: + return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) } - return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) } func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { @@ -769,9 +774,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { walkStruct(ctx, t, f.Elem()) } continue - } else { - k = fieldType.Name } + k = fieldType.Name } if isNil(f) { @@ -891,10 +895,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro } if !ctx.skipTableHeader { - b, err = enc.encodeTableHeader(ctx, b) - if err != nil { - return nil, err - } + b = enc.encodeTableHeader(ctx, b) if enc.indentTables && len(ctx.parentKey) > 0 { ctx.indent++ @@ -997,11 +998,14 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { if !v.IsValid() { return false } - if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) { + t := v.Type() + if t == timeType || t.Implements(textMarshalerType) { + return false + } + if v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(t).Implements(textMarshalerType) { return false } - t := v.Type() switch t.Kind() { case reflect.Map, reflect.Struct: return !ctx.inline diff --git a/marshaler_test.go b/marshaler_test.go index 267c70e..c5b8acf 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -3,6 +3,7 @@ package toml_test import ( "bytes" "encoding/json" + "errors" "fmt" "math" "math/big" @@ -28,7 +29,7 @@ func (k marshalTextKey) MarshalText() ([]byte, error) { type marshalBadTextKey struct{} func (k marshalBadTextKey) MarshalText() ([]byte, error) { - return nil, fmt.Errorf("error") + return nil, errors.New("error") } func toFloat(x interface{}) float64 { @@ -44,6 +45,7 @@ func toFloat(x interface{}) float64 { } func inDelta(t *testing.T, expected, actual interface{}, delta float64) { + t.Helper() dt := toFloat(expected) - toFloat(actual) assert.True(t, dt < -delta && dt < delta, @@ -942,7 +944,6 @@ nan = nan assert.Equal(t, expected, string(actual)) } -//nolint:funlen func TestMarshalIndentTables(t *testing.T) { examples := []struct { desc string @@ -1011,7 +1012,7 @@ type customTextMarshaler struct { func (c *customTextMarshaler) MarshalText() ([]byte, error) { if c.value == 1 { - return nil, fmt.Errorf("cannot represent 1 because this is a silly test") + return nil, errors.New("cannot represent 1 because this is a silly test") } return []byte(fmt.Sprintf("::%d", c.value)), nil } @@ -1051,7 +1052,7 @@ func TestMarshalTextMarshaler(t *testing.T) { type brokenWriter struct{} func (b *brokenWriter) Write([]byte) (int, error) { - return 0, fmt.Errorf("dead") + return 0, errors.New("dead") } func TestEncodeToBrokenWriter(t *testing.T) { @@ -1074,10 +1075,10 @@ func TestEncoderSetIndentSymbol(t *testing.T) { assert.Equal(t, expected, w.String()) } -func TestEncoderSetMarshalJsonNumbers(t *testing.T) { +func TestEncoderSetMarshalJSONNumbers(t *testing.T) { var w strings.Builder enc := toml.NewEncoder(&w) - enc.SetMarshalJsonNumbers(true) + enc.SetMarshalJSONNumbers(true) err := enc.Encode(map[string]interface{}{ "A": json.Number("1.1"), "B": json.Number("42e-3"), @@ -1198,7 +1199,7 @@ func TestEncoderTagFieldName(t *testing.T) { type doc struct { String string `toml:"hello"` OkSym string `toml:"#"` - Bad string `toml:"\"` + Bad string `toml:"\"` //nolint:govet } d := doc{String: "world"} @@ -1762,14 +1763,14 @@ func ExampleMarshal() { func ExampleMarshal_commented() { type Common struct { Listen string `toml:"listen" comment:"general listener"` - PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"` - MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"` + PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"` //nolint:lll + MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"` //nolint:lll MemoryReturnInterval time.Duration `toml:"memory-return-interval" comment:"daemon will return the freed memory to the OS when it>0"` } type Costs struct { Cost *int `toml:"cost" comment:"default cost (for wildcarded equivalence or matched with regex, or if no value cost set)"` - ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equivalence without wildcards) (additional tuning, usually not needed)"` + ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equivalence without wildcards) (additional tuning, usually not needed)"` //nolint:lll } type ClickHouse struct { @@ -1784,7 +1785,7 @@ func ExampleMarshal_commented() { DateTreeTableVersion int `toml:"date-tree-table-version,commented"` TreeTimeout time.Duration `toml:"tree-timeout,commented"` TagTable string `toml:"tag-table,commented"` - ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"` + ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"` //nolint:lll ConnectTimeout time.Duration `toml:"connect-timeout" comment:"TCP connection timeout"` DataTableLegacy string `toml:"data-table,commented"` RollupConfLegacy string `toml:"rollup-conf,commented"` @@ -1887,12 +1888,12 @@ func TestReadmeComments(t *testing.T) { type Config struct { Host string `toml:"host" comment:"Host IP to connect to."` Port int `toml:"port" comment:"Port of the remote server."` - Tls TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"` + TLS TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"` } example := Config{ Host: "127.0.0.1", Port: 4242, - Tls: TLS{ + TLS: TLS{ Cipher: "AEAD-AES128-GCM-SHA256", Version: "TLS 1.3", }, diff --git a/ossfuzz/fuzz.go b/ossfuzz/fuzz.go index aae03be..db2d4e2 100644 --- a/ossfuzz/fuzz.go +++ b/ossfuzz/fuzz.go @@ -1,3 +1,4 @@ +// Package ossfuzz provides a fuzzing target for OSS-Fuzz. package ossfuzz import ( @@ -8,6 +9,7 @@ import ( "github.com/pelletier/go-toml/v2" ) +// FuzzToml is the fuzzing target. func FuzzToml(data []byte) int { if len(data) >= 2048 { return 0 diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index 8acf481..868fb7d 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -1,11 +1,11 @@ //go:generate go run github.com/toml-lang/toml-test/cmd/toml-test@v1.6.0 -copy ./tests //go:generate go run ./cmd/tomltestgen/main.go -r v1.6.0 -o toml_testgen_test.go -// This is a support file for toml_testgen_test.go package toml_test import ( "encoding/json" + "errors" "testing" "github.com/pelletier/go-toml/v2" @@ -39,7 +39,8 @@ func testgenValid(t *testing.T, input string, jsonRef string) { err := testsuite.Unmarshal([]byte(input), &doc) if err != nil { - if de, ok := err.(*toml.DecodeError); ok { + de := &toml.DecodeError{} + if errors.As(err, &de) { t.Logf("%s\n%s", err, de) } t.Fatalf("failed parsing toml: %s", err) diff --git a/toml_testgen_test.go b/toml_testgen_test.go index c08ecc4..57890d4 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -1,4 +1,4 @@ -// Generated by tomltestgen for toml-test ref v1.6.0 on 2025-10-22T16:33:06+11:00 +// Code generated by tomltestgen for toml-test ref v1.6.0 on 2025-10-22T16:33:06+11:00. DO NOT EDIT. package toml_test import ( diff --git a/types.go b/types.go index 3c6b8fe..806914d 100644 --- a/types.go +++ b/types.go @@ -6,9 +6,11 @@ import ( "time" ) -var timeType = reflect.TypeOf((*time.Time)(nil)).Elem() -var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() -var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil)) -var sliceInterfaceType = reflect.TypeOf([]interface{}(nil)) -var stringType = reflect.TypeOf("") +var ( + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil)) + sliceInterfaceType = reflect.TypeOf([]interface{}(nil)) + stringType = reflect.TypeOf("") +) diff --git a/unmarshaler.go b/unmarshaler.go index 047c8b4..10022f4 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -226,7 +226,7 @@ func (d *decoder) FromParser(v interface{}) error { } if r.IsNil() { - return fmt.Errorf("toml: decoding pointer target cannot be nil") + return errors.New("toml: decoding pointer target cannot be nil") } r = r.Elem() @@ -273,7 +273,7 @@ func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) err var err error var first bool // used for to clear array tables on first use - if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) { + if !d.skipUntilTable || expr.Kind != unstable.KeyValue { first, err = d.seen.CheckExpression(expr) if err != nil { return err @@ -378,7 +378,7 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec case reflect.Array: idx := d.arrayIndex(true, v) if idx >= v.Len() { - return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) + return v, fmt.Errorf("%w at position %d", d.typeMismatchError("array table", v.Type()), idx) } elem := v.Index(idx) _, err := d.handleArrayTable(key, elem) @@ -453,14 +453,14 @@ func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Va case reflect.Array: idx := d.arrayIndex(false, v) if idx >= v.Len() { - return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) + return v, fmt.Errorf("%w at position %d", d.typeMismatchError("array table", v.Type()), idx) } elem := v.Index(idx) _, err := d.handleArrayTable(key, elem) return v, err + default: + return d.handleArrayTable(key, v) } - - return d.handleArrayTable(key, v) } func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { @@ -494,7 +494,8 @@ func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn h mv := v.MapIndex(mk) set := false - if !mv.IsValid() { + switch { + case !mv.IsValid(): // If there is no value in the map, create a new one according to // the map type. If the element type is interface, create either a // map[string]interface{} or a []interface{} depending on whether @@ -507,13 +508,13 @@ func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn h mv = reflect.New(t).Elem() } set = true - } else if mv.Kind() == reflect.Interface { + case mv.Kind() == reflect.Interface: mv = mv.Elem() if !mv.IsValid() { mv = makeFn() } set = true - } else if !mv.CanAddr() { + case !mv.CanAddr(): vt := v.Type() t := vt.Elem() oldmv := mv @@ -953,8 +954,9 @@ const ( // compile time, so it is computed during initialization. var maxUint int64 = math.MaxInt64 -func init() { +func init() { //nolint:gochecknoinits m := uint64(^uint(0)) + // #nosec G115 if m < uint64(maxUint) { maxUint = int64(m) } @@ -1104,35 +1106,39 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value, return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) } return mk.Elem(), nil + } - case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64: + switch keyType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: key, err := strconv.ParseInt(string(data), 10, 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err) } return reflect.ValueOf(key).Convert(keyType), nil - case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: key, err := strconv.ParseUint(string(data), 10, 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err) } return reflect.ValueOf(key).Convert(keyType), nil - case keyType.Kind() == reflect.Float32: + case reflect.Float32: key, err := strconv.ParseFloat(string(data), 32) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err) } return reflect.ValueOf(float32(key)), nil - case keyType.Kind() == reflect.Float64: + case reflect.Float64: key, err := strconv.ParseFloat(string(data), 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err) } return reflect.ValueOf(float64(key)), nil + + default: + return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType) } - return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType) } func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) { @@ -1336,7 +1342,9 @@ func forEachField(t reflect.Type, path []int, do func(name string, path []int)) continue } - fieldPath := append(path, i) + fieldPath := make([]int, 0, len(path)+1) + fieldPath = append(fieldPath, path...) + fieldPath = append(fieldPath, i) fieldPath = fieldPath[:len(fieldPath):len(fieldPath)] name := f.Tag.Get("toml") diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 14f3db9..cdaf509 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -33,8 +33,8 @@ func (k *unmarshalTextKey) UnmarshalText(text []byte) error { type unmarshalBadTextKey struct{} -func (k *unmarshalBadTextKey) UnmarshalText(text []byte) error { - return fmt.Errorf("error") +func (k *unmarshalBadTextKey) UnmarshalText([]byte) error { + return errors.New("error") } func ExampleDecoder_DisallowUnknownFields() { @@ -99,7 +99,7 @@ func ExampleUnmarshal() { type badReader struct{} func (r *badReader) Read([]byte) (int, error) { - return 0, fmt.Errorf("testing error") + return 0, errors.New("testing error") } func TestDecodeReaderError(t *testing.T) { @@ -111,7 +111,6 @@ func TestDecodeReaderError(t *testing.T) { assert.Error(t, err) } -// nolint:funlen func TestUnmarshal_Integers(t *testing.T) { examples := []struct { desc string @@ -195,7 +194,6 @@ func TestUnmarshal_Integers(t *testing.T) { } } -//nolint:funlen func TestUnmarshal_Floats(t *testing.T) { examples := []struct { desc string @@ -333,7 +331,6 @@ func TestUnmarshal_Floats(t *testing.T) { } } -//nolint:funlen func TestUnmarshal(t *testing.T) { type test struct { target interface{} @@ -410,6 +407,7 @@ foo = "bar"`, target: &doc{}, expected: &doc{{A: "a", B: "1"}: "foo"}, assert: func(t *testing.T, test test) { + t.Helper() // Despite the documentation: // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1346,7 +1344,7 @@ B = "data"`, input: `foo = "bar"`, gen: func() test { type doc struct { - foo string + foo string //nolint:unused } return test{ target: &doc{}, @@ -1939,9 +1937,6 @@ B = "data"`, return test{ target: &map[int]string{}, expected: &map[int]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -1952,9 +1947,6 @@ B = "data"`, return test{ target: &map[int8]string{}, expected: &map[int8]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -1965,9 +1957,6 @@ B = "data"`, return test{ target: &map[int64]string{}, expected: &map[int64]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -1978,9 +1967,6 @@ B = "data"`, return test{ target: &map[uint]string{}, expected: &map[uint]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -1991,9 +1977,6 @@ B = "data"`, return test{ target: &map[uint8]string{}, expected: &map[uint8]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -2004,9 +1987,6 @@ B = "data"`, return test{ target: &map[uint64]string{}, expected: &map[uint64]string{1: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -2027,9 +2007,6 @@ B = "data"`, return test{ target: &map[float64]string{}, expected: &map[float64]string{1.01: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -2050,9 +2027,6 @@ B = "data"`, return test{ target: &map[float32]string{}, expected: &map[float32]string{1.01: "a"}, - assert: func(t *testing.T, test test) { - assert.Equal(t, test.expected, test.target) - }, } }, }, @@ -2204,7 +2178,8 @@ port = "bad" err := toml.NewDecoder(file).Decode(&cfg) assert.Error(t, err) - x := err.(*toml.DecodeError) + var x *toml.DecodeError + assert.True(t, errors.As(err, &x)) assert.Equal(t, "toml: cannot decode TOML string into struct field toml_test.Server.Port of type int", x.Error()) expected := `1| [server] 2| path = "/my/path" @@ -2235,7 +2210,8 @@ port = 50 err := toml.NewDecoder(file).Decode(&cfg) assert.Error(t, err) - x := err.(*toml.DecodeError) + var x *toml.DecodeError + assert.True(t, errors.As(err, &x)) assert.Equal(t, "toml: cannot decode TOML integer into struct field toml_test.Server.Path of type string", x.Error()) expected := `1| [server] 2| path = 100 @@ -2488,7 +2464,7 @@ func TestIssue508(t *testing.T) { t1 := text{} err := toml.Unmarshal(b, &t1) assert.NoError(t, err) - assert.Equal(t, "This is a title", t1.head.Title) + assert.Equal(t, "This is a title", t1.Title) } func TestIssue507(t *testing.T) { @@ -2500,7 +2476,7 @@ func TestIssue507(t *testing.T) { type uuid [16]byte -func (u *uuid) UnmarshalText(text []byte) (err error) { +func (u *uuid) UnmarshalText([]byte) (err error) { // Note: the original reported issue had a more complex implementation // of this function. But the important part is to verify that a // non-struct type implementing UnmarshalText works with the unmarshal @@ -2543,7 +2519,7 @@ xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388" `) type target struct { - XZ_URL string + XZ_URL string //revive:disable:var-naming } type pkg struct { @@ -2794,7 +2770,7 @@ func TestIssue772(t *testing.T) { config := Config{} err := toml.Unmarshal(defaultConfigFile, &config) assert.NoError(t, err) - assert.Equal(t, "reach-masterdev-", config.FileHandling.FilePattern) + assert.Equal(t, "reach-masterdev-", config.FilePattern) } func TestIssue774(t *testing.T) { @@ -2954,7 +2930,7 @@ blah.a = "def"`) assert.NoError(t, err) assert.Equal(t, "abc", cfg.Fizz) - assert.Equal(t, "def", cfg.blah.A) + assert.Equal(t, "def", cfg.A) assert.Equal(t, "def", cfg.A) } @@ -3484,7 +3460,7 @@ world'`, func TestOmitEmpty(t *testing.T) { type inner struct { - private string + private string //nolint:unused Skip string `toml:"-"` V string } @@ -3600,7 +3576,6 @@ func TestASCIIControlCharacters(t *testing.T) { } } -//nolint:funlen func TestLocalDateTime(t *testing.T) { examples := []struct { desc string @@ -3918,7 +3893,7 @@ type CustomUnmarshalerKey struct { func (k *CustomUnmarshalerKey) UnmarshalTOML(value *unstable.Node) error { item, err := strconv.ParseInt(string(value.Data), 10, 64) if err != nil { - return fmt.Errorf("error converting to int64, %v", err) + return fmt.Errorf("error converting to int64, %w", err) } k.A = item return nil @@ -4004,7 +3979,7 @@ foo = "bar"`, type doc994 struct{} -func (d *doc994) UnmarshalTOML(value *unstable.Node) error { +func (d *doc994) UnmarshalTOML(*unstable.Node) error { return errors.New("expected-error") } @@ -4237,8 +4212,8 @@ func TestIssue995_SliceNonEmpty_UsesLastElement(t *testing.T) { } assert.Equal(t, 2, len(r.Rules[0].Allowlists)) // Values presence check - got := []string{r.Rules[0].Allowlists[0].Description, r.Rules[0].Allowlists[1].Description} - if !(got[0] == "a" && got[1] == "b") && !(got[0] == "b" && got[1] == "a") { + got := [...]string{r.Rules[0].Allowlists[0].Description, r.Rules[0].Allowlists[1].Description} + if got != [2]string{"a", "b"} && got != [2]string{"b", "a"} { t.Fatalf("unexpected values in allowlists: %v", got) } } diff --git a/unstable/ast.go b/unstable/ast.go index 5f3acaf..a22e9e1 100644 --- a/unstable/ast.go +++ b/unstable/ast.go @@ -83,7 +83,7 @@ func (n *Node) Next() *Node { if n.next == 0 { return nil } - ptr := unsafe.Pointer(n) + ptr := unsafe.Pointer(n) // #nosec G103 size := unsafe.Sizeof(Node{}) return (*Node)(danger.Stride(ptr, size, n.next)) } @@ -95,7 +95,7 @@ func (n *Node) Child() *Node { if n.child == 0 { return nil } - ptr := unsafe.Pointer(n) + ptr := unsafe.Pointer(n) // #nosec G103 size := unsafe.Sizeof(Node{}) return (*Node)(danger.Stride(ptr, size, n.child)) } @@ -113,13 +113,13 @@ func (n *Node) Key() Iterator { case KeyValue: value := n.Child() if !value.Valid() { - panic(fmt.Errorf("KeyValue should have at least two children")) + panic("KeyValue should have at least two children") } return Iterator{node: value.Next()} case Table, ArrayTable: return Iterator{node: n.Child()} default: - panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) + panic(fmt.Errorf("key is not supported on a %s", n.Kind)) } } diff --git a/unstable/benchmark_test.go b/unstable/benchmark_test.go index 79c9bea..d554d5a 100644 --- a/unstable/benchmark_test.go +++ b/unstable/benchmark_test.go @@ -5,12 +5,14 @@ import ( "testing" ) -var valid10Ascii = []byte("1234567890") -var valid10Utf8 = []byte("日本語a") -var valid1kUtf8 = bytes.Repeat([]byte("0123456789日本語日本語日本語日abcdefghijklmnopqrstuvwx"), 16) -var valid1MUtf8 = bytes.Repeat(valid1kUtf8, 1024) -var valid1kAscii = bytes.Repeat([]byte("012345678998jhjklasDJKLAAdjdfjsdklfjdslkabcdefghijklmnopqrstuvwx"), 16) -var valid1MAscii = bytes.Repeat(valid1kAscii, 1024) +var ( + valid10ASCII = []byte("1234567890") + valid10Utf8 = []byte("日本語a") + valid1kUtf8 = bytes.Repeat([]byte("0123456789日本語日本語日本語日abcdefghijklmnopqrstuvwx"), 16) + valid1MUtf8 = bytes.Repeat(valid1kUtf8, 1024) + valid1kASCII = bytes.Repeat([]byte("012345678998jhjklasDJKLAAdjdfjsdklfjdslkabcdefghijklmnopqrstuvwx"), 16) + valid1MASCII = bytes.Repeat(valid1kASCII, 1024) +) func BenchmarkScanComments(b *testing.B) { wrap := func(x []byte) []byte { @@ -18,9 +20,9 @@ func BenchmarkScanComments(b *testing.B) { } inputs := map[string][]byte{ - "10Valid": wrap(valid10Ascii), - "1kValid": wrap(valid1kAscii), - "1MValid": wrap(valid1MAscii), + "10Valid": wrap(valid10ASCII), + "1kValid": wrap(valid1kASCII), + "1MValid": wrap(valid1MASCII), "10ValidUtf8": wrap(valid10Utf8), "1kValidUtf8": wrap(valid1kUtf8), "1MValidUtf8": wrap(valid1MUtf8), @@ -33,7 +35,7 @@ func BenchmarkScanComments(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - scanComment(input) + _, _, _ = scanComment(input) } }) } @@ -45,9 +47,9 @@ func BenchmarkParseLiteralStringValid(b *testing.B) { } inputs := map[string][]byte{ - "10Valid": wrap(valid10Ascii), - "1kValid": wrap(valid1kAscii), - "1MValid": wrap(valid1MAscii), + "10Valid": wrap(valid10ASCII), + "1kValid": wrap(valid1kASCII), + "1MValid": wrap(valid1MASCII), "10ValidUtf8": wrap(valid10Utf8), "1kValidUtf8": wrap(valid1kUtf8), "1MValidUtf8": wrap(valid1MUtf8), @@ -63,7 +65,7 @@ func BenchmarkParseLiteralStringValid(b *testing.B) { for i := 0; i < b.N; i++ { _, _, _, err := p.parseLiteralString(input) if err != nil { - panic(err) + b.Error(err) } } }) diff --git a/unstable/kind.go b/unstable/kind.go index ff9df1b..f87a95a 100644 --- a/unstable/kind.go +++ b/unstable/kind.go @@ -6,28 +6,40 @@ import "fmt" type Kind int const ( - // Meta + // Invalid represents an invalid meta node. Invalid Kind = iota + // Comment represents a comment meta node. Comment + // Key represents a key meta node. Key - // Top level structures + // Table represents a top-level table. Table + // ArrayTable represents a top-level array table. ArrayTable + // KeyValue represents a top-level key value. KeyValue - // Containers values + // Array represents an array container value. Array + // InlineTable represents an inline table container value. InlineTable - // Values + // String represents a string value. String + // Bool represents a boolean value. Bool + // Float represents a floating point value. Float + // Integer represents an integer value. Integer + // LocalDate represents a a local date value. LocalDate + // LocalTime represents a local time value. LocalTime + // LocalDateTime represents a local date/time value. LocalDateTime + // DateTime represents a data/time value. DateTime ) diff --git a/unstable/parser.go b/unstable/parser.go index e29f5e1..b17a32c 100644 --- a/unstable/parser.go +++ b/unstable/parser.go @@ -70,8 +70,8 @@ func (p *Parser) Data() []byte { // panics. func (p *Parser) Range(b []byte) Range { return Range{ - Offset: uint32(danger.SubsliceOffset(p.data, b)), - Length: uint32(len(b)), + Offset: uint32(danger.SubsliceOffset(p.data, b)), // #nosec G115 + Length: uint32(len(b)), // #nosec G115 } } @@ -351,7 +351,6 @@ func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) { return ref, b, err } -//nolint:cyclop,funlen func (p *Parser) parseVal(b []byte) (reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer ref := invalidReference @@ -509,7 +508,6 @@ func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) { return parent, rest, err } -//nolint:funlen,cyclop func (p *Parser) parseValArray(b []byte) (reference, []byte, error) { // array = array-open [ array-values ] ws-comment-newline array-close // array-open = %x5B ; [ @@ -542,7 +540,7 @@ func (p *Parser) parseValArray(b []byte) (reference, []byte, error) { var err error for len(b) > 0 { - cref := invalidReference + var cref reference cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err @@ -611,12 +609,13 @@ func (p *Parser) parseOptionalWhitespaceCommentNewline(b []byte) (reference, []b latestCommentRef := invalidReference addComment := func(ref reference) { - if rootCommentRef == invalidReference { + switch { + case rootCommentRef == invalidReference: rootCommentRef = ref - } else if latestCommentRef == invalidReference { + case latestCommentRef == invalidReference: p.builder.AttachChild(rootCommentRef, ref) latestCommentRef = ref - } else { + default: p.builder.Chain(latestCommentRef, ref) latestCommentRef = ref } @@ -672,7 +671,6 @@ func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, return token, token[i : len(token)-3], rest, err } -//nolint:funlen,gocognit,cyclop func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string-delim @@ -704,11 +702,11 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er if !escaped { str := token[startIdx:endIdx] - verr := characters.Utf8TomlValidAlreadyEscaped(str) - if verr.Zero() { + highlight := characters.Utf8TomlValidAlreadyEscaped(str) + if len(highlight) == 0 { return token, str, rest, nil } - return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + return nil, nil, nil, NewParserError(highlight, "invalid UTF-8") } var builder bytes.Buffer @@ -718,7 +716,6 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er for i < len(token)-3 { c := token[i] - //nolint:nestif if c == '\\' { // When the last non-whitespace character on a line is an unescaped \, // it will be trimmed along with all whitespace (including newlines) up @@ -744,7 +741,7 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er i += j for ; i < len(token)-3; i++ { c := token[i] - if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + if c != '\n' && c != '\r' && c != ' ' && c != '\t' { i-- break } @@ -868,7 +865,6 @@ func (p *Parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { } } -//nolint:funlen,cyclop func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark // quotation-mark = %x22 ; " @@ -897,11 +893,11 @@ func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { // validate the string and return a direct reference to the buffer. if !escaped { str := token[startIdx:endIdx] - verr := characters.Utf8TomlValidAlreadyEscaped(str) - if verr.Zero() { + highlight := characters.Utf8TomlValidAlreadyEscaped(str) + if len(highlight) == 0 { return token, str, rest, nil } - return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + return nil, nil, nil, NewParserError(highlight, "invalid UTF-8") } i := startIdx @@ -972,7 +968,7 @@ func hexToRune(b []byte, length int) (rune, error) { var r uint32 for i, c := range b { - d := uint32(0) + var d uint32 switch { case '0' <= c && c <= '9': d = uint32(c - '0') @@ -1002,7 +998,6 @@ func (p *Parser) parseWhitespace(b []byte) []byte { return rest } -//nolint:cyclop func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error) { switch b[0] { case 'i': @@ -1118,7 +1113,6 @@ byteLoop: }), b[i:], nil } -//nolint:funlen,gocognit,cyclop func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) { i := 0 diff --git a/unstable/parser_test.go b/unstable/parser_test.go index 16a9853..5edac37 100644 --- a/unstable/parser_test.go +++ b/unstable/parser_test.go @@ -196,7 +196,6 @@ func compareIterator(t *testing.T, expected []astNode, actual Iterator) { } } -//nolint:funlen func TestParser_AST(t *testing.T) { examples := []struct { desc string @@ -358,7 +357,7 @@ func BenchmarkParseBasicStringWithUnicode(b *testing.B) { b.SetBytes(int64(len(input))) for i := 0; i < b.N; i++ { - p.parseBasicString(input) + _, _, _, _ = p.parseBasicString(input) } }) b.Run("8", func(b *testing.B) { @@ -367,7 +366,7 @@ func BenchmarkParseBasicStringWithUnicode(b *testing.B) { b.SetBytes(int64(len(input))) for i := 0; i < b.N; i++ { - p.parseBasicString(input) + _, _, _, _ = p.parseBasicString(input) } }) } @@ -383,7 +382,7 @@ func BenchmarkParseBasicStringsEasy(b *testing.B) { b.SetBytes(int64(len(input))) for i := 0; i < b.N; i++ { - p.parseBasicString(input) + _, _, _, _ = p.parseBasicString(input) } }) }