Upgrade to golangci-lint v2 (#1008)

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