| id | 85 |
|---|---|
| title | Increase test coverage to 95% by extracting shared rule helpers |
| status | ✅ |
| summary | Refactor duplicated private helpers into shared packages, then test the shared code once. |
Reach 95%+ combined test coverage (unit + e2e) by
eliminating duplicated private helper functions across
rule packages. Instead of testing 17 copies of the same
toInt/headingLine pattern, extract them into shared
packages and test once.
Current combined coverage is 89.4%. Of the remaining
gap, 40% comes from ~31 duplicated private helper
functions spread across 15+ rule packages. Each copy
sits at 40--75% coverage because fixture tests only
exercise the int branch of toInt (missing float64,
int64) and the ATX branch of headingLine (missing
setext, fallback).
Phase 1 --- type conversion helpers:
- Create
internal/rules/settings/settings.gowith exportedToInt(v any) (int, bool),ToFloat(v any) (float64, bool), andToStringSlice(v any) ([]string, bool) - Write table-driven tests in
internal/rules/settings/settings_test.gocovering all type branches (int,float64,int64,string,bool,nil) - Replace private
toIntin 10 rule packages (tablereadability, tableformat, tokenbudget, linelength, paragraphreadability, paragraphstructure, concisenessscoring, nomultipleblanks, maxfilelength, firstlineheading) withsettings.ToInt - Replace private
toFloatin 4 rule packages (tablereadability, tokenbudget, paragraphreadability, concisenessscoring) withsettings.ToFloat - Handle the
emptysectionbodyvariant (has extraint64validation) --- decide whether to fold that branch into the shared version or keep it local (decision: kept local because it rejects non-whole floats, which the sharedToIntaccepts and truncates; same rationale formaxsectionlength) - Delete the now-unused private functions and their per-package coverage tests
Phase 2 --- AST heading and paragraph helpers:
- Create
internal/rules/astutil/astutil.gowithHeadingLine(h, f),ParagraphLine(p, f),IsTable(p, f),HeadingText(h, src), andExtractText(n, src, buf) - Write tests in
internal/rules/astutil/astutil_test.gocovering setext headings, ATX headings, the fallback-to-1 path, table detection, and nested-emphasis extraction - Replace 4 identical
headingLinecopies (blanklinearoundheadings, noduplicateheadings, headingincrement, notrailingpunctuation) withastutil.HeadingLine; keep the extended variants in headingstyle, firstlineheading, and emptysectionbody local - Replace 4 identical
paragraphLinecopies and 3 identicalisTablecopies withastutil.ParagraphLineandastutil.IsTable(noemphasisasheading also replaced) - Replace
headingText+extractTextin noduplicateheadings and notrailingpunctuation; keep the headingstyle extended version local - Delete unused private functions and their per-package coverage tests
Phase 3 --- error-path tests for remaining gaps:
-
internal/fix: test max-passes boundary (10 iterations without convergence) -
internal/lint: testresolveGlobwith invalid patterns,NewGitignoreMatcherwith malformed gitignore files -
internal/rules/include: testreadFSFilewith nonexistent and unreadable files -
cmd/mdsmith: testformatDiagnosticswrite error via the error-writer pattern -
internal/rules/requiredstructure: testcueExprForValuewith[]anyandmap[string]anyinputs; testextractYAMLwith unclosed front matter
Phase 4 --- deep edge cases (pursue only if 95% not met after phases 1--3):
-
requiredstructure: CUE value types ([]any,map[string]any),extractYAMLunclosed front matter,writeNodeTextCodeSpan branch,advanceToMatchno-match path,extractPIFileParammulti-line PI -
crossfilereferenceintegrity: anchor-only refs, encoded URLs,DefaultSettings,configDiagvia invalid glob,toStringSlicemixed types -
concisenessscoring/classifier:validateArtifactfield validation (empty model_id, version, threshold, weights),compileLexiconper-list errors -
catalog:Category,scanIncludesForTargetfallback paths (max depth, read error, no includes, direct match, cycle skip),resolveGitignoreparam variations
Run linter and tests after every phase:
-
go test ./...passes -
go tool golangci-lint runreports no issues
- Combined coverage (unit + e2e) reaches 95%
- No private
toInt/toFloatcopies remain in the 10 + 4 packages listed above (replaced bysettings.ToInt/settings.ToFloat); intentional exceptions:emptysectionbodyandmaxsectionlengthkept local variants because they reject non-whole floats (see Phase 1 task note) - No private
headingLine/paragraphLinecopies remain for the 4-copy and 3-copy groups (replaced byastutil.HeadingLine/astutil.ParagraphLine) - Shared packages have 100% statement coverage
- All tests pass:
go test ./... -
go tool golangci-lint runreports no issues