Skip to content

Tables after link rewriting

Link rewriting changes the length of a link target, and broken-link marking replaces a link with a longer marker. Either change moves the pipes of the table row with that link. A table that a shard aligns would then fail markdownlint’s MD060 rule in the compiled guide. So the last step of each assembly, after broken-link marking, re-aligns that table. It prints the table as Prettier does when proseWrap is preserve, Prettier’s default, or always: each column is as wide as its widest cell and three characters at least, and each cell pads to the alignment of its delimiter cell. When proseWrap is never, Prettier prints a table compact once it outgrows printWidth. Compile prints that table aligned, which MD060 accepts and Prettier’s check rejects.

Compile measures a cell in display columns. A wide or fullwidth East Asian character takes two columns, and so does an emoji shown as an emoji. A nonspacing or enclosing mark, a control character or a format character takes none. Any other character takes one, a spacing mark included. These widths match Prettier and markdownlint wherever the two tools agree.

Where Prettier and markdownlint disagree, a layout that passes one tool fails the other. Prettier gives one column to each format character, such as a zero-width space or a soft hyphen. It also gives one to each nonspacing mark outside U+0300 to U+036F, such as a Hebrew point or a Thai vowel mark. Markdownlint gives these none. Prettier counts a text-style emoji like ☝ as two columns where markdownlint counts one. Compile follows markdownlint on these. Indic text differs again. Markdownlint counts a grapheme cluster, such as a conjunct with its vowel sign, as one column. Prettier counts each code point, and compile counts each code point except a nonspacing mark, so it matches neither tool.

If a cell without a link contains such text, markdownlint’s MD060 rule already flags the table in the shard, and it flags the compiled table too. Where compile and Prettier measure that cell differently, as for a Hebrew point, compile leaves the table out of line where its links changed width. Where they agree, as for Hindi text without a nonspacing mark, compile re-aligns the table to Prettier’s layout. If only a cell with a link contains such text, compile re-aligns the table by its own measure. For text other than Indic, the result passes MD060, and Prettier’s check fails on it. Indic text fails MD060, and Prettier’s check passes only when the text has no nonspacing mark.

Compile re-aligns a table when its cells without a link match the width of their column’s delimiter cell and some cell with a link doesn’t. The delimiter row has no link, so it keeps the width the shard gave each column. A table qualifies when some cell differs in width from the delimiter cell of its column, and each such cell contains a link or a broken-link marker. That rule also re-aligns a shard table whose link cells were out of line before compile. The table must also pad some cell with more than one space, which a compact table never does. A compact or tight table stays as written, and so does a table whose pipes still line up. When something else changes the width of a cell, the table stays as compile left it. Stripping a {#id} anchor from a cell does that, and so does inlineInserts when it changes an insert link in a code span. A custom compile hook can do it too.

The padding rule misses an aligned table where each column’s cells all have the same width, since no cell then pads past one space. Compile reads that table as compact and doesn’t re-align it after a link changes width. MD060 accepts the result as a compact table, but Prettier’s check fails on it.

The table ends at a blank line or a fence, and at a line that leaves its blockquote. It also ends at a line indented as far as the rows or less that opens another block, such as a list item, a heading, a blockquote, an HTML block or a thematic break. So a table in a tight list ends at the next list item, or at a nested list right under it. A list item ends a table even where it couldn’t interrupt a paragraph, such as an item numbered 10., so re-alignment reads a fence in that item as fenced code. Every other line up to the end is a row. Each row must start and end with a pipe after the indentation and blockquote markers of the delimiter row. Compile leaves the whole table as written when any row breaks that rule or has more cells than the delimiter row. A tab anywhere in a row also leaves the table as written. When compile leaves a table, it leaves every row up to the table’s end, even a row that looks like a delimiter row.

A table in a list item or a blockquote keeps its indentation and its markers. A table in more than ten nested blockquotes stays as written, since finding the fences of each quote around it takes time that grows with the square of the depth. When a table opens its list item, Prettier prints the header row on the marker’s line, such as - | Page | Purpose |, and indents the other rows to the item’s text. Compile re-aligns that table when the marker is as wide as that indentation, and keeps the marker on the header row. Compile leaves a table whose header row sits in fewer blockquotes than a non-blank line right above it, or in as many and indented less than that line’s text, unless the header row opens a list item. That row may continue the paragraph above it, and GFM then doesn’t read a table there. Compile doesn’t re-align a table in fenced code, in a blockquote or out of one. The link passes, codeEvidence and broken-link marking leave the links in fenced code as written. Their fence scan reads a table as a paragraph, though, so a list item that can’t interrupt one, such as 10., doesn’t open a fence for them. A table in that fence can end up out of line. inlineInserts still changes an insert link in fenced code, as inlineInserts link matching says, so a fenced table that links to an insert can also end up out of line. Compile reads an indented code block or an HTML block as prose, as link rewriting does, so it re-aligns a table there too. When the line right after the last row closes a comment, such as -->, compile reads that line as one more row and leaves the table as written. A pipe escaped as \| stays in its cell, in a code span or out of one.

Code: realignTables in packages/merlon-docs-core/src/compile/align-tables.ts, which measures cells with displayWidth in packages/merlon-docs-core/src/markdown/display-width.ts. Tests in packages/merlon-docs-core/test/align-tables.test.ts and packages/merlon-docs-core/test/display-width.test.ts map to this section.