Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning,
and yes, platform and engine support are part of the public API.
Please file a bug if you notice a violation of semantic versioning.
Unreleased
Added
Changed
Deprecated
Removed
Fixed
Security
5.0.5 - 2026-02-18
- TAG: v5.0.5
- COVERAGE: 84.39% – 2146/2543 lines in 30 files
- BRANCH COVERAGE: 74.18% – 882/1189 branches in 30 files
- 94.78% documented
Added
- Many more specs
- AGENTS.md
- Truffleruby 24.2, 25.0, 33.0 added to CI
- Ruby 3.4 added to CI
Changed
- appraisal2 v3.0.6
- tree_stump v0.2.0
- fork no longer required, updates all applied upstream
- kettle-test v1.0.10
- Updated documentation on hostile takeover of RubyGems
- https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo
5.0.4 - 2026-02-04
- TAG: v5.0.4
- COVERAGE: 83.68% – 2128/2543 lines in 30 files
- BRANCH COVERAGE: 72.58% – 863/1189 branches in 30 files
- 94.78% documented
Changed
- Update documentation on which fork/SHA to use for tree_stump & ruby_tree_sitter
5.0.3 - 2026-01-30
- TAG: v5.0.3
- COVERAGE: 83.68% – 2128/2543 lines in 30 files
- BRANCH COVERAGE: 72.50% – 862/1189 branches in 30 files
- 94.78% documented
Changed
- test against Prism v1.9.0
- CI updated to use latest version of ore
Fixed
-
Improved dependency handling and test robustness:
- Added missing RSpec backend tags (
:parslet_backend,:citrus_backend, etc.) to ensure tests are skipped when dependencies are unavailable. - Enhanced
GrammarFinderto support bothENV.key?andENV[var]checks, fixing issues with environment stubbing in tests. - Improved
GrammarFinderspec reliability by usingallow(File).to receive(:exist?).and_call_original. - Configured RSpec to mark grammar-dependent tests as
pendingwith helpful instructions when shared libraries are missing. - Renamed
:toml_rbtag to:toml_rb_gemfor consistency across the codebase.
- Added missing RSpec backend tags (
- Documentation fixes related to gem family section
5.0.2 - 2026-01-13
- TAG: v5.0.2
- COVERAGE: 90.79% – 2308/2542 lines in 30 files
- BRANCH COVERAGE: 78.09% – 930/1191 branches in 30 files
- 94.78% documented
Added
- More documentation about the Merge Gem Family
-
:json_parsingand:jsonc_parsingRSpec dependency tags: Added missing parsing capability tags
for JSON and JSONC (JSON with Comments) languages-
any_json_backend_available?- Checks if tree-sitter-json is available -
any_jsonc_backend_available?- Checks if tree-sitter-jsonc is available - Tests tagged with
:jsonc_parsingwill now be properly skipped on TruffleRuby and other
platforms where tree-sitter backends are not available - Fixes issue where jsonc-merge specs were running on TruffleRuby and failing because
the tag was undefined and therefore not excluded
-
Changed
- Restored README.md (was accidentally corrupted during the last release)
5.0.1 - 2026-01-11
- TAG: v5.0.1
- COVERAGE: 90.79% – 2308/2542 lines in 30 files
- BRANCH COVERAGE: 78.09% – 930/1191 branches in 30 files
- 94.76% documented
Added
-
TreeHaver::RSpec::TestableNode- A testable node class for creating mock TreeHaver::Node instances
in tests without requiring an actual parser backend. Available viarequire "tree_haver/rspec/testable_node"
or automatically when usingrequire "tree_haver/rspec".-
TestableNode.create(type:, text:, ...)- Create a single test node -
TestableNode.create_list(...)- Create multiple test nodes -
MockInnerNode- The underlying mock that simulates backend-specific nodes - Top-level
TestableNodeconstant for convenience in specs
-
-
Fully Dynamic Tag Registration in
TreeHaver::BackendRegistry:-
register_tag(tag_name, category:, backend_name:, require_path:)- Register a complete dependency tag
with lazy loading support. External gems can now get full RSpec tag support without any hardcoded
knowledge in tree_haver. -
tag_available?(tag_name)- Check if a tag’s dependency is available, with automatic lazy loading
via the registeredrequire_path -
registered_tags- Get all registered tag names -
tags_by_category(category)- Get tags filtered by category (:backend, :gem, :parsing, :grammar, :engine, :other) -
tag_metadata(tag_name)- Get full metadata for a registered tag -
tag_summary- Get availability status of all registered tags
-
Changed
-
Fully Dynamic Backend Availability in
BackendRegistryandDependencyTags:-
register_tagnow dynamically defines*_available?methods onDependencyTagsat registration time - External gems automatically get availability methods when they call
register_tag - No changes to tree_haver are needed for new external backend gems
- Built-in backends (prism, psych, citrus, parslet) retain explicit methods
-
summarymethod dynamically includes registered backends from BackendRegistry -
backend_availability_methodsandbackend_tagshashes are built dynamically
-
- RSpec exclusion filters for backend tags are configured dynamically from BackendRegistry
Fixed
-
TreeHaver::Parser#unwrap_languagebug fix for MRI and Rust backends-
:mriand:rustcases were not returning the unwrapped language value - The code called
lang.to_language/lang.inner_language/lang.namebut didn’treturnthe result - Now properly returns the unwrapped language for all backend types
-
-
any_markdown_backend_available?now usesBackendRegistry.tag_available?instead of calling
markly_available?andcommonmarker_available?directly. This fixesNoMethodErrorwhen
the external markdown backend gems haven’t registered their tags yet.
5.0.0 - 2026-01-11
- TAG: v5.0.0
- COVERAGE: 92.04% – 2289/2487 lines in 30 files
- BRANCH COVERAGE: 79.33% – 929/1171 branches in 30 files
- 96.21% documented
Added
-
Shared Example Groups for Backend API Compliance Testing
-
node_api_examples.rb- Tests for Node API compliance:-
"node api compliance"- Core Node interface (type, start_byte, end_byte, children) -
"node position api"- Position API (start_point, end_point, start_line, end_line, source_position) -
"node children api"- Children traversal (#child, #first_child, #last_child) -
"node enumerable behavior"- Enumerable methods (#each, #map, #select, #find) -
"node comparison behavior"- Comparison and equality (#==, #<=>, #hash) -
"node text extraction"- Text content (#text, #to_s) -
"node inspection"- Debug output (#inspect)
-
-
tree_api_examples.rb- Tests for Tree API compliance:-
"tree api compliance"- Core Tree interface (root_node, source, errors, warnings, comments) -
"tree error handling"- Error detection (#has_error?, #errors) -
"tree traversal"- Depth-first traversal via root_node
-
-
parser_api_examples.rb- Tests for Parser API compliance:-
"parser api compliance"- Core Parser interface (#parse, #parse_string, #language=) -
"parser incremental parsing"- Incremental parsing support -
"parser error handling"- Error recovery behavior
-
-
language_api_examples.rb- Tests for Language API compliance:-
"language api compliance"- Core Language interface (#backend, #name/#language_name) -
"language comparison"- Comparison and equality -
"language factory methods"- Factory methods (.from_library, .from_path)
-
-
backend_api_examples.rb- Tests for Backend module API compliance:-
"backend module api"- Backend availability and capabilities -
"backend class structure"- Nested class verification -
"backend integration"- Full parse cycle testing
-
-
spec/support/shared_examples.rb- Master loader for all shared examples -
spec/integration/backend_api_compliance_spec.rb- Integration tests using all shared examples
-
-
Parslet Backend: New pure Ruby PEG parser backend (
TreeHaver::Backends::Parslet)- Wraps Parslet-based parsers (like the
tomlgem) to provide a pure Ruby alternative to tree-sitter -
Parslet.available?- Check if parslet gem is available -
Parslet.capabilities- Returns{ backend: :parslet, query: false, bytes_field: true, incremental: false, pure_ruby: true } -
Parslet::Language- Wrapper for Parslet grammar classes-
Language.new(grammar_class)- Create from a Parslet::Parser subclass -
Language.from_library(path, symbol:, name:)- API-compatible lookup via LanguageRegistry -
#language_name/#name- Derive language name from grammar class
-
-
Parslet::Parser- Wrapper that creates parser instances from grammar classes- Accepts both raw grammar class and Language wrapper (normalized API)
-
Parslet::Tree- Wraps Parslet parse results, inherits fromBase::Tree -
Parslet::Node- Unified node interface, inherits fromBase::Node- Supports both Hash nodes (with named children) and Array nodes (with indexed children)
-
#type- Returns the node type (key name or “array”/”document”) -
#children- Returns child nodes -
#child_by_field_name(name)- Access named children in Hash nodes -
#text- Returns the matched text from Parslet::Slice -
#start_byte,#end_byte- Byte positions from Parslet::Slice -
#start_point,#end_point- Line/column positions (computed from source)
- Registered with
BackendRegistry.register_availability_checker(:parslet)
- Wraps Parslet-based parsers (like the
-
RSpec Dependency Tags: Added
parslet_available?method- Checks if parslet gem is installed via
BackendRegistry.available?(:parslet) -
:parslet_backendtag for specs requiring Parslet -
:not_parslet_backendnegated tag for specs that should skip when Parslet is available
- Checks if parslet gem is installed via
-
RSpec Dependency Tags: Added
toml_gem_available?method and updatedany_toml_backend_available?-
:toml_gemtag for specs requiring thetomlgem to be available -
:not_toml_gemnegated tag for specs that should skip when thetomlgem is not available
-
-
ParsletGrammarFinder: Utility for discovering and registering Parslet grammar gems
-
ParsletGrammarFinder.new(language:, gem_name:, grammar_const:, require_path:)- Find Parslet grammars -
#available?- Check if the Parslet grammar gem is installed and functional -
#grammar_class- Get the resolved Parslet::Parser subclass -
#register!- Register the grammar with TreeHaver - Auto-loads via
TreeHaver::PARSLET_DEFAULTSfor known languages (toml)
-
-
TreeHaver.register_language: Extended with
grammar_class:parameter for Parslet grammars -
TreeHaver.parser_for: Extended with
parslet_config:parameter for explicit Parslet configuration -
MRI::Language#language_name/#name- Derive language name from symbol or path -
FFI::Language#language_name/#name- Derive language name from symbol or path -
spec_helper.rb: Added
require "toml"to load the toml gem for Parslet backend tests
Changed
-
BREAKING:
TreeHaver::Languageconverted from class to module- Previously
TreeHaver::Languagewas a class that wrapped backend language objects - Now
TreeHaver::Languageis a module providing factory methods (method_missingfor dynamic language loading) - Backend-specific language classes (e.g.,
TreeHaver::Backends::MRI::Language) are now the concrete implementations - Code that instantiated
TreeHaver::Language.new(...)directly must be updated to use backend-specific classes or the factory methods
- Previously
-
BREAKING:
TreeHaver::Treenow inherits fromTreeHaver::Base::Tree-
TreeHaver::Treeis now a proper subclass ofTreeHaver::Base::Tree - Inherits
inner_tree,source,linesattributes from base class - Base class provides default implementations; subclass documents divergence
-
-
BREAKING:
TreeHaver::Nodenow inherits fromTreeHaver::Base::Node-
TreeHaver::Nodeis now a proper subclass ofTreeHaver::Base::Node - Inherits
inner_node,source,linesattributes from base class - Base class documents the API contract; subclass documents divergence
-
-
BREAKING:
Citrus::NodeandCitrus::Treenow inherit from Base classes-
Citrus::Nodenow inherits fromTreeHaver::Base::Node -
Citrus::Treenow inherits fromTreeHaver::Base::Tree - Removes duplicated methods, uses inherited implementations
- Adds
#language_name/#namemethods for API compliance
-
-
BREAKING:
Parslet::NodeandParslet::Treenow inherit from Base classes-
Parslet::Nodenow inherits fromTreeHaver::Base::Node -
Parslet::Treenow inherits fromTreeHaver::Base::Tree - Removes duplicated methods, uses inherited implementations
-
- Base::Node#child now returns nil for negative indices (tree-sitter API compatibility)
-
Citrus::Parser#language= now accepts Language wrapper or raw grammar module
- Both patterns now work:
parser.language = TomlRB::Documentorparser.language = Citrus::Language.new(TomlRB::Document)
- Both patterns now work:
-
Parslet::Parser#language= now accepts Language wrapper or raw grammar class
- Both patterns now work:
parser.language = TOML::Parsletorparser.language = Parslet::Language.new(TOML::Parslet)
- Both patterns now work:
-
TreeHaver::Parser#unwrap_language now passes Language wrappers directly to Citrus/Parslet backends
- Previously unwrapped to raw grammar; now backends handle their own Language wrappers
-
Language.method_missing: Now recognizes
:parsletbackend type and createsParslet::Languageinstances -
Parser: Updated to recognize Parslet languages and switch to Parslet parser automatically
-
#backendnow returns:parsletfor Parslet-based parsers -
#language=detectsParslet::Languageand switches implementation -
handle_parser_creation_failuretries Parslet as fallback after Citrus -
unwrap_languageextractsgrammar_classfor Parslet languages
-
Fixed
-
FFI Backend Compliance Tests: Fixed tests to use
TreeHaver::Parserwrapper instead of rawFFI::Parser- Raw FFI classes (
FFI::Tree,FFI::Node) don’t have full API (missing#children,#text,#source, etc.) - TreeHaver wrapper classes (
TreeHaver::Tree,TreeHaver::Node) provide the complete unified API - Tests now properly test the wrapped API that users actually interact with
- Raw FFI classes (
-
Parslet TOML Sources: Fixed test sources to be valid for the
tomlgem’s Parslet grammar- Grammar requires table sections (not bare key-value pairs at root)
- Grammar requires trailing newlines
-
Examples: Fixed broken markdown examples that referenced non-existent TreeHaver backends
-
commonmarker_markdown.rb- Rewrote to use commonmarker gem directly (not a TreeHaver backend) -
markly_markdown.rb- Rewrote to use markly gem directly with correctsource_positionAPI -
commonmarker_merge_example.rb- Fixed to usecommonmarker/mergegem properly -
markly_merge_example.rb- Fixed to usemarkly/mergegem properly -
parslet_toml.rb- Rewrote to properly use TreeHaver’s Parslet backend with language registration
-
-
Examples: Fixed
run_all.rbtest runner- Added parslet example to the test list
- Changed markdown examples to use
backend: "standalone"(they’re not TreeHaver backends) - Added MRI+TOML to known incompatibilities (parse returns nil)
- Added proper skip reason messages for all known incompatibilities
-
Examples: Updated
examples/README.mddocumentation- Added Parslet backend section with usage examples
- Renamed “Commonmarker Backend” and “Markly Backend” to “Commonmarker (Standalone)” and “Markly (Standalone)”
- Clarified that commonmarker and markly are standalone parsers, not TreeHaver backends
-
Duplicate Constants: Removed duplicate
CITRUS_DEFAULTSandPARSLET_DEFAULTSdefinitions- Constants were defined twice in
tree_haver.rb(lines 170 and 315) - This was causing “already initialized constant” warnings on every require
- Constants were defined twice in
4.0.5 - 2026-01-09
- TAG: v4.0.5
- COVERAGE: 93.50% – 2058/2201 lines in 28 files
- BRANCH COVERAGE: 81.11% – 803/990 branches in 28 files
- 95.60% documented
Added
-
FFI Backend: Added
child_by_field_namemethod toTreeHaver::Backends::FFI::Node- Enables field-based child access using tree-sitter’s
ts_node_child_by_field_nameC API - Works with all grammars (JSON, JSONC, TOML, Bash, etc.) that define field names
- Fixes compatibility issues with json-merge, jsonc-merge, and other gems that use field access
- Example:
pair.child_by_field_name("key")returns the key node from a JSON pair
- Enables field-based child access using tree-sitter’s
-
RSpec Dependency Tags: Added
compute_blocked_backendsmethod- Determines blocked backends from
TREE_HAVER_BACKENDenv and ARGV--tagoptions - Called by
summarywhen@blocked_backendsisn’t set yet (before RSpec.configure runs) - Fixes issue where gem-specific
before(:suite)hooks could load blocked backends
- Determines blocked backends from
-
RSpec Dependency Tags: Added
LD_LIBRARY_PATHandDYLD_LIBRARY_PATHtoenv_summary- These library paths are relevant for tree-sitter shared library loading
- Useful for debugging grammar loading issues
-
RSpec Dependency Tags: Added
TREE_SITTER_RBS_PATHtoenv_summary
Changed
-
Language#method_missing: Simplified error handling in
Language#method_missing- Removed unreachable rescue block for
FFI::NotFoundError -
FFI::NotFoundErrorinherits fromLoadError, so it’s already caught by the prior rescue clause - Reduces code complexity without changing behavior
- Removed unreachable rescue block for
-
Parser#initialize: Simplified error handling in
Parser#initialize- Same fix as Language - removed unreachable
FFI::NotFoundErrorhandling - Added comment noting that
FFI::NotFoundErrorinherits fromLoadError
- Same fix as Language - removed unreachable
-
FFI Backend Native#try_load!: Removed redundant
FFI::NotFoundErrorfrom rescue clause- Only rescues
LoadErrornow with comment explaining inheritance
- Only rescues
-
GrammarFinder.tree_sitter_runtime_usable?: Removed redundant
StandardErrorrescue clause-
LoadErroralready catchesFFI::NotFoundError - Added comment explaining the inheritance relationship
-
Fixed
-
Test Isolation: Fixed state leakage in
language_registry_spec.rb- Tests were registering real language names (
:toml,:json,:yaml) with fake paths - These registrations persisted and polluted other tests that expected real grammar paths
- Changed all tests to use unique test-only language names (prefixed with
test_lang_) - Fixes 2 spec failures when running all tests together (
TreeHaver::Tree#editspecs)
- Tests were registering real language names (
4.0.4 - 2026-01-09
- TAG: v4.0.4
- COVERAGE: 95.27% – 2033/2134 lines in 28 files
- BRANCH COVERAGE: 84.07% – 802/954 branches in 28 files
- 95.49% documented
Fixed
-
RSpec Dependency Tags: Fixed blocked backend tests not being excluded on JRuby
- When
TREE_HAVER_BACKEND=ffiis set, MRI backend is blocked to prevent conflicts - Previously, this skipped BOTH the availability check AND the exclusion
- Now blocked backends are excluded without checking availability
- Tests tagged with
:mri_backendnow properly skip on JRuby when FFI is selected
- When
4.0.3 - 2026-01-08
- TAG: v4.0.3
- COVERAGE: 95.27% – 2033/2134 lines in 28 files
- BRANCH COVERAGE: 84.17% – 803/954 branches in 28 files
- 95.49% documented
Changed
-
RSpec Dependency Tags: Refactored FFI backend isolation to use standard
:ffi_backendtag- The
--tag ffi_backendnow triggersisolated_test_modeindependency_tags.rb - This prevents MRI backend from loading during availability checks
- Legacy
*_backend_onlytags are still supported for backwards compatibility - Simplifies the testing pattern: one tag serves as both dependency tag and isolation trigger
- The
Deprecated
-
:ffi_backend_onlytag: Use:ffi_backendinstead. The*_backend_onlytags are now redundant.
4.0.2 - 2026-01-08
- TAG: v4.0.2
- COVERAGE: 95.27% – 2033/2134 lines in 28 files
- BRANCH COVERAGE: 84.07% – 802/954 branches in 28 files
- 95.49% documented
Added
-
FFI Backend: Implemented
missing?method onTreeHaver::Backends::FFI::Node- Added
ts_node_is_missingFFI function attachment - This method was missing entirely, causing
NoMethodErrorwhen checking for MISSING nodes
- Added
Changed
-
TreeHaver::Node: Removed defensive
respond_to?checks fromhas_error?andmissing?methods- All tree-sitter backends (MRI, Rust, FFI, Java) must implement these methods on their inner nodes
- This enforces proper backend API compliance rather than silently masking missing implementations
Fixed
-
FFI Backend: Added explicit boolean conversion (
!!) tohas_error?return value- FFI
:boolreturn type may behave inconsistently across Ruby versions and platforms - Ensures
has_error?always returnstrueorfalse, not truthy/falsy values
- FFI
4.0.1 - 2026-01-08
- TAG: v4.0.1
- COVERAGE: 95.31% – 2032/2132 lines in 28 files
- BRANCH COVERAGE: 84.10% – 804/956 branches in 28 files
- 95.48% documented
Fixed
-
FFI Backend: Implemented
has_error?method onTreeHaver::Backends::FFI::Node- Previously was a stub that always returned
false, causing parse errors to go undetected - Now properly calls
ts_node_has_errorFFI function to detect syntax errors in parsed trees - This fixes error detection on JRuby when using the FFI backend with tree-sitter grammars
- Previously was a stub that always returned
4.0.0 - 2026-01-08
- TAG: v4.0.0
- COVERAGE: 95.31% – 2031/2131 lines in 28 files
- BRANCH COVERAGE: 84.21% – 805/956 branches in 28 files
- 95.48% documented
Added
-
BackendRegistry: New
TreeHaver::BackendRegistrymodule for registering backend availability checkers- Allows external gems (like
commonmarker-merge,markly-merge,rbs-merge) to register their availability checkers -
register_availability_checker(backend_name, &block)- Register a callable that returns true if backend is available -
available?(backend_name)- Check if a backend is available (results are cached) -
registered?(backend_name)- Check if a checker is registered -
registered_backends- Get all registered backend names - Used by
TreeHaver::RSpec::DependencyTagsfor dynamic backend detection
- Allows external gems (like
-
Plugin System:
commonmarker-mergeandmarkly-mergenow provide their own backends viaTreeHaver’s registry system, removing them fromTreeHavercore. -
Backend Architecture Documentation: Added comprehensive documentation to base classes and all tree-sitter backends explaining the two backend categories:
- Tree-sitter backends (MRI, Rust, FFI, Java): Use
TreeHaver::TreeandTreeHaver::Nodewrappers for raw tree-sitter objects - Pure-Ruby/Plugin backends (Citrus, Prism, Psych, Commonmarker, Markly): Define own
Backend::X::TreeandBackend::X::Nodeclasses
- Tree-sitter backends (MRI, Rust, FFI, Java): Use
Changed
-
Base Class Inheritance:
TreeHaver::TreeandTreeHaver::Nodenow properly inherit from their respectiveBase::classes-
TreeHaver::Tree < Base::Tree- inheritsinner_tree,source,linesattributes and default implementations -
TreeHaver::Node < Base::Node- inheritsinner_node,source,linesattributes and API contract - Base classes document the API contract; subclasses document divergence
-
-
Base::Node#initialize: Now accepts keyword arguments
source:andlines:instead of positional for consistency with subclasses -
DependencyTags: Now uses
BackendRegistry.available?(:backend_name)instead of hardcodedTreeHaver::Backends::*checks -
TreeHaver:
commonmarkerandmarklybackends are no longer built-in. Usecommonmarker-mergeandmarkly-mergegems which register themselves. -
All backends: Now register their availability checkers with
BackendRegistrywhen loaded (MRI, Rust, FFI, Java, Prism, Psych, Citrus)
Removed
-
TreeHaver: Removed
TreeHaver::Backends::CommonmarkerandTreeHaver::Backends::Marklymodules. These implementations have moved to their respective gems.
3.2.6 - 2026-01-06
- TAG: v3.2.6
- COVERAGE: 92.07% – 2230/2422 lines in 23 files
- BRANCH COVERAGE: 74.69% – 788/1055 branches in 23 files
- 90.37% documented
Fixed
-
Java backend: Fixed Optional handling in Node methods that could return nil incorrectly
-
child(index),child_by_field_name(name),parent,next_sibling,prev_siblingnow properly check for nil before attempting to unwrap Java Optional - Previously, the ternary-based Optional check could fail when jtreesitter returned null directly instead of Optional.empty()
- This fixes JRuby test failures where
key_namereturned nil and object keys were not extracted
-
3.2.5 - 2026-01-05
- TAG: v3.2.5
- COVERAGE: 92.07% – 2230/2422 lines in 23 files
- BRANCH COVERAGE: 74.69% – 788/1055 branches in 23 files
- 90.37% documented
Fixed
-
Markly backend:
Node#textnow correctly handles container nodes (headings, paragraphs, etc.)- Previously returned empty string because
string_contentwas checked first (responds but returns empty for containers) - Now falls through to
to_plaintextor children concatenation whenstring_contentis empty
- Previously returned empty string because
-
Commonmarker backend:
Node#textnow correctly handles container nodes- Previously could return empty string in edge cases
- Now consistently falls through to children concatenation when
string_contentis empty or raises TypeError
3.2.4 - 2026-01-04
- TAG: v3.2.4
- COVERAGE: 92.07% – 2229/2421 lines in 23 files
- BRANCH COVERAGE: 74.79% – 786/1051 branches in 23 files
- 90.37% documented
Added
-
External backend registration via
backend_module- External gems can now register
their own pure Ruby backends using the same API as built-in backends. This enables gems
like rbs-merge to integrate withTreeHaver.parser_forwithout modifying tree_haver:TreeHaver.register_language( :rbs, backend_module: Rbs::Merge::Backends::RbsBackend, backend_type: :rbs, gem_name: "rbs", ) # Now TreeHaver.parser_for(:rbs) works! -
Backends::PURE_RUBY_BACKENDSconstant - Maps pure Ruby backend names to their
language and module info. Used for auto-registration of built-in backends. -
TreeHaver.register_builtin_backends!- Registers built-in pure Ruby backends
(Prism, Psych, Commonmarker, Markly) in the LanguageRegistry using the same API that
external backends use. Called automatically byparser_foron first use. -
TreeHaver.ensure_builtin_backends_registered!- Idempotent helper that ensures
built-in backends are registered exactly once. -
parser_fornow supports registeredbackend_modulebackends - When a language
has a registeredbackend_module,parser_forwill use it. This enables external
gems to provide language support without tree-sitter grammars:- Checks LanguageRegistry for registered
backend_moduleentries - Creates parser from the backend module’s
ParserandLanguageclasses - Falls back to tree-sitter and Citrus if no backend_module matches
- Checks LanguageRegistry for registered
-
RBS dependency tags in
DependencyTags- New RSpec tags for RBS parsing:-
:rbs_grammar- tree-sitter-rbs grammar is available and parsing works -
:rbs_parsing- at least one RBS parser (rbs gem OR tree-sitter-rbs) is available -
:rbs_gem- the official rbs gem is available (MRI only) - Negated versions:
:not_rbs_grammar,:not_rbs_parsing,:not_rbs_gem - New availability methods:
tree_sitter_rbs_available?,rbs_gem_available?,any_rbs_backend_available?
-
-
Support for tree-sitter 0.26.x ABI - TreeHaver now fully supports grammars built
against tree-sitter 0.26.x (LANGUAGE_VERSION 15). This required updates to vendored
dependencies:-
ruby-tree-sitter: Updated to support tree-sitter 0.26.3 C library API changes
including newts_language_abi_version()function, UTF-16 encoding split, and
removal of deprecated parser timeout/cancellation APIs -
tree_stump (Rust backend): Updated to tree-sitter Rust crate 0.26.3 with new
abi_version()method,u32child indices, and streaming iterator-based query matches
-
ruby-tree-sitter: Updated to support tree-sitter 0.26.3 C library API changes
-
MRI backend now loads grammars with LANGUAGE_VERSION 15 - Previously, MRI backend
using ruby_tree_sitter could only load grammars with LANGUAGE_VERSION ≤ 14. Now supports
grammars built against tree-sitter 0.26.x. -
Rust backend now loads grammars with LANGUAGE_VERSION 15 - Previously, the tree_stump
Rust backend reported “Incompatible language version 15. Expected minimum 13, maximum 14”.
Now supports the latest grammar format. -
BackendAPI validation module - New
TreeHaver::BackendAPImodule for validating
backend API compliance:-
BackendAPI.validate(backend_module)- Returns validation results hash -
BackendAPI.validate!(backend_module)- Raises on validation failure -
BackendAPI.validate_node_instance(node)- Validates a node instance - Defines required and optional methods for Language, Parser, Tree, and Node classes
- Documents API contract for wrapper vs raw backends
- New
examples/validate_backends.rbscript to validate all backends
-
-
Java backend Node class now implements full API - Added missing methods to ensure
API consistency with other backends:-
parent- Get parent node -
next_sibling- Get next sibling node -
prev_sibling- Get previous sibling node -
named?- Check if node is named -
child_by_field_name- Get child by field name - All methods properly handle jtreesitter 0.26.0’s
Optional<Node>return types
-
-
Three environment variables for backend control - Fine-grained control over which
backends are available:-
TREE_HAVER_BACKEND- Single backend selection (auto, mri, ffi, rust, java, citrus, etc.) -
TREE_HAVER_NATIVE_BACKEND- Allow list for native backends (auto, none, or comma-separated
list likemri,ffi). Usenonefor pure-Ruby-only mode. -
TREE_HAVER_RUBY_BACKEND- Allow list for pure Ruby backends (auto, none, or comma-separated
list likecitrus,prism). Usenonefor native-only mode.
-
-
Backend availability now respects allow lists - When
TREE_HAVER_NATIVE_BACKENDis set
to specific backends (e.g.,mri,ffi), all other native backends are treated as unavailable.
This applies to ALL backend selection mechanisms:- Auto-selection in
backend_module - Explicit selection via
with_backend(:rust)- returns nil/unavailable - Explicit selection via
resolve_backend_module(:rust)- returns nil - RSpec dependency tags (
ffi_available?, etc.)
This makes the environment variables a hard restriction, not just a hint for auto-selection.
UseTREE_HAVER_NATIVE_BACKEND=nonefor pure-Ruby-only mode, or specify exactly which
native backends are permitted (e.g.,mri,ffi). - Auto-selection in
-
Java backend updated for jtreesitter 0.26.0 - Full compatibility with jtreesitter 0.26.0:
- Updated
Parser#parseandParser#parse_stringto handleOptional<Tree>return type - Updated
Tree#root_nodeto handleOptional<Node>return type - Fixed
parse_stringargument order to match jtreesitter 0.26.0 API:parse(String, Tree) - Updated
Language.load_by_nameto useSymbolLookupAPI (single-argload(name)removed) - Added
bin/setup-jtreesitterscript to download jtreesitter JAR from Maven Central - Added
bin/build-grammarscript to build tree-sitter grammars from source - Older versions of jtreesitter are NOT supported
- Updated
-
TREE_HAVER_BACKEND_PROTECTenvironment variable - Explicit control over backend
conflict protection. Set tofalseto disable protection that prevents mixing
incompatible native backends (e.g., FFI after MRI). Useful for testing scenarios
where you understand the risks. Default behavior (protection enabled) unchanged.
Changed
-
API normalized:
from_libraryis now universal - All language-specific backends
(Psych, Prism, Commonmarker, Markly) now implementLanguage.from_libraryfor API
consistency. This allowsTreeHaver.parser_for(:yaml)to work uniformly regardless
of which backend is active:-
Psych:
from_libraryaccepts (and ignores) path/symbol, returns YAML language -
Prism:
from_libraryaccepts (and ignores) path/symbol, returns Ruby language -
Commonmarker:
from_libraryaccepts (and ignores) path/symbol, returns Markdown language -
Markly:
from_libraryaccepts (and ignores) path/symbol, returns Markdown language - All raise
TreeHaver::NotAvailableif a different language is requested
-
Psych:
-
Citrus backend
from_librarynow looks up registered grammars - Instead of always
raising an error,Backends::Citrus::Language.from_librarynow looks up registered
Citrus grammars by name viaLanguageRegistry. This enablesTreeHaver.parser_for(:toml)
to work seamlessly when a Citrus grammar has been registered with
TreeHaver.register_language(:toml, grammar_module: TomlRB::Document). -
Java backend requires jtreesitter >= 0.26.0 - Due to API changes in jtreesitter,
older versions are no longer supported. The tree-sitter runtime library must also be
version 0.26.x to match.
by the RSpec dependency tags. This ensures tests tagged with:mri_backendonly run when
MRI is in the allow list. Same forTREE_HAVER_RUBY_BACKENDand pure Ruby backends. - New
TreeHaver.allowed_native_backendsmethod returns the allow list for native backends. - New
TreeHaver.allowed_ruby_backendsmethod returns the allow list for pure Ruby backends. - New
TreeHaver.backend_allowed?(backend)method checks if a specific backend is allowed
based on the current environment variable settings. - New
DependencyTags.allowed_native_backendsandDependencyTags.allowed_ruby_backendsmethods. - Updated
examples/test_backend_selection.rbscript to test all three environment variables. -
LanguageRegistrynow supports any backend type - Previously only:tree_sitterand
:citruswere documented. Now supports arbitrary backend types including:prism,:psych,
:commonmarker,:markly,:rbs, or any custom type. External gems can register their
own backend types using the same API. -
register_languageacceptsbackend_moduleparameter - New parameter for registering
pure Ruby backends. The module must provideLanguageandParserclasses with the
standard TreeHaver API (available?,capabilities,from_library, etc.).
Fixed
-
TreeHaver::Node#textnow handles backends with differenttextmethod signatures -
Previously,Node#textwould call@inner_node.textdirectly, butTreeStump::Node#text
(Rust backend) requires the source as an argument (text(source)). This caused
ArgumentError: wrong number of arguments (given 0, expected 1)when using the Rust
backend. NowNode#textchecks the method arity and passes the source when required:- Arity 0 or -1: calls
@inner_node.textwithout arguments - Arity >= 1: calls
@inner_node.text(@source)with source - Falls back to byte-based extraction if source is available
- Arity 0 or -1: calls
-
AUTO mode now gracefully falls back when explicitly requested backend is blocked -
Previously, ifTREE_HAVER_BACKEND=ffiwas set in the environment but FFI was blocked
due to MRI being used first (backend conflict protection),parser_forwould raise a
BackendConflicterror. Now, when the explicitly requested backend is blocked by a
backend conflict (e.g., FFI after MRI causes segfaults):-
backend_moduledetects the conflict and falls back to auto-selection -
resolve_native_backend_modulerescuesBackendConflictand continues to the next
backend in the priority list - This enables seamless multi-backend usage in test suites where different tests use
different backends, but one backend has already “poisoned” the process for another.
Note: This fallback only applies to backend conflicts (runtime incompatibility).
If a backend is disallowed byTREE_HAVER_NATIVE_BACKENDorTREE_HAVER_RUBY_BACKEND,
it will simply be unavailable—no error is raised, but no fallback occurs either. -
-
java_backend_available?now verifies grammar loading works - Previously, the
DependencyTags.java_backend_available?method only checked if java-tree-sitter
classes could be loaded, but didn’t verify that grammars could actually be used.
This caused tests tagged with:java_backendto run on JRuby even when the grammar
.sofiles (built for MRI) were incompatible with java-tree-sitter’s Foreign Function
Memory API. Now the check does a live test by attempting to load a grammar, ensuring
the tag accurately reflects whether the Java backend is fully functional.
3.2.3 - 2026-01-02
- TAG: v3.2.3
- COVERAGE: 94.91% – 2088/2200 lines in 22 files
- BRANCH COVERAGE: 81.37% – 738/907 branches in 22 files
- 90.14% documented
Fixed
-
parser_fornow respects explicitly requested non-native backends - Previously,
parser_forwould always try tree-sitter backends first and only fall back to alternative
backends if tree-sitter was unavailable. Now it checkseffective_backendand skips
tree-sitter attempts entirely when a non-native backend is explicitly requested via:-
TREE_HAVER_BACKEND=citrus(orprism,psych,commonmarker,markly) TreeHaver.backend = :citrusTreeHaver.with_backend(:citrus) { ... }
Native backends (
:mri,:rust,:ffi,:java) still use tree-sitter grammar discovery. -
-
load_tree_sitter_languagenow correctly ignores Citrus registrations - Previously,
if a language was registered with Citrus first,load_tree_sitter_languagewould
incorrectly try to use it even when a native backend was explicitly requested. Now it
only uses registrations that have a:tree_sitterkey, allowing proper backend switching
between Citrus and native tree-sitter backends. -
load_tree_sitter_languagenow validates registered paths exist - Previously,
if a language had a stale/invalid tree-sitter registration with a non-existent path
(e.g., from a test), the code would try to use it and fail. Now it checks
File.exist?(path)before using a registered path, falling back to auto-discovery
viaGrammarFinderif the registered path doesn’t exist. -
Language.method_missingno longer falls back to Citrus when native backend explicitly requested -
Previously, when tree-sitter loading failed (e.g., .so file missing), the code would
silently fall back to Citrus even if the user explicitly requested:mri,:rust,
:ffi, or:java. Now fallback to Citrus only happens wheneffective_backendis:auto.
This is a breaking change for users who relied on silent fallback behavior. -
Simplified
parser_forimplementation - Refactored from complex nested conditionals to
cleaner helper methods (load_tree_sitter_language,load_citrus_language). The logic is
now easier to follow and maintain.
3.2.2 - 2026-01-01
- TAG: v3.2.2
- COVERAGE: 94.79% – 2076/2190 lines in 22 files
- BRANCH COVERAGE: 81.35% – 733/901 branches in 22 files
- 90.14% documented
Fixed
- RSpec dependency tags now respect
TREE_HAVER_BACKENDenvironment variable- When
TREE_HAVER_BACKEND=ffiis set, MRI backend availability is not checked - Prevents
BackendConflicterrors when loading gems that use tree-sitter grammars - The
blocked_backendsset now includes backends that would conflict with the explicitly selected backend - This allows
*-mergegems to load correctly in test suites when a specific backend is selected
- When
3.2.1 - 2025-12-31
- TAG: v3.2.1
- COVERAGE: 94.75% – 2075/2190 lines in 22 files
- BRANCH COVERAGE: 81.35% – 733/901 branches in 22 files
- 90.14% documented
Added
-
TreeHaver::LibraryPathUtilsmodule for consistent path parsing across all backends-
derive_symbol_from_path(path)- derives tree-sitter symbol (e.g.,tree_sitter_toml) from library path -
derive_language_name_from_path(path)- derives language name (e.g.,toml) from library path -
derive_language_name_from_symbol(symbol)- stripstree_sitter_prefix from symbol - Handles various naming conventions:
libtree-sitter-toml.so,libtree_sitter_toml.so,tree-sitter-toml.so,toml.so
-
- Isolated backend RSpec tags for running tests without loading conflicting backends
-
:ffi_backend_only- runs FFI tests without triggeringmri_backend_available?check -
:mri_backend_only- runs MRI tests without triggeringffi_available?check - Uses
TreeHaver::Backends::BLOCKED_BYto dynamically determine which availability checks to skip - Enables
rake ffi_specsto run FFI tests before MRI is loaded
-
-
DependencyTags.ffi_backend_only_available?- checks FFI availability without loading MRI -
DependencyTags.mri_backend_only_available?- checks MRI availability without checking FFI
Changed
- All backends now use shared
LibraryPathUtilsfor path parsing- MRI, Rust, FFI, and Java backends updated for consistency
- Ensures identical behavior across all tree-sitter backends
-
TreeHaver::Languageclass extracted tolib/tree_haver/language.rb- No API changes, just file organization
- Loaded via autoload for lazy loading
-
TreeHaver::Parserclass extracted tolib/tree_haver/parser.rb- No API changes, just file organization
- Loaded via autoload for lazy loading
- Backend availability exclusions in
dependency_tags.rbare now dynamic- Uses
TreeHaver::Backends::BLOCKED_BYto skip availability checks for blocked backends - When running with
--tag ffi_backend_only, MRI availability is not checked - Prevents MRI from being loaded before FFI tests can run
- Uses
- Rakefile
ffi_specstask now uses:ffi_backend_onlytag- Ensures FFI tests run without loading MRI backend first
Fixed
- Rakefile now uses correct RSpec tags for FFI isolation
- The
ffi_specstask uses:ffi_backend_onlyto prevent MRI from loading - The
remaining_specstask excludes:ffi_backend_onlytests - Tags in Rakefile align with canonical tags from
dependency_tags.rb
- The
-
TreeHaver::RSpec::DependencyTags.mri_backend_available?now uses correct require path- Was:
require "ruby_tree_sitter"(wrong - causes LoadError) - Now:
require "tree_sitter"(correct - gem name is ruby_tree_sitter but require path is tree_sitter) - This fix ensures the MRI backend is correctly detected as available in CI environments
- Was:
-
TreeHaver::Backends::MRI::Language.from_librarynow properly derives symbol from path- Previously, calling
from_library(path)withoutsymbol:would fail becauselanguage_namewas nil - Now delegates to private
from_pathafter deriving symbol, ensuring proper language name derivation -
from_pathis now private (but still accessible viasendfor testing if needed) - Extracts language name from paths like
/usr/lib/libtree-sitter-toml.so→tree_sitter_toml - Handles both dash and underscore separators in filenames
- Handles simple language names like
toml.so→tree_sitter_toml
- Previously, calling
-
TreeHaver::Backends::MRI::Parser#language=now unwrapsTreeHaver::Backends::MRI::Languagewrappers- Accepts both raw
TreeSitter::Languageand wrappedTreeHaver::Backends::MRI::Language
- Accepts both raw
-
TreeHaver::GrammarFinder.tree_sitter_runtime_usable?no longer referencesFFI::NotFoundErrordirectly- Prevents
NameErrorwhen FFI gem is not loaded
- Prevents
-
TreeHaver::Parser#initializeno longer referencesFFI::NotFoundErrordirectly in rescue clause- Uses
defined?(::FFI::NotFoundError)check to safely handle FFI errors when FFI is loaded - Prevents
NameError: uninitialized constant TreeHaver::Parser::FFIwhen FFI gem is not available - Extracted error handling to
handle_parser_creation_failureprivate method for clarity
- Uses
- RSpec
dependency_tags.rbnow correctly detects--tagoptions during configuration- RSpec’s
config.inclusion_filter.rulesis empty during configuration phase - Now parses
ARGVdirectly to detect--tag ffi_backend_onlyand similar tags - Skips grammar availability checks (which load MRI) when running isolated backend tests
- Skips full dependency summary in
before(:suite)when backends are blocked
- RSpec’s
-
TreeHaver::Backends::FFI.reset!now uses consistent pattern with other backends- Was using
@ffi_gem_availablewithdefined?()check, which returned truthy afterreset!set it to nil - Now uses
@load_attempted/@loadedpattern like MRI, Rust, Citrus, Prism, Psych, etc. - This fixes FFI tests failing after the first test when
reset!was called inafterblocks
- Was using
-
TreeHaver::Language.method_missingno longer referencesFFI::NotFoundErrordirectly in rescue clause- Uses
defined?(::FFI::NotFoundError)check to safely handle FFI errors when FFI is loaded - Prevents
NameErrorwhen FFI gem is not available but tree-sitter backends are used - Extracted Citrus fallback logic to
handle_tree_sitter_load_failureprivate method
- Uses
3.2.0 - 2025-12-30
- TAG: v3.2.0
- COVERAGE: 86.82% – 2167/2496 lines in 22 files
- BRANCH COVERAGE: 66.79% – 734/1099 branches in 22 files
- 90.03% documented
Added
-
TreeHaver::CITRUS_DEFAULTSconstant with default Citrus configurations for known languages- Enables automatic Citrus fallback for TOML without explicit
citrus_configparameter - Currently includes configuration for
:toml(gem:toml-rb, const:TomlRB::Document)
- Enables automatic Citrus fallback for TOML without explicit
- Regression test suite for Citrus fallback (
spec/integration/citrus_fallback_spec.rb)- Tests
parser_forwith all tree-sitter backends stubbed as unavailable (simulating TruffleRuby) - Tests
CitrusGrammarFinderwith nilgem_nameandrequire_path - Tests explicit Citrus backend usage on MRI via
with_backend(:citrus)
- Tests
- Shared examples for TOML parsing tests (
spec/support/shared_examples/toml_parsing_examples.rb)-
"toml parsing basics"- tests basic parsing, positions, children, text extraction -
"toml node navigation"- tests first_child, named_children navigation
-
- Multi-backend TOML test suite (
spec/integration/multi_backend_toml_spec.rb)- Runs shared examples against both tree-sitter-toml and Citrus/toml-rb backends
- Tests backend equivalence for parsing results and positions
- Tagged appropriately so tests run on whichever backends are available
- Backend Platform Compatibility section to README
- Complete compatibility matrix showing which backends work on MRI, JRuby, TruffleRuby
- Detailed explanations for TruffleRuby and JRuby limitations
-
FFI.available?method at module level for API consistency with other backends -
TreeHaver.resolve_native_backend_modulemethod for resolving only tree-sitter backends -
TreeHaver::NATIVE_BACKENDSconstant listing backends that support shared libraries - TruffleRuby short-circuit in
resolve_native_backend_modulefor efficiency- Avoids trying 3 backends that are all known to fail on TruffleRuby
-
citrus_available?method to check if Citrus backend is available
Fixed
-
TreeHaver::Node#childnow returnsnilfor out-of-bounds indices on all backends- MRI backend (ruby_tree_sitter) raises
IndexErrorfor invalid indices - Other backends return
nilfor invalid indices - Now consistently returns
nilacross all backends for API compatibility
- MRI backend (ruby_tree_sitter) raises
-
Citrus backend
calculate_pointreturns negative column values- When
offsetwas 0,@source.rindex("\n", -1)searched from end of string - This caused
column = 0 - (position_of_last_newline) - 1to be negative (e.g., -34) - Fix: Early return
{row: 0, column: 0}foroffset <= 0 - This bug affected both MRI and TruffleRuby when using Citrus backend
- When
-
Citrus fallback fails on TruffleRuby when no explicit
citrus_configprovided-
parser_for(:toml)would fail withTypeError: no implicit conversion of nil into String - Root cause:
citrus_configdefaulted to{}, socitrus_config[:gem_name]wasnil -
CitrusGrammarFinderwas instantiated withgem_name: nil, causingrequire nil - On TruffleRuby, this triggered a bug in
bundled_gems.rbcallingFile.pathon nil - Fix: Added
CITRUS_DEFAULTSwith known Citrus configurations (TOML currently) - Fix:
parser_fornow usesCITRUS_DEFAULTS[name]when no explicit config provided - Fix: Added guard in
CitrusGrammarFinder#available?to return false whenrequire_pathis nil - Fix: Added
TypeErrorto rescue clause for TruffleRuby-specific edge cases
-
-
from_libraryno longer falls back to pure-Ruby backends- Previously, calling
Language.from_library(path)on TruffleRuby would fall back to Citrus
backend which then raised a confusing error about not supporting shared libraries - Now
from_libraryonly considers native tree-sitter backends (MRI, Rust, FFI, Java) - Clear error message when no native backend is available explaining the situation
- Previously, calling
-
Integration specs now use
parser_forinstead of explicit paths-
tree_edge_cases_spec.rbandnode_edge_cases_spec.rbnow useTreeHaver.parser_for(:toml)
which auto-discovers the best available backend (tree-sitter or Citrus fallback) - Tests now work correctly on all platforms (MRI, JRuby, TruffleRuby)
- Tagged with
:toml_parsingwhich passes if ANY toml parser is available
-
-
Core specs now use
parser_forinstead of explicit paths-
tree_spec.rb,node_spec.rb,parser_spec.rbconverted to useTreeHaver.parser_for(:toml) - All
:toml_grammartags changed to:toml_parsingfor cross-platform compatibility - Tests now run on JRuby and TruffleRuby via Citrus/toml-rb fallback
-
- FFI backend now properly reports as unavailable on TruffleRuby
-
ffi_gem_available?returnsfalseon TruffleRuby since tree-sitter uses STRUCT_BY_VALUE return types -
FFI.available?added at module level (was only in Native submodule) - Prevents confusing runtime errors (Polyglot::ForeignException) by detecting incompatibility upfront
- Dependency tags now check
truffleruby?before attempting FFI backend tests
-
- MRI backend now properly reports as unavailable on JRuby and TruffleRuby
-
available?returnsfalseon non-MRI platforms (C extension only works on MRI)
-
- Rust backend now properly reports as unavailable on JRuby and TruffleRuby
-
available?returnsfalseon non-MRI platforms (magnus requires MRI’s C API)
-
- Backend compatibility matrix spec now properly skips tests for platform-incompatible backends
- MRI and Rust backends skip on JRuby/TruffleRuby with clear skip messages
- FFI backend skips on TruffleRuby with clear skip message
Changed
-
BREAKING: RSpec Dependency Tag Naming Convention Overhaul
- All dependency tags now follow consistent naming conventions with suffixes
- Backend tags now use
*_backendsuffix (e.g.,:commonmarker_backend,:markly_backend) - Engine tags now use
*_enginesuffix (e.g.,:mri_engine,:jruby_engine,:truffleruby_engine) - Grammar tags now use
*_grammarsuffix (e.g.,:bash_grammar,:toml_grammar,:json_grammar) - Parsing capability tags now use
*_parsingsuffix (e.g.,:toml_parsing,:markdown_parsing) -
Migration required: Update specs using legacy tags:
-
:commonmarker→:commonmarker_backend -
:markly→:markly_backend -
:mri→:mri_engine -
:jruby→:jruby_engine -
:truffleruby→:truffleruby_engine -
:tree_sitter_bash→:bash_grammar -
:tree_sitter_toml→:toml_grammar -
:tree_sitter_json→:json_grammar -
:tree_sitter_jsonc→:jsonc_grammar -
:toml_backend→:toml_parsing -
:markdown_backend→:markdown_parsing
-
-
Removed inner-merge dependency tags from tree_haver
- Tags
:toml_merge,:json_merge,:prism_merge,:psych_mergeremoved - These belong in ast-merge gem, not tree_haver
- Use
require "ast/merge/rspec/dependency_tags"for merge gem tags
- Tags
-
API Consistency: All backends now have uniform
available?API at module level:-
TreeHaver::Backends::FFI.available?- checks ffi gem + not TruffleRuby + MRI not loaded -
TreeHaver::Backends::MRI.available?- checks MRI platform + ruby_tree_sitter gem -
TreeHaver::Backends::Rust.available?- checks MRI platform + tree_stump gem -
TreeHaver::Backends::Java.available?- checks JRuby platform + jtreesitter JAR -
TreeHaver::Backends::Prism.available?- checks prism gem (all platforms) -
TreeHaver::Backends::Psych.available?- checks psych stdlib (all platforms) -
TreeHaver::Backends::Commonmarker.available?- checks commonmarker gem (all platforms) -
TreeHaver::Backends::Markly.available?- checks markly gem (all platforms) -
TreeHaver::Backends::Citrus.available?- checks citrus gem (all platforms)
-
- README now accurately documents TruffleRuby backend support
- FFI backend doesn’t work on TruffleRuby due to
STRUCT_BY_VALUElimitation in TruffleRuby’s FFI - Rust backend (tree_stump) doesn’t work due to magnus/rb-sys incompatibility with TruffleRuby’s C API
- TruffleRuby users should use Prism, Psych, Commonmarker, Markly, or Citrus backends
- FFI backend doesn’t work on TruffleRuby due to
- Documented confirmed tree-sitter backend limitations:
- TruffleRuby: No tree-sitter backend works (FFI, MRI, Rust all fail)
- JRuby: Only Java and FFI backends work; Rust/MRI don’t
- Updated Rust Backend section with platform compatibility notes
- Updated FFI Backend section with TruffleRuby limitation details
- Use kettle-rb/ts-grammar-setup GHA in CI workflows
Fixed
- Rakefile now properly overrides
testtask afterrequire "kettle/dev"- Works around a bug in kettle-dev where test task runs minitest loader in CI
- Ensures
rake testruns RSpec specs instead of empty minitest suite
-
TreeHaver::RSpec::DependencyTagsnow catches TruffleRuby FFI exceptions- TruffleRuby’s FFI raises
Polyglot::ForeignExceptionfor unsupported types likeSTRUCT_BY_VALUE -
ffi_available?andlibtree_sitter_available?now returnfalseinstead of crashing - Fixes spec loading errors on TruffleRuby
- TruffleRuby’s FFI raises
-
TreeHaver::Backends::FFI::Language.from_librarynow catchesRuntimeErrorfrom TruffleRuby- TruffleRuby raises
RuntimeErrorinstead ofLoadErrorwhen a shared library cannot be opened - Now properly converts to
TreeHaver::NotAvailablewith descriptive message
- TruffleRuby raises
-
TreeHaver::Backends::FFI::Native.try_load!now only sets@loaded = trueafter allattach_functioncalls succeed- Previously,
loaded?returnedtrueeven whenattach_functionfailed (e.g., on TruffleRuby) - Now
loaded?correctly returnsfalsewhen FFI functions couldn’t be attached - Ensures FFI tests are properly skipped on TruffleRuby
- Previously,
3.1.2 - 2025-12-29
- TAG: v3.1.2
- COVERAGE: 87.40% – 2171/2484 lines in 22 files
- BRANCH COVERAGE: 67.04% – 726/1083 branches in 22 files
- 90.03% documented
Added
- Enhanced
TreeHaver::RSpec::DependencyTagsdebugging-
env_summarymethod returns relevant environment variables for diagnosis -
grammar_works?now logs detailed trace whenTREE_HAVER_DEBUG=1 -
before(:suite)prints both env vars and dependency status when debugging - Helps diagnose differences between local and CI environments
-
- Many new specs for:
- TreeHaver::GrammarFinder
- TreeHaver::Node
- TreeHaver::Tree
3.1.1 - 2025-12-28
- TAG: v3.1.1
- COVERAGE: 87.44% – 2152/2461 lines in 22 files
- BRANCH COVERAGE: 66.67% – 710/1065 branches in 22 files
- 90.02% documented
Added
-
TreeHaver::RSpec::DependencyTags: Shared RSpec dependency detection for the entire gem family- New
lib/tree_haver/rspec.rbentry point - other gems can simplyrequire "tree_haver/rspec" - Detects all TreeHaver backends: FFI, MRI, Rust, Java, Prism, Psych, Commonmarker, Markly, Citrus
- Ruby engine detection:
jruby?,truffleruby?,mri? - Language grammar detection:
tree_sitter_bash_available?,tree_sitter_toml_available?,tree_sitter_json_available?,tree_sitter_jsonc_available? - Inner-merge dependency detection:
toml_merge_available?,json_merge_available?,prism_merge_available?,psych_merge_available? - Composite checks:
any_toml_backend_available?,any_markdown_backend_available? - Records MRI backend usage when checking availability (critical for FFI conflict detection)
- Configures RSpec exclusion filters for all dependency tags automatically
- Supports debug output via
TREE_HAVER_DEBUG=1environment variable - Comprehensive documentation with usage examples
- New
-
TreeHaver.parser_for: New high-level factory method for creating configured parsers- Handles all language loading complexity in one call
- Auto-discovers tree-sitter grammar via
GrammarFinder - Falls back to Citrus grammar if tree-sitter unavailable
- Accepts
library_pathfor explicit grammar location - Accepts
citrus_configfor Citrus fallback configuration - Raises
NotAvailablewith helpful message if no backend works - Example:
parser = TreeHaver.parser_for(:toml) - Raises
NotAvailableif the specified path doesn’t exist (Principle of Least Surprise) - Does not back to auto-discovery when an explicit path is provided
- Re-raises with context-rich error message if loading from explicit path fails
- Auto-discovery still works normally when no
library_pathis provided
Changed
-
Backend sibling navigation: Backends that don’t support sibling/parent navigation now raise
NotImplementedErrorinstead of returningnil- This distinguishes “not implemented” from “no sibling exists”
- Affected backends: Prism, Psych
- Affected methods:
next_sibling,prev_sibling,parent
-
Canonical sibling method name: All backends now use
prev_siblingas the canonical method name (notprevious_sibling)- Matches the universal
TreeHaver::NodeAPI
- Matches the universal
Fixed
-
Backend conflict detection: Fixed bug where MRI backend usage wasn’t being recorded during availability checks
-
mri_backend_available?now callsTreeHaver.record_backend_usage(:mri)after successfully loading ruby_tree_sitter - This ensures FFI conflict detection works correctly even when MRI is loaded indirectly
-
-
GrammarFinder#not_found_message: Improved error message when grammar file exists but no tree-sitter runtime is available
- Now suggests adding
ruby_tree_sitter,ffi, ortree_stumpgem to Gemfile - Clearer guidance for users who have grammar files but are missing the Ruby tree-sitter bindings
- Now suggests adding
3.1.0 - 2025-12-18
- TAG: v3.1.0
- COVERAGE: 82.65% – 943/1141 lines in 11 files
- BRANCH COVERAGE: 63.80% – 349/547 branches in 11 files
- 88.97% documented
Added
-
Position API Enhancements – Added consistent position methods to all backend Node classes for compatibility with
*-mergegems-
start_line- Returns 1-based line number where node starts (converts 0-basedstart_point.rowto 1-based) -
end_line- Returns 1-based line number where node ends (converts 0-basedend_point.rowto 1-based) -
source_position- Returns hash{start_line:, end_line:, start_column:, end_column:}with 1-based lines and 0-based columns -
first_child- Convenience method that returnschildren.firstfor iteration compatibility -
Fixed:
TreeHaver::Node#start_pointand#end_pointnow handle both Point objects and hashes from backends (Prism, Citrus return hashes) -
Fixed: Added Psych, Commonmarker, and Markly backends to
resolve_backend_moduleandbackend_modulecase statements so they can be explicitly selected withTreeHaver.backend = :psychetc. -
Fixed: Added Prism, Psych, Commonmarker, and Markly backends to
unwrap_languagemethod so language objects are properly passed to backend parsers -
Fixed: Commonmarker backend’s
textmethod now safely handles container nodes that don’t have string_content (wraps in rescue TypeError) -
Added to:
- Main
TreeHaver::Nodewrapper (used by tree-sitter backends: MRI, FFI, Java, Rust) -
Backends::Commonmarker::Node- uses Commonmarker’ssourcepos(already 1-based) -
Backends::Markly::Node- uses Markly’ssource_position(already 1-based) -
Backends::Prism::Node- uses Prism’slocation(already 1-based) -
Backends::Psych::Node- calculates fromstart_point/end_point(0-based) -
Backends::Citrus::Node- calculates fromstart_point/end_point(0-based)
- Main
-
Backward Compatible: Existing
start_point/end_pointmethods continue to work unchanged -
Purpose: Enables all
*-mergegems to use consistent position API without backend-specific workarounds
-
-
Prism Backend – New backend wrapping Ruby’s official Prism parser (stdlib in Ruby 3.4+, gem for 3.2+)
-
TreeHaver::Backends::Prism::Language- Language wrapper (Ruby-only) -
TreeHaver::Backends::Prism::Parser- Parser withparseandparse_stringmethods -
TreeHaver::Backends::Prism::Tree- Tree wrapper withroot_node,errors,warnings,comments -
TreeHaver::Backends::Prism::Node- Node wrapper implementing full TreeHaver::Node protocol - Registered with
:prismbackend name, no conflicts with other backends
-
-
Psych Backend – New backend wrapping Ruby’s standard library YAML parser
-
TreeHaver::Backends::Psych::Language- Language wrapper (YAML-only) -
TreeHaver::Backends::Psych::Parser- Parser withparseandparse_stringmethods -
TreeHaver::Backends::Psych::Tree- Tree wrapper withroot_node,errors -
TreeHaver::Backends::Psych::Node- Node wrapper implementing TreeHaver::Node protocol - Psych-specific methods:
mapping?,sequence?,scalar?,alias?,mapping_entries,anchor,tag,value - Registered with
:psychbackend name, no conflicts with other backends
-
-
Commonmarker Backend – New backend wrapping the Commonmarker gem (comrak Rust parser)
-
TreeHaver::Backends::Commonmarker::Language- Language wrapper with parse options passthrough -
TreeHaver::Backends::Commonmarker::Parser- Parser withparseandparse_stringmethods -
TreeHaver::Backends::Commonmarker::Tree- Tree wrapper withroot_node -
TreeHaver::Backends::Commonmarker::Node- Node wrapper implementing TreeHaver::Node protocol - Commonmarker-specific methods:
header_level,fence_info,url,title,next_sibling,previous_sibling,parent - Registered with
:commonmarkerbackend name, no conflicts with other backends
-
-
Markly Backend – New backend wrapping the Markly gem (cmark-gfm C library)
-
TreeHaver::Backends::Markly::Language- Language wrapper with flags and extensions passthrough -
TreeHaver::Backends::Markly::Parser- Parser withparseandparse_stringmethods -
TreeHaver::Backends::Markly::Tree- Tree wrapper withroot_node -
TreeHaver::Backends::Markly::Node- Node wrapper implementing TreeHaver::Node protocol - Type normalization:
:header→"heading",:hrule→"thematic_break",:html→"html_block" - Markly-specific methods:
header_level,fence_info,url,title,next_sibling,previous_sibling,parent,raw_type - Registered with
:marklybackend name, no conflicts with other backends
-
-
Automatic Citrus Fallback – When tree-sitter fails, automatically fall back to Citrus backend
-
TreeHaver::Language.method_missingnow catches tree-sitter loading errors (NotAvailable,ArgumentError,LoadError,FFI::NotFoundError) and falls back to registered Citrus grammar -
TreeHaver::Parser#initializenow catches parser creation errors and falls back to Citrus parser when backend is:auto -
TreeHaver::Parser#language=automatically switches to Citrus parser when a Citrus language is assigned - Enables seamless use of pure-Ruby parsers (like toml-rb) when tree-sitter runtime is unavailable
-
-
GrammarFinder Runtime Check –
GrammarFinder#available?now verifies tree-sitter runtime is actually usable- New
GrammarFinder.tree_sitter_runtime_usable?class method tests if parser can be created -
TREE_SITTER_BACKENDSconstant defines which backends use tree-sitter (MRI, FFI, Rust, Java) - Prevents registration of grammars when tree-sitter runtime isn’t functional
-
GrammarFinder.reset_runtime_check!for testing
- New
-
Empty ENV Variable as Explicit Skip – Setting
TREE_SITTER_<LANG>_PATH=''explicitly disables that grammar- Previously, empty string was treated same as unset (would search paths)
- Now, empty string means “do not use tree-sitter for this language”
- Allows explicit opt-out to force fallback to alternative backends like Citrus
- Useful for testing and environments where tree-sitter isn’t desired
-
TOML Examples – New example scripts demonstrating TOML parsing with various backends
-
examples/auto_toml.rb- Auto backend selection with Citrus fallback demonstration -
examples/ffi_toml.rb- FFI backend with TOML -
examples/mri_toml.rb- MRI backend with TOML -
examples/rust_toml.rb- Rust backend with TOML -
examples/java_toml.rb- Java backend with TOML (JRuby only)
-
Fixed
-
BREAKING:
TreeHaver::Language.method_missingno longer raisesArgumentErrorwhen only Citrus grammar is registered and tree-sitter backend is active – it now falls back to Citrus instead- Previously: Would raise “No grammar registered for :lang compatible with tree_sitter backend”
- Now: Returns
TreeHaver::Backends::Citrus::Languageif Citrus grammar is registered - Migration: If you were catching this error, update your code to handle the fallback behavior
- This is a bug fix, but would be a breaking change for some users who were relying on the old behavior
3.0.0 - 2025-12-16
- TAG: v3.0.0
- COVERAGE: 85.19% – 909/1067 lines in 11 files
- BRANCH COVERAGE: 67.47% – 338/501 branches in 11 files
- 92.93% documented
Added
Backend Requirements
-
MRI Backend: Requires
ruby_tree_sitterv2.0+ (exceptions inherit fromExceptionnotStandardError)- In ruby_tree_sitter v2.0, TreeSitter errors were changed to inherit from Exception for thread-safety
- TreeHaver now properly handles:
ParserNotFoundError,LanguageLoadError,SymbolNotFoundError, etc.
Thread-Safe Backend Selection (Hybrid Approach)
-
NEW: Block-based backend API -
TreeHaver.with_backend(:ffi) { ... }for thread-safe backend selection- Thread-local context with proper nesting support
- Exception-safe (context restored even on errors)
- Fully backward compatible with existing global backend setting
-
NEW: Explicit backend parameters
-
Parser.new(backend: :mri)- specify backend when creating parser -
Language.from_library(path, backend: :ffi)- specify backend when loading language - Backend parameters override thread context and global settings
-
-
NEW: Backend introspection -
parser.backendreturns the current backend name (:ffi,:mri, etc.) -
Backend precedence chain:
explicit parameter > thread context > global setting > :auto - Backend-aware caching - Language cache now includes backend in cache key to prevent cross-backend pollution
- Added
TreeHaver.effective_backend- returns the currently effective backend considering precedence - Added
TreeHaver.current_backend_context- returns thread-local backend context - Added
TreeHaver.resolve_backend_module(explicit_backend)- resolves backend module with precedence
Examples and Discovery
- Added 18 comprehensive examples demonstrating all backends and languages
- JSON examples (5): auto, MRI, Rust, FFI, Java
- JSONC examples (5): auto, MRI, Rust, FFI, Java
- Bash examples (5): auto, MRI, Rust, FFI, Java
- Citrus examples (3): TOML, Finitio, Dhall
- All examples use bundler inline (self-contained, no Gemfile needed)
- Added
examples/run_all.rb- comprehensive test runner with colored output - Updated
examples/README.md- complete guide to all examples
- Added
TreeHaver::CitrusGrammarFinderfor language-agnostic discovery and registration of Citrus-based grammar gems- Automatically discovers Citrus grammar gems by gem name and grammar constant path
- Validates grammar modules respond to
.parse(source)before registration - Provides helpful error messages when grammars are not found
- Added multi-backend language registry supporting multiple backends per language simultaneously
- Restructured
LanguageRegistryto use nested hash:{ language: { backend_type: config } } - Enables registering both tree-sitter and Citrus grammars for the same language without conflicts
- Supports runtime backend switching, benchmarking, and fallback scenarios
- Restructured
- Added
LanguageRegistry.register(name, backend_type, **config)with backend-specific configuration storage - Added
LanguageRegistry.registered(name, backend_type = nil)to query by specific backend or get all backends - Added
TreeHaver::Backends::Citrus::Node#structural?method to distinguish structural nodes from terminals- Uses Citrus grammar’s
terminal?method to dynamically determine node classification - Works with any Citrus grammar without language-specific knowledge
- Uses Citrus grammar’s
Changed
-
BREAKING: All errors now inherit from
TreeHaver::Errorwhich inherits fromException- see: https://github.com/Faveod/ruby-tree-sitter/pull/83 for reasoning
-
BREAKING:
LanguageRegistry.registersignature changed fromregister(name, path:, symbol:)toregister(name, backend_type, **config)- This enables proper separation of tree-sitter and Citrus configurations
- Users should update to use
TreeHaver.register_languageinstead of callingLanguageRegistry.registerdirectly
- Updated
TreeHaver.register_languageto support both tree-sitter and Citrus grammars in single call or separate calls- Can now register:
register_language(:toml, path: "...", symbol: "...", grammar_module: TomlRB::Document) -
INTENTIONAL DESIGN: Uses separate
ifstatements (notelsif) to allow registering both backends simultaneously - Enables maximum flexibility: runtime backend switching, performance benchmarking, fallback scenarios
- Multiple registrations for same language now merge instead of overwrite
- Can now register:
Improved
Code Quality and Documentation
-
Uniform backend API: All backends now implement
reset!method for consistent testing interface- Eliminates need for tests to manipulate private instance variables
- Provides clean way to reset backend state between tests
-
Documented design decisions with inline rationale
- FFI Tree finalizer behavior and why Parser doesn’t use finalizers
-
resolve_backend_moduleearly-return pattern with comprehensive comments -
register_languagemulti-backend registration capability extensively documented
-
Enhanced YARD documentation
- All Citrus examples now include
gem_nameparameter (matches actual usage patterns) - Added complete examples showing both single-backend and multi-backend registration
- Documented backend precedence chain and thread-safety guarantees
- All Citrus examples now include
-
Comprehensive test coverage for thread-safe backend selection
- Thread-local context tests
- Parser backend parameter tests
- Language backend parameter tests
- Concurrent parsing tests with multiple backends
- Backend-aware cache isolation tests
- Nested block behavior tests (inner blocks override outer blocks)
- Exception safety tests (context restored even on errors)
- Explicit parameter precedence tests
- Updated
Language.method_missingto automatically select appropriate grammar based on active backend- tree-sitter backends (MRI, Rust, FFI, Java) query
:tree_sitterregistry key - Citrus backend queries
:citrusregistry key - Provides clear error messages when requested backend has no registered grammar
- tree-sitter backends (MRI, Rust, FFI, Java) query
- Improved
TreeHaver::Backends::Citrus::Node#typeto use dynamic Citrus grammar introspection- Uses event
.namemethod and Symbol events for accurate type extraction - Works with any Citrus grammar without language-specific code
- Handles compound rules (Repeat, Choice, Optional) intelligently
- Uses event
Fixed
Thread-Safety and Backend Selection
- Fixed
resolve_backend_moduleto properly handle mocked backends withoutavailable?method- Assumes modules without
available?are available (for test compatibility and backward compatibility) - Only rejects if module explicitly has
available?method and returns false - Makes code more defensive and test-friendly
- Assumes modules without
- Fixed Language cache to include backend in cache key
- Prevents returning wrong backend’s Language object when switching backends
- Essential for correctness with multiple backends in use
- Cache key now:
"#{path}:#{symbol}:#{backend}"instead of just"#{path}:#{symbol}"
- Fixed
TreeHaver.register_languageto properly support multi-backend registration- Documented intentional design: uses
ifnotelsifto allow both backends in one call - Added comprehensive inline comments explaining why no early return
- Added extensive YARD documentation with examples
- Documented intentional design: uses
Backend Bug Fixes
- Fixed critical double-wrapping bug in ALL backends (MRI, Rust, FFI, Java, Citrus)
- Backend
Parser#parseandparse_stringmethods now return raw backend trees - TreeHaver::Parser wraps the raw tree in TreeHaver::Tree (single wrapping)
- Previously backends were returning TreeHaver::Tree, then TreeHaver::Parser wrapped it again (double wrapping)
- This caused
@inner_treeto be a TreeHaver::Tree instead of raw backend tree, leading to nil errors
- Backend
- Fixed TreeHaver::Parser to pass source parameter when wrapping backend trees
- Enables
Node#textto work correctly by providing source for text extraction - Fixes all parse and parse_string methods to include
source: sourceparameter
- Enables
- Fixed MRI backend to properly use ruby_tree_sitter API
- Fixed
require "tree_sitter"(gem name isruby_tree_sitterbut requirestree_sitter) - Fixed
Language.loadto use correct argument order:(symbol_name, path) - Fixed
Parser#parseto useparse_string(nil, source)instead of creating Input objects - Fixed
Language.from_libraryto implement the expected signature matching other backends
- Fixed
- Fixed FFI backend missing essential node methods
- Added
ts_node_start_byte,ts_node_end_byte,ts_node_start_point,ts_node_end_point - Added
ts_node_is_null,ts_node_is_named - These methods are required for accessing node byte positions and metadata
- Fixes
NoMethodErrorwhen using FFI backend to traverse AST nodes
- Added
- Fixed GrammarFinder error messages for environment variable validation
- Detects leading/trailing whitespace in paths and provides correction suggestions
- Shows when TREE_SITTER_*_PATH is set but points to nonexistent file
- Provides helpful guidance for setting environment variables correctly
- Fixed registry conflicts when registering multiple backend types for the same language
- Fixed
CitrusGrammarFinderto use gem name as-is for require path (e.g.,require "toml-rb"notrequire "toml/rb") - Fixed Citrus backend infinite recursion in
Node#extract_type_from_event- Added cycle detection to prevent stack overflow when traversing recursive grammar structures
Known Issues
-
MRI backend + Bash grammar: ABI/symbol loading incompatibility
- The ruby_tree_sitter gem cannot load tree-sitter-bash grammar (symbol not found)
- Workaround: Use FFI backend instead (works perfectly)
- This is documented in examples and test runner
-
Rust backend + Bash grammar: Version mismatch due to static linking
- tree_stump statically links tree-sitter at compile time
- System bash.so may be compiled with different tree-sitter version
- Workaround: Use FFI backend (dynamic linking avoids version conflicts)
- This is documented in examples with detailed explanations
Notes on Backward Compatibility
Despite the major version bump to 3.0.0 (following semver due to the breaking LanguageRegistry.register signature change), most users will experience NO BREAKING CHANGES:
Why 3.0.0?
-
LanguageRegistry.registersignature changed to support multi-backend registration - However, most users should use
TreeHaver.register_language(which remains backward compatible) - Direct calls to
LanguageRegistry.registerare rare in practice
What Stays the Same?
-
Global backend setting:
TreeHaver.backend = :ffiworks unchanged -
Parser creation:
Parser.newwithout parameters works as before -
Language loading:
Language.from_library(path)works as before -
Auto-detection: Backend auto-selection still works when backend is
:auto - All existing code continues to work without modifications
What’s New (All Optional)?
- Thread-safe block API:
TreeHaver.with_backend(:ffi) { ... } - Explicit backend parameters:
Parser.new(backend: :mri) - Backend introspection:
parser.backend - Multi-backend language registration
Migration Path: Existing codebases can upgrade to 3.0.0 and gain access to new thread-safe features without changing any existing code. The new features are purely additive and opt-in.
2.0.0 - 2025-12-15
- TAG: v2.0.0
- COVERAGE: 82.78% – 601/726 lines in 11 files
- BRANCH COVERAGE: 70.45% – 186/264 branches in 11 files
- 91.90% documented
Added
- Added support for Citrus backend (
backends/citrus.rb) - a pure Ruby grammar parser with its own distinct grammar structure - Added
TreeHaver::Treeunified wrapper class providing consistent API across all backends - Added
TreeHaver::Nodeunified wrapper class providing consistent API across all backends - Added
TreeHaver::Pointclass that works as both object and hash for position compatibility - Added passthrough mechanism via
method_missingfor accessing backend-specific features - Added
inner_nodeaccessor onTreeHaver::Nodefor advanced backend-specific usage - Added
inner_treeaccessor onTreeHaver::Treefor advanced backend-specific usage - Added comprehensive test suite for
TreeHaver::Nodewrapper class (88 examples) - Added comprehensive test suite for
TreeHaver::Treewrapper class (17 examples) - Added comprehensive test suite for
TreeHaver::Parserclass (12 examples) - Added complete test coverage for Citrus backend (41 examples)
- Enhanced
TreeHaver::Languagetests for dynamic language helpers
Changed
-
BREAKING: All backends now return
TreeHaver::TreefromParser#parseandParser#parse_string -
BREAKING:
TreeHaver::Tree#root_nodenow returnsTreeHaver::Nodeinstead of backend-specific node -
BREAKING: All child/sibling/parent methods on nodes now return
TreeHaver::Nodewrappers - Updated MRI backend (
backends/mri.rb) to return wrappedTreeHaver::Treewith source - Updated Rust backend (
backends/rust.rb) to return wrappedTreeHaver::Treewith source - Updated FFI backend (
backends/ffi.rb) to return wrappedTreeHaver::Treewith source - Updated Java backend (
backends/java.rb) to return wrappedTreeHaver::Treewith source - Updated Citrus backend (
backends/citrus.rb) to return wrappedTreeHaver::Treewith source - Disabled old pass-through stub classes in
tree_haver.rb(wrapped inif falsefor reference)
Fixed
- Fixed
TreeHaver::Tree#supports_editing?and#editto handle Delegator wrappers correctly by using.method(:edit)check instead ofrespond_to? - Fixed
PathValidatorto accept versioned.sofiles (e.g.,.so.0,.so.14) which are standard on Linux systems - Fixed backend portability - code now works identically across MRI, Rust, FFI, Java, and Citrus backends
- Fixed inconsistent API -
node.typenow works on all backends (wasnode.kindon TreeStump) - Fixed position objects -
start_pointandend_pointnow return objects that work as both.rowand[:row] - Fixed child iteration -
node.eachandnode.childrennow consistently returnTreeHaver::Nodeobjects - Fixed text extraction -
node.textnow works consistently by storing source inTreeHaver::Tree
1.0.0 - 2025-12-15
- TAG: v1.0.0
- COVERAGE: 97.21% – 487/501 lines in 8 files
- BRANCH COVERAGE: 90.75% – 157/173 branches in 8 files
- 97.31% documented
Added
- Initial release