Skip to content

Commit 637205e

Browse files
author
merge-queue-bot
committed
Merge PR #195: Add MDS047 rule for detecting ambiguous emphasis sequences
2 parents 08e6cee + c967ceb commit 637205e

17 files changed

Lines changed: 977 additions & 20 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ footer: |
2727
| 108 | 🔲 | sonnet | [Horizontal rule style rule](plan/108_horizontal-rule-style.md) |
2828
| 109 | 🔲 | sonnet | [List marker style rule](plan/109_list-marker-style.md) |
2929
| 110 || sonnet | [Ordered list numbering rule](plan/110_ordered-list-numbering.md) |
30-
| 111 | 🔲 | sonnet | [Ambiguous emphasis rule](plan/111_ambiguous-emphasis.md) |
30+
| 111 | | sonnet | [Ambiguous emphasis rule](plan/111_ambiguous-emphasis.md) |
3131
| 112 || opus | [Markdown convention bundles for MDS034](plan/112_flavor-profiles.md) |
3232
| 113 | 🔲 | sonnet | [User-defined Markdown conventions](plan/113_user-defined-profiles.md) |
3333
| 114 || sonnet | [MDS034 message clarity and flavor-vs-rule docs](plan/114_mds034-message-and-flavor-vs-rule-docs.md) |

cmd/mdsmith/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
ruledocs "github.com/jeduden/mdsmith/internal/rules"
2525

2626
// Import all rule packages so their init() functions register rules.
27+
_ "github.com/jeduden/mdsmith/internal/rules/ambiguousemphasis"
2728
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundfencedcode"
2829
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundheadings"
2930
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundlists"

internal/config/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"gopkg.in/yaml.v3"
1212

1313
// Import all rule packages so their init() functions register rules.
14+
_ "github.com/jeduden/mdsmith/internal/rules/ambiguousemphasis"
1415
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundfencedcode"
1516
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundheadings"
1617
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundlists"

internal/engine/categories_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/stretchr/testify/require"
1313

1414
// Import all rule packages so their init() functions register rules.
15+
_ "github.com/jeduden/mdsmith/internal/rules/ambiguousemphasis"
1516
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundfencedcode"
1617
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundheadings"
1718
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundlists"

internal/integration/rules_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/yuin/goldmark/text"
1717
"go.abhg.dev/goldmark/frontmatter"
1818

19+
_ "github.com/jeduden/mdsmith/internal/rules/ambiguousemphasis"
1920
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundfencedcode"
2021
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundheadings"
2122
_ "github.com/jeduden/mdsmith/internal/rules/blanklinearoundlists"
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
id: MDS047
3+
name: ambiguous-emphasis
4+
status: ready
5+
description: Forbid emphasis sequences whose meaning a human cannot predict at a glance.
6+
---
7+
# MDS047: ambiguous-emphasis
8+
9+
Forbid emphasis sequences whose meaning a human cannot predict at a glance.
10+
11+
CommonMark pairs `*` and `_` runs by counting flanking delimiters. The
12+
output is well-defined; the source is not. This rule names three shapes
13+
that read poorly and refuses them.
14+
15+
The shapes are:
16+
17+
- long runs of the same delimiter character
18+
- backslash escapes glued to a run
19+
- the same delimiter repeated three times across word boundaries
20+
21+
The check runs on raw source bytes. It skips ranges covered by inline
22+
code spans, fenced code blocks, and indented code blocks.
23+
24+
## Settings
25+
26+
| Setting | Type | Default | Description |
27+
|------------------------------|------|---------|------------------------------------------------------------------------------|
28+
| `max-run` | int | 0 | Maximum allowed length of a contiguous `*` or `_` run; 0 disables the check |
29+
| `forbid-escaped-in-run` | bool | false | Flag `*\*` or `_\_` where a backslash-escaped delimiter butts against a run |
30+
| `forbid-adjacent-same-delim` | bool | false | Flag three same-delimiter runs glued by non-whitespace (`*a*b*`, `__a__b__`) |
31+
32+
The defaults make the rule a no-op even when enabled, so it ships safe.
33+
Profile activation in plan 112 (`portable`, `plain`) sets `max-run: 2`
34+
and both bool flags to `true`. User overrides on top still win via
35+
deep-merge.
36+
37+
## Config
38+
39+
Enable with the active profile values:
40+
41+
```yaml
42+
rules:
43+
ambiguous-emphasis:
44+
max-run: 2
45+
forbid-escaped-in-run: true
46+
forbid-adjacent-same-delim: true
47+
```
48+
49+
Disable:
50+
51+
```yaml
52+
rules:
53+
ambiguous-emphasis: false
54+
```
55+
56+
Long-run only (skip the other two detectors):
57+
58+
```yaml
59+
rules:
60+
ambiguous-emphasis:
61+
max-run: 2
62+
```
63+
64+
## Examples
65+
66+
### Bad -- long delimiter run
67+
68+
<?include
69+
file: bad/long-run.md
70+
wrap: markdown
71+
?>
72+
73+
```markdown
74+
# Title
75+
76+
***bold-italic*** at the start of a paragraph.
77+
```
78+
79+
<?/include?>
80+
81+
### Bad -- escaped delimiter inside a run
82+
83+
<?include
84+
file: bad/escaped-in-run.md
85+
wrap: markdown
86+
?>
87+
88+
```markdown
89+
# Title
90+
91+
*****\*a* in the rant string.
92+
```
93+
94+
<?/include?>
95+
96+
### Bad -- adjacent same-delimiter runs
97+
98+
<?include
99+
file: bad/adjacent-same-delim.md
100+
wrap: markdown
101+
?>
102+
103+
```markdown
104+
# Title
105+
106+
__a__b__ ambiguous between bold(a) literal-b and literal-a bold(b).
107+
```
108+
109+
<?/include?>
110+
111+
### Bad -- the rant's Peter Piper example
112+
113+
<?include
114+
file: bad/peter-piper.md
115+
wrap: markdown
116+
?>
117+
118+
```markdown
119+
# Title
120+
121+
***Peter* Piper** is the rant's other Exhibit-A example.
122+
```
123+
124+
<?/include?>
125+
126+
### Good
127+
128+
<?include
129+
file: good/default.md
130+
wrap: markdown
131+
?>
132+
133+
```markdown
134+
# Title
135+
136+
This sentence has **bold** and *italic* and even `*****\*literal*` in a
137+
code span without flagging.
138+
139+
Multiple separate emphases like *one* and *two* and *three* read
140+
naturally because spaces split the runs.
141+
```
142+
143+
<?/include?>
144+
145+
### Good -- patterns inside code spans
146+
147+
<?include
148+
file: good/in-code-span.md
149+
wrap: markdown
150+
?>
151+
152+
```markdown
153+
# In code span
154+
155+
The string `*****\*a*` inside an inline code span must not flag, and
156+
neither must `__a__b__` nor `***Peter* Piper**` when wrapped this way.
157+
```
158+
159+
<?/include?>
160+
161+
### Good -- patterns inside fenced code blocks
162+
163+
<?include
164+
file: good/in-fenced-block.md
165+
wrap: markdown
166+
?>
167+
168+
````markdown
169+
# In fenced block
170+
171+
The patterns below sit inside a fenced code block and must not flag.
172+
173+
```text
174+
*****\*a*
175+
***bold-italic***
176+
__a__b__
177+
***Peter* Piper**
178+
```
179+
````
180+
181+
<?/include?>
182+
183+
## Diagnostics
184+
185+
- `emphasis run of {n} delimiters; max is {max-run}`
186+
- `escaped delimiter inside emphasis run`
187+
- `adjacent same-delimiter emphasis is ambiguous`
188+
189+
## Edge Cases
190+
191+
- **No auto-fix.** The right rewrite depends on author intent: add a
192+
space, swap to an HTML entity, or split the run. The rule reports
193+
and lets the author choose.
194+
- **Symmetric openers and closers collapse.** `***x***` has two
195+
three-star runs but emits a single long-run diagnostic, anchored at
196+
the first occurrence on the line.
197+
- **Whitespace clears the gap.** `*a* *b* *c*` does not flag adjacent
198+
same-delimiter because each gap contains a space; CommonMark resolves
199+
the runs unambiguously.
200+
- **Interaction with MDS042.** MDS042 (emphasis-style) pins which
201+
delimiter is used. MDS047 catches the ambiguous combinations that
202+
survive a delimiter pin. Both can fire on the same line.
203+
204+
## Meta-Information
205+
206+
- **ID**: MDS047
207+
- **Name**: `ambiguous-emphasis`
208+
- **Status**: ready
209+
- **Default**: disabled
210+
- **Fixable**: no
211+
- **Implementation**:
212+
[source](./)
213+
- **Category**: meta
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
settings:
3+
max-run: 2
4+
forbid-escaped-in-run: true
5+
forbid-adjacent-same-delim: true
6+
diagnostics:
7+
- line: 3
8+
column: 1
9+
message: "adjacent same-delimiter emphasis is ambiguous"
10+
---
11+
# Title
12+
13+
__a__b__ ambiguous between bold(a) literal-b and literal-a bold(b).
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
settings:
3+
max-run: 2
4+
forbid-escaped-in-run: true
5+
forbid-adjacent-same-delim: true
6+
diagnostics:
7+
- line: 3
8+
column: 1
9+
message: "emphasis run of 5 delimiters; max is 2"
10+
- line: 3
11+
column: 6
12+
message: "escaped delimiter inside emphasis run"
13+
---
14+
# Title
15+
16+
*****\*a* in the rant string.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
settings:
3+
max-run: 2
4+
forbid-escaped-in-run: true
5+
forbid-adjacent-same-delim: true
6+
diagnostics:
7+
- line: 3
8+
column: 1
9+
message: "emphasis run of 3 delimiters; max is 2"
10+
---
11+
# Title
12+
13+
***bold-italic*** at the start of a paragraph.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
settings:
3+
max-run: 2
4+
forbid-escaped-in-run: true
5+
forbid-adjacent-same-delim: true
6+
diagnostics:
7+
- line: 3
8+
column: 1
9+
message: "emphasis run of 3 delimiters; max is 2"
10+
---
11+
# Title
12+
13+
***Peter* Piper** is the rant's other Exhibit-A example.

0 commit comments

Comments
 (0)