Skip to content

Commit cc1f4c8

Browse files
authored
Merge pull request #88 from ruby-syntax-tree/bare-visitor
Provide a BasicVisitor
2 parents 3ee5d0c + 933f685 commit cc1f4c8

File tree

5 files changed

+93
-69
lines changed

5 files changed

+93
-69
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ It is built with only standard library dependencies. It additionally ships with
3232
- [construct_keys](#construct_keys)
3333
- [Visitor](#visitor)
3434
- [visit_method](#visit_method)
35+
- [BasicVisitor](#basicvisitor)
3536
- [Language server](#language-server)
3637
- [textDocument/formatting](#textdocumentformatting)
3738
- [textDocument/inlayHints](#textdocumentinlayhints)
@@ -373,6 +374,20 @@ Did you mean? visit_binary
373374
from bin/console:8:in `<main>'
374375
```
375376

377+
### BasicVisitor
378+
379+
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`.
380+
381+
```ruby
382+
class MyVisitor < SyntaxTree::BasicVisitor
383+
def visit_int(node)
384+
# ...
385+
end
386+
end
387+
```
388+
389+
The visitor defined above will error out unless it's only visiting a `SyntaxTree::Int` node. This is useful in a couple of ways, e.g., if you're trying to define a visitor to handle the whole tree but it's currently a work-in-progress.
390+
376391
## Language server
377392

378393
Syntax Tree additionally ships with a language server conforming to the [language server protocol](https://microsoft.github.io/language-server-protocol/). It can be invoked through the CLI by running:

lib/syntax_tree.rb

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
require_relative "syntax_tree/node"
1111
require_relative "syntax_tree/parser"
1212
require_relative "syntax_tree/version"
13+
14+
require_relative "syntax_tree/basic_visitor"
1315
require_relative "syntax_tree/visitor"
1416
require_relative "syntax_tree/visitor/field_visitor"
1517
require_relative "syntax_tree/visitor/json_visitor"

lib/syntax_tree/basic_visitor.rb

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxTree
4+
# BasicVisitor is the parent class of the Visitor class that provides the
5+
# ability to walk down the tree. It does not define any handlers, so you
6+
# should extend this class if you want your visitor to raise an error if you
7+
# attempt to visit a node that you don't handle.
8+
class BasicVisitor
9+
# This is raised when you use the Visitor.visit_method method and it fails.
10+
# It is correctable to through DidYouMean.
11+
class VisitMethodError < StandardError
12+
attr_reader :visit_method
13+
14+
def initialize(visit_method)
15+
@visit_method = visit_method
16+
super("Invalid visit method: #{visit_method}")
17+
end
18+
end
19+
20+
# This class is used by DidYouMean to offer corrections to invalid visit
21+
# method names.
22+
class VisitMethodChecker
23+
attr_reader :visit_method
24+
25+
def initialize(error)
26+
@visit_method = error.visit_method
27+
end
28+
29+
def corrections
30+
@corrections ||=
31+
DidYouMean::SpellChecker.new(
32+
dictionary: Visitor.visit_methods
33+
).correct(visit_method)
34+
end
35+
36+
DidYouMean.correct_error(VisitMethodError, self)
37+
end
38+
39+
class << self
40+
# This method is here to help folks write visitors.
41+
#
42+
# It's not always easy to ensure you're writing the correct method name in
43+
# the visitor since it's perfectly valid to define methods that don't
44+
# override these parent methods.
45+
#
46+
# If you use this method, you can ensure you're writing the correct method
47+
# name. It will raise an error if the visit method you're defining isn't
48+
# actually a method on the parent visitor.
49+
def visit_method(method_name)
50+
return if visit_methods.include?(method_name)
51+
52+
raise VisitMethodError, method_name
53+
end
54+
55+
# This is the list of all of the valid visit methods.
56+
def visit_methods
57+
@visit_methods ||=
58+
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
59+
end
60+
end
61+
62+
def visit(node)
63+
node&.accept(self)
64+
end
65+
66+
def visit_all(nodes)
67+
nodes.map { |node| visit(node) }
68+
end
69+
70+
def visit_child_nodes(node)
71+
visit_all(node.child_nodes)
72+
end
73+
end
74+
end

lib/syntax_tree/visitor.rb

+1-66
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,7 @@ module SyntaxTree
44
# Visitor is a parent class that provides the ability to walk down the tree
55
# and handle a subset of nodes. By defining your own subclass, you can
66
# explicitly handle a node type by defining a visit_* method.
7-
class Visitor
8-
# This is raised when you use the Visitor.visit_method method and it fails.
9-
# It is correctable to through DidYouMean.
10-
class VisitMethodError < StandardError
11-
attr_reader :visit_method
12-
13-
def initialize(visit_method)
14-
@visit_method = visit_method
15-
super("Invalid visit method: #{visit_method}")
16-
end
17-
end
18-
19-
# This class is used by DidYouMean to offer corrections to invalid visit
20-
# method names.
21-
class VisitMethodChecker
22-
attr_reader :visit_method
23-
24-
def initialize(error)
25-
@visit_method = error.visit_method
26-
end
27-
28-
def corrections
29-
@corrections ||=
30-
DidYouMean::SpellChecker.new(
31-
dictionary: Visitor.visit_methods
32-
).correct(visit_method)
33-
end
34-
35-
DidYouMean.correct_error(VisitMethodError, self)
36-
end
37-
38-
class << self
39-
# This method is here to help folks write visitors.
40-
#
41-
# It's not always easy to ensure you're writing the correct method name in
42-
# the visitor since it's perfectly valid to define methods that don't
43-
# override these parent methods.
44-
#
45-
# If you use this method, you can ensure you're writing the correct method
46-
# name. It will raise an error if the visit method you're defining isn't
47-
# actually a method on the parent visitor.
48-
def visit_method(method_name)
49-
return if visit_methods.include?(method_name)
50-
51-
raise VisitMethodError, method_name
52-
end
53-
54-
# This is the list of all of the valid visit methods.
55-
def visit_methods
56-
@visit_methods ||=
57-
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
58-
end
59-
end
60-
61-
def visit(node)
62-
node&.accept(self)
63-
end
64-
65-
def visit_all(nodes)
66-
nodes.map { |node| visit(node) }
67-
end
68-
69-
def visit_child_nodes(node)
70-
visit_all(node.child_nodes)
71-
end
72-
7+
class Visitor < BasicVisitor
738
# Visit an ARef node.
749
alias visit_aref visit_child_nodes
7510

lib/syntax_tree/visitor/field_visitor.rb

+1-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ class Visitor
4949
# of circumstances, like when visiting the list of optional parameters
5050
# defined on a method.
5151
#
52-
class FieldVisitor < Visitor
53-
attr_reader :q
54-
52+
class FieldVisitor < BasicVisitor
5553
def visit_aref(node)
5654
node(node, "aref") do
5755
field("collection", node.collection)

0 commit comments

Comments
 (0)