Skip to content

Parse a list item's blocks in one piece - #316

Merged
dereuromark merged 2 commits into
masterfrom
fix/list-item-one-body
Oct 16, 2026
Merged

dereuromark merged 2 commits into
masterfrom
fix/list-item-one-body

Conversation

@dereuromark

Copy link
Copy Markdown
Contributor

The list item restructure left open in #315, plus the renderer change it needs. One commit each.

Parse a list item's blocks in one piece

A list item's first block and the indented lines after a blank line were parsed separately, so a container the first block opened could not take them:

- : t

    tail

djot.js puts tail in the definition. djot-php closed the definition list and added a paragraph after it. A note (- [^n]: para, blank, indented tail) lost its second paragraph the same way. The item's lines are now collected and parsed once when the item ends. The syntax description: a list item is "a list marker followed by a space (or a newline) followed by one or more lines, indented relative to the list marker".

Two changes follow from that:

  • Any line indented past the marker belongs to the item. A paragraph indented less than an earlier one used to end the list (- a, blank, deep, blank, back put back after the list).
  • Lines keep their columns relative to the item, so [r]: /u followed by tail still continues the reference definition.

Tightness is now decided from the parsed item. The syntax description: "A list is classed as tight if it does not contain blank lines between items, or between blocks inside an item", and its example keeps a list tight when a blank line comes before a sublist. For blank lines inside nested containers the text leaves it open, so this follows djot.js, which gives a blank line to the innermost open list. A blank line inside a nested definition or note, after a nested list or note, or before a sublist does not loosen the outer list. Before, any indented non-marker line after a blank line made the list loose.

Render every paragraph of a tight item without its wrapper

The HTML renderer only unwrapped the first paragraph of a tight item:

- ```
  x
  ```
  para

djot.js renders para bare. Each direct paragraph of a tight item is now unwrapped. A paragraph with its own attributes keeps its tag, as the first one already did. With the tightness change above, a tight item can hold more than one paragraph (- A, blank, - B, blank, C), so this commit comes first.

Behavior changes

Documents render differently in these cases, hence bc-breaking:

  • content after a blank line now stays in a definition or note opened by the item's first block;
  • some lists that rendered loose are now tight (- : t, blank, para, - two; - - x, blank, - b);
  • a less indented paragraph after a deeper one stays in the item;
  • later paragraphs in a tight item lose their <p>.

Three existing tests encoded the old output and now expect the djot.js output.

Measured against djot.js

On the 4853 generated block-family inputs, djot-php differed from djot.js 9790d4d on 978 before, 972 after the renderer commit and 852 after the restructure. One case moved the other way: - [^n]: para, blank, an indented {#x}, then tail. djot.js applies the note's attribute to the paragraph after the list, which looks like the known attribute leak out of a note.

On a separate set of 1080 list item bodies (definitions, notes, quotes, code, nested lists, tight and loose, followed by a sibling or by text), matches with djot.js went from 617 to 880, and none of the 617 stopped matching.

Still differing for definitions: djot.js renders <dd> para </dd> without <p> inside a tight list, because its renderer gives a definition list the outer list's tightness. A definition list on its own renders <p> in both, so djot-php keeps the <p>.

The HTML renderer only stripped the <p> from the first paragraph of a
tight item. A paragraph after a code block, a table or a nested list kept
its tag:

    - ```
      x
      ```
      para

djot.js renders `para` bare, like the first paragraph. The renderer now
unwraps each direct paragraph child of a tight item. A paragraph with its
own attributes keeps the tag, as the first one already did.

Two tests encoded the old mixed output (`A` bare, `<p>C</p>` in the same
tight item) and now expect the djot.js output.
A list item's first block and the indented lines after a blank line were
parsed separately, so a container the first block opens could not take
them. In

    - : t

        tail

djot.js puts `tail` in the definition; djot-php closed the definition list
and added a paragraph after it. A note (`- [^n]: para`, blank, indented
`tail`) lost its second paragraph the same way. The item's lines are now
collected and parsed once when the item ends. The syntax description: a
list item is "a list marker followed by a space (or a newline) followed by
one or more lines, indented relative to the list marker".

Any line indented past the marker belongs to the item, so a paragraph
indented less than an earlier one no longer ends the list:

    - a

        deep

      back

Lines keep their columns relative to the item, so a reference definition
continued one column deeper (` [r]: /u`, `  tail`) still continues.

Tightness is decided from the parsed item. "A list is classed as tight if
it does not contain blank lines between items, or between blocks inside an
item", and the example after it keeps a list tight when a blank line comes
before a sublist. For blank lines inside nested containers the text leaves
it open, so this follows djot.js, which gives a blank line to the innermost
open list: one inside a nested definition or note, after a nested list or
note, or before a sublist does not loosen the outer list. Before, any
indented non-marker line after a blank line made the list loose, so
`- : t`, blank, `    para`, `- two` rendered loose.

A test that matched `<p>A</p>` in `- A`, blank, `  - B`, blank, `    P`,
blank, `  C` now expects the djot.js output, a tight outer list.
@dereuromark dereuromark added bug Something isn't working bc-breaking labels Oct 16, 2026
@dereuromark
dereuromark merged commit 5b9c33c into master Oct 16, 2026
4 checks passed
@dereuromark
dereuromark deleted the fix/list-item-one-body branch October 16, 2026 14:56
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.90110% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.13%. Comparing base (f55fb95) to head (38fda12).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/Parser/BlockParser.php 98.82% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #316      +/-   ##
============================================
+ Coverage     93.07%   93.13%   +0.05%     
- Complexity     4735     4757      +22     
============================================
  Files           118      118              
  Lines         12642    12706      +64     
============================================
+ Hits          11767    11834      +67     
+ Misses          875      872       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bc-breaking bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant