|
| 1 | +--- |
| 2 | +id: 125 |
| 3 | +title: No space inside link text rule |
| 4 | +status: "🔲" |
| 5 | +summary: >- |
| 6 | + New rule MDS049 that flags Markdown links and |
| 7 | + images whose visible text has leading or trailing |
| 8 | + whitespace inside the brackets. Closes the gap with |
| 9 | + markdownlint MD039. |
| 10 | +model: sonnet |
| 11 | +--- |
| 12 | +# No space inside link text rule |
| 13 | + |
| 14 | +## Goal |
| 15 | + |
| 16 | +Let users forbid stray whitespace inside the visible |
| 17 | +text of links and images. `[ click here ](url)` |
| 18 | +renders the spaces as part of the underlined link |
| 19 | +text in most renderers, which looks broken. The same |
| 20 | +applies to image alt text. markdownlint covers this |
| 21 | +as [MD039][md039]; mdsmith does not. |
| 22 | + |
| 23 | +## Background |
| 24 | + |
| 25 | +### What goldmark exposes |
| 26 | + |
| 27 | +Inline links are `*ast.Link`. Inline images are |
| 28 | +`*ast.Image`. The visible text is the node's child |
| 29 | +sequence, but the rule needs the *source bytes* |
| 30 | +inside the `[...]` brackets to detect whitespace |
| 31 | +flanking the text — the AST already trims to text |
| 32 | +nodes that may have lost the whitespace. |
| 33 | + |
| 34 | +The rule reads `f.Source` between the opening `[` and |
| 35 | +the closing `]` for each link/image node and inspects |
| 36 | +the boundary bytes. |
| 37 | + |
| 38 | +### Why a separate rule |
| 39 | + |
| 40 | +MDS012 (no-bare-urls) and MDS027 (cross-file |
| 41 | +reference integrity) handle URLs and link targets. |
| 42 | +Neither looks at the *text* between the brackets. A |
| 43 | +dedicated rule keeps text-formatting policy |
| 44 | +independent of URL policy. |
| 45 | + |
| 46 | +Reference-style links are out of scope here — |
| 47 | +[plan 107](107_no-reference-style.md) forbids them |
| 48 | +entirely. Until that rule is enabled, MDS049 also |
| 49 | +applies to reference-link text (`[ text ][ref]`) |
| 50 | +because the text portion is still visible. |
| 51 | + |
| 52 | +## Design |
| 53 | + |
| 54 | +### Configuration |
| 55 | + |
| 56 | +```yaml |
| 57 | +rules: |
| 58 | + no-space-in-link-text: |
| 59 | + enabled: true |
| 60 | + check-images: true |
| 61 | +``` |
| 62 | +
|
| 63 | +Category: `link`. Disabled by default (opt-in). |
| 64 | +`check-images` allows opting out of image alt-text |
| 65 | +checking when MDS032 (no-empty-alt-text) is doing |
| 66 | +heavier alt-text work. |
| 67 | + |
| 68 | +### Detection |
| 69 | + |
| 70 | +Walk `*ast.Link` and `*ast.Image`. For each node: |
| 71 | + |
| 72 | +1. Locate the bytes between the opening `[` and the |
| 73 | + matching closing `]` in `f.Source`. |
| 74 | +2. If the first byte is whitespace (space, tab), |
| 75 | + emit `link text has leading whitespace` (or |
| 76 | + `image alt text has leading whitespace`). |
| 77 | +3. If the last byte is whitespace, emit the |
| 78 | + trailing-whitespace variant. |
| 79 | +4. Skip when `check-images: false` and the node is |
| 80 | + `*ast.Image`. |
| 81 | + |
| 82 | +Newlines inside the brackets are *not* flagged — |
| 83 | +they often reflect intentional wrapping of long |
| 84 | +link text. |
| 85 | + |
| 86 | +### Auto-fix |
| 87 | + |
| 88 | +Trim leading and trailing whitespace inside the |
| 89 | +brackets. Keep the brackets and any trailing |
| 90 | +reference label or URL parenthetical untouched. |
| 91 | + |
| 92 | +### Error messages |
| 93 | + |
| 94 | +```text |
| 95 | +link text has leading whitespace |
| 96 | +link text has trailing whitespace |
| 97 | +image alt text has leading whitespace |
| 98 | +image alt text has trailing whitespace |
| 99 | +``` |
| 100 | + |
| 101 | +## Tasks |
| 102 | + |
| 103 | +1. Scaffold `internal/rules/nospaceinlinktext/` with |
| 104 | + `rule.go`, `rule_test.go`, and the `init()` |
| 105 | + `rule.Register` call. |
| 106 | +2. Implement `Check()` walking `*ast.Link` and |
| 107 | + `*ast.Image`, locating the bracket span in |
| 108 | + `f.Source`. |
| 109 | +3. Implement `rule.Configurable` for `check-images`. |
| 110 | +4. Implement `Fix()` that trims whitespace inside |
| 111 | + the brackets. |
| 112 | +5. Implement `rule.Defaultable` returning `false`. |
| 113 | +6. Register as MDS049 in category `link`. |
| 114 | +7. Add fixture tests in |
| 115 | + `internal/rules/MDS049-no-space-in-link-text/` |
| 116 | + covering: clean link, leading space, trailing |
| 117 | + space, both, image alt with leading space, |
| 118 | + reference link with whitespace text, and a link |
| 119 | + whose text wraps across a newline (not flagged). |
| 120 | +8. Add rule README following the MDS012 template. |
| 121 | + |
| 122 | +## Acceptance Criteria |
| 123 | + |
| 124 | +- [ ] `[text](url)` emits no diagnostic. |
| 125 | +- [ ] `[ text ](url)` emits leading and trailing |
| 126 | + diagnostics and fixes to `[text](url)`. |
| 127 | +- [ ] `[text ](url)` emits one trailing diagnostic. |
| 128 | +- [ ] `` emits two diagnostics with |
| 129 | + `image alt text` wording and fixes to |
| 130 | + ``. |
| 131 | +- [ ] `` emits no diagnostic when |
| 132 | + `check-images: false`. |
| 133 | +- [ ] A link whose text spans two source lines |
| 134 | + (newline between words) emits no diagnostic. |
| 135 | +- [ ] `[ text ][ref]` emits diagnostics on the text |
| 136 | + portion only. |
| 137 | +- [ ] Rule is disabled by default. |
| 138 | +- [ ] All tests pass: `go test ./...` |
| 139 | +- [ ] `go tool golangci-lint run` reports no issues |
| 140 | +- [ ] `mdsmith check .` passes on the repo with the |
| 141 | + rule disabled (no regression for existing |
| 142 | + docs). |
0 commit comments