syntax_tree 3.6.3 → 4.0.0
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
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +15 -1
- data/Gemfile.lock +5 -5
- data/README.md +32 -10
- data/bin/profile +5 -6
- data/lib/syntax_tree/cli.rb +3 -3
- data/lib/syntax_tree/formatter.rb +67 -8
- data/lib/syntax_tree/node.rb +775 -527
- data/lib/syntax_tree/parser.rb +335 -245
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/environment.rb +81 -0
- data/lib/syntax_tree/visitor/with_environment.rb +141 -0
- data/lib/syntax_tree.rb +14 -2
- data/syntax_tree.gemspec +1 -1
- metadata +6 -4
data/lib/syntax_tree/version.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# The environment class is used to keep track of local variables and arguments
|
5
|
+
# inside a particular scope
|
6
|
+
class Environment
|
7
|
+
# [Array[Local]] The local variables and arguments defined in this
|
8
|
+
# environment
|
9
|
+
attr_reader :locals
|
10
|
+
|
11
|
+
# This class tracks the occurrences of a local variable or argument
|
12
|
+
class Local
|
13
|
+
# [Symbol] The type of the local (e.g. :argument, :variable)
|
14
|
+
attr_reader :type
|
15
|
+
|
16
|
+
# [Array[Location]] The locations of all definitions and assignments of
|
17
|
+
# this local
|
18
|
+
attr_reader :definitions
|
19
|
+
|
20
|
+
# [Array[Location]] The locations of all usages of this local
|
21
|
+
attr_reader :usages
|
22
|
+
|
23
|
+
# initialize: (Symbol type) -> void
|
24
|
+
def initialize(type)
|
25
|
+
@type = type
|
26
|
+
@definitions = []
|
27
|
+
@usages = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# add_definition: (Location location) -> void
|
31
|
+
def add_definition(location)
|
32
|
+
@definitions << location
|
33
|
+
end
|
34
|
+
|
35
|
+
# add_usage: (Location location) -> void
|
36
|
+
def add_usage(location)
|
37
|
+
@usages << location
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# initialize: (Environment | nil parent) -> void
|
42
|
+
def initialize(parent = nil)
|
43
|
+
@locals = {}
|
44
|
+
@parent = parent
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adding a local definition will either insert a new entry in the locals
|
48
|
+
# hash or append a new definition location to an existing local. Notice that
|
49
|
+
# it's not possible to change the type of a local after it has been
|
50
|
+
# registered
|
51
|
+
# add_local_definition: (Ident | Label identifier, Symbol type) -> void
|
52
|
+
def add_local_definition(identifier, type)
|
53
|
+
name = identifier.value.delete_suffix(":")
|
54
|
+
|
55
|
+
@locals[name] ||= Local.new(type)
|
56
|
+
@locals[name].add_definition(identifier.location)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adding a local usage will either insert a new entry in the locals
|
60
|
+
# hash or append a new usage location to an existing local. Notice that
|
61
|
+
# it's not possible to change the type of a local after it has been
|
62
|
+
# registered
|
63
|
+
# add_local_usage: (Ident | Label identifier, Symbol type) -> void
|
64
|
+
def add_local_usage(identifier, type)
|
65
|
+
name = identifier.value.delete_suffix(":")
|
66
|
+
|
67
|
+
@locals[name] ||= Local.new(type)
|
68
|
+
@locals[name].add_usage(identifier.location)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Try to find the local given its name in this environment or any of its
|
72
|
+
# parents
|
73
|
+
# find_local: (String name) -> Local | nil
|
74
|
+
def find_local(name)
|
75
|
+
local = @locals[name]
|
76
|
+
return local unless local.nil?
|
77
|
+
|
78
|
+
@parent&.find_local(name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# WithEnvironment is a module intended to be included in classes inheriting
|
5
|
+
# from Visitor. The module overrides a few visit methods to automatically keep
|
6
|
+
# track of local variables and arguments defined in the current environment.
|
7
|
+
# Example usage:
|
8
|
+
# class MyVisitor < Visitor
|
9
|
+
# include WithEnvironment
|
10
|
+
#
|
11
|
+
# def visit_ident(node)
|
12
|
+
# # Check if we're visiting an identifier for an argument, a local
|
13
|
+
# variable or something else
|
14
|
+
# local = current_environment.find_local(node)
|
15
|
+
#
|
16
|
+
# if local.type == :argument
|
17
|
+
# # handle identifiers for arguments
|
18
|
+
# elsif local.type == :variable
|
19
|
+
# # handle identifiers for variables
|
20
|
+
# else
|
21
|
+
# # handle other identifiers, such as method names
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module WithEnvironment
|
25
|
+
def current_environment
|
26
|
+
@current_environment ||= Environment.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_new_environment
|
30
|
+
previous_environment = @current_environment
|
31
|
+
@current_environment = Environment.new(previous_environment)
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
@current_environment = previous_environment
|
35
|
+
end
|
36
|
+
|
37
|
+
# Visits for nodes that create new environments, such as classes, modules
|
38
|
+
# and method definitions
|
39
|
+
def visit_class(node)
|
40
|
+
with_new_environment { super }
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_module(node)
|
44
|
+
with_new_environment { super }
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_method_add_block(node)
|
48
|
+
with_new_environment { super }
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_def(node)
|
52
|
+
with_new_environment { super }
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_defs(node)
|
56
|
+
with_new_environment { super }
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_def_endless(node)
|
60
|
+
with_new_environment { super }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Visit for keeping track of local arguments, such as method and block
|
64
|
+
# arguments
|
65
|
+
def visit_params(node)
|
66
|
+
node.requireds.each do |param|
|
67
|
+
@current_environment.add_local_definition(param, :argument)
|
68
|
+
end
|
69
|
+
|
70
|
+
node.posts.each do |param|
|
71
|
+
@current_environment.add_local_definition(param, :argument)
|
72
|
+
end
|
73
|
+
|
74
|
+
node.keywords.each do |param|
|
75
|
+
@current_environment.add_local_definition(param.first, :argument)
|
76
|
+
end
|
77
|
+
|
78
|
+
node.optionals.each do |param|
|
79
|
+
@current_environment.add_local_definition(param.first, :argument)
|
80
|
+
end
|
81
|
+
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_rest_param(node)
|
86
|
+
name = node.name
|
87
|
+
@current_environment.add_local_definition(name, :argument) if name
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
def visit_kwrest_param(node)
|
93
|
+
name = node.name
|
94
|
+
@current_environment.add_local_definition(name, :argument) if name
|
95
|
+
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_blockarg(node)
|
100
|
+
name = node.name
|
101
|
+
@current_environment.add_local_definition(name, :argument) if name
|
102
|
+
|
103
|
+
super
|
104
|
+
end
|
105
|
+
|
106
|
+
# Visit for keeping track of local variable definitions
|
107
|
+
def visit_var_field(node)
|
108
|
+
value = node.value
|
109
|
+
|
110
|
+
if value.is_a?(SyntaxTree::Ident)
|
111
|
+
@current_environment.add_local_definition(value, :variable)
|
112
|
+
end
|
113
|
+
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
alias visit_pinned_var_ref visit_var_field
|
118
|
+
|
119
|
+
# Visits for keeping track of variable and argument usages
|
120
|
+
def visit_aref_field(node)
|
121
|
+
name = node.collection.value
|
122
|
+
@current_environment.add_local_usage(name, :variable) if name
|
123
|
+
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
127
|
+
def visit_var_ref(node)
|
128
|
+
value = node.value
|
129
|
+
|
130
|
+
if value.is_a?(SyntaxTree::Ident)
|
131
|
+
definition = @current_environment.find_local(value.value)
|
132
|
+
|
133
|
+
if definition
|
134
|
+
@current_environment.add_local_usage(value, definition.type)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/syntax_tree.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "delegate"
|
4
3
|
require "etc"
|
5
4
|
require "json"
|
6
5
|
require "pp"
|
@@ -10,7 +9,6 @@ require "stringio"
|
|
10
9
|
|
11
10
|
require_relative "syntax_tree/formatter"
|
12
11
|
require_relative "syntax_tree/node"
|
13
|
-
require_relative "syntax_tree/parser"
|
14
12
|
require_relative "syntax_tree/version"
|
15
13
|
|
16
14
|
require_relative "syntax_tree/basic_visitor"
|
@@ -19,6 +17,20 @@ require_relative "syntax_tree/visitor/field_visitor"
|
|
19
17
|
require_relative "syntax_tree/visitor/json_visitor"
|
20
18
|
require_relative "syntax_tree/visitor/match_visitor"
|
21
19
|
require_relative "syntax_tree/visitor/pretty_print_visitor"
|
20
|
+
require_relative "syntax_tree/visitor/environment"
|
21
|
+
require_relative "syntax_tree/visitor/with_environment"
|
22
|
+
|
23
|
+
require_relative "syntax_tree/parser"
|
24
|
+
|
25
|
+
# We rely on Symbol#name being available, which is only available in Ruby 3.0+.
|
26
|
+
# In case we're running on an older Ruby version, we polyfill it here.
|
27
|
+
unless :+.respond_to?(:name)
|
28
|
+
class Symbol # rubocop:disable Style/Documentation
|
29
|
+
def name
|
30
|
+
to_s.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
22
34
|
|
23
35
|
# Syntax Tree is a suite of tools built on top of the internal CRuby parser. It
|
24
36
|
# provides the ability to generate a syntax tree from source, as well as the
|
data/syntax_tree.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
26
|
spec.require_paths = %w[lib]
|
27
27
|
|
28
|
-
spec.add_dependency "prettier_print"
|
28
|
+
spec.add_dependency "prettier_print", ">= 1.0.0"
|
29
29
|
|
30
30
|
spec.add_development_dependency "bundler"
|
31
31
|
spec.add_development_dependency "minitest"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntax_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,10 +137,12 @@ files:
|
|
137
137
|
- lib/syntax_tree/rake_tasks.rb
|
138
138
|
- lib/syntax_tree/version.rb
|
139
139
|
- lib/syntax_tree/visitor.rb
|
140
|
+
- lib/syntax_tree/visitor/environment.rb
|
140
141
|
- lib/syntax_tree/visitor/field_visitor.rb
|
141
142
|
- lib/syntax_tree/visitor/json_visitor.rb
|
142
143
|
- lib/syntax_tree/visitor/match_visitor.rb
|
143
144
|
- lib/syntax_tree/visitor/pretty_print_visitor.rb
|
145
|
+
- lib/syntax_tree/visitor/with_environment.rb
|
144
146
|
- syntax_tree.gemspec
|
145
147
|
homepage: https://github.com/kddnewton/syntax_tree
|
146
148
|
licenses:
|