syntax_tree 3.6.3 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "3.6.3"
4
+ VERSION = "4.0.0"
5
5
  end
@@ -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: 3.6.3
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 00:00:00.000000000 Z
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: '0'
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: '0'
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: