Skip to content

refactor(markdown): replace tree-sitter kind mimicry with pulldown-native NodeKind#174

Merged
tinovyatkin merged 3 commits into
mainfrom
refactor/markdown-pulldown-native-nodekind
Jul 10, 2026
Merged

refactor(markdown): replace tree-sitter kind mimicry with pulldown-native NodeKind#174
tinovyatkin merged 3 commits into
mainfrom
refactor/markdown-pulldown-native-nodekind

Conversation

@tinovyatkin

Copy link
Copy Markdown
Contributor

Why

mehen-markdown migrated off tree-sitter to pulldown-cmark, but it kept the generated tree-sitter kind table (src/grammar.rs) as its internal node vocabulary and wrapped the owned tree in a tree-sitter-shaped API. That mimicry cost us without buying anything:

  • grammar.rs — a 685-line u16 Markdown enum with grammar ordinals, ~half of whose variants (MdxJsx*, HtmlBlock1/3..7, _line_repeat1, …) pulldown-cmark can never construct.
  • A kind_id() -> u16 + From<u16> round-trip at 48 call sites, even though each node already stores the enum.
  • A mutable Cursor (goto_first_child/goto_next_sibling) driving 45 hand-rolled child-iteration loops, when all navigation in the crate is strictly top-down.
  • A named-field table used for only two fields (level, content), one of which fed dead code.
  • A ~330-line dead From<Markdown> for &'static str impl that escaped the dead-code lint via the trait-impl exemption.

This surfaced from the question "why does mehen-markdown/src/grammar.rs exist?" — it's a fossil from the pre-migration tree-sitter implementation, repurposed as a costume the crate no longer needs.

What

Replace the generated enum with a hand-authored NodeKind (src/kind.rs) modeling only the ~62 kinds the builder actually emits, and make the owned tree pulldown-native:

Concern Before (tree-sitter costume) After (pulldown-native)
Node vocabulary 321-variant u16 enum, ~half dead 62 hand-authored variants, all constructed
Kind access kind_id() -> u16 then .into() ×48 kind() -> NodeKind directly
Traversal Cursor + goto_* (45 loops) children() -> impl Iterator
Level / flags encoded in the type (AtxHeading2), read via marker child + child_by_field_name("level") data on the variant: Heading { level, style }
Deps num, num-derive, num-traits dropped (only grammar.rs used them)

Numbered tree-sitter families fold into data-carrying variants — AtxHeading2..6/SetextHeading2Heading { level, style }, Section1..6Section { level }, ListItem2..5/TaskListItem*ListItem { task } — because every consumer matched them as whole groups, never individually.

Behavior preservation

This is a behavior-preserving refactor — no metric formula, weight, threshold, or control-flow change. The two spots folding could have broken are explicitly preserved:

  • Halstead per-level heading-marker operators (###) via HeadingMarker { level, style } + a heading_marker_op() mapping.
  • MCC list-vs-task cognitive weights (0.50 vs 0.35) via routing on ListItem { task }.

The degenerate heading_kinds fallback (a setext underline detected at level >2 coerces to ATX H1) is reproduced in resolve_heading().

Verification

  • cargo clippy -p mehen-markdown --all-targets --all-features --locked0 warnings (incl. the crate's stricter -W dead_code -W unreachable_pub sweep).
  • cargo insta test --workspace --all-features --check --unreferenced reject905/905 tests pass, no snapshot changes, no unreferenced snapshots. Since NodeKind is never serialized, byte-identical snapshots are the proof that metric outputs are unchanged.
  • cargo check --workspace (dependents compile), cargo machete (no unused deps), cargo xtask tree-sitter check-generated (still green — markdown was never a codegen target).

Net

−1,346 lines (+877 / −2,223), the bulk from deleting the 685-line grammar.rs and collapsing tree-sitter-shaped boilerplate across 17 metric passes.

CLAUDE.md is updated to document the pulldown-cmark backing and carve mehen-markdown out of the "never edit generated grammar.rs" hard rule (it no longer has one; kind.rs is hand-authored and edited directly).

…tive NodeKind

`mehen-markdown` migrated off tree-sitter to pulldown-cmark, but kept the
generated tree-sitter kind table (`grammar.rs`) as its internal node
vocabulary and wrapped the owned tree in a tree-sitter-shaped API. This
carried real cost for no benefit:

- a 685-line `u16` `Markdown` enum with grammar ordinals, ~half of whose
  variants pulldown-cmark can never construct;
- a `kind_id() -> u16` + `From<u16>` round-trip at 48 call sites, when the
  node already stores the enum;
- a mutable `Cursor` (`goto_first_child`/`goto_next_sibling`) over 45
  hand-rolled loops, when all navigation is strictly top-down;
- a named-field table used only for heading `level`/`content`;
- a ~330-line dead `From<Markdown> for &'static str` impl (escaped the
  dead-code lint via the trait-impl exemption).

Replace it with a hand-authored `NodeKind` enum (`src/kind.rs`) modeling
only the ~62 kinds the builder emits. Numbered tree-sitter families
(`atx_heading2..6`, `section1..6`, `list_item2..5`, task variants) fold
into data-carrying variants (`Heading { level, style }`, `Section { level }`,
`ListItem { task }`); `kind_id()` becomes `kind() -> NodeKind`; the cursor
becomes a `children()` iterator; the field table is dropped.

Behavior-preserving: heading level and atx/setext style survive as variant
data, Halstead's per-level heading-marker operators and MCC's list-vs-task
weights are kept intact. Drops the now-unused `num` / `num-derive` /
`num-traits` dependencies.

Verified: `cargo clippy --all-targets --all-features --locked` clean
(incl. `-W dead_code -W unreachable_pub`); `cargo insta test --workspace
--check --unreferenced reject` passes 905/905 with no snapshot changes.
@github-actions

Copy link
Copy Markdown
Contributor

Copy/Paste Detection

🟢 No duplications found in 20 changed Rust file(s) (threshold: 100 tokens).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the mehen-markdown crate to fully migrate away from tree-sitter and its generated grammar.rs file. It introduces a hand-authored NodeKind enum in src/kind.rs that mirrors the pulldown-cmark event surface, consolidating various numbered variants (such as headings, list items, and sections) into data-carrying variants. Additionally, the mutable Cursor traversal API has been replaced with a clean, top-down Children iterator, and unused dependencies (num, num-derive, num-traits) have been removed. All metric passes and helpers have been updated to use the new NodeKind and iterator-based traversal. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tinovyatkin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e247ffb3-fe36-4f11-9d6f-e96604f571cf

📥 Commits

Reviewing files that changed from the base of the PR and between 24d1242 and 0b92ece.

📒 Files selected for processing (1)
  • crates/mehen-markdown/src/mrpc.rs

Walkthrough

The Markdown analyzer replaces its generated Markdown grammar enum with a hand-authored NodeKind model. Its event-derived syntax tree stores typed kinds and direct child indices, exposes children() traversal, and removes cursor and field-based navigation. Analyzer, metrics, prose, section, table, visual, and graph modules now match NodeKind directly. The generated grammar file and unused numeric dependencies are removed, while documentation and tests are updated.

Poem

I'm a rabbit with typed nodes to share,
Hopping through children with care.
Old grammar sleeps deep,
New kinds softly leap,
While tables and headings grow fair! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main refactor from tree-sitter-style internals to a pulldown-native NodeKind model.
Description check ✅ Passed The description is directly about the refactor and matches the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Binary Size Report

Measured artifact: release Linux mehen binary built from this workflow.

Size
main 30MiB
This PR 30MiB
Delta -22KiB (-0.07%)
Size history
xychart-beta
  title "Binary size (bytes)"
  x-axis ["build(deps): bump ra_ap_syntax", "build(deps): bump gix from 0.8", "build(deps): bump log from 0.4", "build(deps): bump the ruff gro", "build(deps): bump the mago gro", "build: sync workspace crate ve", "build(deps): bump the oxc grou", "build(deps): bump tree-sitter ", "build(deps): bump camino from ", "feat: add Java analyzer and up", "build(deps): bump sqruff-lib-d", "build(deps): bump the mago gro", "build(deps): bump ra_ap_syntax", "build(deps): bump the oxc grou", "build(deps): bump mago-syntax-", "chore(main): release 1.3.0 (#1", "docs: surface SQL + ANTLR acro", "This PR"]
  y-axis "Bytes"
  bar [29151560, 29154336, 29184128, 29154616, 29184664, 29184664, 29194704, 29194696, 29194704, 30767472, 30833880, 30833888, 30834016, 30811952, 30811944, 30811984, 31135680, 31113224]
Loading

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3820ebe3f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CLAUDE.md
Codex P1: CLAUDE.md was updated to note that mehen-markdown is
pulldown-cmark-backed and has no generated grammar.rs, but AGENTS.md
still carried the unqualified "never edit grammar.rs" rule — leaving the
authoritative agent instructions in conflict with this PR's deletion of
crates/mehen-markdown/src/grammar.rs. Mirror the CLAUDE.md carve-out here.

Addresses: #174 (comment)
…ion_mark

CI runs clippy 1.97 (stricter `question_mark` lint under `-D warnings`)
and flagged `extract_domain`'s `match dest.find("://") { Some(pos) => …,
None => return None }`. Rewrite as `let pos = dest.find("://")?;`.
Behavior-identical; local clippy (1.96) did not surface this.
@github-actions

Copy link
Copy Markdown
Contributor

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/mehen-markdown/src/syntax_tree.rs 350 (main: 382) 🟢 164 (main: 179) 🟢 97 (main: 100) 🟢 485 (main: 509) 🟢 0 ⚪
crates/mehen-markdown/src/mrpc.rs 134 (main: 146) 🟢 103 (main: 138) 🟢 37 ⚪ 267 (main: 294) 🟢 0 ⚪
crates/mehen-markdown/src/prose/lang_detect.rs 129 (main: 135) 🟢 112 (main: 132) 🟢 28 ⚪ 190 (main: 210) 🟢 0 ⚪
crates/mehen-markdown/src/mcc.rs 106 (main: 135) 🟢 101 (main: 141) 🟢 26 (main: 29) 🟢 226 (main: 267) 🟢 0 ⚪
crates/mehen-markdown/src/filler.rs 86 (main: 90) 🟢 63 (main: 73) 🟢 20 ⚪ 170 (main: 178) 🟢 0 ⚪
crates/mehen-markdown/src/grounding.rs 110 (main: 116) 🟢 88 (main: 103) 🟢 18 ⚪ 179 (main: 191) 🟢 0 ⚪
crates/mehen-markdown/src/halstead.rs 77 (main: 86) 🟢 39 (main: 52) 🟢 17 (main: 16) 🔴 142 (main: 151) 🟢 1.87 (main: 0) 🟢
crates/mehen-markdown/src/sections.rs 42 (main: 69) 🟢 30 (main: 61) 🟢 13 (main: 16) 🟢 79 (main: 116) 🟢 13.98 (main: 6.31) 🟢
crates/mehen-markdown/src/loc.rs 41 (main: 44) 🟢 16 (main: 25) 🟢 9 ⚪ 43 (main: 48) 🟢 17.68 (main: 15.05) 🟢
crates/mehen-markdown/src/tree_helpers.rs 38 (main: 48) 🟢 32 (main: 67) 🟢 8 ⚪ 41 (main: 65) 🟢 18.70 (main: 14.10) 🟢
crates/mehen-markdown/src/tables.rs 44 (main: 54) 🟢 34 (main: 54) 🟢 7 ⚪ 89 (main: 121) 🟢 15.81 (main: 12.24) 🟢
crates/mehen-markdown/src/visuals.rs 43 (main: 48) 🟢 29 (main: 36) 🟢 7 ⚪ 83 (main: 90) 🟢 12.92 (main: 11.53) 🟢
crates/mehen-markdown/src/nearby.rs 15 (main: 17) 🟢 11 (main: 16) 🟢 5 ⚪ 27 (main: 31) 🟢 29.40 (main: 26.48) 🟢
crates/mehen-markdown/src/prose/mod.rs 32 (main: 36) 🟢 19 (main: 27) 🟢 5 ⚪ 59 (main: 67) 🟢 14.23 (main: 12.73) 🟢
crates/mehen-markdown/src/analyzer.rs 25 (main: 27) 🟢 18 (main: 23) 🟢 4 ⚪ 87 (main: 91) 🟢 9.58 (main: 8.90) 🟢
crates/mehen-markdown/src/ecu.rs 20 (main: 26) 🟢 18 (main: 36) 🟢 4 ⚪ 43 (main: 56) 🟢 25.35 (main: 22.55) 🟢
crates/mehen-markdown/src/kind.rs 14 🆕 2 🆕 4 🆕 4 🆕 24.38 🆕
crates/mehen-markdown/src/math_burden.rs 17 (main: 19) 🟢 17 (main: 22) 🟢 3 ⚪ 39 (main: 43) 🟢 28.12 (main: 27.18) 🟢
crates/mehen-markdown/src/words.rs 9 (main: 11) 🟢 5 (main: 10) 🟢 3 ⚪ 12 (main: 16) 🟢 39.53 (main: 37.37) 🟢
crates/mehen-markdown/src/grammar.rs 0 (was: 330) 🟢 0 (was: 1) 🟢 0 (was: 4) 🟢 0 (was: 5) 🟢 0 (was: 0) ⚪

Generated by mehen v1.3.0 — the code quality watcher.

📝 Documentation Metrics (this PR vs main)

File DMI Words FKGL Link Debt Filler Risk
AGENTS.md 97 (main: 97) ⚪ 112 (main: 92) ⚪ 5.8 (main: 5.8) ⚪ 0.00 ⚪ 0.32 (main: 0.31) ⚪
CLAUDE.md 93 (main: 93) ⚪ 400 (main: 353) ⚪ 7.8 (main: 8.1) ⚪ 0.00 ⚪ 0.35 ⚪
Full metric breakdown (structural · wording · lexical · readability)

Structural / review

File RCI MCC MRPC Evidence Grounding
AGENTS.md 5 ⚪ 5 ⚪ 1 ⚪ 0.15 ⚪ 0.20 ⚪
CLAUDE.md 21 ⚪ 20 ⚪ 1 ⚪ 0.18 ⚪ 0.25 ⚪

English wording quality

File WQS Passive % Hedges /100w Long sent. Nominalizations
AGENTS.md 0.99 (main: 1.00) ⚪ 18% (main: 19%) ⚪ 0.0 ⚪ 0 ⚪ 0% ⚪
CLAUDE.md 1.00 (main: 1.00) ⚪ 8% (main: 8%) ⚪ 0.0 ⚪ 0 ⚪ 2% (main: 3%) ⚪

English lexical & readability ensemble

File MATTR₅₀ Hapax Fog SMOG ARI Coleman-Liau
AGENTS.md 0.85 (main: 0.86) ⚪ 0.71 (main: 0.71) ⚪ 6.7 (main: 6.8) ⚪ 5.0 (main: 5.0) ⚪ 8.5 (main: 8.6) ⚪
CLAUDE.md 0.86 ⚪ 0.72 (main: 0.74) 🟢 8.8 (main: 9.0) ⚪ 9.9 (main: 9.9) ⚪ 7.2 (main: 7.4) ⚪ 11.1 (main: 11.5) ⚪

Legend: 🟢 improvement · 🔴 regression · ⚠️ attention · 🆕 new file · ⚪ no material change

Generated by mehen — the code quality watcher.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 0b92ecef8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@tinovyatkin tinovyatkin merged commit 24b52e6 into main Jul 10, 2026
12 checks passed
@tinovyatkin tinovyatkin deleted the refactor/markdown-pulldown-native-nodekind branch July 10, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant