Skip to content

Commit d52125e

Browse files
committed
fix(parser): prevent nested lists from inheriting parent title and metadata
Nested lists (e.g., ** inside *, .. inside .) were incorrectly inheriting the block title and metadata from their parent list. This caused `.Title` annotations to propagate down to all nesting levels instead of applying only to the top-level list.
1 parent 48f3130 commit d52125e

File tree

4 files changed

+219
-49
lines changed

4 files changed

+219
-49
lines changed

acdc-parser/fixtures/tests/nested_ordered_list_with_checkmarks.json

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,6 @@
7474
"type": "block",
7575
"variant": "unordered",
7676
"marker": "**",
77-
"title": [
78-
{
79-
"name": "text",
80-
"type": "string",
81-
"value": "Possible DefOps manual locations",
82-
"location": [
83-
{
84-
"line": 3,
85-
"col": 2
86-
},
87-
{
88-
"line": 3,
89-
"col": 33
90-
}
91-
]
92-
}
93-
],
9477
"items": [
9578
{
9679
"name": "listItem",
@@ -119,23 +102,6 @@
119102
"type": "block",
120103
"variant": "unordered",
121104
"marker": "***",
122-
"title": [
123-
{
124-
"name": "text",
125-
"type": "string",
126-
"value": "Possible DefOps manual locations",
127-
"location": [
128-
{
129-
"line": 3,
130-
"col": 2
131-
},
132-
{
133-
"line": 3,
134-
"col": 33
135-
}
136-
]
137-
}
138-
],
139105
"items": [
140106
{
141107
"name": "listItem",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.Lexicon and concepts
2+
* markup character
3+
** blocks are always defined by a balanced pair
4+
** the first and last items are always blocks
5+
* The end
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
{
2+
"name": "document",
3+
"type": "block",
4+
"blocks": [
5+
{
6+
"name": "list",
7+
"type": "block",
8+
"variant": "unordered",
9+
"marker": "*",
10+
"title": [
11+
{
12+
"name": "text",
13+
"type": "string",
14+
"value": "Lexicon and concepts",
15+
"location": [
16+
{
17+
"line": 1,
18+
"col": 2
19+
},
20+
{
21+
"line": 1,
22+
"col": 21
23+
}
24+
]
25+
}
26+
],
27+
"items": [
28+
{
29+
"name": "listItem",
30+
"type": "block",
31+
"marker": "*",
32+
"principal": [
33+
{
34+
"name": "text",
35+
"type": "string",
36+
"value": "markup character",
37+
"location": [
38+
{
39+
"line": 2,
40+
"col": 3
41+
},
42+
{
43+
"line": 2,
44+
"col": 18
45+
}
46+
]
47+
}
48+
],
49+
"blocks": [
50+
{
51+
"name": "list",
52+
"type": "block",
53+
"variant": "unordered",
54+
"marker": "**",
55+
"items": [
56+
{
57+
"name": "listItem",
58+
"type": "block",
59+
"marker": "**",
60+
"principal": [
61+
{
62+
"name": "text",
63+
"type": "string",
64+
"value": "blocks are always defined by a balanced pair",
65+
"location": [
66+
{
67+
"line": 3,
68+
"col": 4
69+
},
70+
{
71+
"line": 3,
72+
"col": 47
73+
}
74+
]
75+
}
76+
],
77+
"location": [
78+
{
79+
"line": 3,
80+
"col": 1
81+
},
82+
{
83+
"line": 3,
84+
"col": 47
85+
}
86+
]
87+
},
88+
{
89+
"name": "listItem",
90+
"type": "block",
91+
"marker": "**",
92+
"principal": [
93+
{
94+
"name": "text",
95+
"type": "string",
96+
"value": "the first and last items are always blocks",
97+
"location": [
98+
{
99+
"line": 4,
100+
"col": 4
101+
},
102+
{
103+
"line": 4,
104+
"col": 45
105+
}
106+
]
107+
}
108+
],
109+
"location": [
110+
{
111+
"line": 4,
112+
"col": 1
113+
},
114+
{
115+
"line": 4,
116+
"col": 45
117+
}
118+
]
119+
}
120+
],
121+
"location": [
122+
{
123+
"line": 3,
124+
"col": 1
125+
},
126+
{
127+
"line": 4,
128+
"col": 45
129+
}
130+
]
131+
}
132+
],
133+
"location": [
134+
{
135+
"line": 2,
136+
"col": 1
137+
},
138+
{
139+
"line": 4,
140+
"col": 45
141+
}
142+
]
143+
},
144+
{
145+
"name": "listItem",
146+
"type": "block",
147+
"marker": "*",
148+
"principal": [
149+
{
150+
"name": "text",
151+
"type": "string",
152+
"value": "The end",
153+
"location": [
154+
{
155+
"line": 5,
156+
"col": 3
157+
},
158+
{
159+
"line": 5,
160+
"col": 9
161+
}
162+
]
163+
}
164+
],
165+
"location": [
166+
{
167+
"line": 5,
168+
"col": 1
169+
},
170+
{
171+
"line": 5,
172+
"col": 9
173+
}
174+
]
175+
}
176+
],
177+
"location": [
178+
{
179+
"line": 1,
180+
"col": 1
181+
},
182+
{
183+
"line": 5,
184+
"col": 9
185+
}
186+
]
187+
}
188+
],
189+
"location": [
190+
{
191+
"line": 1,
192+
"col": 1
193+
},
194+
{
195+
"line": 5,
196+
"col": 9
197+
}
198+
]
199+
}

acdc-parser/src/grammar/document.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2359,8 +2359,8 @@ peg::parser! {
23592359
// to prevent nested lists from consuming parent-level continuations.
23602360
rule list_with_continuation(start: usize, offset: usize, block_metadata: &BlockParsingMetadata, allow_continuation: bool) -> Result<Block, Error>
23612361
= callout_list(start, offset, block_metadata)
2362-
/ unordered_list(start, offset, block_metadata, false, allow_continuation)
2363-
/ ordered_list(start, offset, block_metadata, false, allow_continuation)
2362+
/ unordered_list(start, offset, block_metadata, false, allow_continuation, false)
2363+
/ ordered_list(start, offset, block_metadata, false, allow_continuation, false)
23642364
/ description_list(start, offset, block_metadata)
23652365

23662366
rule unordered_list_marker() -> &'input str = $("*"+ / "-")
@@ -2448,7 +2448,7 @@ peg::parser! {
24482448
/ ("[[" [^']']+ "]]" whitespace()* eol())
24492449
)
24502450

2451-
rule unordered_list(start: usize, offset: usize, block_metadata: &BlockParsingMetadata, parent_is_ordered: bool, allow_continuation: bool) -> Result<Block, Error>
2451+
rule unordered_list(start: usize, offset: usize, block_metadata: &BlockParsingMetadata, parent_is_ordered: bool, allow_continuation: bool, is_nested: bool) -> Result<Block, Error>
24522452
// Parse whitespace + marker first to capture base_marker for rest items
24532453
// marker_start captures position before marker for correct first item location
24542454
= whitespace()* marker_start:position!() base_marker:$(unordered_list_marker()) &whitespace()
@@ -2466,8 +2466,8 @@ peg::parser! {
24662466
let marker = items.first().map_or(String::new(), |item| item.marker.clone());
24672467

24682468
Ok(Block::UnorderedList(UnorderedList {
2469-
title: block_metadata.title.clone(),
2470-
metadata: block_metadata.metadata.clone(),
2469+
title: if is_nested { Title::default() } else { block_metadata.title.clone() },
2470+
metadata: if is_nested { BlockMetadata::default() } else { block_metadata.metadata.clone() },
24712471
items,
24722472
marker,
24732473
location: state.create_location(start+offset, end+offset),
@@ -2522,7 +2522,7 @@ peg::parser! {
25222522
}
25232523
}
25242524

2525-
rule ordered_list(start: usize, offset: usize, block_metadata: &BlockParsingMetadata, parent_is_ordered: bool, allow_continuation: bool) -> Result<Block, Error>
2525+
rule ordered_list(start: usize, offset: usize, block_metadata: &BlockParsingMetadata, parent_is_ordered: bool, allow_continuation: bool, is_nested: bool) -> Result<Block, Error>
25262526
// Parse whitespace + marker first to capture base_marker for rest items
25272527
// marker_start captures position before marker for correct first item location
25282528
= whitespace()* marker_start:position!() base_marker:$(ordered_list_marker()) &whitespace()
@@ -2540,8 +2540,8 @@ peg::parser! {
25402540
let marker = items.first().map_or(String::new(), |item| item.marker.clone());
25412541

25422542
Ok(Block::OrderedList(OrderedList {
2543-
title: block_metadata.title.clone(),
2544-
metadata: block_metadata.metadata.clone(),
2543+
title: if is_nested { Title::default() } else { block_metadata.title.clone() },
2544+
metadata: if is_nested { BlockMetadata::default() } else { block_metadata.metadata.clone() },
25452545
items,
25462546
marker,
25472547
location: state.create_location(start+offset, end+offset),
@@ -2819,7 +2819,7 @@ peg::parser! {
28192819
// whitespace) from being incorrectly parsed as nested. Without this, `. item` at
28202820
// column 1 would be nested inside the parent unordered item instead of being a
28212821
// sibling list.
2822-
= !at_root_ordered_marker() nested_start:position!() list:ordered_list(nested_start, offset, block_metadata, true, false) {
2822+
= !at_root_ordered_marker() nested_start:position!() list:ordered_list(nested_start, offset, block_metadata, true, false, true) {
28232823
Some(list)
28242824
}
28252825
// Nested unordered list with deeper markers (e.g., ** inside *)
@@ -2854,8 +2854,8 @@ peg::parser! {
28542854
let marker = items.first().map_or(String::new(), |item| item.marker.clone());
28552855

28562856
Ok(Block::UnorderedList(UnorderedList {
2857-
title: block_metadata.title.clone(),
2858-
metadata: block_metadata.metadata.clone(),
2857+
title: Title::default(),
2858+
metadata: BlockMetadata::default(),
28592859
items,
28602860
marker,
28612861
location: state.create_location(start+offset, end+offset),
@@ -3105,7 +3105,7 @@ peg::parser! {
31053105
// whitespace) from being incorrectly parsed as nested. Without this, `* item` at
31063106
// column 1 would be nested inside the parent ordered item instead of being a
31073107
// sibling list.
3108-
= !at_root_unordered_marker() nested_start:position!() list:unordered_list(nested_start, offset, block_metadata, true, false) {
3108+
= !at_root_unordered_marker() nested_start:position!() list:unordered_list(nested_start, offset, block_metadata, true, false, true) {
31093109
Some(list)
31103110
}
31113111
// Nested ordered list with deeper markers (e.g., .. inside .)
@@ -3140,8 +3140,8 @@ peg::parser! {
31403140
let marker = items.first().map_or(String::new(), |item| item.marker.clone());
31413141

31423142
Ok(Block::OrderedList(OrderedList {
3143-
title: block_metadata.title.clone(),
3144-
metadata: block_metadata.metadata.clone(),
3143+
title: Title::default(),
3144+
metadata: BlockMetadata::default(),
31453145
items,
31463146
marker,
31473147
location: state.create_location(start+offset, end+offset),
@@ -3486,7 +3486,7 @@ peg::parser! {
34863486
= eol()* // Consume any blank lines before the list
34873487
&(whitespace()* (unordered_list_marker() / ordered_list_marker()) whitespace())
34883488
list_start:position!()
3489-
list:(unordered_list(list_start, offset, block_metadata, false, true) / ordered_list(list_start, offset, block_metadata, false, true))
3489+
list:(unordered_list(list_start, offset, block_metadata, false, true, true) / ordered_list(list_start, offset, block_metadata, false, true, true))
34903490
{
34913491
tracing::info!("Auto-attaching list to description list item");
34923492
Ok(vec![list?])

0 commit comments

Comments
 (0)