Skip to content

Commit 8cee9dd

Browse files
committed
Plan 85: extract shared settings.ToInt/ToFloat helpers (Phase 1)
Phase 1 of plan 85: Replace 10 duplicated private toInt copies and 4 toFloat copies with a single internal/rules/settings package covered by table-driven unit tests. Each rule package's ApplySettings parameter is renamed from settings to s so the package can be imported under its natural name. The new settings package has 100% statement coverage. The emptysectionbody and maxsectionlength variants are kept local because they reject non-whole floats.
1 parent 3291efc commit 8cee9dd

16 files changed

Lines changed: 224 additions & 322 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ footer: |
4242
| 82 || [YAML billion-laughs mitigation](plan/82_yaml-billion-laughs.md) |
4343
| 83 | 🔳 | [Security hardening batch](plan/83_security-hardening-batch.md) |
4444
| 84 | 🔲 | [Symlink default-deny for file discovery](plan/84_symlink-default-deny.md) |
45-
| 85 | 🔲 | [Increase test coverage to 95% by extracting shared rule helpers](plan/85_coverage-to-95-percent.md) |
45+
| 85 | 🔳 | [Increase test coverage to 95% by extracting shared rule helpers](plan/85_coverage-to-95-percent.md) |
4646
| 86 | 🔲 | [Markdown flavor validation](plan/86_markdown-flavor-validation.md) |
4747
| 87 | 🔲 | [Flavor validation for GitHub Alerts](plan/87_markdown-flavor-github-alerts.md) |
4848
| 88 || [TOC directive migration aid](plan/88_toc-directive-migration.md) |

internal/rules/concisenessscoring/rule.go

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/jeduden/mdsmith/internal/lint"
1010
"github.com/jeduden/mdsmith/internal/mdtext"
1111
"github.com/jeduden/mdsmith/internal/rule"
12+
"github.com/jeduden/mdsmith/internal/rules/settings"
1213
"github.com/yuin/goldmark/ast"
1314
)
1415

@@ -168,8 +169,8 @@ func isTable(para *ast.Paragraph, f *lint.File) bool {
168169
}
169170

170171
// ApplySettings implements rule.Configurable.
171-
func (r *Rule) ApplySettings(settings map[string]any) error {
172-
for k, v := range settings {
172+
func (r *Rule) ApplySettings(s map[string]any) error {
173+
for k, v := range s {
173174
switch k {
174175
case "min-score":
175176
if err := r.setMinScore(v); err != nil {
@@ -187,7 +188,7 @@ func (r *Rule) ApplySettings(settings map[string]any) error {
187188
}
188189

189190
func (r *Rule) setMinScore(v any) error {
190-
n, ok := toFloat(v)
191+
n, ok := settings.ToFloat(v)
191192
if !ok {
192193
return fmt.Errorf(
193194
"conciseness-scoring: min-score must be a number, got %T",
@@ -205,7 +206,7 @@ func (r *Rule) setMinScore(v any) error {
205206
}
206207

207208
func (r *Rule) setMinWords(v any) error {
208-
n, ok := toInt(v)
209+
n, ok := settings.ToInt(v)
209210
if !ok {
210211
return fmt.Errorf(
211212
"conciseness-scoring: min-words must be an integer, got %T",
@@ -230,29 +231,5 @@ func (r *Rule) DefaultSettings() map[string]any {
230231
}
231232
}
232233

233-
func toFloat(v any) (float64, bool) {
234-
switch n := v.(type) {
235-
case float64:
236-
return n, true
237-
case int:
238-
return float64(n), true
239-
case int64:
240-
return float64(n), true
241-
}
242-
return 0, false
243-
}
244-
245-
func toInt(v any) (int, bool) {
246-
switch n := v.(type) {
247-
case int:
248-
return n, true
249-
case float64:
250-
return int(n), true
251-
case int64:
252-
return int(n), true
253-
}
254-
return 0, false
255-
}
256-
257234
var _ rule.Configurable = (*Rule)(nil)
258235
var _ rule.Defaultable = (*Rule)(nil)

internal/rules/firstlineheading/rule.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/jeduden/mdsmith/internal/lint"
77
"github.com/jeduden/mdsmith/internal/rule"
8+
"github.com/jeduden/mdsmith/internal/rules/settings"
89
"github.com/yuin/goldmark/ast"
910
)
1011

@@ -74,11 +75,11 @@ func (r *Rule) diag(f *lint.File, msg string) []lint.Diagnostic {
7475
}
7576

7677
// ApplySettings implements rule.Configurable.
77-
func (r *Rule) ApplySettings(settings map[string]any) error {
78-
for k, v := range settings {
78+
func (r *Rule) ApplySettings(s map[string]any) error {
79+
for k, v := range s {
7980
switch k {
8081
case "level":
81-
n, ok := toInt(v)
82+
n, ok := settings.ToInt(v)
8283
if !ok {
8384
return fmt.Errorf("first-line-heading: level must be an integer, got %T", v)
8485
}
@@ -100,19 +101,6 @@ func (r *Rule) DefaultSettings() map[string]any {
100101
}
101102
}
102103

103-
// toInt converts a value to int.
104-
func toInt(v any) (int, bool) {
105-
switch n := v.(type) {
106-
case int:
107-
return n, true
108-
case float64:
109-
return int(n), true
110-
case int64:
111-
return int(n), true
112-
}
113-
return 0, false
114-
}
115-
116104
var _ rule.Configurable = (*Rule)(nil)
117105

118106
func headingLine(heading *ast.Heading, f *lint.File) int {

internal/rules/linelength/rule.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/jeduden/mdsmith/internal/lint"
1010
"github.com/jeduden/mdsmith/internal/rule"
11+
"github.com/jeduden/mdsmith/internal/rules/settings"
1112
"github.com/yuin/goldmark/ast"
1213
)
1314

@@ -55,8 +56,8 @@ func (r *Rule) isExcluded(category string) bool {
5556
}
5657

5758
// ApplySettings implements rule.Configurable.
58-
func (r *Rule) ApplySettings(settings map[string]any) error {
59-
for k, v := range settings {
59+
func (r *Rule) ApplySettings(s map[string]any) error {
60+
for k, v := range s {
6061
if err := r.applySetting(k, v); err != nil {
6162
return err
6263
}
@@ -84,7 +85,7 @@ func (r *Rule) applySetting(k string, v any) error {
8485
}
8586

8687
func (r *Rule) applyMax(v any) error {
87-
n, ok := toInt(v)
88+
n, ok := settings.ToInt(v)
8889
if !ok {
8990
return fmt.Errorf("line-length: max must be an integer, got %T", v)
9091
}
@@ -93,7 +94,7 @@ func (r *Rule) applyMax(v any) error {
9394
}
9495

9596
func (r *Rule) applyPositiveIntPtr(v any, name string, target **int) error {
96-
n, ok := toInt(v)
97+
n, ok := settings.ToInt(v)
9798
if !ok {
9899
return fmt.Errorf("line-length: %s must be an integer, got %T", name, v)
99100
}
@@ -315,20 +316,6 @@ func headingLineNum(h *ast.Heading, f *lint.File) int {
315316
return 0
316317
}
317318

318-
// toInt converts a value to int. Supports int and float64 (YAML decodes
319-
// numbers as int or float64 depending on context).
320-
func toInt(v any) (int, bool) {
321-
switch n := v.(type) {
322-
case int:
323-
return n, true
324-
case float64:
325-
return int(n), true
326-
case int64:
327-
return int(n), true
328-
}
329-
return 0, false
330-
}
331-
332319
// toStringSlice converts a value to []string. YAML decodes sequences as
333320
// []any with string elements.
334321
func toStringSlice(v any) ([]string, bool) {

internal/rules/maxfilelength/rule.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/jeduden/mdsmith/internal/lint"
77
"github.com/jeduden/mdsmith/internal/rule"
8+
"github.com/jeduden/mdsmith/internal/rules/settings"
89
)
910

1011
func init() {
@@ -52,11 +53,11 @@ func (r *Rule) Check(f *lint.File) []lint.Diagnostic {
5253
}
5354

5455
// ApplySettings implements rule.Configurable.
55-
func (r *Rule) ApplySettings(settings map[string]any) error {
56-
for k, v := range settings {
56+
func (r *Rule) ApplySettings(s map[string]any) error {
57+
for k, v := range s {
5758
switch k {
5859
case "max":
59-
n, ok := toInt(v)
60+
n, ok := settings.ToInt(v)
6061
if !ok {
6162
return fmt.Errorf(
6263
"max-file-length: max must be an integer, got %T", v,
@@ -77,17 +78,4 @@ func (r *Rule) DefaultSettings() map[string]any {
7778
return map[string]any{"max": 300}
7879
}
7980

80-
// toInt converts a value to int.
81-
func toInt(v any) (int, bool) {
82-
switch n := v.(type) {
83-
case int:
84-
return n, true
85-
case float64:
86-
return int(n), true
87-
case int64:
88-
return int(n), true
89-
}
90-
return 0, false
91-
}
92-
9381
var _ rule.Configurable = (*Rule)(nil)

internal/rules/nomultipleblanks/rule.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/jeduden/mdsmith/internal/lint"
99
"github.com/jeduden/mdsmith/internal/rule"
10+
"github.com/jeduden/mdsmith/internal/rules/settings"
1011
)
1112

1213
func init() {
@@ -100,11 +101,11 @@ func (r *Rule) Fix(f *lint.File) []byte {
100101
}
101102

102103
// ApplySettings implements rule.Configurable.
103-
func (r *Rule) ApplySettings(settings map[string]any) error {
104-
for k, v := range settings {
104+
func (r *Rule) ApplySettings(s map[string]any) error {
105+
for k, v := range s {
105106
switch k {
106107
case "max":
107-
n, ok := toInt(v)
108+
n, ok := settings.ToInt(v)
108109
if !ok {
109110
return fmt.Errorf("no-multiple-blanks: max must be an integer, got %T", v)
110111
}
@@ -123,17 +124,4 @@ func (r *Rule) DefaultSettings() map[string]any {
123124
}
124125
}
125126

126-
// toInt converts a value to int.
127-
func toInt(v any) (int, bool) {
128-
switch n := v.(type) {
129-
case int:
130-
return n, true
131-
case float64:
132-
return int(n), true
133-
case int64:
134-
return int(n), true
135-
}
136-
return 0, false
137-
}
138-
139127
var _ rule.Configurable = (*Rule)(nil)

internal/rules/paragraphreadability/rule.go

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/jeduden/mdsmith/internal/lint"
99
"github.com/jeduden/mdsmith/internal/mdtext"
1010
"github.com/jeduden/mdsmith/internal/rule"
11+
"github.com/jeduden/mdsmith/internal/rules/settings"
1112
"github.com/yuin/goldmark/ast"
1213
)
1314

@@ -111,11 +112,11 @@ func paragraphLine(para *ast.Paragraph, f *lint.File) int {
111112
}
112113

113114
// ApplySettings implements rule.Configurable.
114-
func (r *Rule) ApplySettings(settings map[string]any) error {
115-
for k, v := range settings {
115+
func (r *Rule) ApplySettings(s map[string]any) error {
116+
for k, v := range s {
116117
switch k {
117118
case "max-index":
118-
n, ok := toFloat(v)
119+
n, ok := settings.ToFloat(v)
119120
if !ok {
120121
return fmt.Errorf(
121122
"paragraph-readability: max-index must be a number, got %T",
@@ -124,7 +125,7 @@ func (r *Rule) ApplySettings(settings map[string]any) error {
124125
}
125126
r.MaxIndex = n
126127
case "min-words":
127-
n, ok := toInt(v)
128+
n, ok := settings.ToInt(v)
128129
if !ok {
129130
return fmt.Errorf(
130131
"paragraph-readability: min-words must be an integer, got %T",
@@ -149,30 +150,6 @@ func (r *Rule) DefaultSettings() map[string]any {
149150
}
150151
}
151152

152-
func toFloat(v any) (float64, bool) {
153-
switch n := v.(type) {
154-
case float64:
155-
return n, true
156-
case int:
157-
return float64(n), true
158-
case int64:
159-
return float64(n), true
160-
}
161-
return 0, false
162-
}
163-
164-
func toInt(v any) (int, bool) {
165-
switch n := v.(type) {
166-
case int:
167-
return n, true
168-
case float64:
169-
return int(n), true
170-
case int64:
171-
return int(n), true
172-
}
173-
return 0, false
174-
}
175-
176153
// isTable returns true if the paragraph's first line starts with a pipe,
177154
// indicating it is a markdown table (goldmark without the table extension
178155
// parses tables as paragraphs).

internal/rules/paragraphstructure/rule.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/jeduden/mdsmith/internal/lint"
99
"github.com/jeduden/mdsmith/internal/mdtext"
1010
"github.com/jeduden/mdsmith/internal/rule"
11+
"github.com/jeduden/mdsmith/internal/rules/settings"
1112
"github.com/yuin/goldmark/ast"
1213
)
1314

@@ -108,19 +109,19 @@ func paragraphLine(para *ast.Paragraph, f *lint.File) int {
108109
}
109110

110111
// ApplySettings implements rule.Configurable.
111-
func (r *Rule) ApplySettings(settings map[string]any) error {
112-
for k, v := range settings {
112+
func (r *Rule) ApplySettings(s map[string]any) error {
113+
for k, v := range s {
113114
switch k {
114115
case "max-sentences":
115-
n, ok := toInt(v)
116+
n, ok := settings.ToInt(v)
116117
if !ok {
117118
return fmt.Errorf(
118119
"paragraph-structure: max-sentences must be an integer, got %T", v,
119120
)
120121
}
121122
r.MaxSentences = n
122123
case "max-words-per-sentence":
123-
n, ok := toInt(v)
124+
n, ok := settings.ToInt(v)
124125
if !ok {
125126
return fmt.Errorf(
126127
"paragraph-structure: max-words-per-sentence must be an integer, got %T", v,
@@ -142,18 +143,6 @@ func (r *Rule) DefaultSettings() map[string]any {
142143
}
143144
}
144145

145-
func toInt(v any) (int, bool) {
146-
switch n := v.(type) {
147-
case int:
148-
return n, true
149-
case float64:
150-
return int(n), true
151-
case int64:
152-
return int(n), true
153-
}
154-
return 0, false
155-
}
156-
157146
// isTable returns true if the paragraph's first line starts with a pipe,
158147
// indicating it is a markdown table (goldmark without the table extension
159148
// parses tables as paragraphs).

0 commit comments

Comments
 (0)