fix(download): keep a row-less table from crashing markdown conversion - #2187
Merged
Merged
Conversation
Benjamin-eecs
marked this pull request as ready for review
July 27, 2026 10:38
jackwener
force-pushed
the
fix/rowless-table-crash
branch
from
July 29, 2026 12:05
d58e32d to
047a3a0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
turndown-plugin-gfmreadstable.rows[0].parentNodeinisHeadingRow, and it does so from two places: thetablerule's filter and thekeepfilter it installs. A<table>that carries notrmakesrows[0]undefined, so the read throwsCannot read properties of undefined (reading 'parentNode')while turndown is still deciding which rule applies, before either filter can return a verdict. The throw escapesdownloadArticle()and the whole export fails withcode: UNKNOWN, so one such table costs the reader the entire document.Rich-text editors produce these tables routinely: WeChat's editor wraps plain blocks in a
<table>whose cells arediv/sectionrather thantr, which is what #2154 hit with 34 tables in one article. A bare<table></table>is enough to reproduce it.Rules registered with
addRuleare matched ahead of the plugin's own, and that covers both entry points for a different reason each:addunshifts onto the rule array so the new rule is tried before the plugin'stablerule, andforNodeexhausts that array before it ever consults thekeeplist. Every other table is untouched: a table with a heading row still converts to GFM, and a table with rows but no heading row is still preserved as raw HTML by the plugin's existingkeeprule, byte for byte. Only the crash goes away.To be explicit about what this does not do, since the report also asked for table text as line-broken markdown: heading-less tables that do have rows never crashed and still emit raw HTML, which is deliberate upstream
turndown-plugin-gfmbehaviour. Changing that is a rendering decision affecting every caller includingzhihu downloadandweb read, so it is not folded in here.This sits in the shared conversion path, so it also covers
zhihu downloadandweb read, where the same malformed table on an arbitrary URL fails the command today.Related issue: Closes #2154.
The
auditcheck fails onjs-yamladvisory GHSA-52cp-r559-cp3m, which reproduces onmainitself and is unrelated to this change; no dependency file is touched here. #2186 bumps it.Type of Change
Checklist
Documentation (if adding/modifying an adapter)
docs/adapters/(if new adapter)docs/adapters/index.mdtable (if new adapter)docs/.vitepress/config.mts(if new adapter)README.md/README.zh-CN.mdwhen command discoverability changedCliErrorsubclasses instead of rawErrorScreenshots / Output
The reported article, which failed before, now exports:
Every shape that reaches the failing read is covered, and each one throws the reported error on
main:With the rule in place the same inputs convert, keeping the text and leaving a real table alone:
The two new cases in
src/download/article-download.test.tsare genuine regression tests: restoring onlyarticle-download.tstomainand rerunning fails both withTypeError: Cannot read properties of undefined (reading 'parentNode'), the exact error from the report. The text-preservation case uses a<caption>deliberately, because it is the only child that survives inside a row-less table; any other content is foster-parented out by the HTML parser before turndown sees the tree, which would make the assertion pass without the rule doing anything.src/download,clis/zhihu,clis/weixinandclis/webpass 273 / 273 together, so the two other consumers ofdownloadArticle()are covered.npm run typecheck,npm run check:typed-error-lint,npm run check:silent-column-dropandscripts/check-doc-coverage.sh --strictall pass. No command signature, argument, or column changed, socli-manifest.jsonis untouched.