-
Notifications
You must be signed in to change notification settings - Fork 467
refactor(linters): consolidate duplicated import-editing and file-lookup helpers into internal/astutil #47912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
576a1da
Initial plan
Copilot cabc8ee
refactor: consolidate import-editing and file-lookup helpers into int…
Copilot f11d842
docs(adr): add draft ADR-47912 for consolidating import-editing helpe…
github-actions[bot] 432dcc0
Merge branch 'main' into copilot/refactor-consolidate-import-helpers
github-actions[bot] e32f9fb
fix(astutil): fix SwapImportEdits edit order, use strconv.Quote for p…
Copilot eb4f358
fix(astutil_test): remove dead tokFile variable and guard empty edits…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
docs/adr/47912-consolidate-import-editing-helpers-into-astutil.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # ADR-47912: Consolidate Import-Editing and File-Lookup Helpers into internal/astutil | ||
|
|
||
| **Date**: 2026-07-25 | ||
| **Status**: Draft | ||
| **Deciders**: Unknown | ||
|
|
||
| --- | ||
|
|
||
| ### Context | ||
|
|
||
| Five import-manipulation helpers (`fileForPos`, `countPkgUsesInFile`, `addImportEdit`, `removeImportEdit`, `importSpecLineRange`) were copy-pasted nearly verbatim between `sprintfbool` and `sprintfint`, and the file-containing-position lookup pattern was re-implemented inline in at least five packages (`sprintfbool`, `sprintfint`, `writebytestring`, `bytescomparestring`, `uncheckedtypeassertion`). A latent drift was already visible: `addStrconvRemoveFmtEdits` in the two packages produced TextEdit slices in different order, and `sprintfint` used raw string comparison while `sprintfbool` used a helper — a behavioral gap that would silently grow over time. The shared helper layer `pkg/linters/internal/astutil` already owned `Inspector`, `ImportedAs`, and related AST utilities and was the natural consolidation point. | ||
|
|
||
| ### Decision | ||
|
|
||
| We will promote the duplicated cluster — `FileForPos`, `CountPkgUsesInFile`, `ImportSpecLineRange`, `AddImportEdit`, `RemoveImportEdit`, and a generalized `SwapImportEdits` — into `internal/astutil` as exported, unit-tested functions, and update all call sites in `sprintfbool`, `sprintfint`, `writebytestring`, `bytescomparestring`, and `uncheckedtypeassertion` to use the shared versions. The generalized swap helper replaces the two bespoke `addStrconvRemoveFmtEdits` functions with a parameter-driven implementation, eliminating the ordering drift. | ||
|
|
||
| ### Alternatives Considered | ||
|
|
||
| #### Alternative 1: Keep helpers local to each linter package (status quo) | ||
|
|
||
| Each linter independently owns its import-editing logic. No new shared surface area is introduced. Rejected because the near-verbatim duplication across five packages had already produced subtle behavioral drift (`addStrconvRemoveFmtEdits` edit ordering) and would continue to diverge silently as linters evolve independently. | ||
|
|
||
| #### Alternative 2: Extract into a dedicated internal/importutil package | ||
|
|
||
| Create a new package `pkg/linters/internal/importutil` rather than growing `internal/astutil`. Rejected because the helpers operate on the same `*ast.File`, `token.FileSet`, and `analysis.Pass` types that `astutil` already handles, and adding a second internal package would fragment the shared layer without a clear boundary benefit at this scale. | ||
|
|
||
| ### Consequences | ||
|
|
||
| #### Positive | ||
| - Removes approximately 360 lines of duplicated code across five packages, shrinking per-linter maintenance surface. | ||
| - Eliminates the latent `addStrconvRemoveFmtEdits` ordering drift by replacing both copies with a single generic `SwapImportEdits` implementation. | ||
| - Adds unit tests for `FileForPos`, `CountPkgUsesInFile`, and `ImportSpecLineRange` in `astutil_test.go`, covering the shared logic for all consumers. | ||
| - Import-presence checks in `sprintfbool` switch to `astutil.ImportedAs`, which handles backtick-quoted paths correctly — a correctness improvement over the local `importSpecPathEquals`. | ||
|
|
||
| #### Negative | ||
| - `internal/astutil` grows in scope: it now owns import-editing concerns in addition to AST traversal and type predicates, making the package boundary less crisp. | ||
| - `bytescomparestring`'s `buildBytesImportTextEdit` was intentionally **not** migrated to `AddImportEdit` because it inserts the new import first (rather than appending), which differs from the shared helper's trailing-append behavior. This leaves a documented behavioural inconsistency between `bytescomparestring` and the other linters. | ||
|
|
||
| #### Neutral | ||
| - All existing linter golden-file tests continue to pass unchanged; the refactor is behaviour-preserving at the external test boundary. | ||
| - Future linters that need import-editing utilities now have a discoverable, tested API rather than needing to copy-paste from an existing linter. | ||
|
|
||
| --- | ||
|
|
||
| *ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API inconsistency:
AddImportEdittakes*analysis.Passbut only usespass.FsetIts sibling
RemoveImportEditcorrectly takes*token.FileSetdirectly, making it easy to unit-test and reuse outside analysis passes.AddImportEdittakes the full*analysis.Passsolely to accesspass.Fseton theNodeTextcall inside the single-import expansion branch — an unnecessary coupling.Suggested fix:
All call sites already have
pass.Fsetavailable and can pass it directly.@copilot please address this.