🔍 Duplicate Code Detected: strings.Index/Count Comparison Analyzers
Analysis of commit 31e5fda
Assignee: @copilot
Summary
The analyzers stringsindexcontains, stringscountcontains, and stringsindexhasprefix in pkg/linters/ share a near-identical structure: a BinaryExpr preorder walk, the same skip/nolint guards, a match*Comparison function that normalizes operands and matches a comparison operator against a sentinel literal (-1/0/1), and the same diagnostic-building/BuildContainsFix tail. Only the operator/literal table and the replacement function name differ.
Duplication Details
Pattern: Comparison-to-sentinel analyzer + reporting
- Severity: Medium
- Occurrences: 3 analyzers
- Locations:
pkg/linters/stringsindexcontains/stringsindexcontains.go (lines 35-145)
pkg/linters/stringscountcontains/stringscountcontains.go (lines 35-146)
pkg/linters/stringsindexhasprefix/stringsindexhasprefix.go (lines 35-120)
- Code Sample (shared analyze/report body):
expr, ok := n.(*ast.BinaryExpr)
if !ok { return }
pos := pass.Fset.PositionFor(expr.Pos(), false)
if filecheck.ShouldSkipFilename(pos.Filename, generatedFiles) { return }
if nolint.HasDirectiveForLinter(pos, noLintIndex, "<name>") { return }
call, negated, matched := match<X>Comparison(pass, expr)
if !matched { return }
// ... extract sText/subText/pkgText via astutil.NodeText/CallQualifierText ...
// build "use [!]strings.Contains(...)" message
fix := astutil.BuildContainsFix(pass.Files, expr, pkgText, sText, subText, negated, "...")
pass.Report(analysis.Diagnostic{Pos: expr.Pos(), End: expr.End(), Message: msg, SuggestedFixes: fix})
Each match*Comparison uses the same NormalizeComparisonOperands → AsStringsMethodCall → FlipComparisonOp → ConstIntValue → operator switch skeleton.
Impact Analysis
- Maintainability: Changes to operand normalization, yoda-order handling, or diagnostic wording must be repeated in three analyzers.
- Bug Risk: Subtle inconsistencies in supported operator/literal combinations are easy to introduce when copy-editing one file.
- Code Bloat: ~100 lines of duplicated analyze/report scaffolding.
Refactoring Recommendations
-
Introduce a table-driven comparison analyzer helper
- Add
astutil support for "comparison of strings.(Method)(...) against sentinel literals → suggested replacement", parameterized by method name, an operator/literal → negated table, and the replacement builder.
- Estimated effort: 4-5 hours
- Benefits: the three analyzers become small declarations of their operator tables and messages.
-
Extract the shared report tail
- Factor the
sText/subText/pkgText extraction + message/fix/Report block into one helper taking negated and a label.
Implementation Checklist
Analysis Metadata
- Analyzed Files: pkg/linters (77 analyzer packages)
- Detection Method: Serena semantic code analysis
- Commit: 31e5fda
- Analysis Date: 2026-10-03T22:48:41.609Z
Generated by 🔍 Duplicate Code Detector · pi · opus48 · 361.7 AIC · ⌖ 46.9 AIC · ⊞ 4.4K · ◷
🔍 Duplicate Code Detected: strings.Index/Count Comparison Analyzers
Analysis of commit 31e5fda
Assignee:
@copilotSummary
The analyzers
stringsindexcontains,stringscountcontains, andstringsindexhasprefixinpkg/linters/share a near-identical structure: aBinaryExprpreorder walk, the same skip/nolint guards, amatch*Comparisonfunction that normalizes operands and matches a comparison operator against a sentinel literal (-1/0/1), and the same diagnostic-building/BuildContainsFixtail. Only the operator/literal table and the replacement function name differ.Duplication Details
Pattern: Comparison-to-sentinel analyzer + reporting
pkg/linters/stringsindexcontains/stringsindexcontains.go(lines 35-145)pkg/linters/stringscountcontains/stringscountcontains.go(lines 35-146)pkg/linters/stringsindexhasprefix/stringsindexhasprefix.go(lines 35-120)Each
match*Comparisonuses the sameNormalizeComparisonOperands→AsStringsMethodCall→FlipComparisonOp→ConstIntValue→ operatorswitchskeleton.Impact Analysis
Refactoring Recommendations
Introduce a table-driven comparison analyzer helper
astutilsupport for "comparison of strings.(Method)(...) against sentinel literals → suggested replacement", parameterized by method name, an operator/literal → negated table, and the replacement builder.Extract the shared report tail
sText/subText/pkgTextextraction + message/fix/Report block into one helper takingnegatedand a label.Implementation Checklist
astutilAnalysis Metadata