Releases: kettle-rb/tree_haver
v5.0.5
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
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v5.0.4
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
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v5.0.3
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
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v5.0.2
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) languagesany_json_backend_available?- Checks if tree-sitter-json is availableany_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)
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v5.0.1
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 nodeTestableNode.create_list(...)- Create multiple test nodesMockInnerNode- 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_pathregistered_tags- Get all registered tag namestags_by_category(category)- Get tags filtered by category (:backend, :gem, :parsing, :grammar, :engine, :other)tag_metadata(tag_name)- Get full metadata for a registered tagtag_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 BackendRegistrybackend_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.
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v5.0.0
[5.0.0] - 2026-01-11
- TAG: [v5.0.0][5.0.0t]
- 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 examplesspec/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 availableParslet.capabilities- Returns{ backend: :parslet, query: false, bytes_field: true, incremental: false, pure_ruby: true }Parslet::Language- Wrapper for Parslet grammar classesLanguage.new(grammar_class)- Create from a Parslet::Parser subclassLanguage.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::TreeParslet::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 pathFFI::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::TreeTreeHaver::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::NodeTreeHaver::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 classesCitrus::Nodenow inherits fromTreeHaver::Base::NodeCitrus::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 classesParslet::Nodenow inherits fromTreeHaver::Base::NodeParslet::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 implementationhandle_parser_creation_failuretries Parslet as fallback after Citrusunwrap_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_positionAPIcommonmarker_merge_example.rb- Fixed to usecommonmarker/mergegem properlymarkly_merge_example.rb- Fixed to usemarkly/mergegem properlyparslet_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 bac...
v4.0.5
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 clauseLoadErroralready 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 (
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v4.0.4
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
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v4.0.3
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.
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?
v4.0.2
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
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?