Skip to content

Commit b62004b

Browse files
author
merge-queue-bot
committed
Merge PR #675: feat(MDS070): add same-file-anchor rule for parse-skip-safe fragment resolution
2 parents f429926 + 1626a2e commit b62004b

17 files changed

Lines changed: 964 additions & 16 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,5 @@ footer: |
217217
| 2606192026 || sonnet | [Add per-goroutine recover() to CLI engine runner worker goroutines](plan/2606192026_engine-runner-panic-recovery.md) |
218218
| 2606192027 || sonnet | [Security hardening batch — 2026-06-19 full-repo audit (low/info)](plan/2606192027_security-hardening-batch-2026-06-19-full-repo.md) |
219219
| 2606202100 | 🔲 | opus | [Parity perf: make InlineBlocks a light inline scan, not a goldmark re-parse](plan/2606202100_parity-light-inline-scan.md) |
220-
| 2606210840 | 🔲 | sonnet | [Same-file anchor-resolution rule for true gomarklint parity](plan/2606210840_same-file-anchor-resolution-rule.md) |
220+
| 2606210840 | | sonnet | [Same-file anchor-resolution rule for true gomarklint parity](plan/2606210840_same-file-anchor-resolution-rule.md) |
221221
<?/catalog?>

docs/reference/markdownlint-mapping.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ row-expr: |
8181
| MD046 code-block-style | [MDS065](../../internal/rules/MDS065-code-block-style/README.md) code-block-style |
8282
| MD014 commands-show-output | [MDS066](../../internal/rules/MDS066-commands-show-output/README.md) commands-show-output |
8383
| MD054 link-image-style | [MDS068](../../internal/rules/MDS068-link-style/README.md) link-style |
84+
| MD051 link-fragments | [MDS070](../../internal/rules/MDS070-same-file-anchor/README.md) same-file-anchor |
8485
<?/catalog?>
8586

8687
## See also

docs/research/benchmarks/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ Disabled defaults:
414414
| MDS066 commands-show-output |
415415
| MDS069 unique-frontmatter |
416416

417-
**`mado-parity`** — enables 8 opt-in rules, disables 23 defaults:
417+
**`mado-parity`** — enables 8 opt-in rules, disables 24 defaults:
418418

419419
Enabled opt-in rules:
420420

@@ -456,6 +456,7 @@ Disabled defaults:
456456
| MDS054 no-undefined-reference-labels |
457457
| MDS062 link-validity |
458458
| MDS069 unique-frontmatter |
459+
| MDS070 same-file-anchor |
459460

460461
**`rumdl-parity`** — enables 12 opt-in rules, disables 13 defaults:
461462

docs/research/benchmarks/parity-rules.fragment.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Disabled defaults:
4242
| MDS066 commands-show-output |
4343
| MDS069 unique-frontmatter |
4444

45-
**`mado-parity`** — enables 8 opt-in rules, disables 23 defaults:
45+
**`mado-parity`** — enables 8 opt-in rules, disables 24 defaults:
4646

4747
Enabled opt-in rules:
4848

@@ -84,6 +84,7 @@ Disabled defaults:
8484
| MDS054 no-undefined-reference-labels |
8585
| MDS062 link-validity |
8686
| MDS069 unique-frontmatter |
87+
| MDS070 same-file-anchor |
8788

8889
**`rumdl-parity`** — enables 12 opt-in rules, disables 13 defaults:
8990

docs/research/markdownlint-coverage/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ row-expr: |
521521
| [MDS054](../../../internal/rules/MDS054-no-undefined-reference-labels/README.md) no-undefined-reference-labels | MD052 ✅ reference-links-images | MD052 ✅ reference-links-images || undefined-references ✅ |||
522522
| [MDS062](../../../internal/rules/MDS062-link-validity/README.md) link-validity | MD011 ✅ no-reversed-links, MD042 ✅ no-empty-links | MD011 ✅ reversed-link, MD042 ✅ no-empty-links |||| no-empty-links ✅ |
523523
| [MDS068](../../../internal/rules/MDS068-link-style/README.md) link-style | MD054 ✅ link-image-style | MD054 ✅ link-image-style |||||
524+
| [MDS070](../../../internal/rules/MDS070-same-file-anchor/README.md) same-file-anchor | MD051 ✅ link-fragments | MD051 ✅ link-fragments |||| link-fragments ✅ |
524525
<?/catalog?>
525526

526527
## Tables

internal/convention/convention.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,11 @@ var conventions = map[string]Convention{
258258
// (MDS027). The peers' `link-fragments`/MD051 rules resolve only
259259
// same-file anchors, while mdsmith's MDS027 also walks the workspace
260260
// for cross-file links, so those mappings are partial. Dropping
261-
// MDS027 keeps gomarklint-parity and mado-parity fully
262-
// parse-skip-safe; a future same-file-anchors rule could restore a
263-
// like-for-like anchor check (plan
264-
// 2606210840_same-file-anchor-resolution-rule.md).
261+
// MDS027 keeps parity sets fully parse-skip-safe.
262+
// MDS070 (same-file-anchor) is the like-for-like same-file anchor
263+
// check: gomarklint, markdownlint, and rumdl run it by default so
264+
// their parity sets inherit the mdsmith default (enabled); mado has
265+
// no peer rule so mado-parity disables it.
265266
"gomarklint-parity": {
266267
Name: "gomarklint-parity",
267268
Flavor: FlavorGFM,
@@ -311,7 +312,7 @@ var conventions = map[string]Convention{
311312
"no-space-in-link-text": {Enabled: true},
312313
"ordered-list-numbering": {Enabled: true},
313314
"single-h1": {Enabled: true},
314-
// Disable the 23 mdsmith defaults mado does not run by default.
315+
// Disable the 24 mdsmith defaults mado does not run by default.
315316
"blank-line-around-lists": {Enabled: false},
316317
"build": {Enabled: false},
317318
"catalog": {Enabled: false},
@@ -329,6 +330,7 @@ var conventions = map[string]Convention{
329330
"paragraph-readability": {Enabled: false},
330331
"recipe-safety": {Enabled: false},
331332
"required-structure": {Enabled: false},
333+
"same-file-anchor": {Enabled: false},
332334
"table-format": {Enabled: false},
333335
"table-readability": {Enabled: false},
334336
"toc": {Enabled: false},

internal/integration/rules_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import (
7171
_ "github.com/jeduden/mdsmith/internal/rules/requiredmentions"
7272
_ "github.com/jeduden/mdsmith/internal/rules/requiredstructure"
7373
_ "github.com/jeduden/mdsmith/internal/rules/requiredtextpatterns"
74+
_ "github.com/jeduden/mdsmith/internal/rules/samefileanchor"
7475
_ "github.com/jeduden/mdsmith/internal/rules/singleh1"
7576
_ "github.com/jeduden/mdsmith/internal/rules/singletrailingnewline"
7677
_ "github.com/jeduden/mdsmith/internal/rules/tableformat"

internal/integration/testdata/rule_walk_audit.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,5 +746,16 @@
746746
"is_node_checker": false,
747747
"uses_ast_walk": false,
748748
"reads_file_ast": false
749+
},
750+
{
751+
"id": "MDS070",
752+
"name": "same-file-anchor",
753+
"category": "A-no-skipping",
754+
"nil_ast_safe": true,
755+
"code_block_sensitive": false,
756+
"fired": true,
757+
"is_node_checker": false,
758+
"uses_ast_walk": true,
759+
"reads_file_ast": true
749760
}
750761
]

internal/rulelayer/rule_walk_audit.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,5 +746,16 @@
746746
"is_node_checker": false,
747747
"uses_ast_walk": false,
748748
"reads_file_ast": false
749+
},
750+
{
751+
"id": "MDS070",
752+
"name": "same-file-anchor",
753+
"category": "A-no-skipping",
754+
"nil_ast_safe": true,
755+
"code_block_sensitive": false,
756+
"fired": true,
757+
"is_node_checker": false,
758+
"uses_ast_walk": true,
759+
"reads_file_ast": true
749760
}
750761
]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
id: MDS070
3+
name: same-file-anchor
4+
status: ready
5+
description: >-
6+
Every same-file #fragment link must resolve to a heading present in the
7+
same file.
8+
category: link
9+
nature: structure
10+
maintainability: null
11+
markdownlint:
12+
- id: MD051
13+
name: link-fragments
14+
partial: false
15+
default: true
16+
rumdl:
17+
- id: MD051
18+
name: link-fragments
19+
partial: false
20+
default: true
21+
mado: []
22+
panache: []
23+
obsidian-linter: []
24+
gomarklint:
25+
- id: link-fragments
26+
name: link-fragments
27+
partial: false
28+
default: true
29+
---
30+
# MDS070: same-file-anchor
31+
32+
Every same-file #fragment link must resolve to a heading present in the same file.
33+
34+
A same-file fragment link has a destination beginning with `#` and no path
35+
component — for example `[text](#my-heading)`.
36+
37+
The rule computes GitHub-flavored Markdown heading slugs: lowercase, spaces
38+
become `-`, and non-alphanumeric characters except `-` are removed. Any
39+
fragment that does not match a slug in the file is reported.
40+
41+
This rule is parse-skip-safe: it does not require a goldmark AST. It works
42+
on the Layer 0 block-span projection and the shared inline-block parser.
43+
44+
## Config
45+
46+
Enable (default):
47+
48+
```yaml
49+
rules:
50+
same-file-anchor: true
51+
```
52+
53+
Disable:
54+
55+
```yaml
56+
rules:
57+
same-file-anchor: false
58+
```
59+
60+
## Examples
61+
62+
### Bad
63+
64+
```markdown
65+
# My Heading
66+
67+
See [link](#nonexistent-section).
68+
```
69+
70+
Reports: `same-file anchor #nonexistent-section does not match any heading in this file`
71+
72+
### Good
73+
74+
```markdown
75+
# My Heading
76+
77+
See [link](#my-heading).
78+
79+
## Another Section
80+
81+
See [another link](#another-section).
82+
```
83+
84+
## See also
85+
86+
- [MDS027](../MDS027-cross-file-reference-integrity/) — cross-file link and
87+
anchor resolution; this rule handles only same-file `#fragment` links
88+
89+
## Meta-Information
90+
91+
- **ID**: MDS070
92+
- **Name**: `same-file-anchor`
93+
- **Status**: ready
94+
- **Default**: enabled
95+
- **Fixable**: no
96+
- **Implementation**: [source](./)
97+
- **Category**: link
98+
- **markdownlint**: [MD051][mdl-md051] (link-fragments)
99+
- **rumdl**: [MD051][rumdl-md051] (link-fragments)
100+
- **gomarklint**: [link-fragments][gomarklint-rules]
101+
102+
[mdl-md051]: https://github.com/DavidAnson/markdownlint/blob/main/doc/md051.md
103+
[rumdl-md051]: https://rumdl.dev/md051/
104+
[gomarklint-rules]: https://shinagawa-web.github.io/gomarklint/docs/rules/

0 commit comments

Comments
 (0)