tree_haver 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +77 -2
- data/README.md +161 -166
- data/lib/tree_haver/backend_api.rb +12 -12
- data/lib/tree_haver/backend_registry.rb +230 -7
- data/lib/tree_haver/grammar_finder.rb +7 -6
- data/lib/tree_haver/parser.rb +5 -3
- data/lib/tree_haver/rspec/dependency_tags.rb +148 -87
- data/lib/tree_haver/rspec/testable_node.rb +217 -0
- data/lib/tree_haver/rspec.rb +11 -1
- data/lib/tree_haver/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
|
@@ -162,11 +162,17 @@ require "set"
|
|
|
162
162
|
# ==== Language Parsing Capability Tags (*_parsing)
|
|
163
163
|
#
|
|
164
164
|
# [:toml_parsing]
|
|
165
|
-
# At least one TOML parser (tree-sitter-toml OR toml-rb/Citrus) is available.
|
|
165
|
+
# At least one TOML parser (tree-sitter-toml OR toml-rb/Citrus OR toml/Parslet) is available.
|
|
166
166
|
#
|
|
167
167
|
# [:markdown_parsing]
|
|
168
168
|
# At least one markdown parser (commonmarker OR markly) is available.
|
|
169
169
|
#
|
|
170
|
+
# [:json_parsing]
|
|
171
|
+
# At least one JSON parser (tree-sitter-json) is available.
|
|
172
|
+
#
|
|
173
|
+
# [:jsonc_parsing]
|
|
174
|
+
# At least one JSONC (JSON with Comments) parser (tree-sitter-jsonc) is available.
|
|
175
|
+
#
|
|
170
176
|
# [:rbs_parsing]
|
|
171
177
|
# At least one RBS parser (rbs gem OR tree-sitter-rbs) is available.
|
|
172
178
|
#
|
|
@@ -491,9 +497,12 @@ module TreeHaver
|
|
|
491
497
|
def libtree_sitter_available?
|
|
492
498
|
return @libtree_sitter_available if defined?(@libtree_sitter_available)
|
|
493
499
|
@libtree_sitter_available = begin
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
500
|
+
if !ffi_available?
|
|
501
|
+
false
|
|
502
|
+
else
|
|
503
|
+
TreeHaver::Backends::FFI::Native.try_load!
|
|
504
|
+
true
|
|
505
|
+
end
|
|
497
506
|
rescue TreeHaver::NotAvailable, LoadError
|
|
498
507
|
false
|
|
499
508
|
rescue StandardError
|
|
@@ -531,25 +540,26 @@ module TreeHaver
|
|
|
531
540
|
nil
|
|
532
541
|
end
|
|
533
542
|
|
|
534
|
-
#
|
|
543
|
+
# ============================================================
|
|
544
|
+
# Dynamic Backend Availability (via BackendRegistry)
|
|
545
|
+
# ============================================================
|
|
535
546
|
#
|
|
536
|
-
#
|
|
547
|
+
# External gems register tags with BackendRegistry.register_tag which
|
|
548
|
+
# dynamically defines *_available? methods on this module.
|
|
537
549
|
#
|
|
538
|
-
# @
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# Check if markly gem is available
|
|
550
|
+
# @example External gem registers a tag
|
|
551
|
+
# TreeHaver::BackendRegistry.register_tag(
|
|
552
|
+
# :my_backend_backend,
|
|
553
|
+
# category: :backend,
|
|
554
|
+
# require_path: "my_backend/merge"
|
|
555
|
+
# ) { MyBackend::Merge::Backend.available? }
|
|
545
556
|
#
|
|
546
|
-
#
|
|
557
|
+
# # The registration automatically defines:
|
|
558
|
+
# TreeHaver::RSpec::DependencyTags.my_backend_available? # => true/false
|
|
547
559
|
#
|
|
548
|
-
#
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
@markly_available = TreeHaver::BackendRegistry.available?(:markly)
|
|
552
|
-
end
|
|
560
|
+
# Built-in backends (prism, psych, citrus, parslet) have explicit methods
|
|
561
|
+
# defined below. External backends get methods defined dynamically when
|
|
562
|
+
# their gem calls register_tag.
|
|
553
563
|
|
|
554
564
|
# Check if prism gem is available
|
|
555
565
|
#
|
|
@@ -738,9 +748,33 @@ module TreeHaver
|
|
|
738
748
|
|
|
739
749
|
# Check if at least one markdown backend is available
|
|
740
750
|
#
|
|
751
|
+
# Uses BackendRegistry.tag_available? to check external backends that may
|
|
752
|
+
# not have their methods defined yet (registered by external gems).
|
|
753
|
+
#
|
|
741
754
|
# @return [Boolean] true if any markdown backend works
|
|
742
755
|
def any_markdown_backend_available?
|
|
743
|
-
|
|
756
|
+
TreeHaver::BackendRegistry.tag_available?(:markly_backend) ||
|
|
757
|
+
TreeHaver::BackendRegistry.tag_available?(:commonmarker_backend)
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
# Check if at least one JSON parsing backend is available
|
|
761
|
+
#
|
|
762
|
+
# Currently only tree-sitter-json is supported for JSON parsing.
|
|
763
|
+
# Future backends (e.g., pure-Ruby JSON parsers) can be added here.
|
|
764
|
+
#
|
|
765
|
+
# @return [Boolean] true if any JSON parsing backend works
|
|
766
|
+
def any_json_backend_available?
|
|
767
|
+
tree_sitter_json_available?
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Check if at least one JSONC parsing backend is available
|
|
771
|
+
#
|
|
772
|
+
# Currently only tree-sitter-jsonc is supported for JSONC parsing.
|
|
773
|
+
# Future backends (e.g., pure-Ruby JSONC parsers) can be added here.
|
|
774
|
+
#
|
|
775
|
+
# @return [Boolean] true if any JSONC parsing backend works
|
|
776
|
+
def any_jsonc_backend_available?
|
|
777
|
+
tree_sitter_jsonc_available?
|
|
744
778
|
end
|
|
745
779
|
|
|
746
780
|
def any_native_grammar_available?
|
|
@@ -812,46 +846,71 @@ module TreeHaver
|
|
|
812
846
|
# Use stored blocked_backends if available, otherwise compute dynamically
|
|
813
847
|
blocked = @blocked_backends || compute_blocked_backends
|
|
814
848
|
|
|
815
|
-
{
|
|
849
|
+
result = {
|
|
816
850
|
# Backend selection from environment variables
|
|
817
851
|
selected_backend: selected_backend,
|
|
818
852
|
allowed_native_backends: allowed_native_backends,
|
|
819
853
|
allowed_ruby_backends: allowed_ruby_backends,
|
|
820
|
-
# TreeHaver backends (*_backend) - skip blocked backends to avoid loading them
|
|
821
|
-
ffi_backend: blocked.include?(:ffi) ? :blocked : ffi_available?,
|
|
822
|
-
mri_backend: blocked.include?(:mri) ? :blocked : mri_backend_available?,
|
|
823
|
-
rust_backend: blocked.include?(:rust) ? :blocked : rust_backend_available?,
|
|
824
|
-
java_backend: blocked.include?(:java) ? :blocked : java_backend_available?,
|
|
825
|
-
prism_backend: blocked.include?(:prism) ? :blocked : prism_available?,
|
|
826
|
-
psych_backend: blocked.include?(:psych) ? :blocked : psych_available?,
|
|
827
|
-
commonmarker_backend: blocked.include?(:commonmarker) ? :blocked : commonmarker_available?,
|
|
828
|
-
markly_backend: blocked.include?(:markly) ? :blocked : markly_available?,
|
|
829
|
-
citrus_backend: blocked.include?(:citrus) ? :blocked : citrus_available?,
|
|
830
|
-
parslet_backend: blocked.include?(:parslet) ? :blocked : parslet_available?,
|
|
831
|
-
rbs_backend: blocked.include?(:rbs) ? :blocked : rbs_backend_available?,
|
|
832
|
-
# Ruby engines (*_engine)
|
|
833
|
-
ruby_engine: RUBY_ENGINE,
|
|
834
|
-
mri_engine: mri?,
|
|
835
|
-
jruby_engine: jruby?,
|
|
836
|
-
truffleruby_engine: truffleruby?,
|
|
837
|
-
# Tree-sitter grammars (*_grammar) - also respect blocked backends
|
|
838
|
-
# since grammar checks may load backends
|
|
839
|
-
libtree_sitter: libtree_sitter_available?,
|
|
840
|
-
bash_grammar: blocked.include?(:mri) ? :blocked : tree_sitter_bash_available?,
|
|
841
|
-
toml_grammar: blocked.include?(:mri) ? :blocked : tree_sitter_toml_available?,
|
|
842
|
-
json_grammar: blocked.include?(:mri) ? :blocked : tree_sitter_json_available?,
|
|
843
|
-
jsonc_grammar: blocked.include?(:mri) ? :blocked : tree_sitter_jsonc_available?,
|
|
844
|
-
rbs_grammar: blocked.include?(:mri) ? :blocked : tree_sitter_rbs_available?,
|
|
845
|
-
any_native_grammar: blocked.include?(:mri) ? :blocked : any_native_grammar_available?,
|
|
846
|
-
# Language parsing capabilities (*_parsing)
|
|
847
|
-
toml_parsing: any_toml_backend_available?,
|
|
848
|
-
markdown_parsing: any_markdown_backend_available?,
|
|
849
|
-
rbs_parsing: any_rbs_backend_available?,
|
|
850
|
-
# Specific libraries (*_gem)
|
|
851
|
-
toml_rb_gem: toml_rb_gem_available?,
|
|
852
|
-
toml_gem: toml_gem_available?,
|
|
853
|
-
rbs_gem: rbs_gem_available?,
|
|
854
854
|
}
|
|
855
|
+
|
|
856
|
+
# Built-in TreeHaver backends (*_backend) - skip blocked backends to avoid loading them
|
|
857
|
+
builtin_backends = {
|
|
858
|
+
ffi: :ffi_available?,
|
|
859
|
+
mri: :mri_backend_available?,
|
|
860
|
+
rust: :rust_backend_available?,
|
|
861
|
+
java: :java_backend_available?,
|
|
862
|
+
prism: :prism_available?,
|
|
863
|
+
psych: :psych_available?,
|
|
864
|
+
citrus: :citrus_available?,
|
|
865
|
+
parslet: :parslet_available?,
|
|
866
|
+
rbs: :rbs_backend_available?,
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
builtin_backends.each do |backend, method|
|
|
870
|
+
tag = :"#{backend}_backend"
|
|
871
|
+
result[tag] = blocked.include?(backend) ? :blocked : public_send(method)
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
# Dynamically registered backends from BackendRegistry
|
|
875
|
+
TreeHaver::BackendRegistry.registered_tags.each do |tag_name|
|
|
876
|
+
next if result.key?(tag_name) # Don't override built-ins
|
|
877
|
+
|
|
878
|
+
meta = TreeHaver::BackendRegistry.tag_metadata(tag_name)
|
|
879
|
+
next unless meta && meta[:category] == :backend
|
|
880
|
+
|
|
881
|
+
backend = meta[:backend_name]
|
|
882
|
+
result[tag_name] = blocked.include?(backend) ? :blocked : TreeHaver::BackendRegistry.tag_available?(tag_name)
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
# Ruby engines (*_engine)
|
|
886
|
+
result[:ruby_engine] = RUBY_ENGINE
|
|
887
|
+
result[:mri_engine] = mri?
|
|
888
|
+
result[:jruby_engine] = jruby?
|
|
889
|
+
result[:truffleruby_engine] = truffleruby?
|
|
890
|
+
|
|
891
|
+
# Tree-sitter grammars (*_grammar) - also respect blocked backends
|
|
892
|
+
# since grammar checks may load backends
|
|
893
|
+
result[:libtree_sitter] = libtree_sitter_available?
|
|
894
|
+
result[:bash_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_bash_available?
|
|
895
|
+
result[:toml_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_toml_available?
|
|
896
|
+
result[:json_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_json_available?
|
|
897
|
+
result[:jsonc_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_jsonc_available?
|
|
898
|
+
result[:rbs_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_rbs_available?
|
|
899
|
+
result[:any_native_grammar] = blocked.include?(:mri) ? :blocked : any_native_grammar_available?
|
|
900
|
+
|
|
901
|
+
# Language parsing capabilities (*_parsing)
|
|
902
|
+
result[:toml_parsing] = any_toml_backend_available?
|
|
903
|
+
result[:markdown_parsing] = any_markdown_backend_available?
|
|
904
|
+
result[:json_parsing] = any_json_backend_available?
|
|
905
|
+
result[:jsonc_parsing] = any_jsonc_backend_available?
|
|
906
|
+
result[:rbs_parsing] = any_rbs_backend_available?
|
|
907
|
+
|
|
908
|
+
# Specific libraries (*_gem)
|
|
909
|
+
result[:toml_rb_gem] = toml_rb_gem_available?
|
|
910
|
+
result[:toml_gem] = toml_gem_available?
|
|
911
|
+
result[:rbs_gem] = rbs_gem_available?
|
|
912
|
+
|
|
913
|
+
result
|
|
855
914
|
end
|
|
856
915
|
|
|
857
916
|
# Get environment variable summary for debugging
|
|
@@ -907,7 +966,7 @@ module TreeHaver
|
|
|
907
966
|
# @param test_source [String] sample source code to parse
|
|
908
967
|
# @return [Boolean] true if parsing works without errors
|
|
909
968
|
def grammar_works?(language, test_source)
|
|
910
|
-
debug = ENV
|
|
969
|
+
debug = !ENV.fetch("TREE_HAVER_DEBUG", "false").casecmp?("false")
|
|
911
970
|
env_var = "TREE_SITTER_#{language.to_s.upcase}_PATH"
|
|
912
971
|
env_value = ENV[env_var]
|
|
913
972
|
|
|
@@ -972,7 +1031,7 @@ RSpec.configure do |config|
|
|
|
972
1031
|
|
|
973
1032
|
config.before(:suite) do
|
|
974
1033
|
# Print dependency summary if TREE_HAVER_DEBUG is set
|
|
975
|
-
|
|
1034
|
+
unless ENV.fetch("TREE_HAVER_DEBUG", "false").casecmp?("false")
|
|
976
1035
|
puts "\n=== TreeHaver Environment Variables ==="
|
|
977
1036
|
deps.env_summary.each do |var, value|
|
|
978
1037
|
puts " #{var}: #{value.inspect}"
|
|
@@ -1063,35 +1122,35 @@ RSpec.configure do |config|
|
|
|
1063
1122
|
#
|
|
1064
1123
|
# This is dynamic based on TreeHaver::Backends::BLOCKED_BY configuration.
|
|
1065
1124
|
|
|
1066
|
-
#
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1125
|
+
# Build backend maps dynamically from BackendRegistry and built-in backends
|
|
1126
|
+
# This allows external gems to register and automatically get tag support
|
|
1127
|
+
backend_availability_methods = {}
|
|
1128
|
+
backend_tags = {}
|
|
1129
|
+
|
|
1130
|
+
# Built-in backends (always present in tree_haver)
|
|
1131
|
+
builtin_backends = %i[mri rust ffi java prism psych citrus parslet rbs]
|
|
1132
|
+
builtin_backends.each do |backend|
|
|
1133
|
+
# Special case for ffi which uses ffi_available? not ffi_backend_available?
|
|
1134
|
+
availability_method = (backend == :ffi) ? :ffi_available? : :"#{backend}_available?"
|
|
1135
|
+
# Special case for backends that use *_backend_available? naming
|
|
1136
|
+
availability_method = :"#{backend}_backend_available?" if %i[mri rust java rbs].include?(backend)
|
|
1137
|
+
|
|
1138
|
+
backend_availability_methods[backend] = availability_method
|
|
1139
|
+
backend_tags[backend] = :"#{backend}_backend"
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
# Add dynamically registered backends from BackendRegistry
|
|
1143
|
+
# This picks up external gems like commonmarker-merge, markly-merge, etc.
|
|
1144
|
+
TreeHaver::BackendRegistry.registered_tags.each do |tag_name|
|
|
1145
|
+
meta = TreeHaver::BackendRegistry.tag_metadata(tag_name)
|
|
1146
|
+
next unless meta && meta[:category] == :backend
|
|
1147
|
+
|
|
1148
|
+
backend_name = meta[:backend_name]
|
|
1149
|
+
next if backend_availability_methods.key?(backend_name) # Don't override built-ins
|
|
1150
|
+
|
|
1151
|
+
backend_availability_methods[backend_name] = :"#{backend_name}_available?"
|
|
1152
|
+
backend_tags[backend_name] = tag_name
|
|
1153
|
+
end
|
|
1095
1154
|
|
|
1096
1155
|
# Determine which backends should NOT have availability checked
|
|
1097
1156
|
# based on which *_backend_only tag is being run OR which backend is
|
|
@@ -1219,7 +1278,7 @@ RSpec.configure do |config|
|
|
|
1219
1278
|
# Language Parsing Capability Tags
|
|
1220
1279
|
# ============================================================
|
|
1221
1280
|
# Tags: *_parsing - require ANY parser for a language (any backend that can parse it)
|
|
1222
|
-
# :toml_parsing - any TOML parser (tree-sitter-toml OR toml-rb/Citrus)
|
|
1281
|
+
# :toml_parsing - any TOML parser (tree-sitter-toml OR toml-rb/Citrus OR toml/Parslet)
|
|
1223
1282
|
# :markdown_parsing - any Markdown parser (commonmarker OR markly)
|
|
1224
1283
|
# :rbs_parsing - any RBS parser (rbs gem OR tree-sitter-rbs)
|
|
1225
1284
|
# :native_parsing - any native tree-sitter backend + grammar
|
|
@@ -1283,6 +1342,8 @@ RSpec.configure do |config|
|
|
|
1283
1342
|
# Language parsing capabilities
|
|
1284
1343
|
config.filter_run_excluding(not_toml_parsing: true) if deps.any_toml_backend_available?
|
|
1285
1344
|
config.filter_run_excluding(not_markdown_parsing: true) if deps.any_markdown_backend_available?
|
|
1345
|
+
config.filter_run_excluding(not_json_parsing: true) if deps.any_json_backend_available?
|
|
1346
|
+
config.filter_run_excluding(not_jsonc_parsing: true) if deps.any_jsonc_backend_available?
|
|
1286
1347
|
config.filter_run_excluding(not_rbs_parsing: true) if deps.any_rbs_backend_available?
|
|
1287
1348
|
end
|
|
1288
1349
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ensure TreeHaver::Node and TreeHaver::Point are loaded
|
|
4
|
+
require "tree_haver"
|
|
5
|
+
|
|
6
|
+
module TreeHaver
|
|
7
|
+
module RSpec
|
|
8
|
+
# A mock inner node that provides the minimal interface TreeHaver::Node expects.
|
|
9
|
+
#
|
|
10
|
+
# This is what TreeHaver::Node wraps - it simulates the backend-specific node
|
|
11
|
+
# (like tree-sitter's Node, Markly::Node, etc.)
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
class MockInnerNode
|
|
15
|
+
attr_reader :type, :start_byte, :end_byte, :children_data
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
type:,
|
|
19
|
+
text: nil,
|
|
20
|
+
start_byte: 0,
|
|
21
|
+
end_byte: nil,
|
|
22
|
+
start_row: 0,
|
|
23
|
+
start_column: 0,
|
|
24
|
+
end_row: nil,
|
|
25
|
+
end_column: nil,
|
|
26
|
+
children: []
|
|
27
|
+
)
|
|
28
|
+
@type = type.to_s
|
|
29
|
+
@text_content = text
|
|
30
|
+
@start_byte = start_byte
|
|
31
|
+
@end_byte = end_byte || (text ? start_byte + text.length : start_byte)
|
|
32
|
+
@start_row = start_row
|
|
33
|
+
@start_column = start_column
|
|
34
|
+
@end_row = end_row || start_row
|
|
35
|
+
@end_column = end_column || (text ? start_column + text.length : start_column)
|
|
36
|
+
@children_data = children
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def start_point
|
|
40
|
+
TreeHaver::Point.new(@start_row, @start_column)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def end_point
|
|
44
|
+
TreeHaver::Point.new(@end_row, @end_column)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def child_count
|
|
48
|
+
@children_data.length
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def child(index)
|
|
52
|
+
return if index.nil? || index < 0 || index >= @children_data.length
|
|
53
|
+
|
|
54
|
+
@children_data[index]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Return children array (for enumerable behavior)
|
|
58
|
+
def children
|
|
59
|
+
@children_data
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def first_child
|
|
63
|
+
@children_data.first
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def last_child
|
|
67
|
+
@children_data.last
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Iterate over children
|
|
71
|
+
def each(&block)
|
|
72
|
+
return enum_for(:each) unless block
|
|
73
|
+
|
|
74
|
+
@children_data.each(&block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def named?
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Test nodes are always valid (no parse errors)
|
|
82
|
+
def has_error?
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Test nodes are never missing (not error recovery insertions)
|
|
87
|
+
def missing?
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Some backends provide text directly
|
|
92
|
+
def text
|
|
93
|
+
@text_content
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# For backends that use string_content (like Markly/Commonmarker)
|
|
97
|
+
def string_content
|
|
98
|
+
@text_content
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A real TreeHaver::Node that wraps a MockInnerNode.
|
|
103
|
+
#
|
|
104
|
+
# This gives us full TreeHaver::Node behavior (#text, #type, #source_position, etc.)
|
|
105
|
+
# while allowing us to control the underlying data for testing.
|
|
106
|
+
#
|
|
107
|
+
# TestableNode is designed for testing code that works with TreeHaver nodes
|
|
108
|
+
# without requiring an actual parser backend. It creates real TreeHaver::Node
|
|
109
|
+
# instances with controlled, predictable data.
|
|
110
|
+
#
|
|
111
|
+
# @example Creating a testable node
|
|
112
|
+
# node = TreeHaver::RSpec::TestableNode.create(
|
|
113
|
+
# type: :heading,
|
|
114
|
+
# text: "## My Heading",
|
|
115
|
+
# start_line: 1
|
|
116
|
+
# )
|
|
117
|
+
# node.text # => "## My Heading"
|
|
118
|
+
# node.type # => "heading"
|
|
119
|
+
# node.start_line # => 1
|
|
120
|
+
#
|
|
121
|
+
# @example Creating with children
|
|
122
|
+
# parent = TreeHaver::RSpec::TestableNode.create(
|
|
123
|
+
# type: :document,
|
|
124
|
+
# text: "# Title\n\nParagraph",
|
|
125
|
+
# children: [
|
|
126
|
+
# { type: :heading, text: "# Title", start_line: 1 },
|
|
127
|
+
# { type: :paragraph, text: "Paragraph", start_line: 3 },
|
|
128
|
+
# ]
|
|
129
|
+
# )
|
|
130
|
+
#
|
|
131
|
+
# @example Using the convenience constant
|
|
132
|
+
# # After requiring tree_haver/rspec/testable_node, you can use:
|
|
133
|
+
# node = TestableNode.create(type: :paragraph, text: "Hello")
|
|
134
|
+
#
|
|
135
|
+
class TestableNode < TreeHaver::Node
|
|
136
|
+
class << self
|
|
137
|
+
# Create a TestableNode with the given attributes.
|
|
138
|
+
#
|
|
139
|
+
# @param type [Symbol, String] Node type (e.g., :heading, :paragraph)
|
|
140
|
+
# @param text [String] The text content of the node
|
|
141
|
+
# @param start_line [Integer] 1-based start line number (default: 1)
|
|
142
|
+
# @param end_line [Integer, nil] 1-based end line number (default: calculated from text)
|
|
143
|
+
# @param start_column [Integer] 0-based start column (default: 0)
|
|
144
|
+
# @param end_column [Integer, nil] 0-based end column (default: calculated from text)
|
|
145
|
+
# @param start_byte [Integer] Start byte offset (default: 0)
|
|
146
|
+
# @param end_byte [Integer, nil] End byte offset (default: calculated from text)
|
|
147
|
+
# @param children [Array<Hash>] Child node specifications
|
|
148
|
+
# @param source [String, nil] Full source text (default: uses text param)
|
|
149
|
+
# @return [TestableNode]
|
|
150
|
+
def create(
|
|
151
|
+
type:,
|
|
152
|
+
text: "",
|
|
153
|
+
start_line: 1,
|
|
154
|
+
end_line: nil,
|
|
155
|
+
start_column: 0,
|
|
156
|
+
end_column: nil,
|
|
157
|
+
start_byte: 0,
|
|
158
|
+
end_byte: nil,
|
|
159
|
+
children: [],
|
|
160
|
+
source: nil
|
|
161
|
+
)
|
|
162
|
+
# Convert 1-based line to 0-based row
|
|
163
|
+
start_row = start_line - 1
|
|
164
|
+
end_row = end_line ? end_line - 1 : start_row + text.count("\n")
|
|
165
|
+
|
|
166
|
+
# Calculate end_column if not provided
|
|
167
|
+
if end_column.nil?
|
|
168
|
+
lines = text.split("\n", -1)
|
|
169
|
+
end_column = lines.last&.length || 0
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Build children as MockInnerNodes
|
|
173
|
+
child_nodes = children.map do |child_spec|
|
|
174
|
+
MockInnerNode.new(**child_spec)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
inner = MockInnerNode.new(
|
|
178
|
+
type: type,
|
|
179
|
+
text: text,
|
|
180
|
+
start_byte: start_byte,
|
|
181
|
+
end_byte: end_byte,
|
|
182
|
+
start_row: start_row,
|
|
183
|
+
start_column: start_column,
|
|
184
|
+
end_row: end_row,
|
|
185
|
+
end_column: end_column,
|
|
186
|
+
children: child_nodes,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Create a real TreeHaver::Node wrapping our mock
|
|
190
|
+
# Pass source so TreeHaver::Node can extract text if needed
|
|
191
|
+
new(inner, source: source || text)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Create multiple nodes from an array of specifications.
|
|
195
|
+
#
|
|
196
|
+
# @param specs [Array<Hash>] Array of node specifications
|
|
197
|
+
# @return [Array<TestableNode>]
|
|
198
|
+
def create_list(*specs)
|
|
199
|
+
specs.flatten.map { |spec| create(**spec) }
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Additional test helper methods
|
|
204
|
+
|
|
205
|
+
# Check if this is a testable node (for test assertions)
|
|
206
|
+
#
|
|
207
|
+
# @return [Boolean] true
|
|
208
|
+
def testable?
|
|
209
|
+
true
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Make TestableNode available at top level for convenience in specs.
|
|
216
|
+
# This allows specs to use `TestableNode.create(...)` without the full namespace.
|
|
217
|
+
TestableNode = TreeHaver::RSpec::TestableNode
|
data/lib/tree_haver/rspec.rb
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
#
|
|
10
10
|
# This will load:
|
|
11
11
|
# - Dependency tags for conditional test execution
|
|
12
|
-
# -
|
|
12
|
+
# - TestableNode for creating mock nodes in tests
|
|
13
13
|
#
|
|
14
14
|
# @example spec_helper.rb
|
|
15
15
|
# require "tree_haver/rspec"
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
# # Your additional configuration...
|
|
19
19
|
# end
|
|
20
20
|
#
|
|
21
|
+
# @example Using TestableNode
|
|
22
|
+
# node = TestableNode.create(
|
|
23
|
+
# type: :heading,
|
|
24
|
+
# text: "## My Heading",
|
|
25
|
+
# start_line: 1
|
|
26
|
+
# )
|
|
27
|
+
# expect(node.type).to eq("heading")
|
|
28
|
+
#
|
|
21
29
|
# @see TreeHaver::RSpec::DependencyTags
|
|
30
|
+
# @see TreeHaver::RSpec::TestableNode
|
|
22
31
|
|
|
23
32
|
require_relative "rspec/dependency_tags"
|
|
33
|
+
require_relative "rspec/testable_node"
|
data/lib/tree_haver/version.rb
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tree_haver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.0.
|
|
4
|
+
version: 5.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -288,6 +288,7 @@ files:
|
|
|
288
288
|
- lib/tree_haver/point.rb
|
|
289
289
|
- lib/tree_haver/rspec.rb
|
|
290
290
|
- lib/tree_haver/rspec/dependency_tags.rb
|
|
291
|
+
- lib/tree_haver/rspec/testable_node.rb
|
|
291
292
|
- lib/tree_haver/tree.rb
|
|
292
293
|
- lib/tree_haver/version.rb
|
|
293
294
|
- sig/tree_haver.rbs
|
|
@@ -299,10 +300,10 @@ licenses:
|
|
|
299
300
|
- MIT
|
|
300
301
|
metadata:
|
|
301
302
|
homepage_uri: https://tree-haver.galtzo.com/
|
|
302
|
-
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v5.0.
|
|
303
|
-
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v5.0.
|
|
303
|
+
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v5.0.2
|
|
304
|
+
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v5.0.2/CHANGELOG.md
|
|
304
305
|
bug_tracker_uri: https://github.com/kettle-rb/tree_haver/issues
|
|
305
|
-
documentation_uri: https://www.rubydoc.info/gems/tree_haver/5.0.
|
|
306
|
+
documentation_uri: https://www.rubydoc.info/gems/tree_haver/5.0.2
|
|
306
307
|
funding_uri: https://github.com/sponsors/pboling
|
|
307
308
|
wiki_uri: https://github.com/kettle-rb/tree_haver/wiki
|
|
308
309
|
news_uri: https://www.railsbling.com/tags/tree_haver
|
metadata.gz.sig
CHANGED
|
Binary file
|