Skip to content

Commit 2608e81

Browse files
fix: avoid task checkbox for setext heading text (#3960)
* test: cover task marker before setext heading * fix: avoid task checkbox for setext heading text * test: cover loose setext task candidate * fix: preserve loose list detection for demoted tasks * test: move setext task output coverage to specs
1 parent dba76f6 commit 2608e81

4 files changed

Lines changed: 162 additions & 9 deletions

File tree

src/Tokenizer.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,17 +414,16 @@ export class _Tokenizer<ParserOutput = string, RendererOutput = string> {
414414
for (const item of list.items) {
415415
this.lexer.state.top = false;
416416
item.tokens = this.lexer.blockTokens(item.text, []);
417-
if (item.task) {
417+
const itemToken = item.tokens[0];
418+
if (item.task && (itemToken?.type === 'text' || itemToken?.type === 'paragraph')) {
418419
// Remove checkbox markdown from item tokens
419420
item.text = item.text.replace(this.rules.other.listReplaceTask, '');
420-
if (item.tokens[0]?.type === 'text' || item.tokens[0]?.type === 'paragraph') {
421-
item.tokens[0].raw = item.tokens[0].raw.replace(this.rules.other.listReplaceTask, '');
422-
item.tokens[0].text = item.tokens[0].text.replace(this.rules.other.listReplaceTask, '');
423-
for (let i = this.lexer.inlineQueue.length - 1; i >= 0; i--) {
424-
if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[i].src)) {
425-
this.lexer.inlineQueue[i].src = this.lexer.inlineQueue[i].src.replace(this.rules.other.listReplaceTask, '');
426-
break;
427-
}
421+
itemToken.raw = itemToken.raw.replace(this.rules.other.listReplaceTask, '');
422+
itemToken.text = itemToken.text.replace(this.rules.other.listReplaceTask, '');
423+
for (let i = this.lexer.inlineQueue.length - 1; i >= 0; i--) {
424+
if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[i].src)) {
425+
this.lexer.inlineQueue[i].src = this.lexer.inlineQueue[i].src.replace(this.rules.other.listReplaceTask, '');
426+
break;
428427
}
429428
}
430429

@@ -453,6 +452,8 @@ export class _Tokenizer<ParserOutput = string, RendererOutput = string> {
453452
item.tokens.unshift(checkboxToken);
454453
}
455454
}
455+
} else if (item.task) {
456+
item.task = false;
456457
}
457458

458459
if (!list.loose) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<h1>tight</h1>
2+
<ul>
3+
<li><h2>[x] title</h2>
4+
</li>
5+
</ul>
6+
<h1>unchecked</h1>
7+
<ul>
8+
<li><h2>[ ] title</h2>
9+
</li>
10+
</ul>
11+
<h1>loose</h1>
12+
<ul>
13+
<li><h2>[x] title</h2>
14+
<p>body</p>
15+
</li>
16+
<li><p>second</p>
17+
</li>
18+
</ul>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# tight
2+
3+
- [x] title
4+
---
5+
6+
# unchecked
7+
8+
- [ ] title
9+
---
10+
11+
# loose
12+
13+
- [x] title
14+
---
15+
16+
body
17+
- second

test/unit/Lexer.test.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,123 @@ paragraph
12971297
});
12981298
});
12991299

1300+
it('does not parse setext heading text as a task checkbox', () => {
1301+
expectTokens({
1302+
md: '- [x] title\n ---',
1303+
tokens: [
1304+
{
1305+
type: 'list',
1306+
raw: '- [x] title\n ---',
1307+
ordered: false,
1308+
start: '',
1309+
loose: false,
1310+
items: [
1311+
{
1312+
type: 'list_item',
1313+
raw: '- [x] title\n ---',
1314+
task: false,
1315+
loose: false,
1316+
text: '[x] title\n---',
1317+
tokens: [
1318+
{
1319+
type: 'heading',
1320+
raw: '[x] title\n---',
1321+
depth: 2,
1322+
text: '[x] title',
1323+
tokens: [
1324+
{
1325+
type: 'text',
1326+
raw: '[x] title',
1327+
text: '[x] title',
1328+
},
1329+
],
1330+
},
1331+
],
1332+
},
1333+
],
1334+
},
1335+
],
1336+
});
1337+
});
1338+
1339+
it('keeps setext heading task candidates loose when separated by blank lines', () => {
1340+
expectTokens({
1341+
md: '- [x] title\n ---\n\n body\n- second',
1342+
tokens: [
1343+
{
1344+
type: 'list',
1345+
raw: '- [x] title\n ---\n\n body\n- second',
1346+
ordered: false,
1347+
start: '',
1348+
loose: true,
1349+
items: [
1350+
{
1351+
type: 'list_item',
1352+
raw: '- [x] title\n ---\n\n body\n',
1353+
task: false,
1354+
loose: true,
1355+
text: '[x] title\n---\n\nbody',
1356+
tokens: [
1357+
{
1358+
type: 'heading',
1359+
raw: '[x] title\n---',
1360+
depth: 2,
1361+
text: '[x] title',
1362+
tokens: [
1363+
{
1364+
type: 'text',
1365+
raw: '[x] title',
1366+
text: '[x] title',
1367+
},
1368+
],
1369+
},
1370+
{
1371+
type: 'space',
1372+
raw: '\n\n',
1373+
},
1374+
{
1375+
type: 'paragraph',
1376+
raw: 'body',
1377+
text: 'body',
1378+
tokens: [
1379+
{
1380+
type: 'text',
1381+
raw: 'body',
1382+
text: 'body',
1383+
escaped: false,
1384+
},
1385+
],
1386+
},
1387+
],
1388+
},
1389+
{
1390+
type: 'list_item',
1391+
raw: '- second',
1392+
task: false,
1393+
loose: true,
1394+
text: 'second',
1395+
tokens: [
1396+
{
1397+
type: 'paragraph',
1398+
raw: 'second',
1399+
text: 'second',
1400+
tokens: [
1401+
{
1402+
type: 'text',
1403+
raw: 'second',
1404+
text: 'second',
1405+
escaped: false,
1406+
},
1407+
],
1408+
},
1409+
],
1410+
},
1411+
],
1412+
},
1413+
],
1414+
});
1415+
});
1416+
13001417
it('multiline', () => {
13011418
expectTokens({
13021419
md: `

0 commit comments

Comments
 (0)