Use tight end positions for lazily closed blocks (sourcePositions) - #142
Conversation
Blocks that close when the parser recognizes the following construct (lists, tables, block quotes, definition lists, footnotes) reported their end position from the parse offset at close time. That included the separator blank line, and for lists the end could equal the next sibling's start, so sibling ranges overlapped and a parent list could end before its last item. Tighten these containers to end at their last child's end position, the same convention paragraphs and code fences already follow. List items are tightened as well so the parent range always contains them. Closes jgm#141.
| const tightenListEnd = function(node: Container): void { | ||
| if (!node.pos) { | ||
| return; | ||
| } | ||
| const end = getLastBlockEnd(node); | ||
| if (end) { | ||
| node.pos.end = end; | ||
| } |
There was a problem hiding this comment.
Instead of having a separate function just for lists, why not apply the tightenContainerEnd function to the list_item nodes? Then, when we get to the list nodes, the last child will already have an appropriate end position and we won't need to recurse into its children...
Since -list_item events fire before their parent -list, the last child already carries a tight end position when the list closes. The recursive getLastBlockEnd and the fallback to earlier siblings were unnecessary; one function handles all containers.
|
You were right that the recursion was unnecessary: since The whole thing is now a single small function: const tightenContainerEnd = function(node: Container): void {
const lastChild = node.children[node.children.length - 1];
if (node.pos && lastChild && lastChild.pos) {
node.pos.end = lastChild.pos.end;
}
}I kept it as a named function rather than inlining the conditional, since it is called from five handlers (list, list_item, block_quote, table, footnote). Full suite still passes. |
|
Much better! |
Fixes #141.
With
sourcePositions: true, blocks that close lazily when the parser recognizes the following construct (lists, tables, block quotes, definition lists, footnotes) reportedpos.endfrom the parse offset at close time. That included the separator blank line, and for lists the end could equal the next sibling's start:This change tightens those containers to end at their last child's end position - the convention paragraphs and code fences already follow (paragraph
"One.\n"ends at2:0:4, its trailing newline). Separator blank lines now belong to no block, and sibling ranges no longer overlap. List items are tightened as well, so a parent list's range always contains its last item (previously the tightened list could end before the lazily closed item).Same approach as the earlier end-position fixes in #83 (list_item) and #82 (section); those behaviors are unchanged.
Tests: new cases in
src/parse.spec.tsassert exact start/end for block quote, table, bullet list, definition list, and footnote, plus the following paragraph's positions and non-overlap/containment. One expectation intest/sourcepos.testencoded the old overshoot (list_item (1:2:1-2:1:5)- ending at the first character of the next item's line) and was updated to the tight value. Full suite passes.