Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c776db3

Browse files
authored
Merge pull request #270 from ruby-syntax-tree/visit-methods
BasicVisitor::visit_methods
2 parents 2966122 + 1155f85 commit c776db3

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ It is built with only standard library dependencies. It additionally ships with
4040
- [construct_keys](#construct_keys)
4141
- [Visitor](#visitor)
4242
- [visit_method](#visit_method)
43+
- [visit_methods](#visit_methods)
4344
- [BasicVisitor](#basicvisitor)
4445
- [MutationVisitor](#mutationvisitor)
4546
- [WithEnvironment](#withenvironment)
@@ -517,6 +518,26 @@ Did you mean? visit_binary
517518
from bin/console:8:in `<main>'
518519
```
519520

521+
### visit_methods
522+
523+
Similar to `visit_method`, `visit_methods` also checks that methods defined are valid visit methods. This variation however accepts a block and checks that all methods defined within that block are valid visit methods. It's meant to be used like:
524+
525+
```ruby
526+
class ArithmeticVisitor < SyntaxTree::Visitor
527+
visit_methods do
528+
def visit_binary(node)
529+
# ...
530+
end
531+
532+
def visit_int(node)
533+
# ...
534+
end
535+
end
536+
end
537+
```
538+
539+
This is only checked when the methods are defined and does not impose any kind of runtime overhead after that. It is very useful for upgrading versions of Syntax Tree in case these methods names change.
540+
520541
### BasicVisitor
521542

522543
When you're defining your own visitor, by default it will walk down the tree even if you don't define `visit_*` methods. This is to ensure you can define a subset of the necessary methods in order to only interact with the nodes you're interested in. If you'd like to change this default to instead raise an error if you visit a node you haven't explicitly handled, you can instead inherit from `BasicVisitor`.

lib/syntax_tree/basic_visitor.rb

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def initialize(error)
2929
def corrections
3030
@corrections ||=
3131
DidYouMean::SpellChecker.new(
32-
dictionary: Visitor.visit_methods
32+
dictionary: BasicVisitor.valid_visit_methods
3333
).correct(visit_method)
3434
end
3535

@@ -40,7 +40,40 @@ def corrections
4040
end
4141
end
4242

43+
# This module is responsible for checking all of the methods defined within
44+
# a given block to ensure that they are valid visit methods.
45+
class VisitMethodsChecker < Module
46+
Status = Struct.new(:checking)
47+
48+
# This is the status of the checker. It's used to determine whether or not
49+
# we should be checking the methods that are defined. It is kept as an
50+
# instance variable so that it can be disabled later.
51+
attr_reader :status
52+
53+
def initialize
54+
# We need the status to be an instance variable so that it can be
55+
# accessed by the disable! method, but also a local variable so that it
56+
# can be captured by the define_method block.
57+
status = @status = Status.new(true)
58+
59+
define_method(:method_added) do |name|
60+
BasicVisitor.visit_method(name) if status.checking
61+
super(name)
62+
end
63+
end
64+
65+
def disable!
66+
status.checking = false
67+
end
68+
end
69+
4370
class << self
71+
# This is the list of all of the valid visit methods.
72+
def valid_visit_methods
73+
@valid_visit_methods ||=
74+
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
75+
end
76+
4477
# This method is here to help folks write visitors.
4578
#
4679
# It's not always easy to ensure you're writing the correct method name in
@@ -51,15 +84,21 @@ class << self
5184
# name. It will raise an error if the visit method you're defining isn't
5285
# actually a method on the parent visitor.
5386
def visit_method(method_name)
54-
return if visit_methods.include?(method_name)
87+
return if valid_visit_methods.include?(method_name)
5588

5689
raise VisitMethodError, method_name
5790
end
5891

59-
# This is the list of all of the valid visit methods.
92+
# This method is here to help folks write visitors.
93+
#
94+
# Within the given block, every method that is defined will be checked to
95+
# ensure it's a valid visit method using the BasicVisitor::visit_method
96+
# method defined above.
6097
def visit_methods
61-
@visit_methods ||=
62-
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
98+
checker = VisitMethodsChecker.new
99+
extend(checker)
100+
yield
101+
checker.disable!
63102
end
64103
end
65104

test/visitor_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,19 @@ def test_visit_method_correction
5353
assert_match(/visit_binary/, message)
5454
end
5555
end
56+
57+
class VisitMethodsTestVisitor < BasicVisitor
58+
end
59+
60+
def test_visit_methods
61+
VisitMethodsTestVisitor.visit_methods do
62+
assert_raises(BasicVisitor::VisitMethodError) do
63+
# In reality, this would be a method defined using the def keyword,
64+
# but we're using method_added here to trigger the checker so that we
65+
# aren't defining methods dynamically in the test suite.
66+
VisitMethodsTestVisitor.method_added(:visit_foo)
67+
end
68+
end
69+
end
5670
end
5771
end

0 commit comments

Comments
 (0)