Extract repeated linter analyzer bootstrap into analyzerutil helpers#53220
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
✅ Test Quality Sentinel completed test quality analysis. Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check.
|
|
✅ Ponytail Reviewer completed successfully! Diff contains only generated .lock.yml files (compiled workflow artifacts), which are explicitly out of scope for this review.
|
|
✅ PR Code Quality Reviewer completed the code quality review.
|
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅
|
Comment MemoryNote This comment is managed by comment memory.It stores persistent context for this thread in the code block at the top of this comment.
|
There was a problem hiding this comment.
Verdict
No blocking issues found in the changed lines.
Review notes
I checked the new analyzerutil helpers, a representative sample of migrated linters, and the generated workflow shell-harness changes. The refactor preserves the old index ordering and error propagation, and the workflow command shape matches existing shell_harness-based lockfiles already in the repo. I did not find a changed-line bug worth blocking this PR over.
🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 14.6 AIC · ⌖ 5.59 AIC · ⊞ 4.5K
Comment /review to run again
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /codebase-design and /tdd — approving with one minor observation.
📋 Key Themes & Highlights
Positive Highlights
- ✅ Deep module win:
PreorderIndexedhides 20 lines of boilerplate behind a single call — exactly what/codebase-designcalls "deep modules with simple interfaces." - ✅ Test coverage is comprehensive: happy path, both error cases (
TestIndexesError), and no-visit-on-error guard (TestPreorderIndexedError). Aligns well with/tddprinciples. - ✅ 64-file reduction with near-zero behaviour risk — existing per-linter
analysistestgolden suites continue unchanged. - ✅
Indexesis a useful seam for linters with extra state that can't usePreorderIndexeddirectly.
Minor Observation
The build order inside Indexes is nolint → filecheck, but the return signature is (filecheck.GeneratedIndex, nolint.DirectiveIndex, error) — reversed. The naming in the signature is unambiguous so this isn't a bug, but it creates a small "read the signature, not the body" hurdle. Consider aligning them for future maintainability.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 25.3 AIC · ⌖ 7.54 AIC · ⊞ 7.7K
Comment /matt to run again
There was a problem hiding this comment.
Pull request overview
Centralizes repeated linter initialization through shared analyzer utilities while preserving analyzer behavior.
Changes:
- Adds and tests
IndexesandPreorderIndexed. - Migrates 64 linters to the shared helpers.
- Also includes unrelated generated workflow lockfile changes that should be reverted.
Show a summary per file
| File | Description |
|---|---|
pkg/linters/writebytestring/writebytestring.go |
Uses shared index setup. |
pkg/linters/wgdonenotdeferred/wgdonenotdeferred.go |
Uses shared index setup. |
pkg/linters/walkfuncerrshadow/walkfuncerrshadow.go |
Uses shared index setup. |
pkg/linters/uncheckedtypeassertion/uncheckedtypeassertion.go |
Uses shared index setup. |
pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go |
Uses shared index setup. |
pkg/linters/trimleftright/trimleftright.go |
Uses indexed preorder traversal. |
pkg/linters/tolowerequalfold/tolowerequalfold.go |
Uses shared index setup. |
pkg/linters/timesleepnocontext/timesleepnocontext.go |
Uses shared index setup. |
pkg/linters/timenowsub/timenowsub.go |
Uses indexed preorder traversal. |
pkg/linters/timeafterleak/timeafterleak.go |
Uses shared index setup. |
pkg/linters/stringsjoinone/stringsjoinone.go |
Uses indexed preorder traversal. |
pkg/linters/stringsindexhasprefix/stringsindexhasprefix.go |
Uses indexed preorder traversal. |
pkg/linters/stringsindexcontains/stringsindexcontains.go |
Uses indexed preorder traversal. |
pkg/linters/stringscountcontains/stringscountcontains.go |
Uses indexed preorder traversal. |
pkg/linters/stringsconcatloop/stringsconcatloop.go |
Uses shared index setup. |
pkg/linters/stringreplaceminusone/stringreplaceminusone.go |
Uses shared index setup. |
pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go |
Uses indexed preorder traversal. |
pkg/linters/strconvparseignorederror/strconvparseignorederror.go |
Uses indexed preorder traversal. |
pkg/linters/sprintfint/sprintfint.go |
Uses shared index setup. |
pkg/linters/sprintferrorsnew/sprintferrorsnew.go |
Uses shared index setup. |
pkg/linters/sprintferrdot/sprintferrdot.go |
Uses indexed preorder traversal. |
pkg/linters/sprintfbool/sprintfbool.go |
Uses shared index setup. |
pkg/linters/sortslice/sortslice.go |
Uses shared index setup. |
pkg/linters/seenmapbool/seenmapbool.go |
Uses shared index setup. |
pkg/linters/regexpdynamicpattern/regexpdynamicpattern.go |
Uses shared index setup. |
pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go |
Uses shared index setup. |
pkg/linters/rawloginlib/rawloginlib.go |
Uses shared index setup. |
pkg/linters/panic-in-library-code/panic-in-library-code.go |
Uses shared index setup. |
pkg/linters/packagelevelmutableslicemap/packagelevelmutableslicemap.go |
Uses shared index setup. |
pkg/linters/ossetenvlibrary/ossetenvlibrary.go |
Uses shared index setup. |
pkg/linters/osgetenvlibrary/osgetenvlibrary.go |
Uses shared index setup. |
pkg/linters/osexitinlibrary/osexitinlibrary.go |
Uses shared index setup. |
pkg/linters/nilctxpassed/nilctxpassed.go |
Uses shared index setup. |
pkg/linters/mapdeletecheck/mapdeletecheck.go |
Uses indexed preorder traversal. |
pkg/linters/mapclearloop/mapclearloop.go |
Uses indexed preorder traversal. |
pkg/linters/manualmutexunlock/manualmutexunlock.go |
Uses shared index setup. |
pkg/linters/logfatallibrary/logfatallibrary.go |
Uses shared index setup. |
pkg/linters/lenstringzero/lenstringzero.go |
Uses shared index setup. |
pkg/linters/lenstringsplit/lenstringsplit.go |
Uses shared index setup. |
pkg/linters/largefunc/largefunc.go |
Uses indexed preorder traversal. |
pkg/linters/jsonmarshalignoredeerror/jsonmarshalignoredeerror.go |
Uses shared index setup. |
pkg/linters/ioutildeprecated/ioutildeprecated.go |
Uses shared index setup. |
pkg/linters/internal/analyzerutil/analyzerutil.go |
Adds shared bootstrap helpers. |
pkg/linters/internal/analyzerutil/analyzerutil_test.go |
Tests forwarding and error handling. |
pkg/linters/httpstatuscode/httpstatuscode.go |
Uses shared index setup. |
pkg/linters/httprespbodyclose/httprespbodyclose.go |
Uses shared index setup. |
pkg/linters/httpnoctx/httpnoctx.go |
Uses shared index setup. |
pkg/linters/hardcodedfilepath/hardcodedfilepath.go |
Uses shared index setup. |
pkg/linters/goroutinemissingrecover/goroutinemissingrecover.go |
Uses shared index setup. |
pkg/linters/globwalkignorederror/globwalkignorederror.go |
Uses indexed preorder traversal. |
pkg/linters/fprintlnsprintf/fprintlnsprintf.go |
Uses shared index setup. |
pkg/linters/fmterrorfnoverbs/fmterrorfnoverbs.go |
Uses shared index setup. |
pkg/linters/fileclosenotdeferred/fileclosenotdeferred.go |
Uses shared index setup. |
pkg/linters/execcommandwithoutcontext/execcommandwithoutcontext.go |
Uses shared index setup. |
pkg/linters/excessivefuncparams/excessivefuncparams.go |
Uses shared index setup. |
pkg/linters/errstringmatch/errstringmatch.go |
Uses shared index setup. |
pkg/linters/errortypeassertion/errortypeassertion.go |
Uses shared index setup. |
pkg/linters/errormessage/errormessage.go |
Uses shared index setup. |
pkg/linters/errorfwrapv/errorfwrapv.go |
Uses indexed preorder traversal. |
pkg/linters/deferinloop/deferinloop.go |
Uses shared index setup. |
pkg/linters/ctxbackground/ctxbackground.go |
Uses shared index setup. |
pkg/linters/contextcancelnotdeferred/contextcancelnotdeferred.go |
Uses shared index setup. |
pkg/linters/bytescomparestring/bytescomparestring.go |
Uses shared index setup. |
pkg/linters/bytesbufferstring/bytesbufferstring.go |
Uses indexed preorder traversal. |
pkg/linters/appendoneelement/appendoneelement.go |
Uses indexed preorder traversal. |
pkg/linters/appendbytestring/appendbytestring.go |
Uses indexed preorder traversal. |
.github/workflows/unbloat-docs.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/spec-enforcer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/smoke-pi.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/smoke-gemini.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/schema-consistency-checker.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/pr-sous-chef.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/pr-code-quality-reviewer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/poem-bot.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/lint-monster.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/issue-monster.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/hippo-embed.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/eslint-monster.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-news.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-multi-device-docs-tester.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-hippo-learn.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-function-namer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-doc-updater.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/commit-changes-analyzer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/chaos-pr-bundle-fuzzer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/auto-triage-issues.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/agent-persona-explorer.lock.yml |
Unrelated generated runtime changes. |
.github/workflows/ab-testing-advisor.lock.yml |
Unrelated generated runtime changes. |
Review details
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 89/89 changed files
- Comments generated: 1
- Review effort level: Balanced
| # shellcheck disable=SC1003,SC2016,SC2086 | ||
| awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ | ||
| -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && mkdir -p /tmp/gh-aw/pi-agent-dir && printf '\''%s\n'\'' '\''{"providers":{"aw-gateway":{"api":"openai-completions","apiKey":"COPILOT_GITHUB_TOKEN","baseUrl":"http://api-proxy:10002","models":[{"id":"gpt-5.4"}]}}}'\'' > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/gpt-5.4 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log | ||
| -- /bin/bash -c 'set +o histexpand; GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/shell_harness.cjs pi '\''export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\'\'''\''\n'\''\'\'''\'' '\''\'\'''\'':'\''\'\'''\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && mkdir -p /tmp/gh-aw/pi-agent-dir && printf '\''\'\'''\''%s\n'\''\'\'''\'' '\''\'\'''\''{"providers":{"aw-gateway":{"api":"openai-completions","apiKey":"COPILOT_GITHUB_TOKEN","baseUrl":"http://api-proxy:10002","models":[{"id":"gpt-5.4"}]}}}'\''\'\'''\'' > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/gpt-5.4 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl'\''' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log |
There was a problem hiding this comment.
This PR cleanly extracts the repeated 20-line linter bootstrap into two well-named helpers in pkg/linters/internal/analyzerutil:
Indexes(pass)— returns(filecheck.GeneratedIndex, nolint.DirectiveIndex, error), consolidating two sequential index builds.PreorderIndexed(pass, nodeFilter, visit)— combines index construction with preorder traversal; the visitor signature matches the existing per-linteranalyze*functions so they can be passed by reference.
Overall assessment: the refactoring is correct, consistent, and well-tested. No blocking issues.
Observations
- Return order for
Indexesis(generatedFiles, noLintIndex, err)— slightly non-standard sincenolintis built first internally, but all 60+ callers are consistent, so this is fine. PreorderIndexedcorrectly short-circuits: ifIndexesfails, no nodes are visited (verified byTestPreorderIndexedError).- The
errorfwrapvlinter had additional setup before the index calls; switching toPreorderIndexedis still correct because the extra setup (universeErrorInterface) does not depend on the indexes. - The diff also includes unrelated
.lock.ymlchanges — those appear to be auto-generated recompilations and can be ignored for review purposes. - Test coverage for the new helpers is thorough: happy path, error paths for each missing analyzer result, and visitor-argument forwarding.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 38.1 AIC · ⌖ 7.12 AIC · ⊞ 5.6K
…bootstrap helpers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🏗️ Design Decision Gate — ADR RequiredThis PR makes significant changes to core business logic (181 new lines in 📄 Draft ADR committed:
📋 What to do next
Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision. ❓ Why ADRs Matter
ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you. 📋 Michael Nygard ADR Format ReferenceAn ADR must contain these four sections to be considered complete:
All ADRs are stored in
|
|
@copilot this PR is ready for the next finishing pass. Please address these items, newest first:
|
Every analyzer in
pkg/lintersopened itsrunfunction with the same ~20-line bootstrap: build the nolint index, build the generated-file index, declare anodeFilter, and wire a thinanalyzerutil.Preorderclosure forwarding to a package-specific analyze function. Policy changes to that setup had to be replicated across 60+ packages.pkg/linters/internal/analyzerutilIndexes(pass)— builds both shared indexes in one call, returning(filecheck.GeneratedIndex, nolint.DirectiveIndex, error).PreorderIndexed(pass, nodeFilter, visit)— combines index construction with preorder traversal, invokingvisit(pass, node, generatedFiles, noLintIndex). That signature already matches most linters' existing analyze functions, so they can be passed by reference rather than wrapped.Linter packages (64 files, -456/+90)
runbodies collapse to a single call; the remaining ones (extra state, package-path gating, or inline visitor bodies) useIndexesand keep their existingPreorderclosure.nolint/filecheckdropped where they became unused.Tests
Unit tests for
IndexesandPreorderIndexedcover index forwarding, visitor arguments, and error propagation (no nodes visited when index construction fails). Existing per-linteranalysistestgolden suites are unchanged and still pass, so behavior should be identical.