···1111 extra-rules: true
12121313 golines:
1414- max-len: 140
1515- shorten-comments: true
1616- reformat-tags: true
1414+ max-len: 120
17151816linters:
1917 default: all
2018 disable:
2121- - cyclop # I prefer cognitive complexity
2222- - decorder # Don't care about this
2323- - dupl # Basically every table driven test ever triggers this
2424- - dupword # Messes with test cases more often than not
2525- - err113 # Opaque errors are fine
2626- - exhaustruct # No
2727- - forbidigo # Nothing to forbid
2828- - funlen # Bad metric for complexity
2929- - gocyclo # I prefer cognitive complexity
3030- - godox # "todo" and "fixme" comments are allowed
3131- - goheader # No need
3232- - gomodguard # Deprecated in v2.12.0, replaced by gomodguard_v2 which is enabled by default
3333- - gosmopolitan # No need
3434- - grouper # Imports take care of themselves, rest is common sense
3535- - ireturn # Returning an interface is sometimes a good abstraction
3636- - lll # Auto formatters do this and what they can't do I don't care about
3737- - nlreturn # Similar to wsl
3838- - noinlineerr # Inline errors are fine
3939- - nonamedreturns # Used sparingly in short functions these are fine
4040- - paralleltest # This makes go test -v ./... much more difficult to read
4141- - thelper # Lots of false positives in here due to the way I do table tests
4242- - unparam # gopls is better and more subtle
4343- - varnamelen # Lots of false positives of things that are fine
4444- - wrapcheck # Not every error must be wrapped
4545- - wsl # Deprecated
1919+ - cyclop # gocognit is the single complexity linter, cognitive beats cyclomatic
2020+ - decorder # Don't care about this
2121+ - err113 # Not everything needs this
2222+ - exhaustruct # This is just a bad idea
2323+ - forbidigo # Nothing to forbid
2424+ - funlen # Bad metric for complexity
2525+ - gocyclo # Prefer cognitive complexity over naive metrics
2626+ - godox # "todo" and "fixme" comments are allowed
2727+ - gomodguard # Deprecated
2828+ - ireturn # The Arg and Flag interfaces are intentionally opaque
2929+ - lll # Auto formatters do this and what they can't do I don't care about
3030+ - maintidx # Prefer cognitive
3131+ - mnd # Annoying and too sensitive
3232+ - nestif # Nesting depth is the dominant factor in gocognit's score already
3333+ - noinlineerr # Inline errors are fine
3434+ - nonamedreturns # Named returns are often helpful documentation, it's naked returns that are the issue
3535+ - paralleltest # I've never had Go tests take longer than a few seconds, it's fine
3636+ - unparam # gopls is better and more subtle
3737+ - varnamelen # Lots of false positives of things that are fine
3838+ - wrapcheck # Not every error must be wrapped
3939+ - wsl # Deprecated
46404741 exclusions:
4842 presets:
4943 # See https://golangci-lint.run/usage/false-positives/#exclusion-presets
5050- - std-error-handling
5144 - common-false-positives
4545+ - std-error-handling
5246 rules:
5347 - path: _test\.go
5448 linters:
5555- - prealloc # These kinds of optimisations will make no difference to test code
5656- - gosec # Tests don't need security stuff
5757- - gochecknoglobals # e.g. global test flags
5858- - goconst # Table driven tests naturally repeat string literals
5959- - maintidx # Flags table driven tests
4949+ - dupl # Common false positives with table driven tests
5050+ - dupword # Messes with test cases more often than not
5151+ - gochecknoglobals # Flags and env vars
5252+ - goconst # Sometimes repetition is okay in tests
5353+ - gosec # Tests don't need security stuff
5454+ - gosmopolitan # Testing unicode flags etc.
5555+ - thelper # Lots of false positives because of how I've done CLI tests
5656+ - prealloc # These kinds of optimisations will make no difference to test code
60576158 settings:
6262- cyclop:
6363- max-complexity: 20
6464-6559 depguard:
6660 rules:
6761 main:
···7367 desc: use math/rand/v2 instead
74687569 errcheck:
7676- check-type-assertions: true
7770 check-blank: true
7171+ check-type-assertions: true
78727973 exhaustive:
8074 check:
8181- - switch
8275 - map
7676+ - switch
8377 default-signifies-exhaustive: true
8484-8585- staticcheck:
8686- checks:
8787- - all
88788979 gosec:
9080 excludes:
9191- - G104 # Errors not checked, handled by errcheck
8181+ - G104 # Errors not checked, handled by errcheck
92829383 govet:
9484 enable-all: true
95859686 nakedret:
9797- max-func-lines: 0 # Disallow any naked returns
8787+ max-func-lines: 0 # Disallow any naked returns
98889989 nolintlint:
10090 allow-unused: false
10191 require-explanation: true
10292 require-specific: true
103939494+ revive:
9595+ # every other revive rule overlaps one or more of the other
9696+ # linters. These are the ones nothing else covers.
9797+ enable-all-rules: false
9898+ rules:
9999+ - name: context-as-argument # ctx must be the first parameter
100100+ - name: deep-exit # os.Exit / log.Fatal outside main and init
101101+ - name: modifies-parameter # don't mutate value parameters
102102+ - name: modifies-value-receiver # value receiver mutations are lost
103103+ - name: unexported-return # exported func returning an unexported type
104104+105105+ staticcheck:
106106+ checks:
107107+ - all
108108+104109 usetesting:
105110 context-background: true
106111 context-todo: true
107112 os-chdir: true
113113+ os-create-temp: true
108114 os-mkdir-temp: true
109115 os-setenv: true
110110- os-create-temp: true
111116 os-temp-dir: true
112112-113113- revive:
114114- max-open-files: 256
115115- enable-all-rules: true
116116- rules:
117117- - name: add-constant
118118- disabled: true # goconst does this
119119-120120- - name: argument-limit
121121- arguments:
122122- - 5
123123-124124- - name: cognitive-complexity
125125- disabled: true # gocognit does this
126126-127127- - name: comment-spacings
128128- arguments:
129129- - "nolint:"
130130-131131- - name: cyclomatic
132132- disabled: true # cyclop does this
133133-134134- - name: enforce-switch-style
135135- disabled: true # exhaustive handles this
136136-137137- - name: exported
138138- arguments:
139139- - checkPrivateReceivers
140140- - checkPublicInterface
141141-142142- - name: function-length
143143- disabled: true # Bad proxy for complexity
144144-145145- - name: function-result-limit
146146- arguments:
147147- - 3
148148-149149- - name: import-shadowing
150150- disabled: true # predeclared does this
151151-152152- - name: line-length-limit
153153- disabled: true # gofmt/golines handles this well enough
154154-155155- - name: max-public-structs
156156- disabled: true # This is a dumb rule
157157-158158- - name: redefines-builtin-id
159159- disabled: true # predeclared does this
160160-161161- - name: unhandled-error
162162- disabled: true # errcheck handles this
163163-164164- - name: flag-parameter
165165- disabled: true # As far as I can work out this just doesn't like bools
166166-167167- - name: unused-parameter
168168- disabled: true # The gopls unused analyzer covers this better
169169-170170- - name: unused-receiver
171171- disabled: true # As above
172172-173173- - name: var-naming
174174- disabled: true
175175-176176- - name: package-naming
177177- disabled: true
+23-4
command_test.go
···420420 name: "with named arguments",
421421 options: []cli.Option{
422422 cli.OverrideArgs([]string{"--help"}),
423423- cli.Arg(new(string), "src", "The file to copy"), // This one is required
424424- cli.Arg(new(string), "dest", "Destination to copy to", cli.ArgDefault("default.txt")), // This one is optional
423423+ cli.Arg(
424424+ new(string),
425425+ "src",
426426+ "The file to copy",
427427+ ), // This one is required
428428+ cli.Arg(
429429+ new(string),
430430+ "dest",
431431+ "Destination to copy to",
432432+ cli.ArgDefault("default.txt"),
433433+ ), // This one is optional
425434 cli.Arg(new(int), "other", "Something else", cli.ArgDefault(0)),
426435 cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
427436 },
···431440 name: "with verbosity count",
432441 options: []cli.Option{
433442 cli.OverrideArgs([]string{"--help"}),
434434- cli.Arg(new(string), "src", "The file to copy"), // This one is required
435435- cli.Arg(new(string), "dest", "Destination to copy to", cli.ArgDefault("destination.txt")), // This one is optional
443443+ cli.Arg(
444444+ new(string),
445445+ "src",
446446+ "The file to copy",
447447+ ), // This one is required
448448+ cli.Arg(
449449+ new(string),
450450+ "dest",
451451+ "Destination to copy to",
452452+ cli.ArgDefault("destination.txt"),
453453+ ), // This one is optional
436454 cli.Flag(new(flag.Count), "verbosity", 'v', "Increase the verbosity level"),
437455 cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
438456 },
···945963 cli.OverrideArgs(tt.args),
946964 cli.Run(func(ctx context.Context, cmd *cli.Command) error {
947965 fmt.Fprintf(cmd.Stdout(), "force: %v\n", force)
966966+948967 return nil
949968 }),
950969 )
···22222323[tasks.test]
2424description = "Run the test suite"
2525-usage = '''
2626-arg "[args]..." help="Extra args to pass to go test"
2727-'''
2825# -race needs CGO (https://go.dev/doc/articles/race_detector#Requirements)
2926env = { CGO_ENABLED = "1" }
3030-run = "go test -race ./... {{arg(name='args', default='')}}"
2727+run = "go test -race ./..."
3128sources = ["**/*.go", "**/testdata/**/*", "go.mod", "go.sum"]
32293330[tasks.bench]
3431description = "Run all project benchmarks"
3535-usage = '''
3636-arg "[args]..." help="Extra args to pass to go test"
3737-'''
3838-run = "go test ./... -run None -benchmem -bench . {{arg(name='args', default='')}}"
3232+run = "go test ./... -run None -benchmem -bench ."
3933sources = ["**/*.go"]
40344135[tasks.lint]
+2
option.go
···461461//nolint:unused // Satisfies the unexported ArgOption.apply method, staticcheck can't see across the interface.
462462func (o argDefaultOpt[T]) apply(cfg *internalarg.Config[T]) error {
463463 cfg.DefaultValue = &o.value
464464+464465 return nil
465466}
466467···514515//nolint:unused // Satisfies the unexported FlagOption.apply method, staticcheck can't see across the interface.
515516func (o flagDefaultOpt[T]) apply(cfg *internalflag.Config[T]) error {
516517 cfg.DefaultValue = o.value
518518+517519 return nil
518520}
519521
+25
.tangled/workflows/CI.yml
···11+when:
22+ - event: [push]
33+ branch: [main]
44+ - event: [pull_request]
55+ branch: [main]
66+77+engine: nixery
88+99+dependencies:
1010+ nixpkgs:
1111+ - go
1212+ - golangci-lint
1313+ - gcc # Needed for CGO_ENABLED=1 (go test -race)
1414+1515+steps:
1616+ - name: Test
1717+ environment:
1818+ CGO_ENABLED: "1"
1919+ command: go test -race ./...
2020+2121+ - name: Format
2222+ command: golangci-lint fmt ./... --diff-colored
2323+2424+ - name: Lint
2525+ command: golangci-lint run ./...
+15-2
examples/subcommands/cli.go
···8888 cli.Example("Do it for a specific duration", "demo do something --duration 1m30s"),
8989 cli.Version("do version"),
9090 cli.Arg(&thing, "thing", "Thing to do"),
9191- cli.Flag(&options.count, "count", 'c', "Number of times to do the thing", cli.FlagDefault(1), cli.Env[int]("DEMO_COUNT")),
9191+ cli.Flag(
9292+ &options.count,
9393+ "count",
9494+ 'c',
9595+ "Number of times to do the thing",
9696+ cli.FlagDefault(1),
9797+ cli.Env[int]("DEMO_COUNT"),
9898+ ),
9299 cli.Flag(&options.fast, "fast", 'f', "Do the thing quickly"),
93100 cli.Flag(&options.verbosity, "verbosity", 'v', "Increase the verbosity level"),
9494- cli.Flag(&options.duration, "duration", 'd', "Do the thing for a specific duration", cli.FlagDefault(1*time.Second)),
101101+ cli.Flag(
102102+ &options.duration,
103103+ "duration",
104104+ 'd',
105105+ "Do the thing for a specific duration",
106106+ cli.FlagDefault(1*time.Second),
107107+ ),
95108 cli.Run(func(ctx context.Context, cmd *cli.Command) error {
96109 if options.fast {
97110 fmt.Fprintf(
+1
internal/flag/flag.go
···752752 return len(*parse.Cast[[]string](f.value)) == 0
753753 case kind.Time:
754754 var zero time.Time
755755+755756 return parse.Cast[time.Time](f.value).Equal(zero)
756757 case kind.Duration:
757758 return *parse.Cast[time.Duration](f.value) == 0