Repository: mgechev/revive
Pull request #1292 (2025-04-05 10:14:10)

struct-tag: change reported messages and refactor implementation
This PR proposes  a ~full rewriting of the rule `struct-tag` in an attempt to clean-up the implementation.

This PR introduces the following changes:

1. Defines a common signature for tag-checker functions
2. Normalizes failure messages
3. Reduces the implementation complexity of some checks (protobuf, properties, ...)



Patch:
diff --git a/Makefile b/Makefile
index 645a2db..e6d6ad2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: test
+.PHONY: test lint
 
 export GO111MODULE=on
 
@@ -8,12 +8,17 @@ DATE ?= $(shell date -u '+%Y-%m-%d %H:%M UTC')
 BUILDER ?= Makefile
 VERSION_FLAGS := -X "github.com/mgechev/revive/cli.version=$(GIT_VERSION)" -X "github.com/mgechev/revive/cli.date=$(DATE)" -X "github.com/mgechev/revive/cli.commit=$(GIT_COMMIT)" -X "github.com/mgechev/revive/cli.builtBy=$(BUILDER)"
 
+all: test lint build
+
 install:
 	@go mod vendor
 
 build:
 	@go build -ldflags='$(VERSION_FLAGS)'
 
+lint:
+	revive --config revive.toml ./...
+
 test:
 	@go test -v -race ./...
 
diff --git a/README.md b/README.md
index cb23e3e..d0e5c2b 100644
--- a/README.md
+++ b/README.md
@@ -477,90 +477,90 @@ You can use the following exclude patterns
 List of all available rules. The rules ported from `golint` are left unchanged and indicated in the `golint` column.
 | Name                  | Config | Description                                                      | `golint` | Typed |
 | --------------------- | :----: | :--------------------------------------------------------------- | :------: | :---: |
-| [`context-keys-type`](./RULES_DESCRIPTIONS.md#context-key-types)   |  n/a   | Disallows the usage of basic types in `context.WithValue`.       |   yes    |  yes  |
-| [`time-equal`](./RULES_DESCRIPTIONS.md#time-equal)         |  n/a   | Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.                 |   no    |  yes  |
-| [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming)         |  n/a   | Conventions around the naming of time variables.                 |   yes    |  yes  |
-| [`unchecked-type-assertions`](./RULES_DESCRIPTIONS.md#unchecked-type-assertions)         |  n/a   | Disallows type assertions without checking the result.                 |   no    |  yes  |
-| [`var-declaration`](./RULES_DESCRIPTIONS.md#var-declaration)     |  n/a   | Reduces redundancies around variable declaration.                |   yes    |  yes  |
-| [`unexported-return`](./RULES_DESCRIPTIONS.md#unexported-return)   |  n/a   | Warns when a public return is from unexported type.              |   yes    |  yes  |
-| [`errorf`](./RULES_DESCRIPTIONS.md#errorf)              |  n/a   | Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`   |   yes    |  yes  |
+| [`add-constant`](./RULES_DESCRIPTIONS.md#add-constant)        |  map   | Suggests using constant for magic numbers and string literals    |    no    |  no   |
+| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit)      |  int (defaults to 8)  | Specifies the maximum number of arguments a function can receive |    no    |  no   |
+| [`atomic`](./RULES_DESCRIPTIONS.md#atomic)              |  n/a   | Check for common mistaken usages of the `sync/atomic` package    |    no    |  no   |
+| [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters)          |  []string (defaults to []string{})   |  Checks banned characters in identifiers |    no    |  no   |
+| [`bare-return`](./RULES_DESCRIPTIONS.md#bare-return) | n/a  | Warns on bare returns   |    no    |  no   |
 | [`blank-imports`](./RULES_DESCRIPTIONS.md#blank-imports)       |  n/a   | Disallows blank imports                                          |   yes    |  no   |
+| [`bool-literal-in-expr`](./RULES_DESCRIPTIONS.md#bool-literal-in-expr)|  n/a   | Suggests removing Boolean literals from logic expressions        |    no    |  no   |
+| [`call-to-gc`](./RULES_DESCRIPTIONS.md#call-to-gc)   | n/a    | Warns on explicit call to the garbage collector    |    no    |  no   |
+| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity)          |  int (defaults to 7) | Sets restriction for maximum Cognitive complexity.              |    no    |  no   |
+| [`comment-spacings`](./RULES_DESCRIPTIONS.md#comment-spacings)          |  []string   |  Warns on malformed comments |    no    |  no   |
+| [`comments-density`](./RULES_DESCRIPTIONS.md#comments-density) |  int (defaults to 0)  | Enforces a minimum comment / code relation |    no    |  no   |
+| [`confusing-naming`](./RULES_DESCRIPTIONS.md#confusing-naming)    |  n/a   | Warns on methods with names that differ only by capitalization   |    no    |  no   |
+| [`confusing-results`](./RULES_DESCRIPTIONS.md#confusing-results)   |  n/a   | Suggests to name potentially confusing function results          |    no    |  no   |
+| [`constant-logical-expr`](./RULES_DESCRIPTIONS.md#constant-logical-expr)   |  n/a   | Warns on constant logical expressions                        |    no    |  no   |
 | [`context-as-argument`](./RULES_DESCRIPTIONS.md#context-as-argument) |  n/a   | `context.Context` should be the first argument of a function.    |   yes    |  no   |
+| [`context-keys-type`](./RULES_DESCRIPTIONS.md#context-key-types)   |  n/a   | Disallows the usage of basic types in `context.WithValue`.       |   yes    |  yes  |
+| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic)          |  int (defaults to 10)   | Sets restriction for maximum Cyclomatic complexity.              |    no    |  no   |
+| [`datarace`](./RULES_DESCRIPTIONS.md#datarace)          |  n/a   |  Spots potential dataraces |    no    |  no   |
+| [`deep-exit`](./RULES_DESCRIPTIONS.md#deep-exit)           |  n/a   | Looks for program exits in funcs other than `main()` or `init()` |    no    |  no   |
+| [`defer`](./RULES_DESCRIPTIONS.md#defer)          |  map   |  Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)       |    no    |  no   |
 | [`dot-imports`](./RULES_DESCRIPTIONS.md#dot-imports)         |  n/a   | Forbids `.` imports.                                             |   yes    |  no   |
+| [`duplicated-imports`](./RULES_DESCRIPTIONS.md#duplicated-imports) | n/a  | Looks for packages that are imported two or more times   |    no    |  no   |
+| [`early-return`](./RULES_DESCRIPTIONS.md#early-return)          | []string   | Spots if-then-else statements where the predicate may be inverted to reduce nesting |    no    |  no   |
+| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block)         |  n/a   | Warns on empty code blocks                                       |    no    |  yes   |
+| [`empty-lines`](./RULES_DESCRIPTIONS.md#empty-lines)   | n/a | Warns when there are heading or trailing newlines in a block              |    no    |  no   |
+| [`enforce-map-style`](./RULES_DESCRIPTIONS.md#enforce-map-style) |  string (defaults to "any")  |  Enforces consistent usage of `make(map[type]type)` or `map[type]type{}` for map initialization. Does not affect `make(map[type]type, size)` constructions. |    no    |  no   |
+| [`enforce-repeated-arg-type-style`](./RULES_DESCRIPTIONS.md#enforce-repeated-arg-type-style) |  string (defaults to "any")  |  Enforces consistent style for repeated argument and/or return value types. |    no    |  no   |
+| [`enforce-slice-style`](./RULES_DESCRIPTIONS.md#enforce-slice-style) |  string (defaults to "any")  |  Enforces consistent usage of `make([]type, 0)` or `[]type{}` for slice initialization. Does not affect `make(map[type]type, non_zero_len, or_non_zero_cap)` constructions. |    no    |  no   |
+| [`error-naming`](./RULES_DESCRIPTIONS.md#error-naming)        |  n/a   | Naming of error variables.                                       |   yes    |  no   |
 | [`error-return`](./RULES_DESCRIPTIONS.md#error-return)        |  n/a   | The error return parameter should be last.                       |   yes    |  no   |
 | [`error-strings`](./RULES_DESCRIPTIONS.md#error-strings)       |  []string   | Conventions around error strings.                                |   yes    |  no   |
-| [`error-naming`](./RULES_DESCRIPTIONS.md#error-naming)        |  n/a   | Naming of error variables.                                       |   yes    |  no   |
+| [`errorf`](./RULES_DESCRIPTIONS.md#errorf)              |  n/a   | Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`   |   yes    |  yes  |
 | [`exported`](./RULES_DESCRIPTIONS.md#exported)            |  []string   | Naming and commenting conventions on exported symbols.           |   yes    |  no   |
+| [`file-header`](./RULES_DESCRIPTIONS.md#file-header)         | string (defaults to none)| Header which each file should have.                              |    no    |  no   |
+| [`file-length-limit`](./RULES_DESCRIPTIONS.md#file-length-limit) | map (optional)| Enforces a maximum number of lines per file |    no    |  no   |
+| [`filename-format`](./RULES_DESCRIPTIONS.md#filename-format) | regular expression (optional) | Enforces the formatting of filenames |   no    |  no   |
+| [`flag-parameter`](./RULES_DESCRIPTIONS.md#flag-parameter)      |  n/a   | Warns on boolean parameters that create a control coupling       |    no    |  no   |
+| [`function-length`](./RULES_DESCRIPTIONS.md#function-length)          |  int, int (defaults to 50 statements, 75 lines)   |  Warns on functions exceeding the statements or lines max |    no    |  no   |
+| [`function-result-limit`](./RULES_DESCRIPTIONS.md#function-result-limit) |  int (defaults to 3)| Specifies the maximum number of results a function can return    |    no    |  no   |
+| [`get-return`](./RULES_DESCRIPTIONS.md#get-return)          |  n/a   | Warns on getters that do not yield any result                    |    no    |  no   |
+| [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches)          |  n/a   | Spots if-then-else statements with identical `then` and `else` branches       |    no    |  no   |
 | [`if-return`](./RULES_DESCRIPTIONS.md#if-return)           |  n/a   | Redundant if when returning an error.                            |   no    |  no   |
+| [`import-alias-naming`](./RULES_DESCRIPTIONS.md#import-alias-naming)         | string or map[string]string (defaults to allow regex pattern ^[a-z][a-z0-9]{0,}$) | Conventions around the naming of import aliases.                              |    no    |  no   |
+| [`import-shadowing`](./RULES_DESCRIPTIONS.md#import-shadowing)   | n/a    | Spots identifiers that shadow an import    |    no    |  no   |
+| [`imports-blocklist`](./RULES_DESCRIPTIONS.md#imports-blocklist)   | []string | Disallows importing the specified packages                     |    no    |  no   |
 | [`increment-decrement`](./RULES_DESCRIPTIONS.md#increment-decrement) |  n/a   | Use `i++` and `i--` instead of `i += 1` and `i -= 1`.            |   yes    |  no   |
-| [`var-naming`](./RULES_DESCRIPTIONS.md#var-naming)          |  allowlist & blocklist of initialisms   | Naming rules.                                                    |   yes    |  no   |
-| [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments)    |  n/a   | Package commenting conventions.                                  |   yes    |  no   |
-| [`range`](./RULES_DESCRIPTIONS.md#range)               |  n/a   | Prevents redundant variables when iterating over a collection.   |   yes    |  no   |
-| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming)     |  map (optional)   | Conventions around the naming of receivers.                      |   yes    |  no   |
 | [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)   |  []string   | Prevents redundant else statements.                              |   yes    |  no   |
-| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit)      |  int (defaults to 8)  | Specifies the maximum number of arguments a function can receive |    no    |  no   |
-| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic)          |  int (defaults to 10)   | Sets restriction for maximum Cyclomatic complexity.              |    no    |  no   |
+| [`line-length-limit`](./RULES_DESCRIPTIONS.md#line-length-limit)   | int (defaults to 80) | Specifies the maximum number of characters in a line             |    no    |  no   |
+| [`max-control-nesting`](./RULES_DESCRIPTIONS.md#max-control-nesting) |  int (defaults to 5)  | Sets restriction for maximum nesting of control structures. |    no    |  no   |
 | [`max-public-structs`](./RULES_DESCRIPTIONS.md#max-public-structs)  |  int (defaults to 5)  | The maximum number of public structs in a file.                  |    no    |  no   |
-| [`file-header`](./RULES_DESCRIPTIONS.md#file-header)         | string (defaults to none)| Header which each file should have.                              |    no    |  no   |
-| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block)         |  n/a   | Warns on empty code blocks                                       |    no    |  yes   |
-| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else)    |  []string   | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) |    no    |  no   |
-| [`confusing-naming`](./RULES_DESCRIPTIONS.md#confusing-naming)    |  n/a   | Warns on methods with names that differ only by capitalization   |    no    |  no   |
-| [`get-return`](./RULES_DESCRIPTIONS.md#get-return)          |  n/a   | Warns on getters that do not yield any result                    |    no    |  no   |
 | [`modifies-parameter`](./RULES_DESCRIPTIONS.md#modifies-parameter)  |  n/a   | Warns on assignments to function parameters                      |    no    |  no   |
-| [`confusing-results`](./RULES_DESCRIPTIONS.md#confusing-results)   |  n/a   | Suggests to name potentially confusing function results          |    no    |  no   |
-| [`deep-exit`](./RULES_DESCRIPTIONS.md#deep-exit)           |  n/a   | Looks for program exits in funcs other than `main()` or `init()` |    no    |  no   |
-| [`unused-parameter`](./RULES_DESCRIPTIONS.md#unused-parameter)    |  n/a   | Suggests to rename or remove unused function parameters          |    no    |  no   |
-| [`unreachable-code`](./RULES_DESCRIPTIONS.md#unreachable-code)    |  n/a   | Warns on unreachable code                                        |    no    |  no   |
-| [`add-constant`](./RULES_DESCRIPTIONS.md#add-constant)        |  map   | Suggests using constant for magic numbers and string literals    |    no    |  no   |
-| [`flag-parameter`](./RULES_DESCRIPTIONS.md#flag-parameter)      |  n/a   | Warns on boolean parameters that create a control coupling       |    no    |  no   |
-| [`unnecessary-stmt`](./RULES_DESCRIPTIONS.md#unnecessary-stmt)    |  n/a   | Suggests removing or simplifying unnecessary statements          |    no    |  no   |
-| [`struct-tag`](./RULES_DESCRIPTIONS.md#struct-tag)          |  []string   | Checks common struct tags like `json`, `xml`, `yaml`               |    no    |  no   |
 | [`modifies-value-receiver`](./RULES_DESCRIPTIONS.md#modifies-value-receiver) |  n/a   | Warns on assignments to value-passed method receivers        |    no    |  yes  |
-| [`constant-logical-expr`](./RULES_DESCRIPTIONS.md#constant-logical-expr)   |  n/a   | Warns on constant logical expressions                        |    no    |  no   |
-| [`bool-literal-in-expr`](./RULES_DESCRIPTIONS.md#bool-literal-in-expr)|  n/a   | Suggests removing Boolean literals from logic expressions        |    no    |  no   |
-| [`redefines-builtin-id`](./RULES_DESCRIPTIONS.md#redefines-builtin-id)|  n/a   | Warns on redefinitions of builtin identifiers                    |    no    |  no   |
-| [`function-result-limit`](./RULES_DESCRIPTIONS.md#function-result-limit) |  int (defaults to 3)| Specifies the maximum number of results a function can return    |    no    |  no   |
-| [`imports-blocklist`](./RULES_DESCRIPTIONS.md#imports-blocklist)   | []string | Disallows importing the specified packages                     |    no    |  no   |
-| [`range-val-in-closure`](./RULES_DESCRIPTIONS.md#range-val-in-closure)|  n/a   | Warns if range value is used in a closure dispatched as goroutine|    no    |  no   |
+| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs)          |  n/a   |  Warns on structs within structs |    no    |  no   |
+| [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order)          |  n/a   |  Checks inefficient conditional expressions |    no    |  no   |
+| [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments)    |  n/a   | Package commenting conventions.                                  |   yes    |  no   |
 | [`range-val-address`](./RULES_DESCRIPTIONS.md#range-val-address)|  n/a   | Warns if address of range value is used dangerously |    no    |  yes   |
-| [`waitgroup-by-value`](./RULES_DESCRIPTIONS.md#waitgroup-by-value)  |  n/a   | Warns on functions taking sync.WaitGroup as a by-value parameter |    no    |  no   |
-| [`atomic`](./RULES_DESCRIPTIONS.md#atomic)              |  n/a   | Check for common mistaken usages of the `sync/atomic` package    |    no    |  no   |
-| [`empty-lines`](./RULES_DESCRIPTIONS.md#empty-lines)   | n/a | Warns when there are heading or trailing newlines in a block              |    no    |  no   |
-| [`line-length-limit`](./RULES_DESCRIPTIONS.md#line-length-limit)   | int (defaults to 80) | Specifies the maximum number of characters in a line             |    no    |  no   |
-| [`call-to-gc`](./RULES_DESCRIPTIONS.md#call-to-gc)   | n/a    | Warns on explicit call to the garbage collector    |    no    |  no   |
-| [`duplicated-imports`](./RULES_DESCRIPTIONS.md#duplicated-imports) | n/a  | Looks for packages that are imported two or more times   |    no    |  no   |
-| [`import-shadowing`](./RULES_DESCRIPTIONS.md#import-shadowing)   | n/a    | Spots identifiers that shadow an import    |    no    |  no   |
-| [`bare-return`](./RULES_DESCRIPTIONS.md#bare-return) | n/a  | Warns on bare returns   |    no    |  no   |
-| [`unused-receiver`](./RULES_DESCRIPTIONS.md#unused-receiver)   | n/a    | Suggests to rename or remove unused method receivers    |    no    |  no   |
-| [`unhandled-error`](./RULES_DESCRIPTIONS.md#unhandled-error)   | []string   | Warns on unhandled errors returned by function calls    |    no    |  yes   |
-| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity)          |  int (defaults to 7) | Sets restriction for maximum Cognitive complexity.              |    no    |  no   |
-| [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int)          |  n/a   | Warns on suspicious casts from int to string            |    no    |  yes   |
+| [`range-val-in-closure`](./RULES_DESCRIPTIONS.md#range-val-in-closure)|  n/a   | Warns if range value is used in a closure dispatched as goroutine|    no    |  no   |
+| [`range`](./RULES_DESCRIPTIONS.md#range)               |  n/a   | Prevents redundant variables when iterating over a collection.   |   yes    |  no   |
+| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming)     |  map (optional)   | Conventions around the naming of receivers.                      |   yes    |  no   |
+| [`redefines-builtin-id`](./RULES_DESCRIPTIONS.md#redefines-builtin-id)|  n/a   | Warns on redefinitions of builtin identifiers                    |    no    |  no   |
+| [`redundant-build-tag`](./RULES_DESCRIPTIONS.md#redundant-build-tag) | n/a   | Warns about redundant `// +build` comment lines |   no    |  no   |
+| [`redundant-import-alias`](./RULES_DESCRIPTIONS.md#redundant-import-alias)          |  n/a   |  Warns on import aliases matching the imported package name |    no    |  no   |
+| [`redundant-test-main-exit`](./RULES_DESCRIPTIONS.md#redundant-test-main-exit) |  n/a   | Suggests removing `Exit` call in `TestMain` function for test files |    no    |  no   |
 | [`string-format`](./RULES_DESCRIPTIONS.md#string-format)          |  map   | Warns on specific string literals that fail one or more user-configured regular expressions            |    no    |  no   |
-| [`early-return`](./RULES_DESCRIPTIONS.md#early-return)          | []string   | Spots if-then-else statements where the predicate may be inverted to reduce nesting |    no    |  no   |
+| [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int)          |  n/a   | Warns on suspicious casts from int to string            |    no    |  yes   |
+| [`struct-tag`](./RULES_DESCRIPTIONS.md#struct-tag)          |  []string   | Checks common struct tags like `json`, `xml`, `yaml`               |    no    |  no   |
+| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else)    |  []string   | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) |    no    |  no   |
+| [`time-equal`](./RULES_DESCRIPTIONS.md#time-equal)         |  n/a   | Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.                 |   no    |  yes  |
+| [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming)         |  n/a   | Conventions around the naming of time variables.                 |   yes    |  yes  |
+| [`unchecked-type-assertions`](./RULES_DESCRIPTIONS.md#unchecked-type-assertions)         |  n/a   | Disallows type assertions without checking the result.                 |   no    |  yes  |
 | [`unconditional-recursion`](./RULES_DESCRIPTIONS.md#unconditional-recursion)          |  n/a   | Warns on function calls that will lead to (direct) infinite recursion |    no    |  no   |
-| [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches)          |  n/a   | Spots if-then-else statements with identical `then` and `else` branches       |    no    |  no   |
-| [`defer`](./RULES_DESCRIPTIONS.md#defer)          |  map   |  Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)       |    no    |  no   |
 | [`unexported-naming`](./RULES_DESCRIPTIONS.md#unexported-naming)          |  n/a   |  Warns on wrongly named un-exported symbols       |    no    |  no   |
-| [`function-length`](./RULES_DESCRIPTIONS.md#function-length)          |  int, int (defaults to 50 statements, 75 lines)   |  Warns on functions exceeding the statements or lines max |    no    |  no   |
-| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs)          |  n/a   |  Warns on structs within structs |    no    |  no   |
-| [`useless-break`](./RULES_DESCRIPTIONS.md#useless-break)          |  n/a   |  Warns on useless `break` statements in case clauses |    no    |  no   |
-| [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters)          |  []string (defaults to []string{})   |  Checks banned characters in identifiers |    no    |  no   |
-| [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order)          |  n/a   |  Checks inefficient conditional expressions |    no    |  no   |
+| [`unexported-return`](./RULES_DESCRIPTIONS.md#unexported-return)   |  n/a   | Warns when a public return is from unexported type.              |   yes    |  yes  |
+| [`unhandled-error`](./RULES_DESCRIPTIONS.md#unhandled-error)   | []string   | Warns on unhandled errors returned by function calls    |    no    |  yes   |
+| [`unnecessary-stmt`](./RULES_DESCRIPTIONS.md#unnecessary-stmt)    |  n/a   | Suggests removing or simplifying unnecessary statements          |    no    |  no   |
+| [`unreachable-code`](./RULES_DESCRIPTIONS.md#unreachable-code)    |  n/a   | Warns on unreachable code                                        |    no    |  no   |
+| [`unused-parameter`](./RULES_DESCRIPTIONS.md#unused-parameter)    |  n/a   | Suggests to rename or remove unused function parameters          |    no    |  no   |
+| [`unused-receiver`](./RULES_DESCRIPTIONS.md#unused-receiver)   | n/a    | Suggests to rename or remove unused method receivers    |    no    |  no   |
 | [`use-any`](./RULES_DESCRIPTIONS.md#use-any)          |  n/a   |  Proposes to replace `interface{}` with its alias `any` |    no    |  no   |
-| [`datarace`](./RULES_DESCRIPTIONS.md#datarace)          |  n/a   |  Spots potential dataraces |    no    |  no   |
-| [`comment-spacings`](./RULES_DESCRIPTIONS.md#comment-spacings)          |  []string   |  Warns on malformed comments |    no    |  no   |
-| [`redundant-import-alias`](./RULES_DESCRIPTIONS.md#redundant-import-alias)          |  n/a   |  Warns on import aliases matching the imported package name |    no    |  no   |
-| [`import-alias-naming`](./RULES_DESCRIPTIONS.md#import-alias-naming)         | string or map[string]string (defaults to allow regex pattern ^[a-z][a-z0-9]{0,}$) | Conventions around the naming of import aliases.                              |    no    |  no   |
-| [`enforce-map-style`](./RULES_DESCRIPTIONS.md#enforce-map-style) |  string (defaults to "any")  |  Enforces consistent usage of `make(map[type]type)` or `map[type]type{}` for map initialization. Does not affect `make(map[type]type, size)` constructions. |    no    |  no   |
-| [`enforce-slice-style`](./RULES_DESCRIPTIONS.md#enforce-slice-style) |  string (defaults to "any")  |  Enforces consistent usage of `make([]type, 0)` or `[]type{}` for slice initialization. Does not affect `make(map[type]type, non_zero_len, or_non_zero_cap)` constructions. |    no    |  no   |
-| [`enforce-repeated-arg-type-style`](./RULES_DESCRIPTIONS.md#enforce-repeated-arg-type-style) |  string (defaults to "any")  |  Enforces consistent style for repeated argument and/or return value types. |    no    |  no   |
-| [`max-control-nesting`](./RULES_DESCRIPTIONS.md#max-control-nesting) |  int (defaults to 5)  | Sets restriction for maximum nesting of control structures. |    no    |  no   |
-| [`comments-density`](./RULES_DESCRIPTIONS.md#comments-density) |  int (defaults to 0)  | Enforces a minimum comment / code relation |    no    |  no   |
-| [`file-length-limit`](./RULES_DESCRIPTIONS.md#file-length-limit) | map (optional)| Enforces a maximum number of lines per file |    no    |  no   |
-| [`filename-format`](./RULES_DESCRIPTIONS.md#filename-format) | regular expression (optional) | Enforces the formatting of filenames |   no    |  no   |
-| [`redundant-build-tag`](./RULES_DESCRIPTIONS.md#redundant-build-tag) | n/a   | Warns about redundant `// +build` comment lines |   no    |  no   |
 | [`use-errors-new`](./RULES_DESCRIPTIONS.md#use-errors-new) | n/a   | Spots calls to `fmt.Errorf` that can be replaced by `errors.New` |   no    |  no   |
-| [`redundant-test-main-exit`](./RULES_DESCRIPTIONS.md#redundant-test-main-exit) |  n/a   | Suggests removing `Exit` call in `TestMain` function for test files |    no    |  no   |
+| [`useless-break`](./RULES_DESCRIPTIONS.md#useless-break)          |  n/a   |  Warns on useless `break` statements in case clauses |    no    |  no   |
+| [`var-declaration`](./RULES_DESCRIPTIONS.md#var-declaration)     |  n/a   | Reduces redundancies around variable declaration.                |   yes    |  yes  |
+| [`var-naming`](./RULES_DESCRIPTIONS.md#var-naming)          |  allowlist & blocklist of initialisms   | Naming rules.                                                    |   yes    |  no   |
+| [`waitgroup-by-value`](./RULES_DESCRIPTIONS.md#waitgroup-by-value)  |  n/a   | Warns on functions taking sync.WaitGroup as a by-value parameter |    no    |  no   |
 
 ## Configurable rules
 
diff --git a/go.mod b/go.mod
index ae5ad5d..ec8e398 100644
--- a/go.mod
+++ b/go.mod
@@ -14,8 +14,8 @@ require (
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/spf13/afero v1.14.0
 	golang.org/x/mod v0.24.0
-	golang.org/x/sync v0.12.0
-	golang.org/x/tools v0.31.0
+	golang.org/x/sync v0.13.0
+	golang.org/x/tools v0.32.0
 )
 
 require (
@@ -24,6 +24,6 @@ require (
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/stretchr/testify v1.10.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
+	golang.org/x/sys v0.32.0 // indirect
+	golang.org/x/text v0.24.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 192d8d8..3bb47cc 100644
--- a/go.sum
+++ b/go.sum
@@ -39,15 +39,15 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
 golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
+golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
-golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
-golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
+golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/rule/error_strings.go b/rule/error_strings.go
index a5c2454..ea295f8 100644
--- a/rule/error_strings.go
+++ b/rule/error_strings.go
@@ -178,21 +178,29 @@ func (lintErrorStrings) checkArg(expr *ast.CallExpr, arg int) (s *ast.BasicLit,
 func lintErrorString(s string) (isClean bool, conf float64) {
 	const basicConfidence = 0.8
 	const capConfidence = basicConfidence - 0.2
-	first, firstN := utf8.DecodeRuneInString(s)
+
 	last, _ := utf8.DecodeLastRuneInString(s)
 	if last == '.' || last == ':' || last == '!' || last == '\n' {
 		return false, basicConfidence
 	}
-	if unicode.IsUpper(first) {
-		// People use proper nouns and exported Go identifiers in error strings,
-		// so decrease the confidence of warnings for capitalization.
-		if len(s) <= firstN {
-			return false, capConfidence
+
+	first, firstN := utf8.DecodeRuneInString(s)
+	if !unicode.IsUpper(first) {
+		return true, 0
+	}
+
+	// People use proper nouns and exported Go identifiers in error strings,
+	// so decrease the confidence of warnings for capitalization.
+	for _, r := range s[firstN:] {
+		if unicode.IsSpace(r) {
+			break
 		}
-		// Flag strings starting with something that doesn't look like an initialism.
-		if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) {
-			return false, capConfidence
+
+		if unicode.IsUpper(r) || unicode.IsDigit(r) {
+			return true, 0 // accept words with more than 2 capital letters or digits (e.g. GitHub, URLs, I2000)
 		}
 	}
-	return true, 0
+
+	// Flag strings starting with something that doesn't look like an initialism.
+	return false, capConfidence
 }
diff --git a/rule/exported.go b/rule/exported.go
index 503e25a..2bcc458 100644
--- a/rule/exported.go
+++ b/rule/exported.go
@@ -19,7 +19,7 @@ type disabledChecks struct {
 	Method           bool
 	PrivateReceivers bool
 	PublicInterfaces bool
-	Stuttering       bool
+	RepetitiveNames  bool
 	Type             bool
 	Var              bool
 }
@@ -46,7 +46,7 @@ func (dc *disabledChecks) isDisabled(checkName string) bool {
 	case checkNamePublicInterfaces:
 		return dc.PublicInterfaces
 	case checkNameStuttering:
-		return dc.Stuttering
+		return dc.RepetitiveNames
 	case "type":
 		return dc.Type
 	default:
@@ -65,16 +65,16 @@ var commonMethods = map[string]bool{
 
 // ExportedRule lints naming and commenting conventions on exported symbols.
 type ExportedRule struct {
-	stuttersMsg    string
-	disabledChecks disabledChecks
+	isRepetitiveMsg string
+	disabledChecks  disabledChecks
 }
 
 // Configure validates the rule configuration, and configures the rule accordingly.
 //
-// Configuration implements the [lint.ConfigurableRule] interface.
+// Configure makes the rule implement the [lint.ConfigurableRule] interface.
 func (r *ExportedRule) Configure(arguments lint.Arguments) error {
 	r.disabledChecks = disabledChecks{PrivateReceivers: true, PublicInterfaces: true}
-	r.stuttersMsg = "stutters"
+	r.isRepetitiveMsg = "stutters"
 	for _, flag := range arguments {
 		switch flag := flag.(type) {
 		case string:
@@ -82,9 +82,9 @@ func (r *ExportedRule) Configure(arguments lint.Arguments) error {
 			case isRuleOption(flag, "checkPrivateReceivers"):
 				r.disabledChecks.PrivateReceivers = false
 			case isRuleOption(flag, "disableStutteringCheck"):
-				r.disabledChecks.Stuttering = true
+				r.disabledChecks.RepetitiveNames = true
 			case isRuleOption(flag, "sayRepetitiveInsteadOfStutters"):
-				r.stuttersMsg = "is repetitive"
+				r.isRepetitiveMsg = "is repetitive"
 			case isRuleOption(flag, "checkPublicInterface"):
 				r.disabledChecks.PublicInterfaces = false
 			case isRuleOption(flag, "disableChecksOnConstants"):
@@ -115,20 +115,17 @@ func (r *ExportedRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
 		return failures
 	}
 
-	fileAst := file.AST
-
 	walker := lintExported{
-		file:    file,
-		fileAst: fileAst,
+		file: file,
 		onFailure: func(failure lint.Failure) {
 			failures = append(failures, failure)
 		},
 		genDeclMissingComments: map[*ast.GenDecl]bool{},
-		stuttersMsg:            r.stuttersMsg,
+		isRepetitiveMsg:        r.isRepetitiveMsg,
 		disabledChecks:         r.disabledChecks,
 	}
 
-	ast.Walk(&walker, fileAst)
+	ast.Walk(&walker, file.AST)
 
 	return failures
 }
@@ -140,11 +137,10 @@ func (*ExportedRule) Name() string {
 
 type lintExported struct {
 	file                   *lint.File
-	fileAst                *ast.File
-	lastGen                *ast.GenDecl
+	lastGenDecl            *ast.GenDecl // the last visited general declaration in the AST
 	genDeclMissingComments map[*ast.GenDecl]bool
 	onFailure              func(lint.Failure)
-	stuttersMsg            string
+	isRepetitiveMsg        string
 	disabledChecks         disabledChecks
 }
 
@@ -155,26 +151,13 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
 
 	kind := "function"
 	name := fn.Name.Name
-	isMethod := fn.Recv != nil && len(fn.Recv.List) > 0
-	if isMethod {
-		kind = "method"
-		recv := typeparams.ReceiverType(fn)
-
-		if !ast.IsExported(recv) && w.disabledChecks.PrivateReceivers {
-			return
-		}
-
-		if commonMethods[name] {
+	if isMethod := fn.Recv != nil && len(fn.Recv.List) > 0; isMethod {
+		if !w.mustCheckMethod(fn) {
 			return
 		}
 
-		switch name {
-		case "Len", "Less", "Swap":
-			sortables := w.file.Pkg.Sortable()
-			if sortables[recv] {
-				return
-			}
-		}
+		kind = "method"
+		recv := typeparams.ReceiverType(fn)
 		name = recv + "." + name
 	}
 
@@ -182,42 +165,37 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
 		return
 	}
 
-	if !hasTextComment(fn.Doc) {
-		w.onFailure(lint.Failure{
-			Node:       fn,
-			Confidence: 1,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf("exported %s %s should have comment or be unexported", kind, name),
-		})
+	firstCommentLine := firstCommentLine(fn.Doc)
+
+	if firstCommentLine == "" {
+		w.addFailuref(fn, 1, lint.FailureCategoryComments,
+			"exported %s %s should have comment or be unexported", kind, name,
+		)
 		return
 	}
 
-	s := normalizeText(fn.Doc.Text())
 	prefix := fn.Name.Name + " "
-	if !strings.HasPrefix(s, prefix) {
-		w.onFailure(lint.Failure{
-			Node:       fn.Doc,
-			Confidence: 0.8,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
-		})
+	if !strings.HasPrefix(firstCommentLine, prefix) {
+		w.addFailuref(fn.Doc, 0.8, lint.FailureCategoryComments,
+			`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix,
+		)
 	}
 }
 
-func (w *lintExported) checkStutter(id *ast.Ident, thing string) {
-	if w.disabledChecks.Stuttering {
+func (w *lintExported) checkRepetitiveNames(id *ast.Ident, thing string) {
+	if w.disabledChecks.RepetitiveNames {
 		return
 	}
 
-	pkg, name := w.fileAst.Name.Name, id.Name
+	pkg, name := w.file.AST.Name.Name, id.Name
 	if !ast.IsExported(name) {
 		// unexported name
 		return
 	}
-	// A name stutters if the package name is a strict prefix
+	// A name is repetitive if the package name is a strict prefix
 	// and the next character of the name starts a new word.
 	if len(name) <= len(pkg) {
-		// name is too short to stutter.
+		// name is too short to be a repetition.
 		// This permits the name to be the same as the package name.
 		return
 	}
@@ -226,63 +204,74 @@ func (w *lintExported) checkStutter(id *ast.Ident, thing string) {
 	}
 	// We can assume the name is well-formed UTF-8.
 	// If the next rune after the package name is uppercase or an underscore
-	// the it's starting a new word and thus this name stutters.
+	// the it's starting a new word and thus this name is repetitive.
 	rem := name[len(pkg):]
 	if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) {
-		w.onFailure(lint.Failure{
-			Node:       id,
-			Confidence: 0.8,
-			Category:   lint.FailureCategoryNaming,
-			Failure:    fmt.Sprintf("%s name will be used as %s.%s by other packages, and that %s; consider calling this %s", thing, pkg, name, w.stuttersMsg, rem),
-		})
+		w.addFailuref(id, 0.8, lint.FailureCategoryNaming,
+			"%s name will be used as %s.%s by other packages, and that %s; consider calling this %s", thing, pkg, name, w.isRepetitiveMsg, rem,
+		)
 	}
 }
 
-func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
+var articles = [...]string{"A", "An", "The", "This"}
+
+func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup, firstCommentLine string) {
 	if w.disabledChecks.isDisabled("type") {
 		return
 	}
 
-	if !ast.IsExported(t.Name.Name) {
+	typeName := t.Name.Name
+
+	if !ast.IsExported(typeName) {
 		return
 	}
 
-	if !hasTextComment(doc) {
-		w.onFailure(lint.Failure{
-			Node:       t,
-			Confidence: 1,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf("exported type %v should have comment or be unexported", t.Name),
-		})
+	if firstCommentLine == "" {
+		w.addFailuref(t, 1, lint.FailureCategoryComments,
+			"exported type %v should have comment or be unexported", t.Name,
+		)
 		return
 	}
 
-	s := normalizeText(doc.Text())
-	articles := [...]string{"A", "An", "The", "This"}
 	for _, a := range articles {
-		if t.Name.Name == a {
+		if typeName == a {
 			continue
 		}
 		var found bool
-		if s, found = strings.CutPrefix(s, a+" "); found {
+		if firstCommentLine, found = strings.CutPrefix(firstCommentLine, a+" "); found {
 			break
 		}
 	}
 
 	// if comment starts with name of type and has some text after - it's ok
-	expectedPrefix := t.Name.Name + " "
-	if strings.HasPrefix(s, expectedPrefix) {
+	expectedPrefix := typeName + " "
+	if strings.HasPrefix(firstCommentLine, expectedPrefix) {
 		return
 	}
 
-	w.onFailure(lint.Failure{
-		Node:       doc,
-		Confidence: 1,
-		Category:   lint.FailureCategoryComments,
-		Failure:    fmt.Sprintf(`comment on exported type %v should be of the form "%s..." (with optional leading article)`, t.Name, expectedPrefix),
-	})
+	w.addFailuref(doc, 1, lint.FailureCategoryComments,
+		`comment on exported type %v should be of the form "%s..." (with optional leading article)`, t.Name, expectedPrefix,
+	)
 }
 
+// checkValueNames returns true if names check, false otherwise
+func (w *lintExported) checkValueNames(names []*ast.Ident, nodeToBlame ast.Node, kind string) bool {
+	// Check that none are exported except for the first.
+	if len(names) < 2 {
+		return true // nothing to check
+	}
+
+	for _, n := range names[1:] {
+		if ast.IsExported(n.Name) {
+			w.addFailuref(nodeToBlame, 1, lint.FailureCategoryComments,
+				"exported %s %s should have its own declaration", kind, n.Name,
+			)
+			return false
+		}
+	}
+
+	return true
+}
 func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) {
 	kind := "var"
 	if gd.Tok == token.CONST {
@@ -293,19 +282,8 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
 		return
 	}
 
-	if len(vs.Names) > 1 {
-		// Check that none are exported except for the first.
-		for _, n := range vs.Names[1:] {
-			if ast.IsExported(n.Name) {
-				w.onFailure(lint.Failure{
-					Category:   lint.FailureCategoryComments,
-					Confidence: 1,
-					Failure:    fmt.Sprintf("exported %s %s should have its own declaration", kind, n.Name),
-					Node:       vs,
-				})
-				return
-			}
-		}
+	if !w.checkValueNames(vs.Names, vs, kind) {
+		return
 	}
 
 	// Only one name.
@@ -314,7 +292,9 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
 		return
 	}
 
-	if !hasTextComment(vs.Doc) && !hasTextComment(gd.Doc) {
+	vsFirstCommentLine := firstCommentLine(vs.Doc)
+	gdFirstCommentLine := firstCommentLine(gd.Doc)
+	if vsFirstCommentLine == "" && gdFirstCommentLine == "" {
 		if genDeclMissingComments[gd] {
 			return
 		}
@@ -322,90 +302,95 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
 		if kind == "const" && gd.Lparen.IsValid() {
 			block = " (or a comment on this block)"
 		}
-		w.onFailure(lint.Failure{
-			Confidence: 1,
-			Node:       vs,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf("exported %s %s should have comment%s or be unexported", kind, name, block),
-		})
+		w.addFailuref(vs, 1, lint.FailureCategoryComments,
+			"exported %s %s should have comment%s or be unexported", kind, name, block,
+		)
 		genDeclMissingComments[gd] = true
 		return
 	}
+
 	// If this GenDecl has parens and a comment, we don't check its comment form.
-	if hasTextComment(gd.Doc) && gd.Lparen.IsValid() {
+	if gdFirstCommentLine != "" && gd.Lparen.IsValid() {
 		return
 	}
+
 	// The relevant text to check will be on either vs.Doc or gd.Doc.
 	// Use vs.Doc preferentially.
 	var doc *ast.CommentGroup
 	switch {
-	case hasTextComment(vs.Doc):
+	case vsFirstCommentLine != "":
 		doc = vs.Doc
-	case hasTextComment(vs.Comment) && !hasTextComment(gd.Doc):
+	case vsFirstCommentLine != "" && gdFirstCommentLine == "":
 		doc = vs.Comment
 	default:
 		doc = gd.Doc
 	}
 
 	prefix := name + " "
-	s := normalizeText(doc.Text())
-	if !strings.HasPrefix(s, prefix) {
-		w.onFailure(lint.Failure{
-			Confidence: 1,
-			Node:       doc,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
-		})
+	if !strings.HasPrefix(firstCommentLine(doc), prefix) {
+		w.addFailuref(doc, 1, lint.FailureCategoryComments,
+			`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix,
+		)
 	}
 }
 
-// hasTextComment returns true if the comment contains a text comment
-// e.g. //go:embed foo.txt a directive comment, not a text comment
-// e.g. //nolint:whatever is a directive comment, not a text comment
-func hasTextComment(comment *ast.CommentGroup) bool {
+// firstCommentLine yields the first line of interest in comment group or "" if there is nothing of interest.
+// An "interesting line" is a comment line that is neither a directive (e.g. //go:...) or a deprecation comment
+// (lines from the first line with a prefix // Deprecated: to the end of the comment group)
+// Empty or spaces-only lines are discarded.
+func firstCommentLine(comment *ast.CommentGroup) (result string) {
 	if comment == nil {
-		return false
+		return ""
 	}
 
-	// a comment could be directive and not a text comment
-	text := comment.Text()
-	return text != ""
-}
+	commentWithoutDirectives := comment.Text() // removes directives from the comment block
+	lines := strings.Split(commentWithoutDirectives, "\n")
+	for _, line := range lines {
+		line := strings.TrimSpace(line)
+		if line == "" {
+			continue // ignore empty lines
+		}
+		if strings.HasPrefix(line, "Deprecated: ") {
+			break // ignore deprecation comment line and the subsequent lines of the original comment
+		}
 
-// normalizeText is a helper function that normalizes comment strings by:
-// * removing one leading space
-//
-// This function is needed because ast.CommentGroup.Text() does not handle //-style and /*-style comments uniformly
-func normalizeText(t string) string {
-	return strings.TrimSpace(t)
+		result = line
+		break // first non-directive/non-empty/non-deprecation comment line found
+	}
+
+	return result
 }
 
 func (w *lintExported) Visit(n ast.Node) ast.Visitor {
 	switch v := n.(type) {
 	case *ast.GenDecl:
-		if v.Tok == token.IMPORT {
+		switch v.Tok {
+		case token.IMPORT:
 			return nil
+		case token.CONST, token.TYPE, token.VAR:
+			w.lastGenDecl = v
 		}
-		// token.CONST, token.TYPE or token.VAR
-		w.lastGen = v
 		return w
 	case *ast.FuncDecl:
 		w.lintFuncDoc(v)
 		if v.Recv == nil {
-			// Only check for stutter on functions, not methods.
+			// Only check for repetitive names on functions, not methods.
 			// Method names are not used package-qualified.
-			w.checkStutter(v.Name, "func")
+			w.checkRepetitiveNames(v.Name, "func")
 		}
 		// Don't proceed inside funcs.
 		return nil
 	case *ast.TypeSpec:
 		// inside a GenDecl, which usually has the doc
 		doc := v.Doc
-		if !hasTextComment(doc) {
-			doc = w.lastGen.Doc
+
+		fcl := firstCommentLine(doc)
+		if fcl == "" {
+			doc = w.lastGenDecl.Doc
+			fcl = firstCommentLine(doc)
 		}
-		w.lintTypeDoc(v, doc)
-		w.checkStutter(v.Name, "type")
+		w.lintTypeDoc(v, doc, fcl)
+		w.checkRepetitiveNames(v.Name, "type")
 
 		if !w.disabledChecks.PublicInterfaces {
 			if iface, ok := v.Type.(*ast.InterfaceType); ok {
@@ -417,7 +402,7 @@ func (w *lintExported) Visit(n ast.Node) ast.Visitor {
 
 		return nil
 	case *ast.ValueSpec:
-		w.lintValueSpecDoc(v, w.lastGen, w.genDeclMissingComments)
+		w.lintValueSpecDoc(v, w.lastGenDecl, w.genDeclMissingComments)
 		return nil
 	}
 	return w
@@ -437,23 +422,51 @@ func (w *lintExported) lintInterfaceMethod(typeName string, m *ast.Field) {
 		return
 	}
 	name := m.Names[0].Name
-	if !hasTextComment(m.Doc) {
-		w.onFailure(lint.Failure{
-			Node:       m,
-			Confidence: 1,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf("public interface method %s.%s should be commented", typeName, name),
-		})
+	firstCommentLine := firstCommentLine(m.Doc)
+	if firstCommentLine == "" {
+		w.addFailuref(m, 1, lint.FailureCategoryComments,
+			"public interface method %s.%s should be commented", typeName, name,
+		)
 		return
 	}
-	s := normalizeText(m.Doc.Text())
+
 	expectedPrefix := m.Names[0].Name + " "
-	if !strings.HasPrefix(s, expectedPrefix) {
-		w.onFailure(lint.Failure{
-			Node:       m.Doc,
-			Confidence: 0.8,
-			Category:   lint.FailureCategoryComments,
-			Failure:    fmt.Sprintf(`comment on exported interface method %s.%s should be of the form "%s..."`, typeName, name, expectedPrefix),
-		})
+	if !strings.HasPrefix(firstCommentLine, expectedPrefix) {
+		w.addFailuref(m.Doc, 0.8, lint.FailureCategoryComments,
+			`comment on exported interface method %s.%s should be of the form "%s..."`, typeName, name, expectedPrefix,
+		)
+	}
+}
+
+// mustCheckMethod returns true if the method must be checked by this rule, false otherwise
+func (w *lintExported) mustCheckMethod(fn *ast.FuncDecl) bool {
+	recv := typeparams.ReceiverType(fn)
+
+	if !ast.IsExported(recv) && w.disabledChecks.PrivateReceivers {
+		return false
 	}
+
+	name := fn.Name.Name
+	if commonMethods[name] {
+		return false
+	}
+
+	switch name {
+	case "Len", "Less", "Swap":
+		sortables := w.file.Pkg.Sortable()
+		if sortables[recv] {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (w *lintExported) addFailuref(node ast.Node, confidence float64, category lint.FailureCategory, message string, args ...any) {
+	w.onFailure(lint.Failure{
+		Node:       node,
+		Confidence: confidence,
+		Category:   category,
+		Failure:    fmt.Sprintf(message, args...),
+	})
 }
diff --git a/rule/struct_tag.go b/rule/struct_tag.go
index e6a69e6..5cfa31c 100644
--- a/rule/struct_tag.go
+++ b/rule/struct_tag.go
@@ -13,7 +13,62 @@ import (
 
 // StructTagRule lints struct tags.
 type StructTagRule struct {
-	userDefined map[string][]string // map: key -> []option
+	userDefined map[tagKey][]string // map: key -> []option
+}
+
+type tagKey string
+
+const (
+	keyASN1         tagKey = "asn1"
+	keyBSON         tagKey = "bson"
+	keyDatastore    tagKey = "datastore"
+	keyDefault      tagKey = "default"
+	keyJSON         tagKey = "json"
+	keyMapstructure tagKey = "mapstructure"
+	keyProperties   tagKey = "properties"
+	keyProtobuf     tagKey = "protobuf"
+	keyRequired     tagKey = "required"
+	keyTOML         tagKey = "toml"
+	keyURL          tagKey = "url"
+	keyValidate     tagKey = "validate"
+	keyXML          tagKey = "xml"
+	keyYAML         tagKey = "yaml"
+)
+
+type tagChecker func(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool)
+
+// populate tag checkers map
+var tagCheckers = map[tagKey]tagChecker{
+	keyASN1:         checkASN1Tag,
+	keyBSON:         checkBSONTag,
+	keyDatastore:    checkDatastoreTag,
+	keyDefault:      checkDefaultTag,
+	keyJSON:         checkJSONTag,
+	keyMapstructure: checkMapstructureTag,
+	keyProperties:   checkPropertiesTag,
+	keyProtobuf:     checkProtobufTag,
+	keyRequired:     checkRequiredTag,
+	keyTOML:         checkTOMLTag,
+	keyURL:          checkURLTag,
+	keyValidate:     checkValidateTag,
+	keyXML:          checkXMLTag,
+	keyYAML:         checkYAMLTag,
+}
+
+type checkContext struct {
+	userDefined    map[tagKey][]string // map: key -> []option
+	usedTagNbr     map[int]bool        // list of used tag numbers
+	usedTagName    map[string]bool     // list of used tag keys
+	isAtLeastGo124 bool
+}
+
+func (checkCtx checkContext) isUserDefined(key tagKey, opt string) bool {
+	if checkCtx.userDefined == nil {
+		return false
+	}
+
+	options := checkCtx.userDefined[key]
+	return slices.Contains(options, opt)
 }
 
 // Configure validates the rule configuration, and configures the rule accordingly.
@@ -28,7 +83,8 @@ func (r *StructTagRule) Configure(arguments lint.Arguments) error {
 	if err != nil {
 		return err
 	}
-	r.userDefined = make(map[string][]string, len(arguments))
+
+	r.userDefined = make(map[tagKey][]string, len(arguments))
 	for _, arg := range arguments {
 		item, ok := arg.(string)
 		if !ok {
@@ -38,12 +94,13 @@ func (r *StructTagRule) Configure(arguments lint.Arguments) error {
 		if len(parts) < 2 {
 			return fmt.Errorf("invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item)
 		}
-		key := strings.TrimSpace(parts[0])
+		key := tagKey(strings.TrimSpace(parts[0]))
 		for i := 1; i < len(parts); i++ {
 			option := strings.TrimSpace(parts[i])
 			r.userDefined[key] = append(r.userDefined[key], option)
 		}
 	}
+
 	return nil
 }
 
@@ -58,6 +115,7 @@ func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
 		onFailure:      onFailure,
 		userDefined:    r.userDefined,
 		isAtLeastGo124: file.Pkg.IsAtLeastGoVersion(lint.Go124),
+		tagCheckers:    tagCheckers,
 	}
 
 	ast.Walk(w, file.AST)
@@ -72,10 +130,9 @@ func (*StructTagRule) Name() string {
 
 type lintStructTagRule struct {
 	onFailure      func(lint.Failure)
-	userDefined    map[string][]string // map: key -> []option
-	usedTagNbr     map[int]bool        // list of used tag numbers
-	usedTagName    map[string]bool     // list of used tag keys
+	userDefined    map[tagKey][]string // map: key -> []option
 	isAtLeastGo124 bool
+	tagCheckers    map[tagKey]tagChecker
 }
 
 func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
@@ -86,11 +143,16 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
 			return nil // skip empty structs
 		}
 
-		w.usedTagNbr = map[int]bool{}
-		w.usedTagName = map[string]bool{}
+		checkCtx := &checkContext{
+			userDefined:    w.userDefined,
+			usedTagNbr:     map[int]bool{},
+			usedTagName:    map[string]bool{},
+			isAtLeastGo124: w.isAtLeastGo124,
+		}
+
 		for _, f := range n.Fields.List {
 			if f.Tag != nil {
-				w.checkTaggedField(f)
+				w.checkTaggedField(checkCtx, f)
 			}
 		}
 	}
@@ -98,30 +160,44 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
 	return w
 }
 
-const (
-	keyASN1         = "asn1"
-	keyBSON         = "bson"
-	keyDatastore    = "datastore"
-	keyDefault      = "default"
-	keyJSON         = "json"
-	keyMapstructure = "mapstructure"
-	keyProperties   = "properties"
-	keyProtobuf     = "protobuf"
-	keyRequired     = "required"
-	keyTOML         = "toml"
-	keyURL          = "url"
-	keyValidate     = "validate"
-	keyXML          = "xml"
-	keyYAML         = "yaml"
-)
+// checkTaggedField checks the tag of the given field.
+// precondition: the field has a tag
+func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, f *ast.Field) {
+	if len(f.Names) > 0 && !f.Names[0].IsExported() {
+		w.addFailuref(f, "tag on not-exported field %s", f.Names[0].Name)
+	}
 
-func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool) {
+	tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
+	if err != nil || tags == nil {
+		w.addFailuref(f.Tag, "malformed tag")
+		return
+	}
+
+	for _, tag := range tags.Tags() {
+		if msg, ok := w.checkTagNameIfNeed(checkCtx, tag); !ok {
+			w.addFailureWithTagKey(f.Tag, msg, tag.Key)
+		}
+
+		checker, ok := w.tagCheckers[tagKey(tag.Key)]
+		if !ok {
+			continue // we don't have a checker for the tag
+		}
+
+		msg, ok := checker(checkCtx, tag, f.Type)
+		if !ok {
+			w.addFailureWithTagKey(f.Tag, msg, tag.Key)
+		}
+	}
+}
+
+func (w lintStructTagRule) checkTagNameIfNeed(checkCtx *checkContext, tag *structtag.Tag) (message string, succeeded bool) {
 	isUnnamedTag := tag.Name == "" || tag.Name == "-"
 	if isUnnamedTag {
 		return "", true
 	}
 
-	switch tag.Key {
+	key := tagKey(tag.Key)
+	switch key {
 	case keyBSON, keyJSON, keyXML, keyYAML, keyProtobuf:
 	default:
 		return "", true
@@ -134,22 +210,23 @@ func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool)
 
 	// We concat the key and name as the mapping key here
 	// to allow the same tag name in different tag type.
-	key := tag.Key + ":" + tagName
-	if _, ok := w.usedTagName[key]; ok {
-		return fmt.Sprintf("duplicate tag name: '%s'", tagName), false
+	mapKey := tag.Key + ":" + tagName
+	if _, ok := checkCtx.usedTagName[mapKey]; ok {
+		return fmt.Sprintf("duplicated tag name %q", tagName), false
 	}
 
-	w.usedTagName[key] = true
+	checkCtx.usedTagName[mapKey] = true
 
 	return "", true
 }
 
 func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
-	switch tag.Key {
+	key := tagKey(tag.Key)
+	switch key {
 	case keyProtobuf:
 		for _, option := range tag.Options {
-			if tagName, found := strings.CutPrefix(option, "name="); found {
-				return tagName
+			if tagKey, found := strings.CutPrefix(option, "name="); found {
+				return tagKey
 			}
 		}
 		return "" // protobuf tag lacks 'name' option
@@ -158,286 +235,289 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
 	}
 }
 
-// checkTaggedField checks the tag of the given field.
-// precondition: the field has a tag
-func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
-	if len(f.Names) > 0 && !f.Names[0].IsExported() {
-		w.addFailure(f, "tag on not-exported field "+f.Names[0].Name)
+func checkASN1Tag(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
+	checkList := append(tag.Options, tag.Name)
+	for _, opt := range checkList {
+		switch opt {
+		case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
+			// do nothing
+		default:
+			msg, ok := checkCompoundANS1Option(checkCtx, opt, fieldType)
+			if !ok {
+				return msg, false
+			}
+		}
 	}
 
-	tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
-	if err != nil || tags == nil {
-		w.addFailure(f.Tag, "malformed tag")
-		return
-	}
+	return "", true
+}
 
-	for _, tag := range tags.Tags() {
-		if msg, ok := w.checkTagNameIfNeed(tag); !ok {
-			w.addFailure(f.Tag, msg)
+func checkCompoundANS1Option(checkCtx *checkContext, opt string, fieldType ast.Expr) (message string, succeeded bool) {
+	key, value, _ := strings.Cut(opt, ":")
+	switch key {
+	case "tag":
+		number, err := strconv.Atoi(value)
+		if err != nil {
+			return fmt.Sprintf("tag must be a number but is %q", value), false
 		}
-
-		switch key := tag.Key; key {
-		case keyASN1:
-			msg, ok := w.checkASN1Tag(f.Type, tag)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyBSON:
-			msg, ok := w.checkBSONTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyDatastore:
-			msg, ok := w.checkDatastoreTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyDefault:
-			if !w.typeValueMatch(f.Type, tag.Name) {
-				w.addFailure(f.Tag, "field's type and default value's type mismatch")
-			}
-		case keyJSON:
-			msg, ok := w.checkJSONTag(tag.Name, tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyMapstructure:
-			msg, ok := w.checkMapstructureTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyProperties:
-			msg, ok := w.checkPropertiesTag(f.Type, tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyProtobuf:
-			msg, ok := w.checkProtobufTag(tag)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyRequired:
-			if tag.Name != "true" && tag.Name != "false" {
-				w.addFailure(f.Tag, "required should be 'true' or 'false'")
-			}
-		case keyTOML:
-			msg, ok := w.checkTOMLTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyURL:
-			msg, ok := w.checkURLTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyValidate:
-			opts := append([]string{tag.Name}, tag.Options...)
-			msg, ok := w.checkValidateTag(opts)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyXML:
-			msg, ok := w.checkXMLTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		case keyYAML:
-			msg, ok := w.checkYAMLTag(tag.Options)
-			if !ok {
-				w.addFailure(f.Tag, msg)
-			}
-		default:
-			// unknown key
+		if checkCtx.usedTagNbr[number] {
+			return fmt.Sprintf(msgDuplicatedTagNumber, number), false
+		}
+		checkCtx.usedTagNbr[number] = true
+	case "default":
+		if !typeValueMatch(fieldType, value) {
+			return msgTypeMismatch, false
+		}
+	default:
+		if !checkCtx.isUserDefined(keyASN1, opt) {
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
+	return "", true
 }
 
-func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string, bool) {
-	checkList := append(tag.Options, tag.Name)
-	for _, opt := range checkList {
+func checkDatastoreTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
-		case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
-
+		case "flatten", "noindex", "omitempty":
 		default:
-			if strings.HasPrefix(opt, "tag:") {
-				parts := strings.Split(opt, ":")
-				tagNumber := parts[1]
-				number, err := strconv.Atoi(tagNumber)
-				if err != nil {
-					return fmt.Sprintf("ASN1 tag must be a number, got '%s'", tagNumber), false
-				}
-				if w.usedTagNbr[number] {
-					return fmt.Sprintf("duplicated tag number %v", number), false
-				}
-				w.usedTagNbr[number] = true
-
-				continue
-			}
-
-			if strings.HasPrefix(opt, "default:") {
-				parts := strings.Split(opt, ":")
-				if len(parts) < 2 {
-					return "malformed default for ASN1 tag", false
-				}
-				if !w.typeValueMatch(t, parts[1]) {
-					return "field's type and default value's type mismatch", false
-				}
-
+			if checkCtx.isUserDefined(keyDatastore, opt) {
 				continue
 			}
+			return fmt.Sprintf(msgUnknownOption, opt), false
+		}
+	}
 
-			if w.isUserDefined(keyASN1, opt) {
-				continue
-			}
+	return "", true
+}
 
-			return fmt.Sprintf("unknown option '%s' in ASN1 tag", opt), false
-		}
+func checkDefaultTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
+	if !typeValueMatch(fieldType, tag.Name) {
+		return msgTypeMismatch, false
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkBSONTag(options []string) (string, bool) {
-	for _, opt := range options {
+func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
 		case "inline", "minsize", "omitempty":
 		default:
-			if w.isUserDefined(keyBSON, opt) {
+			if checkCtx.isUserDefined(keyBSON, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in BSON tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkJSONTag(name string, options []string) (string, bool) {
-	for _, opt := range options {
+func checkJSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
 		case "omitempty", "string":
 		case "":
 			// special case for JSON key "-"
-			if name != "-" {
-				return "option can not be empty in JSON tag", false
+			if tag.Name != "-" {
+				return "option can not be empty", false
 			}
 		case "omitzero":
-			if w.isAtLeastGo124 {
+			if checkCtx.isAtLeastGo124 {
 				continue
 			}
-			fallthrough
+			return `prior Go 1.24, option "omitzero" is unsupported`, false
 		default:
-			if w.isUserDefined(keyJSON, opt) {
+			if checkCtx.isUserDefined(keyJSON, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in JSON tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkXMLTag(options []string) (string, bool) {
-	for _, opt := range options {
+func checkMapstructureTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
-		case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
+		case "omitempty", "reminder", "squash":
 		default:
-			if w.isUserDefined(keyXML, opt) {
+			if checkCtx.isUserDefined(keyMapstructure, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in XML tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) {
+func checkPropertiesTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
+	options := tag.Options
+	if len(options) == 0 {
+		return "", true
+	}
+
+	seenOptions := map[string]bool{}
 	for _, opt := range options {
-		switch opt {
-		case "flow", "inline", "omitempty":
-		default:
-			if w.isUserDefined(keyYAML, opt) {
-				continue
-			}
-			return fmt.Sprintf("unknown option '%s' in YAML tag", opt), false
+		msg, ok := fmt.Sprintf("unknown or malformed option %q", opt), false
+		if key, value, found := strings.Cut(opt, "="); found {
+			msg, ok = checkCompoundPropertiesOption(key, value, fieldType, seenOptions)
+		}
+
+		if !ok {
+			return msg, false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkURLTag(options []string) (string, bool) {
-	var delimiter = ""
+func checkCompoundPropertiesOption(key, value string, fieldType ast.Expr, seenOptions map[string]bool) (message string, succeeded bool) {
+	if _, ok := seenOptions[key]; ok {
+		return fmt.Sprintf(msgDuplicatedOption, key), false
+	}
+	seenOptions[key] = true
+
+	if strings.TrimSpace(value) == "" {
+		return fmt.Sprintf("option %q not of the form %s=value", key, key), false
+	}
+
+	switch key {
+	case "default":
+		if !typeValueMatch(fieldType, value) {
+			return msgTypeMismatch, false
+		}
+	case "layout":
+		if gofmt(fieldType) != "time.Time" {
+			return "layout option is only applicable to fields of type time.Time", false
+		}
+	}
+
+	return "", true
+}
+
+func checkProtobufTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	// check name
+	switch tag.Name {
+	case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64":
+		// do nothing
+	default:
+		return fmt.Sprintf("invalid tag name %q", tag.Name), false
+	}
+
+	return checkProtobufOptions(checkCtx, tag.Options)
+}
+
+func checkProtobufOptions(checkCtx *checkContext, options []string) (message string, succeeded bool) {
+	seenOptions := map[string]bool{}
+	hasName := false
 	for _, opt := range options {
-		switch opt {
-		case "int", "omitempty", "numbered", "brackets":
-		case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time
-		case "comma", "semicolon", "space":
-			if delimiter == "" {
-				delimiter = opt
-				continue
+		opt := strings.Split(opt, "=")[0]
+
+		if number, err := strconv.Atoi(opt); err == nil {
+			_, alreadySeen := checkCtx.usedTagNbr[number]
+			if alreadySeen {
+				return fmt.Sprintf(msgDuplicatedTagNumber, number), false
 			}
-			return fmt.Sprintf("can not set both '%s' and '%s' as delimiters in URL tag", opt, delimiter), false
+			checkCtx.usedTagNbr[number] = true
+			continue // option is an integer
+		}
+
+		switch opt {
+		case "json", "opt", "proto3", "rep", "req":
+			// do nothing
+		case "name":
+			hasName = true
 		default:
-			if w.isUserDefined(keyURL, opt) {
+			if checkCtx.isUserDefined(keyProtobuf, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in URL tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
+
+		_, alreadySeen := seenOptions[opt]
+		if alreadySeen {
+			return fmt.Sprintf(msgDuplicatedOption, opt), false
+		}
+		seenOptions[opt] = true
+	}
+
+	if !hasName {
+		return `mandatory option "name" not found`, false
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkDatastoreTag(options []string) (string, bool) {
-	for _, opt := range options {
+func checkRequiredTag(_ *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	switch tag.Name {
+	case "true", "false":
+		return "", true
+	default:
+		return `required should be "true" or "false"`, false
+	}
+}
+
+func checkTOMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
-		case "flatten", "noindex", "omitempty":
+		case "omitempty":
 		default:
-			if w.isUserDefined(keyDatastore, opt) {
+			if checkCtx.isUserDefined(keyTOML, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in Datastore tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkMapstructureTag(options []string) (string, bool) {
-	for _, opt := range options {
+func checkURLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	var delimiter = ""
+	for _, opt := range tag.Options {
 		switch opt {
-		case "omitempty", "reminder", "squash":
+		case "int", "omitempty", "numbered", "brackets":
+		case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time
+		case "comma", "semicolon", "space":
+			if delimiter == "" {
+				delimiter = opt
+				continue
+			}
+			return fmt.Sprintf("can not set both %q and %q as delimiters", opt, delimiter), false
 		default:
-			if w.isUserDefined(keyMapstructure, opt) {
+			if checkCtx.isUserDefined(keyURL, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in Mapstructure tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkValidateTag(options []string) (string, bool) {
+func checkValidateTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
 	previousOption := ""
 	seenKeysOption := false
+	options := append([]string{tag.Name}, tag.Options...)
 	for _, opt := range options {
 		switch opt {
 		case "keys":
 			if previousOption != "dive" {
-				return "option 'keys' must follow a 'dive' option in validate tag", false
+				return `option "keys" must follow a "dive" option`, false
 			}
 			seenKeysOption = true
 		case "endkeys":
 			if !seenKeysOption {
-				return "option 'endkeys' without a previous 'keys' option in validate tag", false
+				return `option "endkeys" without a previous "keys" option`, false
 			}
 			seenKeysOption = false
 		default:
 			parts := strings.Split(opt, "|")
-			errMsg, ok := w.checkValidateOptionsAlternatives(parts)
+			errMsg, ok := checkValidateOptionsAlternatives(checkCtx, parts)
 			if !ok {
 				return errMsg, false
 			}
@@ -448,47 +528,60 @@ func (w lintStructTagRule) checkValidateTag(options []string) (string, bool) {
 	return "", true
 }
 
-func (w lintStructTagRule) checkTOMLTag(options []string) (string, bool) {
-	for _, opt := range options {
+func checkXMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
 		switch opt {
-		case "omitempty":
+		case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
 		default:
-			if w.isUserDefined(keyTOML, opt) {
+			if checkCtx.isUserDefined(keyXML, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in TOML tag", opt), false
+			return fmt.Sprintf(msgUnknownOption, opt), false
 		}
 	}
 
 	return "", true
 }
 
-func (w lintStructTagRule) checkValidateOptionsAlternatives(alternatives []string) (string, bool) {
-	for _, alternative := range alternatives {
-		alternative := strings.TrimSpace(alternative)
-		parts := strings.Split(alternative, "=")
-		switch len(parts) {
-		case 1:
-			badOpt, ok := areValidateOpts(parts[0])
-			if ok || w.isUserDefined(keyValidate, badOpt) {
+func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
+	for _, opt := range tag.Options {
+		switch opt {
+		case "flow", "inline", "omitempty":
+		default:
+			if checkCtx.isUserDefined(keyYAML, opt) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in validate tag", badOpt), false
-		case 2:
-			lhs := parts[0]
+			return fmt.Sprintf(msgUnknownOption, opt), false
+		}
+	}
+
+	return "", true
+}
+
+func checkValidateOptionsAlternatives(checkCtx *checkContext, alternatives []string) (message string, succeeded bool) {
+	for _, alternative := range alternatives {
+		alternative := strings.TrimSpace(alternative)
+		lhs, _, found := strings.Cut(alternative, "=")
+		if found {
 			_, ok := validateLHS[lhs]
-			if ok || w.isUserDefined(keyValidate, lhs) {
+			if ok || checkCtx.isUserDefined(keyValidate, lhs) {
 				continue
 			}
-			return fmt.Sprintf("unknown option '%s' in validate tag", lhs), false
-		default:
-			return fmt.Sprintf("malformed options '%s' in validate tag, not expected more than one '='", alternative), false
+			return fmt.Sprintf(msgUnknownOption, lhs), false
 		}
+
+		badOpt, ok := areValidateOpts(alternative)
+		if ok || checkCtx.isUserDefined(keyValidate, badOpt) {
+			continue
+		}
+
+		return fmt.Sprintf(msgUnknownOption, badOpt), false
 	}
+
 	return "", true
 }
 
-func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
+func typeValueMatch(t ast.Expr, val string) bool {
 	tID, ok := t.(*ast.Ident)
 	if !ok {
 		return true
@@ -512,115 +605,19 @@ func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
 
 	return typeMatches
 }
-func (w lintStructTagRule) checkPropertiesTag(t ast.Expr, options []string) (string, bool) {
-	if len(options) == 0 {
-		return "", true
-	}
-
-	hasDefault := false
-	for _, opt := range options {
-		key, val, found := strings.Cut(opt, "=")
-		switch key {
-		case "default":
-			if hasDefault {
-				return "properties tag accepts only one default option", false
-			}
-			hasDefault = true
-
-			if !found {
-				return "malformed default for properties tag", false
-			}
-
-			if !w.typeValueMatch(t, val) {
-				return "field type and default value type mismatch in properties tag", false
-			}
-		case "layout":
-			if !found || strings.TrimSpace(val) == "" {
-				return "malformed layout option for properties tag", false
-			}
-
-			if gofmt(t) != "time.Time" {
-				return "layout option is only applicable to fields of type time.Time in properties tag", false
-			}
-		default:
-			return fmt.Sprintf("unknown option %q in properties tag", opt), false
-		}
-	}
-
-	return "", true
-}
-
-func (w lintStructTagRule) checkProtobufTag(tag *structtag.Tag) (string, bool) {
-	// check name
-	switch tag.Name {
-	case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64":
-		// do nothing
-	default:
-		return fmt.Sprintf("invalid protobuf tag name '%s'", tag.Name), false
-	}
-
-	// check options
-	seenOptions := map[string]bool{}
-	for _, opt := range tag.Options {
-		if number, err := strconv.Atoi(opt); err == nil {
-			_, alreadySeen := w.usedTagNbr[number]
-			if alreadySeen {
-				return fmt.Sprintf("duplicated tag number %v", number), false
-			}
-			w.usedTagNbr[number] = true
-			continue // option is an integer
-		}
-
-		switch {
-		case opt == "opt" || opt == "proto3" || opt == "rep" || opt == "req":
-			// do nothing
-		case strings.Contains(opt, "="):
-			o := strings.Split(opt, "=")[0]
-			_, alreadySeen := seenOptions[o]
-			if alreadySeen {
-				return fmt.Sprintf("protobuf tag has duplicated option '%s'", o), false
-			}
-			seenOptions[o] = true
-			continue
-		}
-	}
-	_, hasName := seenOptions["name"]
-	if !hasName {
-		return "protobuf tag lacks mandatory option 'name'", false
-	}
 
-	for k := range seenOptions {
-		switch k {
-		case "name", "json":
-			// do nothing
-		default:
-			if w.isUserDefined(keyProtobuf, k) {
-				continue
-			}
-			return fmt.Sprintf("unknown option '%s' in protobuf tag", k), false
-		}
-	}
-
-	return "", true
+func (w lintStructTagRule) addFailureWithTagKey(n ast.Node, msg string, tagKey string) {
+	w.addFailuref(n, "%s in %s tag", msg, tagKey)
 }
 
-func (w lintStructTagRule) addFailure(n ast.Node, msg string) {
+func (w lintStructTagRule) addFailuref(n ast.Node, msg string, args ...any) {
 	w.onFailure(lint.Failure{
 		Node:       n,
-		Failure:    msg,
+		Failure:    fmt.Sprintf(msg, args...),
 		Confidence: 1,
 	})
 }
 
-func (w lintStructTagRule) isUserDefined(key, opt string) bool {
-	if w.userDefined == nil {
-		return false
-	}
-
-	options := w.userDefined[key]
-	return slices.Contains(options, opt)
-}
-
 func areValidateOpts(opts string) (string, bool) {
 	parts := strings.Split(opts, "|")
 	for _, opt := range parts {
@@ -633,6 +630,13 @@ func areValidateOpts(opts string) (string, bool) {
 	return "", true
 }
 
+const (
+	msgDuplicatedOption    = "duplicated option %q"
+	msgDuplicatedTagNumber = "duplicated tag number %v"
+	msgUnknownOption       = "unknown option %q"
+	msgTypeMismatch        = "type mismatch between field type and default value type"
+)
+
 var validateSingleOptions = map[string]struct{}{
 	"alpha":                     {},
 	"alphanum":                  {},


Test patch:
diff --git a/rule/exported_test.go b/rule/exported_test.go
index cfbb355..9064148 100644
--- a/rule/exported_test.go
+++ b/rule/exported_test.go
@@ -9,11 +9,11 @@ import (
 
 func TestExportedRule_Configure(t *testing.T) {
 	tests := []struct {
-		name               string
-		arguments          lint.Arguments
-		wantErr            error
-		wantDisabledChecks disabledChecks
-		wantStuttersMsg    string
+		name                string
+		arguments           lint.Arguments
+		wantErr             error
+		wantDisabledChecks  disabledChecks
+		wantIsRepetitiveMsg string
 	}{
 		{
 			name:      "default configuration",
@@ -23,7 +23,7 @@ func TestExportedRule_Configure(t *testing.T) {
 				PrivateReceivers: true,
 				PublicInterfaces: true,
 			},
-			wantStuttersMsg: "stutters",
+			wantIsRepetitiveMsg: "stutters",
 		},
 		{
 			name: "valid arguments",
@@ -44,11 +44,11 @@ func TestExportedRule_Configure(t *testing.T) {
 				Const:            true,
 				Function:         true,
 				Method:           true,
-				Stuttering:       true,
+				RepetitiveNames:  true,
 				Type:             true,
 				Var:              true,
 			},
-			wantStuttersMsg: "stutters",
+			wantIsRepetitiveMsg: "stutters",
 		},
 		{
 			name: "valid lowercased arguments",
@@ -69,11 +69,11 @@ func TestExportedRule_Configure(t *testing.T) {
 				Const:            true,
 				Function:         true,
 				Method:           true,
-				Stuttering:       true,
+				RepetitiveNames:  true,
 				Type:             true,
 				Var:              true,
 			},
-			wantStuttersMsg: "stutters",
+			wantIsRepetitiveMsg: "stutters",
 		},
 		{
 			name: "valid kebab-cased arguments",
@@ -94,11 +94,11 @@ func TestExportedRule_Configure(t *testing.T) {
 				Const:            true,
 				Function:         true,
 				Method:           true,
-				Stuttering:       true,
+				RepetitiveNames:  true,
 				Type:             true,
 				Var:              true,
 			},
-			wantStuttersMsg: "stutters",
+			wantIsRepetitiveMsg: "stutters",
 		},
 		{
 			name: "valid sayRepetitiveInsteadOfStutters",
@@ -110,7 +110,7 @@ func TestExportedRule_Configure(t *testing.T) {
 				PrivateReceivers: true,
 				PublicInterfaces: true,
 			},
-			wantStuttersMsg: "is repetitive",
+			wantIsRepetitiveMsg: "is repetitive",
 		},
 		{
 			name: "valid lowercased sayRepetitiveInsteadOfStutters",
@@ -122,7 +122,7 @@ func TestExportedRule_Configure(t *testing.T) {
 				PrivateReceivers: true,
 				PublicInterfaces: true,
 			},
-			wantStuttersMsg: "is repetitive",
+			wantIsRepetitiveMsg: "is repetitive",
 		},
 		{
 			name: "valid kebab-cased sayRepetitiveInsteadOfStutters",
@@ -134,7 +134,7 @@ func TestExportedRule_Configure(t *testing.T) {
 				PrivateReceivers: true,
 				PublicInterfaces: true,
 			},
-			wantStuttersMsg: "is repetitive",
+			wantIsRepetitiveMsg: "is repetitive",
 		},
 		{
 			name:      "unknown configuration flag",
@@ -170,8 +170,8 @@ func TestExportedRule_Configure(t *testing.T) {
 			if rule.disabledChecks != tt.wantDisabledChecks {
 				t.Errorf("unexpected disabledChecks: got = %+v, want %+v", rule.disabledChecks, tt.wantDisabledChecks)
 			}
-			if rule.stuttersMsg != tt.wantStuttersMsg {
-				t.Errorf("unexpected stuttersMsg: got = %v, want %v", rule.stuttersMsg, tt.wantStuttersMsg)
+			if rule.isRepetitiveMsg != tt.wantIsRepetitiveMsg {
+				t.Errorf("unexpected stuttersMsg: got = %v, want %v", rule.isRepetitiveMsg, tt.wantIsRepetitiveMsg)
 			}
 		})
 	}
diff --git a/test/exported_test.go b/test/exported_test.go
index 6b35cbc..e517fc6 100644
--- a/test/exported_test.go
+++ b/test/exported_test.go
@@ -43,3 +43,7 @@ func TestCheckDisablingOnDeclarationTypes(t *testing.T) {
 func TestCheckDirectiveComment(t *testing.T) {
 	testRule(t, "exported_issue_1202", &rule.ExportedRule{}, &lint.RuleConfig{})
 }
+
+func TestCheckDeprecationComment(t *testing.T) {
+	testRule(t, "exported_issue_1231", &rule.ExportedRule{}, &lint.RuleConfig{})
+}
diff --git a/testdata/exported_issue_1231.go b/testdata/exported_issue_1231.go
new file mode 100644
index 0000000..ce7ccab
--- /dev/null
+++ b/testdata/exported_issue_1231.go
@@ -0,0 +1,53 @@
+package golint
+
+// Deprecated: this is deprecated, use math.PI instead
+const PI = 3.14 // MATCH /exported const PI should have comment or be unexported/
+
+// Deprecated: this is deprecated
+var Buffer []byte // MATCH /exported var Buffer should have comment or be unexported/
+
+// Eq returns true if a == b, false otherwise.
+// Deprecated: this is deprecated
+func Eq(a, b int) bool {
+	return a == b
+}
+
+// Deprecated: this is deprecated, use min instead
+// Min returns a if a <= b, b otherwise.
+func Min(a, b int) int { // MATCH /exported function Min should have comment or be unexported/
+	if a < b {
+		return a
+	}
+	return b
+}
+
+// Maximum returns a if a >= b, b otherwise.
+// Deprecated: this is deprecated, use max instead
+func Max(a, b int) int { // MATCH:24 /comment on exported function Max should be of the form "Max ..."/
+	if a > b {
+		return a
+	}
+	return b
+}
+
+// Deprecated: this is deprecated
+type Number float64 // MATCH /exported type Number should have comment or be unexported/
+
+// Name is a type that represents a name.
+type Name string
+
+// Greet returns a greeting for the name.
+func (n Name) Greet() string {
+	return "Hello, " + string(n)
+}
+
+// Deprecated: this is deprecated, use Name.ToString instead
+func (n Name) ToString() string { // MATCH /exported method Name.ToString should have comment or be unexported/
+	return string(n)
+}
+
+// String returns the string representation of the name.
+// Deprecated: this is deprecated, use Name.Greet instead
+func (n Name) String() string {
+	return string(n)
+}
diff --git a/testdata/go1.24/struct_tag.go b/testdata/go1.24/struct_tag.go
index 5982c76..91dadc7 100644
--- a/testdata/go1.24/struct_tag.go
+++ b/testdata/go1.24/struct_tag.go
@@ -1,22 +1,20 @@
 package fixtures
 
-import "time"
-
 type decodeAndValidateRequest struct {
 	// BEAWRE : the flag of URLParam should match the const string URLParam
 	URLParam    string `json:"-" path:"url_param" validate:"numeric"`
 	Text        string `json:"text" validate:"max=10"`
-	DefaultInt  int    `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
+	DefaultInt  int    `json:"defaultInt" default:"10.0"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultInt2 int    `json:"defaultInt2" default:"10"`
-	// MATCH:12 /unknown option 'inline' in JSON tag/
-	DefaultInt3      int             `json:"defaultInt2,inline" default:"11"` // MATCH /duplicate tag name: 'defaultInt2'/
+	// MATCH:10 /unknown option "inline" in json tag/
+	DefaultInt3      int             `json:"defaultInt2,inline" default:"11"` // MATCH /duplicated tag name "defaultInt2" in json tag/
 	DefaultString    string          `json:"defaultString" default:"foo"`
-	DefaultBool      bool            `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
+	DefaultBool      bool            `json:"defaultBool" default:"trues"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultBool2     bool            `json:"defaultBool2" default:"true"`
 	DefaultBool3     bool            `json:"defaultBool3" default:"false"`
-	DefaultFloat     float64         `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
+	DefaultFloat     float64         `json:"defaultFloat" default:"f10.0"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultFloat2    float64         `json:"defaultFloat2" default:"10.0"`
-	MandatoryStruct  mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
+	MandatoryStruct  mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be "true" or "false" in required tag/
 	MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
 	MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
 	OptionalStruct   *optionalStruct `json:"optionalStruct,omitempty"`
@@ -24,102 +22,13 @@ type decodeAndValidateRequest struct {
 	optionalQuery    string          `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
 	// No-reg test for bug https://github.com/mgechev/revive/issues/208
 	Tiret       string `json:"-,"`
-	BadTiret    string `json:"other,"`               // MATCH /option can not be empty in JSON tag/
-	ForOmitzero string `json:"forOmitZero,omitzero"` // 'omitzero' is valid in go 1.24
+	BadTiret    string `json:"other,"`               // MATCH /option can not be empty in json tag/
+	ForOmitzero string `json:"forOmitZero,omitzero"` // Go 1.24 introduces omitzero
 }
 
 type RangeAllocation struct {
-	metav1.TypeMeta   `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
+	metav1.TypeMeta   `json:",inline"` // MATCH /unknown option "inline" in json tag/
 	metav1.ObjectMeta `json:"metadata,omitempty"`
-	Range             string `json:"range,flow"`  // MATCH /unknown option 'flow' in JSON tag/
-	Data              []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
-}
-
-type RangeAllocation struct {
-	metav1.TypeMeta   `bson:",minsize"`
-	metav1.ObjectMeta `bson:"metadata,omitempty"`
-	Range             string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
-	Data              []byte `bson:"data,inline"`
-}
-
-type TestContextSpecificTags2 struct {
-	A       int       `asn1:"explicit,tag:1"`
-	B       int       `asn1:"tag:2"`
-	S       string    `asn1:"tag:0,utf8"`
-	Ints    []int     `asn1:"set"`
-	Version int       `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
-	Time    time.Time `asn1:"explicit,tag:4,other"`                // MATCH /unknown option 'other' in ASN1 tag/
-	X       int       `asn1:"explicit,tag:invalid"`                // MATCH /ASN1 tag must be a number, got 'invalid'/
-}
-
-type VirtualMachineRelocateSpecDiskLocator struct {
-	DynamicData
-
-	DiskId          int32                           `xml:"diskId,attr,cdata"`
-	Datastore       ManagedObjectReference          `xml:"datastore,chardata,innerxml"`
-	DiskMoveType    string                          `xml:"diskMoveType,omitempty,comment"`
-	DiskBackingInfo BaseVirtualDeviceBackingInfo    `xml:"diskBackingInfo,omitempty,any"`
-	Profile         []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
-}
-
-type TestDuplicatedXMLTags struct {
-	A int `xml:"a"`
-	B int `xml:"a"` // MATCH /duplicate tag name: 'a'/
-	C int `xml:"c"`
-}
-
-type TestDuplicatedBSONTags struct {
-	A int `bson:"b"`
-	B int `bson:"b"` // MATCH /duplicate tag name: 'b'/
-	C int `bson:"c"`
-}
-
-type TestDuplicatedYAMLTags struct {
-	A int `yaml:"b"`
-	B int `yaml:"c"`
-	C int `yaml:"c"` // MATCH /duplicate tag name: 'c'/
-}
-
-type TestDuplicatedProtobufTags struct {
-	A int `protobuf:"varint,name=b"`
-	B int `protobuf:"varint,name=c"`
-	C int `protobuf:"varint,name=c"` // MATCH /duplicate tag name: 'c'/
-}
-
-// test case from
-// sigs.k8s.io/kustomize/api/types/helmchartargs.go
-
-type HelmChartArgs struct {
-	ChartName        string                 `json:"chartName,omitempty" yaml:"chartName,omitempty"`
-	ChartVersion     string                 `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
-	ChartRepoURL     string                 `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
-	ChartHome        string                 `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
-	ChartRepoName    string                 `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"`
-	HelmBin          string                 `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
-	HelmHome         string                 `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
-	Values           string                 `json:"values,omitempty" yaml:"values,omitempty"`
-	ValuesLocal      map[string]interface{} `json:"valuesLocal,omitempty" yaml:"valuesLocal,omitempty"`
-	ValuesMerge      string                 `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"`
-	ReleaseName      string                 `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
-	ReleaseNamespace string                 `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
-	ExtraArgs        []string               `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
-}
-
-// Test message for holding primitive types.
-type Simple struct {
-	OBool                *bool    `protobuf:"varint,1,req,json=oBool"`                           // MATCH /protobuf tag lacks mandatory option 'name'/
-	OInt32               *int32   `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"`            // MATCH /unknown option 'jsonx' in protobuf tag/
-	OInt32Str            *int32   `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"`      // MATCH /protobuf tag has duplicated option 'name'/
-	OInt64               *int64   `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
-	OSint32Str           *int32   `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
-	OSint64Str           *int64   `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
-	OFloat               *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
-	ODouble              *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"`      // MATCH /duplicated tag number 14/
-	ODoubleStr           *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
-	OString              *string  `protobuf:"bytes,18,opt,name=o_string,json=oString"`
-	OString2             *string  `protobuf:"bytes,name=ameno"`
-	OString3             *string  `protobuf:"bytes,name=ameno"` // MATCH /duplicate tag name: 'ameno'/
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Range             string `json:"range,flow"`  // MATCH /unknown option "flow" in json tag/
+	Data              []byte `json:"data,inline"` // MATCH /unknown option "inline" in json tag/
 }
diff --git a/testdata/golint/error_strings.go b/testdata/golint/error_strings.go
index e3ae153..004dc13 100644
--- a/testdata/golint/error_strings.go
+++ b/testdata/golint/error_strings.go
@@ -16,6 +16,13 @@ func g(x int) error {
 	err = fmt.Errorf("Newlines are really fun\n") // MATCH /error strings should not be capitalized or end with punctuation or a newline/
 	err = errors.New(`too much stuff.`)           // MATCH /error strings should not be capitalized or end with punctuation or a newline/
 	err = errors.New("This %d is too low", x)     // MATCH /error strings should not be capitalized or end with punctuation or a newline/
+	err = errors.New("GitHub should be ok", x)
+	err = errors.New("OTP should be ok", x)
+	err = errors.New("A JSON should be not ok", x) // MATCH /error strings should not be capitalized or end with punctuation or a newline/
+	err = errors.New("H264 should be ok", x)
+	err = errors.New("I/O should be ok", x)
+	err = errors.New("U.S. should be ok", x)
+	err = errors.New("Wi-Fi should be ok", x)
 
 	// Non-regression test for issue #610
 	d.stack.Push(from)
diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go
index 4518140..d039619 100644
--- a/testdata/struct_tag.go
+++ b/testdata/struct_tag.go
@@ -6,17 +6,17 @@ type decodeAndValidateRequest struct {
 	// BEAWRE : the flag of URLParam should match the const string URLParam
 	URLParam    string `json:"-" path:"url_param" validate:"numeric"`
 	Text        string `json:"text" validate:"max=10"`
-	DefaultInt  int    `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
+	DefaultInt  int    `json:"defaultInt" default:"10.0"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultInt2 int    `json:"defaultInt2" default:"10"`
-	// MATCH:12 /unknown option 'inline' in JSON tag/
-	DefaultInt3      int             `json:"defaultInt2,inline" default:"11"` // MATCH /duplicate tag name: 'defaultInt2'/
+	// MATCH:12 /unknown option "inline" in json tag/
+	DefaultInt3      int             `json:"defaultInt2,inline" default:"11"` // MATCH /duplicated tag name "defaultInt2" in json tag/
 	DefaultString    string          `json:"defaultString" default:"foo"`
-	DefaultBool      bool            `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
+	DefaultBool      bool            `json:"defaultBool" default:"trues"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultBool2     bool            `json:"defaultBool2" default:"true"`
 	DefaultBool3     bool            `json:"defaultBool3" default:"false"`
-	DefaultFloat     float64         `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
+	DefaultFloat     float64         `json:"defaultFloat" default:"f10.0"` // MATCH /type mismatch between field type and default value type in default tag/
 	DefaultFloat2    float64         `json:"defaultFloat2" default:"10.0"`
-	MandatoryStruct  mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
+	MandatoryStruct  mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be "true" or "false" in required tag/
 	MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
 	MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
 	OptionalStruct   *optionalStruct `json:"optionalStruct,omitempty"`
@@ -24,21 +24,23 @@ type decodeAndValidateRequest struct {
 	optionalQuery    string          `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
 	// No-reg test for bug https://github.com/mgechev/revive/issues/208
 	Tiret       string `json:"-,"`
-	BadTiret    string `json:"other,"`               // MATCH /option can not be empty in JSON tag/
-	ForOmitzero string `json:"forOmitZero,omitzero"` // MATCH /unknown option 'omitzero' in JSON tag/
+	BadTiret    string `json:"other,"`               // MATCH /option can not be empty in json tag/
+	ForOmitzero string `json:"forOmitZero,omitzero"` // MATCH /prior Go 1.24, option "omitzero" is unsupported in json tag/
+	// MATCH:30 /option can not be empty in json tag/
+	BadTiret string `json:"other,"` // MATCH /duplicated tag name "other" in json tag/
 }
 
 type RangeAllocation struct {
-	metav1.TypeMeta   `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
+	metav1.TypeMeta   `json:",inline"` // MATCH /unknown option "inline" in json tag/
 	metav1.ObjectMeta `json:"metadata,omitempty"`
-	Range             string `json:"range,flow"`  // MATCH /unknown option 'flow' in JSON tag/
-	Data              []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
+	Range             string `json:"range,flow"`  // MATCH /unknown option "flow" in json tag/
+	Data              []byte `json:"data,inline"` // MATCH /unknown option "inline" in json tag/
 }
 
 type RangeAllocation struct {
 	metav1.TypeMeta   `bson:",minsize"`
 	metav1.ObjectMeta `bson:"metadata,omitempty"`
-	Range             string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
+	Range             string `bson:"range,flow"` // MATCH /unknown option "flow" in bson tag/
 	Data              []byte `bson:"data,inline"`
 }
 
@@ -47,9 +49,9 @@ type TestContextSpecificTags2 struct {
 	B       int       `asn1:"tag:2"`
 	S       string    `asn1:"tag:0,utf8"`
 	Ints    []int     `asn1:"set"`
-	Version int       `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
-	Time    time.Time `asn1:"explicit,tag:4,other"`                // MATCH /unknown option 'other' in ASN1 tag/
-	X       int       `asn1:"explicit,tag:invalid"`                // MATCH /ASN1 tag must be a number, got 'invalid'/
+	Version int       `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0 in asn1 tag/
+	Time    time.Time `asn1:"explicit,tag:4,other"`                // MATCH /unknown option "other" in asn1 tag/
+	X       int       `asn1:"explicit,tag:invalid"`                // MATCH /tag must be a number but is "invalid" in asn1 tag/
 }
 
 type VirtualMachineRelocateSpecDiskLocator struct {
@@ -59,31 +61,31 @@ type VirtualMachineRelocateSpecDiskLocator struct {
 	Datastore       ManagedObjectReference          `xml:"datastore,chardata,innerxml"`
 	DiskMoveType    string                          `xml:"diskMoveType,omitempty,comment"`
 	DiskBackingInfo BaseVirtualDeviceBackingInfo    `xml:"diskBackingInfo,omitempty,any"`
-	Profile         []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
+	Profile         []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option "other" in xml tag/
 }
 
-type TestDuplicatedXMLTags struct {
+type TestDuplicatedxmlTags struct {
 	A int `xml:"a"`
-	B int `xml:"a"` // MATCH /duplicate tag name: 'a'/
+	B int `xml:"a"` // MATCH /duplicated tag name "a" in xml tag/
 	C int `xml:"c"`
 }
 
-type TestDuplicatedBSONTags struct {
+type TestDuplicatedbsonTags struct {
 	A int `bson:"b"`
-	B int `bson:"b"` // MATCH /duplicate tag name: 'b'/
+	B int `bson:"b"` // MATCH /duplicated tag name "b" in bson tag/
 	C int `bson:"c"`
 }
 
 type TestDuplicatedYAMLTags struct {
 	A int `yaml:"b"`
 	B int `yaml:"c"`
-	C int `yaml:"c"` // MATCH /duplicate tag name: 'c'/
+	C int `yaml:"c"` // MATCH /duplicated tag name "c" in yaml tag/
 }
 
 type TestDuplicatedProtobufTags struct {
 	A int `protobuf:"varint,name=b"`
 	B int `protobuf:"varint,name=c"`
-	C int `protobuf:"varint,name=c"` // MATCH /duplicate tag name: 'c'/
+	C int `protobuf:"varint,name=c"` // MATCH /duplicated tag name "c" in protobuf tag/
 }
 
 // test case from
@@ -107,18 +109,18 @@ type HelmChartArgs struct {
 
 // Test message for holding primitive types.
 type Simple struct {
-	OBool                *bool    `protobuf:"varint,1,req,json=oBool"`                           // MATCH /protobuf tag lacks mandatory option 'name'/
-	OInt32               *int32   `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"`            // MATCH /unknown option 'jsonx' in protobuf tag/
-	OInt32Str            *int32   `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"`      // MATCH /protobuf tag has duplicated option 'name'/
-	OInt64               *int64   `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
+	OBool                *bool    `protobuf:"varint,1,req,json=oBool"`                           // MATCH /mandatory option "name" not found in protobuf tag/
+	OInt32               *int32   `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"`            // MATCH /unknown option "jsonx" in protobuf tag/
+	OInt32Str            *int32   `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"`      // MATCH /duplicated option "name" in protobuf tag/
+	OInt64               *int64   `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /duplicated option "json" in protobuf tag/
 	OSint32Str           *int32   `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
-	OSint64Str           *int64   `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
+	OSint64Str           *int64   `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicated tag name "o_sint32_str" in protobuf tag/
 	OFloat               *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
-	ODouble              *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"`      // MATCH /duplicated tag number 14/
-	ODoubleStr           *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
+	ODouble              *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"`      // MATCH /duplicated tag number 14 in protobuf tag/
+	ODoubleStr           *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid tag name "fixed6" in protobuf tag/
 	OString              *string  `protobuf:"bytes,18,opt,name=o_string,json=oString"`
 	OString2             *string  `protobuf:"bytes,name=ameno"`
-	OString3             *string  `protobuf:"bytes,name=ameno"` // MATCH /duplicate tag name: 'ameno'/
+	OString3             *string  `protobuf:"bytes,name=ameno"` // MATCH /duplicated tag name "ameno" in protobuf tag/
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -131,20 +133,20 @@ type RequestQueryOption struct {
 	Associations2        []string `url:"associations2,semicolon,omitempty"`
 	Associations3        []string `url:"associations3,space,brackets,omitempty"`
 	Associations4        []string `url:"associations4,numbered,omitempty"`
-	Associations5        []string `url:"associations5,space,semicolon,omitempty"` // MATCH /can not set both 'semicolon' and 'space' as delimiters in URL tag/
+	Associations5        []string `url:"associations5,space,semicolon,omitempty"` // MATCH /can not set both "semicolon" and "space" as delimiters in url tag/
 	PaginateAssociations bool     `url:"paginateAssociations,int,omitempty"`
-	Archived             bool     `url:"archived,myURLOption"` // MATCH /unknown option 'myURLOption' in URL tag/
+	Archived             bool     `url:"archived,myURLOption"` // MATCH /unknown option "myURLOption" in url tag/
 	IDProperty           string   `url:"idProperty,omitempty"`
 }
 
 type Fields struct {
 	Field      string `datastore:",noindex,flatten,omitempty"`
-	OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
+	OtherField string `datastore:",unknownOption"` // MATCH /unknown option "unknownOption" in datastore tag/
 }
 
 type MapStruct struct {
 	Field1     string `mapstructure:",squash,reminder,omitempty"`
-	OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option 'unknownOption' in Mapstructure tag/
+	OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option "unknownOption" in mapstructure tag/
 }
 
 type ValidateUser struct {
@@ -152,30 +154,30 @@ type ValidateUser struct {
 	Email       string `validate:"required,email"`
 	Password    string `validate:"required,min=8,max=32"`
 	Biography   string `validate:"min=0,max=1000"`
-	DisplayName string `validate:"displayName,min=3,max=32"` // MATCH /unknown option 'displayName' in validate tag/
+	DisplayName string `validate:"displayName,min=3,max=32"` // MATCH /unknown option "displayName" in validate tag/
 	Complex     string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
-	BadComplex  string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"`              // MATCH /option 'keys' must follow a 'dive' option in validate tag/
-	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
-	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
+	BadComplex  string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"`              // MATCH /option "keys" must follow a "dive" option in validate tag/
+	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option "endkeys" without a previous "keys" option in validate tag/
+	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
 }
 
 type TomlUser struct {
 	Username string `toml:"username,omitempty"`
-	Location string `toml:"location,unknown"` // MATCH /unknown option 'unknown' in TOML tag/
+	Location string `toml:"location,unknown"` // MATCH /unknown option "unknown" in toml tag/
 }
 
 type PropertiesTags struct {
 	Field int               `properties:"-"`
 	Field int               `properties:"myName"`
 	Field int               `properties:"myName,default=15"`
-	Field int               `properties:"myName,default=sString"` // MATCH /field type and default value type mismatch in properties tag/
-	Field int               `properties:",default:15"`            // MATCH /unknown option "default:15" in properties tag/
-	Field int               `properties:",default=15,default=2"`  // MATCH /properties tag accepts only one default option/
+	Field int               `properties:"myName,default=sString"` // MATCH /type mismatch between field type and default value type in properties tag/
+	Field int               `properties:",default:15"`            // MATCH /unknown or malformed option "default:15" in properties tag/
+	Field int               `properties:",default=15,default=2"`  // MATCH /duplicated option "default" in properties tag/
 	Field time.Time         `properties:"date,layout=2006-01-02"`
 	Field time.Time         `properties:",layout=2006-01-02"`
-	Field time.Time         `properties:"date,layout"`            // MATCH /malformed layout option for properties tag/
-	Field time.Time         `properties:"date,layout=  "`         // MATCH /malformed layout option for properties tag/
+	Field time.Time         `properties:"date,layout"`            // MATCH /unknown or malformed option "layout" in properties tag/
+	Field time.Time         `properties:"date,layout=  "`         // MATCH /option "layout" not of the form layout=value in properties tag/
 	Field string            `properties:"date,layout=2006-01-02"` // MATCH /layout option is only applicable to fields of type time.Time in properties tag/
 	Field []string          `properties:",default=a;b;c"`
-	Field map[string]string `properties:"myName,omitempty"` // MATCH /unknown option "omitempty" in properties tag/
+	Field map[string]string `properties:"myName,omitempty"` // MATCH /unknown or malformed option "omitempty" in properties tag/
 }
diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go
index 66585e6..f2a925b 100644
--- a/testdata/struct_tag_user_options.go
+++ b/testdata/struct_tag_user_options.go
@@ -4,30 +4,30 @@ type RangeAllocation struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 	Range             string `json:"range,outline"`
-	Data              []byte `json:"data,flow"` // MATCH /unknown option 'flow' in JSON tag/
+	Data              []byte `json:"data,flow"` // MATCH /unknown option "flow" in json tag/
 }
 
 type RangeAllocation struct {
 	metav1.TypeMeta   `bson:",minsize,gnu"`
 	metav1.ObjectMeta `bson:"metadata,omitempty"`
-	Range             string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
+	Range             string `bson:"range,flow"` // MATCH /unknown option "flow" in bson tag/
 	Data              []byte `bson:"data,inline"`
 }
 
 type RequestQueryOptions struct {
-	Properties       []string `url:"properties,commmma,omitempty"` // MATCH /unknown option 'commmma' in URL tag/
+	Properties       []string `url:"properties,commmma,omitempty"` // MATCH /unknown option "commmma" in url tag/
 	CustomProperties []string `url:"-"`
 	Archived         bool     `url:"archived,myURLOption"`
 }
 
 type Fields struct {
 	Field      string `datastore:",noindex,flatten,omitempty,myDatastoreOption"`
-	OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
+	OtherField string `datastore:",unknownOption"` // MATCH /unknown option "unknownOption" in datastore tag/
 }
 
 type MapStruct struct {
 	Field1     string `mapstructure:",squash,reminder,omitempty,myMapstructureOption"`
-	OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option 'unknownOption' in Mapstructure tag/
+	OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option "unknownOption" in mapstructure tag/
 }
 
 type ValidateUser struct {
@@ -37,9 +37,9 @@ type ValidateUser struct {
 	Biography   string `validate:"min=0,max=1000"`
 	DisplayName string `validate:"displayName,min=3,max=32"`
 	Complex     string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
-	BadComplex  string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"`              // MATCH /option 'keys' must follow a 'dive' option in validate tag/
-	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
-	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
+	BadComplex  string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"`              // MATCH /option "keys" must follow a "dive" option in validate tag/
+	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option "endkeys" without a previous "keys" option in validate tag/
+	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
 }
 
 type TomlUser struct {