Skip to content

lint: add config-versioning robustness cops + fix v6/v7 package names#2568

Merged
dgageot merged 4 commits intodocker:mainfrom
dgageot:board/custom-go-linter-ideas-for-code-robustne-bdb66fc1
Apr 28, 2026
Merged

lint: add config-versioning robustness cops + fix v6/v7 package names#2568
dgageot merged 4 commits intodocker:mainfrom
dgageot:board/custom-go-linter-ideas-for-code-robustne-bdb66fc1

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented Apr 28, 2026

Summary

Adds three new project-specific linter cops under ./lint/ to harden the
pkg/config/ versioned-schema invariants, fixes a latent regex bug in the
existing Lint/ConfigVersionImport cop that prevented it from firing
under mise lint, and corrects 16 stale package latest declarations in
pkg/config/v6/ and pkg/config/v7/.

Bugs caught and fixed

  1. pkg/config/v6/*.go and pkg/config/v7/*.go declared package latest
    instead of package v6 / package v7. Copy-paste artifact from when those
    directories were the work-in-progress version. Stayed compilable only
    because every importer used an explicit alias (v6, v7, previous).
  2. Lint/ConfigVersionImport's path regexes required a leading /, so
    they never matched when invoked as go run ./lint . (the form mise lint
    uses). Anchored with (?:^|/) and added a black-box test-package skip.

New cops

Cop What it enforces
Lint/ConfigPackageName Files under pkg/config/<dir>/ must declare package <dir> (allowing <dir>_test for black-box tests). Catches the v6/v7 class of bug.
Lint/ConfigVersionConstant const Version = "N" in pkg/config/vN/ must literally be "N". Guards against version-constant / directory-name drift.
Lint/LatestImportsPredecessor pkg/config/latest/ may only import the highest existing vN/ sibling, not arbitrary historical versions. Closes a gap in Lint/ConfigVersionImport for the latest case.

Architecture

A small shared helper file lint/configpath.go centralises path/version
parsing so each cop is small (50–90 LOC) and focused on its rule:

  • configDir(filename) → name of the dir under pkg/config/
  • versionFromDir("vN")N or false
  • versionFromImport(path)N if path ends in pkg/config/vN
  • importPath(*ast.ImportSpec) → unquoted import path
  • offense(c, fset, node, msg)Offense covering an AST node
  • highestSiblingVersion(...) → cached scan for the highest vN (sync.Map-backed)

Cops are registered in a single var cops = []cop.Cop{...} slice in
main.go — adding a new cop is one line.

Validation

  • mise lintgolangci-lint: 0 issues; custom cops: 880 files, 0 offenses; go mod tidy --diff: clean.
  • mise test → all packages pass.
  • Fault injection — confirmed each cop still fires on:
    • bad package name (Lint/ConfigPackageName)
    • bad Version constant (Lint/ConfigVersionConstant)
    • latest importing wrong predecessor (Lint/LatestImportsPredecessor)
    • vN importing latest (Lint/ConfigVersionImport)
    • vN importing wrong predecessor (Lint/ConfigVersionImport)

Commits

  1. fix(config): use proper package names for v6 and v7 — 16 files, package clause only.
  2. lint: add config-versioning robustness cops — adds 3 new cops + fixes the path regex bug in the existing one.
  3. lint: extract shared config-path helpers to simplify cops — refactor; ~130 lines of duplication removed across cops.
  4. fix(lint): add test file exemption and improve error handling — review-fix pass: black-box test exemption in LatestImportsPredecessor, explicit strconv.Atoi error handling, error-message wording consistency.

dgageot added 4 commits April 28, 2026 10:50
Files in pkg/config/v6/ and pkg/config/v7/ declared 'package latest'
instead of 'package v6' / 'package v7'. This was a copy-paste artifact
from when those directories were the work-in-progress 'latest' version
and got frozen without renaming the package clause.

The bug stayed compilable only because every importer used an explicit
alias (v6, v7, previous), masking the fact that the actual package name
was 'latest'.

Assisted-By: docker-agent
Adds three new cops to ./lint and fixes a regex bug in the existing
ConfigVersionImport cop.

New cops:
- Lint/ConfigPackageName: files under pkg/config/<dir>/ must declare
  'package <dir>' (with the standard '<dir>_test' exception for
  black-box tests). This catches the package-clause copy-paste bug
  that historically slipped into v6 and v7.
- Lint/ConfigVersionConstant: 'const Version' in pkg/config/vN/ must
  equal "N". Guards against version-constant / directory-name drift
  during freeze.
- Lint/LatestImportsPredecessor: pkg/config/latest/ may only import
  the highest existing vN sibling, not arbitrary historical versions.

Bug fix:

ConfigVersionImport's path regexes required a leading '/', so they
silently never matched when running 'go run ./lint .' (the form used
by 'mise lint'). Anchored with '(?:^|/)' and added a black-box test
package skip to keep latest_test files happy.

Assisted-By: docker-agent
Each of the four cops re-implemented its own path parsing and offense
construction. This commit factors the common ground into a single
helper file and slims every cop down to its rule.

Changes:

- New lint/configpath.go centralises:
    * configDir(filename)        — name of the dir under pkg/config/
    * versionFromDir("vN")        — N or false
    * versionFromImport(path)     — N if path ends in pkg/config/vN
    * importPath(*ast.ImportSpec) — unquoted import path
    * offense(c, fset, node, msg) — Offense covering an AST node
    * highestSiblingVersion(...)  — cached scan for the highest vN
- ConfigVersionImport: extracted a pure importViolation() function so
  the rule reads as a flat switch over the import path; folded the two
  inline checkVersionedImport / checkLatestImport helpers away.
- LatestImportsPredecessor: dropped the bespoke mutex+map cache in
  favour of the shared sync.Map-backed helper.
- main.go: cops are now declared in a single var slice, making it
  obvious where to register new ones.
- 4 regexes → 2; ~130 lines of duplication removed; per-cop file size
  down to 50–90 lines.

Behaviour is unchanged: all four cops still flag the same regressions
(verified by injecting and reverting four faults across the config
chain) and all pkg/config/... tests pass.

Assisted-By: docker-agent
- Add black-box test file exemption to LatestImportsPredecessor cop
  (consistent with ConfigVersionImport and ConfigPackageName)
- Check strconv.Atoi error in versionFromImport for consistency
- Use 'must' instead of 'should' in error message for consistency

Assisted-By: docker-agent
@dgageot dgageot requested a review from a team as a code owner April 28, 2026 09:21
@dgageot dgageot merged commit 84b859b into docker:main Apr 28, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants