syntax_tree 6.1.1 → 6.3.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.
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ # Provides the ability to index source files into a database, then query for
5
+ # the nodes.
6
+ module Database
7
+ class IndexingVisitor < SyntaxTree::FieldVisitor
8
+ attr_reader :database, :filepath, :node_id
9
+
10
+ def initialize(database, filepath)
11
+ @database = database
12
+ @filepath = filepath
13
+ @node_id = nil
14
+ end
15
+
16
+ private
17
+
18
+ def comments(node)
19
+ end
20
+
21
+ def field(name, value)
22
+ return unless value.is_a?(SyntaxTree::Node)
23
+
24
+ binds = [node_id, visit(value), name]
25
+ database.execute(<<~SQL, binds)
26
+ INSERT INTO edges (from_id, to_id, name)
27
+ VALUES (?, ?, ?)
28
+ SQL
29
+ end
30
+
31
+ def list(name, values)
32
+ values.each_with_index do |value, index|
33
+ binds = [node_id, visit(value), name, index]
34
+ database.execute(<<~SQL, binds)
35
+ INSERT INTO edges (from_id, to_id, name, list_index)
36
+ VALUES (?, ?, ?, ?)
37
+ SQL
38
+ end
39
+ end
40
+
41
+ def node(node, _name)
42
+ previous = node_id
43
+ binds = [
44
+ node.class.name.delete_prefix("SyntaxTree::"),
45
+ filepath,
46
+ node.location.start_line,
47
+ node.location.start_column
48
+ ]
49
+
50
+ database.execute(<<~SQL, binds)
51
+ INSERT INTO nodes (type, path, line, column)
52
+ VALUES (?, ?, ?, ?)
53
+ SQL
54
+
55
+ begin
56
+ @node_id = database.last_insert_row_id
57
+ yield
58
+ @node_id
59
+ ensure
60
+ @node_id = previous
61
+ end
62
+ end
63
+
64
+ def text(name, value)
65
+ end
66
+
67
+ def pairs(name, values)
68
+ values.each_with_index do |(key, value), index|
69
+ binds = [node_id, visit(key), "#{name}[0]", index]
70
+ database.execute(<<~SQL, binds)
71
+ INSERT INTO edges (from_id, to_id, name, list_index)
72
+ VALUES (?, ?, ?, ?)
73
+ SQL
74
+
75
+ binds = [node_id, visit(value), "#{name}[1]", index]
76
+ database.execute(<<~SQL, binds)
77
+ INSERT INTO edges (from_id, to_id, name, list_index)
78
+ VALUES (?, ?, ?, ?)
79
+ SQL
80
+ end
81
+ end
82
+ end
83
+
84
+ # Query for a specific type of node.
85
+ class TypeQuery
86
+ attr_reader :type
87
+
88
+ def initialize(type)
89
+ @type = type
90
+ end
91
+
92
+ def each(database, &block)
93
+ sql = "SELECT * FROM nodes WHERE type = ?"
94
+ database.execute(sql, type).each(&block)
95
+ end
96
+ end
97
+
98
+ # Query for the attributes of a node, optionally also filtering by type.
99
+ class AttrQuery
100
+ attr_reader :type, :attrs
101
+
102
+ def initialize(type, attrs)
103
+ @type = type
104
+ @attrs = attrs
105
+ end
106
+
107
+ def each(database, &block)
108
+ joins = []
109
+ binds = []
110
+
111
+ attrs.each do |name, query|
112
+ ids = query.each(database).map { |row| row[0] }
113
+ joins << <<~SQL
114
+ JOIN edges AS #{name}
115
+ ON #{name}.from_id = nodes.id
116
+ AND #{name}.name = ?
117
+ AND #{name}.to_id IN (#{(["?"] * ids.size).join(", ")})
118
+ SQL
119
+
120
+ binds.push(name).concat(ids)
121
+ end
122
+
123
+ sql = +"SELECT nodes.* FROM nodes, edges #{joins.join(" ")}"
124
+
125
+ if type
126
+ sql << " WHERE nodes.type = ?"
127
+ binds << type
128
+ end
129
+
130
+ sql << " GROUP BY nodes.id"
131
+ database.execute(sql, binds).each(&block)
132
+ end
133
+ end
134
+
135
+ # Query for the results of either query.
136
+ class OrQuery
137
+ attr_reader :left, :right
138
+
139
+ def initialize(left, right)
140
+ @left = left
141
+ @right = right
142
+ end
143
+
144
+ def each(database, &block)
145
+ left.each(database, &block)
146
+ right.each(database, &block)
147
+ end
148
+ end
149
+
150
+ # A lazy query result.
151
+ class QueryResult
152
+ attr_reader :database, :query
153
+
154
+ def initialize(database, query)
155
+ @database = database
156
+ @query = query
157
+ end
158
+
159
+ def each(&block)
160
+ return enum_for(__method__) unless block_given?
161
+ query.each(database, &block)
162
+ end
163
+ end
164
+
165
+ # A pattern matching expression that will be compiled into a query.
166
+ class Pattern
167
+ class CompilationError < StandardError
168
+ end
169
+
170
+ attr_reader :query
171
+
172
+ def initialize(query)
173
+ @query = query
174
+ end
175
+
176
+ def compile
177
+ program =
178
+ begin
179
+ SyntaxTree.parse("case nil\nin #{query}\nend")
180
+ rescue Parser::ParseError
181
+ raise CompilationError, query
182
+ end
183
+
184
+ compile_node(program.statements.body.first.consequent.pattern)
185
+ end
186
+
187
+ private
188
+
189
+ def compile_error(node)
190
+ raise CompilationError, PP.pp(node, +"").chomp
191
+ end
192
+
193
+ # Shortcut for combining two queries into one that returns the results of
194
+ # if either query matches.
195
+ def combine_or(left, right)
196
+ OrQuery.new(left, right)
197
+ end
198
+
199
+ # in foo | bar
200
+ def compile_binary(node)
201
+ compile_error(node) if node.operator != :|
202
+
203
+ combine_or(compile_node(node.left), compile_node(node.right))
204
+ end
205
+
206
+ # in Ident
207
+ def compile_const(node)
208
+ value = node.value
209
+
210
+ if SyntaxTree.const_defined?(value, false)
211
+ clazz = SyntaxTree.const_get(value)
212
+ TypeQuery.new(clazz.name.delete_prefix("SyntaxTree::"))
213
+ else
214
+ compile_error(node)
215
+ end
216
+ end
217
+
218
+ # in SyntaxTree::Ident
219
+ def compile_const_path_ref(node)
220
+ parent = node.parent
221
+ if !parent.is_a?(SyntaxTree::VarRef) ||
222
+ !parent.value.is_a?(SyntaxTree::Const)
223
+ compile_error(node)
224
+ end
225
+
226
+ if parent.value.value == "SyntaxTree"
227
+ compile_node(node.constant)
228
+ else
229
+ compile_error(node)
230
+ end
231
+ end
232
+
233
+ # in Ident[value: String]
234
+ def compile_hshptn(node)
235
+ compile_error(node) unless node.keyword_rest.nil?
236
+
237
+ attrs = {}
238
+ node.keywords.each do |keyword, value|
239
+ compile_error(node) unless keyword.is_a?(SyntaxTree::Label)
240
+ attrs[keyword.value.chomp(":")] = compile_node(value)
241
+ end
242
+
243
+ type = node.constant ? compile_node(node.constant).type : nil
244
+ AttrQuery.new(type, attrs)
245
+ end
246
+
247
+ # in Foo
248
+ def compile_var_ref(node)
249
+ value = node.value
250
+
251
+ if value.is_a?(SyntaxTree::Const)
252
+ compile_node(value)
253
+ else
254
+ compile_error(node)
255
+ end
256
+ end
257
+
258
+ def compile_node(node)
259
+ case node
260
+ when SyntaxTree::Binary
261
+ compile_binary(node)
262
+ when SyntaxTree::Const
263
+ compile_const(node)
264
+ when SyntaxTree::ConstPathRef
265
+ compile_const_path_ref(node)
266
+ when SyntaxTree::HshPtn
267
+ compile_hshptn(node)
268
+ when SyntaxTree::VarRef
269
+ compile_var_ref(node)
270
+ else
271
+ compile_error(node)
272
+ end
273
+ end
274
+ end
275
+
276
+ class Connection
277
+ attr_reader :raw_connection
278
+
279
+ def initialize(raw_connection)
280
+ @raw_connection = raw_connection
281
+ end
282
+
283
+ def execute(query, binds = [])
284
+ raw_connection.execute(query, binds)
285
+ end
286
+
287
+ def index_file(filepath)
288
+ program = SyntaxTree.parse(SyntaxTree.read(filepath))
289
+ program.accept(IndexingVisitor.new(self, filepath))
290
+ end
291
+
292
+ def last_insert_row_id
293
+ raw_connection.last_insert_row_id
294
+ end
295
+
296
+ def prepare
297
+ raw_connection.execute(<<~SQL)
298
+ CREATE TABLE nodes (
299
+ id integer primary key,
300
+ type varchar(20),
301
+ path varchar(200),
302
+ line integer,
303
+ column integer
304
+ );
305
+ SQL
306
+
307
+ raw_connection.execute(<<~SQL)
308
+ CREATE INDEX nodes_type ON nodes (type);
309
+ SQL
310
+
311
+ raw_connection.execute(<<~SQL)
312
+ CREATE TABLE edges (
313
+ id integer primary key,
314
+ from_id integer,
315
+ to_id integer,
316
+ name varchar(20),
317
+ list_index integer
318
+ );
319
+ SQL
320
+
321
+ raw_connection.execute(<<~SQL)
322
+ CREATE INDEX edges_name ON edges (name);
323
+ SQL
324
+ end
325
+
326
+ def search(query)
327
+ QueryResult.new(self, Pattern.new(query).compile)
328
+ end
329
+ end
330
+ end
331
+ end
@@ -60,7 +60,7 @@ module SyntaxTree
60
60
  # constant. That constant is responsible for determining the default
61
61
  # disable ternary value. If it's defined, then we default to true.
62
62
  # Otherwise we default to false.
63
- defined?(DISABLE_TERNARY)
63
+ defined?(DISABLE_AUTO_TERNARY)
64
64
  else
65
65
  disable_auto_ternary
66
66
  end
@@ -217,11 +217,13 @@ module SyntaxTree
217
217
  def initialize(
218
218
  input: $stdin,
219
219
  output: $stdout,
220
- print_width: DEFAULT_PRINT_WIDTH
220
+ print_width: DEFAULT_PRINT_WIDTH,
221
+ ignore_files: []
221
222
  )
222
223
  @input = input.binmode
223
224
  @output = output.binmode
224
225
  @print_width = print_width
226
+ @ignore_files = ignore_files
225
227
  end
226
228
 
227
229
  # rubocop:disable Layout/LineLength
@@ -255,8 +257,12 @@ module SyntaxTree
255
257
  store.delete(request.dig(:params, :textDocument, :uri))
256
258
  when Request[method: "textDocument/formatting", id: :any, params: { textDocument: { uri: :any } }]
257
259
  uri = request.dig(:params, :textDocument, :uri)
260
+ filepath = uri.split("///").last
261
+ ignore = @ignore_files.any? do |glob|
262
+ File.fnmatch(glob, filepath)
263
+ end
258
264
  contents = store[uri]
259
- write(id: request[:id], result: contents ? format(contents, uri.split(".").last) : nil)
265
+ write(id: request[:id], result: contents && !ignore ? format(contents, uri.split(".").last) : nil)
260
266
  when Request[method: "textDocument/inlayHint", id: :any, params: { textDocument: { uri: :any } }]
261
267
  uri = request.dig(:params, :textDocument, :uri)
262
268
  contents = store[uri]
@@ -288,7 +288,7 @@ module SyntaxTree
288
288
  q.text(value)
289
289
  else
290
290
  q.text(q.quote)
291
- q.text(value[1] == "\"" ? "\\\"" : value[1])
291
+ q.text(value[1] == q.quote ? "\\#{q.quote}" : value[1])
292
292
  q.text(q.quote)
293
293
  end
294
294
  end
@@ -1299,7 +1299,7 @@ module SyntaxTree
1299
1299
  end
1300
1300
  end
1301
1301
 
1302
- # [nil | VarRef] the optional constant wrapper
1302
+ # [nil | VarRef | ConstPathRef] the optional constant wrapper
1303
1303
  attr_reader :constant
1304
1304
 
1305
1305
  # [Array[ Node ]] the regular positional arguments that this array
@@ -1783,45 +1783,60 @@ module SyntaxTree
1783
1783
  end
1784
1784
  end
1785
1785
 
1786
- def self.for(container)
1787
- container.assocs.each do |assoc|
1788
- if assoc.is_a?(AssocSplat)
1789
- # Splat nodes do not impact the formatting choice.
1790
- elsif assoc.value.nil?
1791
- # If the value is nil, then it has been omitted. In this case we have
1792
- # to match the existing formatting because standardizing would
1793
- # potentially break the code. For example:
1794
- #
1795
- # { first:, "second" => "value" }
1796
- #
1797
- return Identity.new
1798
- else
1799
- # Otherwise, we need to check the type of the key. If it's a label or
1800
- # dynamic symbol, we can use labels. If it's a symbol literal then it
1801
- # needs to match a certain pattern to be used as a label. If it's
1802
- # anything else, then we need to use hash rockets.
1803
- case assoc.key
1804
- when Label, DynaSymbol
1805
- # Here labels can be used.
1806
- when SymbolLiteral
1807
- # When attempting to convert a hash rocket into a hash label,
1808
- # you need to take care because only certain patterns are
1809
- # allowed. Ruby source says that they have to match keyword
1810
- # arguments to methods, but don't specify what that is. After
1811
- # some experimentation, it looks like it's:
1812
- value = assoc.key.value.value
1813
-
1814
- if !value.match?(/^[_A-Za-z]/) || value.end_with?("=")
1815
- return Rockets.new
1816
- end
1786
+ class << self
1787
+ def for(container)
1788
+ (assocs = container.assocs).each_with_index do |assoc, index|
1789
+ if assoc.is_a?(AssocSplat)
1790
+ # Splat nodes do not impact the formatting choice.
1791
+ elsif assoc.value.nil?
1792
+ # If the value is nil, then it has been omitted. In this case we
1793
+ # have to match the existing formatting because standardizing would
1794
+ # potentially break the code. For example:
1795
+ #
1796
+ # { first:, "second" => "value" }
1797
+ #
1798
+ return Identity.new
1817
1799
  else
1818
- # If the value is anything else, we have to use hash rockets.
1819
- return Rockets.new
1800
+ # Otherwise, we need to check the type of the key. If it's a label
1801
+ # or dynamic symbol, we can use labels. If it's a symbol literal
1802
+ # then it needs to match a certain pattern to be used as a label. If
1803
+ # it's anything else, then we need to use hash rockets.
1804
+ case assoc.key
1805
+ when Label, DynaSymbol
1806
+ # Here labels can be used.
1807
+ when SymbolLiteral
1808
+ # When attempting to convert a hash rocket into a hash label,
1809
+ # you need to take care because only certain patterns are
1810
+ # allowed. Ruby source says that they have to match keyword
1811
+ # arguments to methods, but don't specify what that is. After
1812
+ # some experimentation, it looks like it's:
1813
+ value = assoc.key.value.value
1814
+
1815
+ if !value.match?(/^[_A-Za-z]/) || value.end_with?("=")
1816
+ if omitted_value?(assocs[(index + 1)..])
1817
+ return Identity.new
1818
+ else
1819
+ return Rockets.new
1820
+ end
1821
+ end
1822
+ else
1823
+ if omitted_value?(assocs[(index + 1)..])
1824
+ return Identity.new
1825
+ else
1826
+ return Rockets.new
1827
+ end
1828
+ end
1820
1829
  end
1821
1830
  end
1831
+
1832
+ Labels.new
1822
1833
  end
1823
1834
 
1824
- Labels.new
1835
+ private
1836
+
1837
+ def omitted_value?(assocs)
1838
+ assocs.any? { |assoc| !assoc.is_a?(AssocSplat) && assoc.value.nil? }
1839
+ end
1825
1840
  end
1826
1841
  end
1827
1842
 
@@ -2849,7 +2864,10 @@ module SyntaxTree
2849
2864
  # to print the operator trailing in order to keep it working.
2850
2865
  last_child = children.last
2851
2866
  if last_child.is_a?(CallNode) && last_child.message != :call &&
2852
- last_child.message.comments.any? && last_child.operator
2867
+ (
2868
+ (last_child.message.comments.any? && last_child.operator) ||
2869
+ (last_child.operator && last_child.operator.comments.any?)
2870
+ )
2853
2871
  q.format(CallOperatorFormatter.new(last_child.operator))
2854
2872
  skip_operator = true
2855
2873
  else
@@ -5413,7 +5431,7 @@ module SyntaxTree
5413
5431
  # end
5414
5432
  #
5415
5433
  class FndPtn < Node
5416
- # [nil | Node] the optional constant wrapper
5434
+ # [nil | VarRef | ConstPathRef] the optional constant wrapper
5417
5435
  attr_reader :constant
5418
5436
 
5419
5437
  # [VarField] the splat on the left-hand side
@@ -6035,7 +6053,7 @@ module SyntaxTree
6035
6053
  end
6036
6054
  end
6037
6055
 
6038
- # [nil | Node] the optional constant wrapper
6056
+ # [nil | VarRef | ConstPathRef] the optional constant wrapper
6039
6057
  attr_reader :constant
6040
6058
 
6041
6059
  # [Array[ [DynaSymbol | Label, nil | Node] ]] the set of tuples
@@ -7207,36 +7225,17 @@ module SyntaxTree
7207
7225
  q.text(" ")
7208
7226
  q
7209
7227
  .if_break do
7210
- force_parens =
7211
- q.parents.any? do |node|
7212
- node.is_a?(Command) || node.is_a?(CommandCall)
7213
- end
7214
-
7215
- if force_parens
7216
- q.text("{")
7228
+ q.text("do")
7217
7229
 
7218
- unless statements.empty?
7219
- q.indent do
7220
- q.breakable_space
7221
- q.format(statements)
7222
- end
7230
+ unless statements.empty?
7231
+ q.indent do
7223
7232
  q.breakable_space
7233
+ q.format(statements)
7224
7234
  end
7225
-
7226
- q.text("}")
7227
- else
7228
- q.text("do")
7229
-
7230
- unless statements.empty?
7231
- q.indent do
7232
- q.breakable_space
7233
- q.format(statements)
7234
- end
7235
- end
7236
-
7237
- q.breakable_space
7238
- q.text("end")
7239
7235
  end
7236
+
7237
+ q.breakable_space
7238
+ q.text("end")
7240
7239
  end
7241
7240
  .if_flat do
7242
7241
  q.text("{")
@@ -8293,8 +8292,8 @@ module SyntaxTree
8293
8292
  # parameter
8294
8293
  attr_reader :rest
8295
8294
 
8296
- # [Array[ Ident ]] any positional parameters that exist after a rest
8297
- # parameter
8295
+ # [Array[ Ident | MLHSParen ]] any positional parameters that exist after a
8296
+ # rest parameter
8298
8297
  attr_reader :posts
8299
8298
 
8300
8299
  # [Array[ [ Label, nil | Node ] ]] any keyword parameters and their
@@ -11660,6 +11659,10 @@ module SyntaxTree
11660
11659
  elsif value.is_a?(Array) && (index = value.index(self))
11661
11660
  parent.public_send(key)[index] = replace
11662
11661
  break
11662
+ elsif value.is_a?(Array) &&
11663
+ (index = value.index { |(_k, v)| v == self })
11664
+ parent.public_send(key)[index][1] = replace
11665
+ break
11663
11666
  end
11664
11667
  end
11665
11668
  end
@@ -670,7 +670,11 @@ module SyntaxTree
670
670
 
671
671
  visit_methods do
672
672
  def visit_var_ref(node)
673
- node.pin(stack[-2], pins.shift)
673
+ if node.start_char > pins.first.start_char
674
+ node.pin(stack[-2], pins.shift)
675
+ else
676
+ super
677
+ end
674
678
  end
675
679
  end
676
680
 
@@ -1732,13 +1736,13 @@ module SyntaxTree
1732
1736
  # :call-seq:
1733
1737
  # on_field: (
1734
1738
  # untyped parent,
1735
- # (:"::" | Op | Period) operator
1739
+ # (:"::" | Op | Period | 73) operator
1736
1740
  # (Const | Ident) name
1737
1741
  # ) -> Field
1738
1742
  def on_field(parent, operator, name)
1739
1743
  Field.new(
1740
1744
  parent: parent,
1741
- operator: operator,
1745
+ operator: operator == 73 ? :"::" : operator,
1742
1746
  name: name,
1743
1747
  location: parent.location.to(name.location)
1744
1748
  )
@@ -2867,6 +2871,7 @@ module SyntaxTree
2867
2871
  alias on_assign_error on_parse_error
2868
2872
  alias on_class_name_error on_parse_error
2869
2873
  alias on_param_error on_parse_error
2874
+ alias compile_error on_parse_error
2870
2875
 
2871
2876
  # :call-seq:
2872
2877
  # on_period: (String value) -> Period
@@ -70,6 +70,7 @@ module SyntaxTree
70
70
  raise CompilationError, query
71
71
  end
72
72
 
73
+ raise CompilationError, query if program.nil?
73
74
  compile_node(program.statements.body.first.consequent.pattern)
74
75
  end
75
76
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SyntaxTree
4
4
  class Formatter
5
- DISABLE_TERNARY = true
5
+ DISABLE_AUTO_TERNARY = true
6
6
  end
7
7
  end
@@ -64,7 +64,7 @@ module SyntaxTree
64
64
 
65
65
  class << self
66
66
  def parse(comment)
67
- comment = comment.gsub(/\n/, " ")
67
+ comment = comment.gsub("\n", " ")
68
68
 
69
69
  unless comment.start_with?("[")
70
70
  raise "Comment does not start with a bracket: #{comment.inspect}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "6.1.1"
4
+ VERSION = "6.3.0"
5
5
  end
@@ -152,10 +152,7 @@ module SyntaxTree
152
152
  # arguments.
153
153
  def visit_params(node)
154
154
  add_argument_definitions(node.requireds)
155
-
156
- node.posts.each do |param|
157
- current_scope.add_local_definition(param, :argument)
158
- end
155
+ add_argument_definitions(node.posts)
159
156
 
160
157
  node.keywords.each do |param|
161
158
  current_scope.add_local_definition(param.first, :argument)
@@ -31,7 +31,6 @@ module SyntaxTree
31
31
  "FCALL" => CallData::CALL_FCALL,
32
32
  "VCALL" => CallData::CALL_VCALL,
33
33
  "ARGS_SIMPLE" => CallData::CALL_ARGS_SIMPLE,
34
- "BLOCKISEQ" => CallData::CALL_BLOCKISEQ,
35
34
  "KWARG" => CallData::CALL_KWARG,
36
35
  "KW_SPLAT" => CallData::CALL_KW_SPLAT,
37
36
  "TAILCALL" => CallData::CALL_TAILCALL,
@@ -409,7 +408,7 @@ module SyntaxTree
409
408
  def find_local(iseq, operands)
410
409
  name_string, level_string = operands.split(/,\s*/)
411
410
  name = name_string.to_sym
412
- level = level_string&.to_i || 0
411
+ level = level_string.to_i
413
412
 
414
413
  iseq.local_table.plain(name)
415
414
  iseq.local_table.find(name, level)
@@ -456,7 +455,7 @@ module SyntaxTree
456
455
  CallData::CALL_ARGS_SIMPLE
457
456
  end
458
457
 
459
- YARV.calldata(message.to_sym, argc_value&.to_i || 0, flags)
458
+ YARV.calldata(message.to_sym, argc_value.to_i, flags)
460
459
  end
461
460
  end
462
461
  end