Skip to content

Commit 613bae3

Browse files
committed
Fix list tightness.
According the specification, blank lines in a block quote doesn't separate list items: https://spec.commonmark.org/0.30/#example-320 Therefore, the following example should be tight: - > - a > - b The specification also say that link reference definitions can be children of list items when checking list tightness: https://spec.commonmark.org/0.30/#example-317 Therefore, the following example should be loose: - [aaa]: / [bbb]: / - b This commit fixes those problems with the following strategy: - Using source end position and start position of adjoining elements to check tightness. This requires adjusting source end position of some block types to exclude trailing blank lines. - Delaying removal of link reference definitions until the entire document is parsed.
1 parent 9a16ff4 commit 613bae3

File tree

3 files changed

+224
-69
lines changed

3 files changed

+224
-69
lines changed

lib/blocks.js

Lines changed: 69 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,10 @@ var peek = function(ln, pos) {
7474

7575
// These are methods of a Parser object, defined below.
7676

77-
// Returns true if block ends with a blank line, descending if needed
78-
// into lists and sublists.
77+
// Returns true if block ends with a blank line.
7978
var endsWithBlankLine = function(block) {
80-
while (block) {
81-
if (block._lastLineBlank) {
82-
return true;
83-
}
84-
var t = block.type;
85-
if (!block._lastLineChecked && (t === "list" || t === "item")) {
86-
block._lastLineChecked = true;
87-
block = block._lastChild;
88-
} else {
89-
block._lastLineChecked = true;
90-
break;
91-
}
92-
}
93-
return false;
79+
return block.next &&
80+
block.sourcepos[1][0] !== block.next.sourcepos[0][0] - 1;
9481
};
9582

9683
// Add a line to the block at the tip. We assume the tip
@@ -221,6 +208,43 @@ var closeUnmatchedBlocks = function() {
221208
}
222209
};
223210

211+
// Remove link reference definitions from given tree.
212+
var removeLinkReferenceDefinitions = function(parser, tree) {
213+
var event, node;
214+
var walker = tree.walker();
215+
var emptyNodes = [];
216+
217+
while ((event = walker.next())) {
218+
node = event.node;
219+
if (event.entering && node.type === "paragraph") {
220+
var pos;
221+
var hasReferenceDefs = false;
222+
223+
// Try parsing the beginning as link reference definitions;
224+
// Note that link reference definitions must be the beginning of a
225+
// paragraph node since link reference definitions cannot interrupt
226+
// paragraphs.
227+
while (
228+
peek(node._string_content, 0) === C_OPEN_BRACKET &&
229+
(pos = parser.inlineParser.parseReference(
230+
node._string_content,
231+
parser.refmap
232+
))
233+
) {
234+
node._string_content = node._string_content.slice(pos);
235+
hasReferenceDefs = true;
236+
}
237+
if (hasReferenceDefs && isBlank(node._string_content)) {
238+
emptyNodes.push(node);
239+
}
240+
}
241+
}
242+
243+
for (node of emptyNodes) {
244+
node.unlink();
245+
}
246+
};
247+
224248
// 'finalize' is run when the block is closed.
225249
// 'continue' is run to check whether the block is continuing
226250
// at a certain line and offset (e.g. whether a block quote
@@ -231,7 +255,8 @@ var blocks = {
231255
continue: function() {
232256
return 0;
233257
},
234-
finalize: function() {
258+
finalize: function(parser, block) {
259+
removeLinkReferenceDefinitions(parser, block);
235260
return;
236261
},
237262
canContain: function(t) {
@@ -247,7 +272,7 @@ var blocks = {
247272
var item = block._firstChild;
248273
while (item) {
249274
// check for non-final list item ending with blank line:
250-
if (endsWithBlankLine(item) && item._next) {
275+
if (item._next && endsWithBlankLine(item)) {
251276
block._listData.tight = false;
252277
break;
253278
}
@@ -256,8 +281,8 @@ var blocks = {
256281
var subitem = item._firstChild;
257282
while (subitem) {
258283
if (
259-
endsWithBlankLine(subitem) &&
260-
(item._next || subitem._next)
284+
subitem._next &&
285+
endsWithBlankLine(subitem)
261286
) {
262287
block._listData.tight = false;
263288
break;
@@ -266,6 +291,7 @@ var blocks = {
266291
}
267292
item = item._next;
268293
}
294+
block.sourcepos[1] = block._lastChild.sourcepos[1];
269295
},
270296
canContain: function(t) {
271297
return t === "item";
@@ -320,7 +346,16 @@ var blocks = {
320346
}
321347
return 0;
322348
},
323-
finalize: function() {
349+
finalize: function(parser, block) {
350+
if (block._lastChild) {
351+
block.sourcepos[1] = block._lastChild.sourcepos[1];
352+
} else {
353+
// Empty list item
354+
block.sourcepos[1][0] = block.sourcepos[0][0];
355+
block.sourcepos[1][1] =
356+
block._listData.markerOffset + block._listData.padding;
357+
}
358+
324359
return;
325360
},
326361
canContain: function(t) {
@@ -402,10 +437,17 @@ var blocks = {
402437
block._literal = rest;
403438
} else {
404439
// indented
405-
block._literal = block._string_content.replace(
406-
/(\n *)+$/,
407-
"\n"
408-
);
440+
var lines = block._string_content.split("\n");
441+
// Note that indented code block cannot be empty, so
442+
// lines.length cannot be zero.
443+
while (/^[ \t]*$/.test(lines[lines.length - 1])) {
444+
lines.pop();
445+
}
446+
block._literal = lines.join("\n") + "\n";
447+
block.sourcepos[1][0] =
448+
block.sourcepos[0][0] + lines.length - 1;
449+
block.sourcepos[1][1] =
450+
block.sourcepos[0][1] + lines[lines.length - 1].length - 1;
409451
}
410452
block._string_content = null; // allow GC
411453
},
@@ -435,24 +477,8 @@ var blocks = {
435477
continue: function(parser) {
436478
return parser.blank ? 1 : 0;
437479
},
438-
finalize: function(parser, block) {
439-
var pos;
440-
var hasReferenceDefs = false;
441-
442-
// try parsing the beginning as link reference definitions:
443-
while (
444-
peek(block._string_content, 0) === C_OPEN_BRACKET &&
445-
(pos = parser.inlineParser.parseReference(
446-
block._string_content,
447-
parser.refmap
448-
))
449-
) {
450-
block._string_content = block._string_content.slice(pos);
451-
hasReferenceDefs = true;
452-
}
453-
if (hasReferenceDefs && isBlank(block._string_content)) {
454-
block.unlink();
455-
}
480+
finalize: function() {
481+
return;
456482
},
457483
canContain: function() {
458484
return false;
@@ -835,33 +861,9 @@ var incorporateLine = function(ln) {
835861

836862
// finalize any blocks not matched
837863
this.closeUnmatchedBlocks();
838-
if (this.blank && container.lastChild) {
839-
container.lastChild._lastLineBlank = true;
840-
}
841864

842865
t = container.type;
843866

844-
// Block quote lines are never blank as they start with >
845-
// and we don't count blanks in fenced code for purposes of tight/loose
846-
// lists or breaking out of lists. We also don't set _lastLineBlank
847-
// on an empty list item, or if we just closed a fenced block.
848-
var lastLineBlank =
849-
this.blank &&
850-
!(
851-
t === "block_quote" ||
852-
(t === "code_block" && container._isFenced) ||
853-
(t === "item" &&
854-
!container._firstChild &&
855-
container.sourcepos[0][0] === this.lineNumber)
856-
);
857-
858-
// propagate lastLineBlank up through parents:
859-
var cont = container;
860-
while (cont) {
861-
cont._lastLineBlank = lastLineBlank;
862-
cont = cont._parent;
863-
}
864-
865867
if (this.blocks[t].acceptsLines) {
866868
this.addLine();
867869
// if HtmlBlock, check for end condition

lib/node.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ var Node = function(nodeType, sourcepos) {
7474
this._prev = null;
7575
this._next = null;
7676
this._sourcepos = sourcepos;
77-
this._lastLineBlank = false;
78-
this._lastLineChecked = false;
7977
this._open = true;
8078
this._string_content = null;
8179
this._literal = null;

test/regression.txt

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,158 @@ x <!A>
253253
<p>x <!A></p>
254254
````````````````````````````````
255255

256+
Block-quoted blank line shouldn't make parent list loose.
257+
```````````````````````````````` example
258+
## Case 1
259+
260+
- > a
261+
>
262+
- b
263+
264+
265+
## Case 2
266+
267+
- > - a
268+
>
269+
- b
270+
271+
272+
## Case 3
273+
274+
- > > a
275+
>
276+
- b
277+
278+
279+
## Case 4
280+
281+
- > # a
282+
>
283+
- b
284+
285+
286+
## Case 5
287+
288+
- ```
289+
The following line is part of code block.
290+
291+
- b
292+
293+
## Case 6
294+
295+
- The following line is **not** part of code block.
296+
297+
- b
298+
.
299+
<h2>Case 1</h2>
300+
<ul>
301+
<li>
302+
<blockquote>
303+
<p>a</p>
304+
</blockquote>
305+
</li>
306+
<li>b</li>
307+
</ul>
308+
<h2>Case 2</h2>
309+
<ul>
310+
<li>
311+
<blockquote>
312+
<ul>
313+
<li>a</li>
314+
</ul>
315+
</blockquote>
316+
</li>
317+
<li>b</li>
318+
</ul>
319+
<h2>Case 3</h2>
320+
<ul>
321+
<li>
322+
<blockquote>
323+
<blockquote>
324+
<p>a</p>
325+
</blockquote>
326+
</blockquote>
327+
</li>
328+
<li>b</li>
329+
</ul>
330+
<h2>Case 4</h2>
331+
<ul>
332+
<li>
333+
<blockquote>
334+
<h1>a</h1>
335+
</blockquote>
336+
</li>
337+
<li>b</li>
338+
</ul>
339+
<h2>Case 5</h2>
340+
<ul>
341+
<li>
342+
<pre><code>The following line is part of code block.
343+
344+
</code></pre>
345+
</li>
346+
<li>b</li>
347+
</ul>
348+
<h2>Case 6</h2>
349+
<ul>
350+
<li>
351+
<pre><code>The following line is **not** part of code block.
352+
</code></pre>
353+
</li>
354+
<li>
355+
<p>b</p>
356+
</li>
357+
</ul>
358+
````````````````````````````````
359+
360+
Link reference definitions are blocks when checking list tightness.
361+
```````````````````````````````` example
362+
## Case 1
363+
364+
- [aaa]: /
365+
366+
[aaa]: /
367+
- b
368+
369+
370+
## Case 2
371+
372+
- a
373+
374+
[aaa]: /
375+
- b
376+
377+
378+
## Case 3
379+
380+
- [aaa]: /
381+
382+
a
383+
- b
384+
.
385+
<h2>Case 1</h2>
386+
<ul>
387+
<li></li>
388+
<li>
389+
<p>b</p>
390+
</li>
391+
</ul>
392+
<h2>Case 2</h2>
393+
<ul>
394+
<li>
395+
<p>a</p>
396+
</li>
397+
<li>
398+
<p>b</p>
399+
</li>
400+
</ul>
401+
<h2>Case 3</h2>
402+
<ul>
403+
<li>
404+
<p>a</p>
405+
</li>
406+
<li>
407+
<p>b</p>
408+
</li>
409+
</ul>
410+
````````````````````````````````

0 commit comments

Comments
 (0)