syntax_tree 4.2.0 → 4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 444c1c74319e55d9c3a656ff1796e6658237c0433a257eb6e2c66d8625d47243
4
- data.tar.gz: 2662a60c92265c6b257f6545ee1947269e1990f96003c60caef790eeb871eb32
3
+ metadata.gz: 4ca1eae46ba326b73e129d5b066d144aeac2743947956efc1453a3539c2caac9
4
+ data.tar.gz: 7c833e7b7bf25df7a82653d9dddb30aa172320a625a0655c52a29640c7a2154f
5
5
  SHA512:
6
- metadata.gz: 9dddc08b2d7c2c3c7cec089f301b88220cf18a625ef7b155ad71810eaefd4cfe54c063279a4f817bc823b1eb586a222b106aa5889f3b736da901ca0404feedb5
7
- data.tar.gz: eaabea185c2f53aaf62ea76b1bb89987d7c472538025ae3f3e457f97eafe38b2533895f504100ed534785ffebaa18a013dd8d1f27432da974beedf0b7202861c
6
+ metadata.gz: 1720e9ef9dd52399564607a9eca7794875e41f36f8ee2c4af2d97fb973c38ec0bf3a3cc254d26f3d656e67256543afed7c818e274395af122790ae6730ccab8c
7
+ data.tar.gz: 0f00dbe7739f71bfa1b81ff204fdd4dc505277a65e374810d2f482c6ef3c81078b39f864d55d8deed001a5b7a294491feb0b930948546bcd4194a88058ca9783
@@ -12,10 +12,12 @@ jobs:
12
12
  - '3.0'
13
13
  - '3.1'
14
14
  - head
15
+ - truffleruby-head
15
16
  name: CI
16
17
  runs-on: ubuntu-latest
17
18
  env:
18
19
  CI: true
20
+ TESTOPTS: --verbose
19
21
  steps:
20
22
  - uses: actions/checkout@master
21
23
  - uses: ruby/setup-ruby@v1
data/.rubocop.yml CHANGED
@@ -46,6 +46,9 @@ Naming/MethodParameterName:
46
46
  Naming/RescuedExceptionsVariableName:
47
47
  PreferredName: error
48
48
 
49
+ Style/CaseEquality:
50
+ Enabled: false
51
+
49
52
  Style/ExplicitBlockArgument:
50
53
  Enabled: false
51
54
 
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [4.3.0] - 2022-10-28
10
+
11
+ ### Added
12
+
13
+ - [#183](https://github.com/ruby-syntax-tree/syntax_tree/pull/183) - Support TruffleRuby by eliminating internal pattern matching in some places and stopping some tests from running in other places.
14
+ - [#184](https://github.com/ruby-syntax-tree/syntax_tree/pull/184) - Remove internal pattern matching entirely.
15
+
16
+ ### Changed
17
+
18
+ - [#183](https://github.com/ruby-syntax-tree/syntax_tree/pull/183) - Pattern matching works against dynamic symbols now.
19
+ - [#184](https://github.com/ruby-syntax-tree/syntax_tree/pull/184) - Exit with the correct exit status within the rake tasks.
20
+
9
21
  ## [4.2.0] - 2022-10-25
10
22
 
11
23
  ### Added
@@ -414,7 +426,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
414
426
 
415
427
  - 🎉 Initial release! 🎉
416
428
 
417
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.2.0...HEAD
429
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.3.0...HEAD
430
+ [4.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.2.0...v4.3.0
418
431
  [4.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.1.0...v4.2.0
419
432
  [4.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.2...v4.1.0
420
433
  [4.0.2]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.1...v4.0.2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (4.2.0)
4
+ syntax_tree (4.3.0)
5
5
  prettier_print (>= 1.0.2)
6
6
 
7
7
  GEM
@@ -192,9 +192,10 @@ module SyntaxTree
192
192
  # would match the first expression of the input given.
193
193
  class Expr < Action
194
194
  def run(item)
195
- case item.handler.parse(item.source)
196
- in Program[statements: Statements[body: [expression]]]
197
- puts expression.construct_keys
195
+ program = item.handler.parse(item.source)
196
+
197
+ if (expressions = program.statements.body) && expressions.size == 1
198
+ puts expressions.first.construct_keys
198
199
  else
199
200
  warn("The input to `stree expr` must be a single expression.")
200
201
  exit(1)
@@ -69,11 +69,10 @@ module SyntaxTree
69
69
  #
70
70
  def visit_binary(node)
71
71
  case stack[-2]
72
- in Assign | OpAssign
72
+ when Assign, OpAssign
73
73
  parentheses(node.location)
74
- in Binary[operator: operator] if operator != node.operator
75
- parentheses(node.location)
76
- else
74
+ when Binary
75
+ parentheses(node.location) if stack[-2].operator != node.operator
77
76
  end
78
77
 
79
78
  super
@@ -91,9 +90,8 @@ module SyntaxTree
91
90
  #
92
91
  def visit_if_op(node)
93
92
  case stack[-2]
94
- in Assign | Binary | IfOp | OpAssign
93
+ when Assign, Binary, IfOp, OpAssign
95
94
  parentheses(node.location)
96
- else
97
95
  end
98
96
 
99
97
  super
@@ -13,6 +13,50 @@ module SyntaxTree
13
13
  # stree lsp
14
14
  #
15
15
  class LanguageServer
16
+ # This is a small module that effectively mirrors pattern matching. We're
17
+ # using it so that we can support truffleruby without having to ignore the
18
+ # language server.
19
+ module Request
20
+ # Represents a hash pattern.
21
+ class Shape
22
+ attr_reader :values
23
+
24
+ def initialize(values)
25
+ @values = values
26
+ end
27
+
28
+ def ===(other)
29
+ values.all? do |key, value|
30
+ value == :any ? other.key?(key) : value === other[key]
31
+ end
32
+ end
33
+ end
34
+
35
+ # Represents an array pattern.
36
+ class Tuple
37
+ attr_reader :values
38
+
39
+ def initialize(values)
40
+ @values = values
41
+ end
42
+
43
+ def ===(other)
44
+ values.each_with_index.all? { |value, index| value === other[index] }
45
+ end
46
+ end
47
+
48
+ def self.[](value)
49
+ case value
50
+ when Array
51
+ Tuple.new(value.map { |child| self[child] })
52
+ when Hash
53
+ Shape.new(value.transform_values { |child| self[child] })
54
+ else
55
+ value
56
+ end
57
+ end
58
+ end
59
+
16
60
  attr_reader :input, :output, :print_width
17
61
 
18
62
  def initialize(
@@ -39,30 +83,33 @@ module SyntaxTree
39
83
 
40
84
  # stree-ignore
41
85
  case request
42
- in { method: "initialize", id: }
86
+ when Request[method: "initialize", id: :any]
43
87
  store.clear
44
- write(id: id, result: { capabilities: capabilities })
45
- in { method: "initialized" }
88
+ write(id: request[:id], result: { capabilities: capabilities })
89
+ when Request[method: "initialized"]
46
90
  # ignored
47
- in { method: "shutdown" } # tolerate missing ID to be a good citizen
91
+ when Request[method: "shutdown"] # tolerate missing ID to be a good citizen
48
92
  store.clear
49
93
  write(id: request[:id], result: {})
50
94
  return
51
- in { method: "textDocument/didChange", params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } }
52
- store[uri] = text
53
- in { method: "textDocument/didOpen", params: { textDocument: { uri:, text: } } }
54
- store[uri] = text
55
- in { method: "textDocument/didClose", params: { textDocument: { uri: } } }
56
- store.delete(uri)
57
- in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } }
95
+ when Request[method: "textDocument/didChange", params: { textDocument: { uri: :any }, contentChanges: [{ text: :any }] }]
96
+ store[request.dig(:params, :textDocument, :uri)] = request.dig(:params, :contentChanges, 0, :text)
97
+ when Request[method: "textDocument/didOpen", params: { textDocument: { uri: :any, text: :any } }]
98
+ store[request.dig(:params, :textDocument, :uri)] = request.dig(:params, :textDocument, :text)
99
+ when Request[method: "textDocument/didClose", params: { textDocument: { uri: :any } }]
100
+ store.delete(request.dig(:params, :textDocument, :uri))
101
+ when Request[method: "textDocument/formatting", id: :any, params: { textDocument: { uri: :any } }]
102
+ uri = request.dig(:params, :textDocument, :uri)
58
103
  contents = store[uri]
59
- write(id: id, result: contents ? format(contents, uri.split(".").last) : nil)
60
- in { method: "textDocument/inlayHint", id:, params: { textDocument: { uri: } } }
104
+ write(id: request[:id], result: contents ? format(contents, uri.split(".").last) : nil)
105
+ when Request[method: "textDocument/inlayHint", id: :any, params: { textDocument: { uri: :any } }]
106
+ uri = request.dig(:params, :textDocument, :uri)
61
107
  contents = store[uri]
62
- write(id: id, result: contents ? inlay_hints(contents) : nil)
63
- in { method: "syntaxTree/visualizing", id:, params: { textDocument: { uri: } } }
64
- write(id: id, result: PP.pp(SyntaxTree.parse(store[uri]), +""))
65
- in { method: %r{\$/.+} }
108
+ write(id: request[:id], result: contents ? inlay_hints(contents) : nil)
109
+ when Request[method: "syntaxTree/visualizing", id: :any, params: { textDocument: { uri: :any } }]
110
+ uri = request.dig(:params, :textDocument, :uri)
111
+ write(id: request[:id], result: PP.pp(SyntaxTree.parse(store[uri]), +""))
112
+ when Request[method: %r{\$/.+}]
66
113
  # ignored
67
114
  else
68
115
  raise ArgumentError, "Unhandled: #{request}"
@@ -75,98 +75,213 @@ module SyntaxTree
75
75
 
76
76
  private
77
77
 
78
+ # Shortcut for combining two procs into one that returns true if both return
79
+ # true.
78
80
  def combine_and(left, right)
79
- ->(node) { left.call(node) && right.call(node) }
81
+ ->(other) { left.call(other) && right.call(other) }
80
82
  end
81
83
 
84
+ # Shortcut for combining two procs into one that returns true if either
85
+ # returns true.
82
86
  def combine_or(left, right)
83
- ->(node) { left.call(node) || right.call(node) }
87
+ ->(other) { left.call(other) || right.call(other) }
84
88
  end
85
89
 
86
- def compile_node(root)
87
- case root
88
- in AryPtn[constant:, requireds:, rest: nil, posts: []]
89
- compiled_constant = compile_node(constant) if constant
90
+ # Raise an error because the given node is not supported.
91
+ def compile_error(node)
92
+ raise CompilationError, PP.pp(node, +"").chomp
93
+ end
94
+
95
+ # There are a couple of nodes (string literals, dynamic symbols, and regexp)
96
+ # that contain list of parts. This can include plain string content,
97
+ # interpolated expressions, and interpolated variables. We only support
98
+ # plain string content, so this method will extract out the plain string
99
+ # content if it is the only element in the list.
100
+ def extract_string(node)
101
+ parts = node.parts
90
102
 
91
- preprocessed = requireds.map { |required| compile_node(required) }
103
+ if parts.length == 1 && (part = parts.first) && part.is_a?(TStringContent)
104
+ part.value
105
+ end
106
+ end
92
107
 
93
- compiled_requireds = ->(node) do
94
- deconstructed = node.deconstruct
108
+ # in [foo, bar, baz]
109
+ def compile_aryptn(node)
110
+ compile_error(node) if !node.rest.nil? || node.posts.any?
95
111
 
96
- deconstructed.length == preprocessed.length &&
97
- preprocessed
98
- .zip(deconstructed)
99
- .all? { |(matcher, value)| matcher.call(value) }
100
- end
112
+ constant = node.constant
113
+ compiled_constant = compile_node(constant) if constant
101
114
 
102
- if compiled_constant
103
- combine_and(compiled_constant, compiled_requireds)
104
- else
105
- compiled_requireds
106
- end
107
- in Binary[left:, operator: :|, right:]
108
- combine_or(compile_node(left), compile_node(right))
109
- in Const[value:] if SyntaxTree.const_defined?(value)
115
+ preprocessed = node.requireds.map { |required| compile_node(required) }
116
+
117
+ compiled_requireds = ->(other) do
118
+ deconstructed = other.deconstruct
119
+
120
+ deconstructed.length == preprocessed.length &&
121
+ preprocessed
122
+ .zip(deconstructed)
123
+ .all? { |(matcher, value)| matcher.call(value) }
124
+ end
125
+
126
+ if compiled_constant
127
+ combine_and(compiled_constant, compiled_requireds)
128
+ else
129
+ compiled_requireds
130
+ end
131
+ end
132
+
133
+ # in foo | bar
134
+ def compile_binary(node)
135
+ compile_error(node) if node.operator != :|
136
+
137
+ combine_or(compile_node(node.left), compile_node(node.right))
138
+ end
139
+
140
+ # in Ident
141
+ # in String
142
+ def compile_const(node)
143
+ value = node.value
144
+
145
+ if SyntaxTree.const_defined?(value)
110
146
  clazz = SyntaxTree.const_get(value)
111
147
 
112
- ->(node) { node.is_a?(clazz) }
113
- in Const[value:] if Object.const_defined?(value)
148
+ ->(other) { clazz === other }
149
+ elsif Object.const_defined?(value)
114
150
  clazz = Object.const_get(value)
115
151
 
116
- ->(node) { node.is_a?(clazz) }
117
- in ConstPathRef[
118
- parent: VarRef[value: Const[value: "SyntaxTree"]], constant:
119
- ]
120
- compile_node(constant)
121
- in DynaSymbol[parts: []]
152
+ ->(other) { clazz === other }
153
+ else
154
+ compile_error(node)
155
+ end
156
+ end
157
+
158
+ # in SyntaxTree::Ident
159
+ def compile_const_path_ref(node)
160
+ parent = node.parent
161
+ compile_error(node) if !parent.is_a?(VarRef) || !parent.value.is_a?(Const)
162
+
163
+ if parent.value.value == "SyntaxTree"
164
+ compile_node(node.constant)
165
+ else
166
+ compile_error(node)
167
+ end
168
+ end
169
+
170
+ # in :""
171
+ # in :"foo"
172
+ def compile_dyna_symbol(node)
173
+ if node.parts.empty?
122
174
  symbol = :""
123
175
 
124
- ->(node) { node == symbol }
125
- in DynaSymbol[parts: [TStringContent[value:]]]
176
+ ->(other) { symbol === other }
177
+ elsif (value = extract_string(node))
126
178
  symbol = value.to_sym
127
179
 
128
- ->(attribute) { attribute == value }
129
- in HshPtn[constant:, keywords:, keyword_rest: nil]
130
- compiled_constant = compile_node(constant)
131
-
132
- preprocessed =
133
- keywords.to_h do |keyword, value|
134
- raise NoMatchingPatternError unless keyword.is_a?(Label)
135
- [keyword.value.chomp(":").to_sym, compile_node(value)]
136
- end
180
+ ->(other) { symbol === other }
181
+ else
182
+ compile_error(root)
183
+ end
184
+ end
137
185
 
138
- compiled_keywords = ->(node) do
139
- deconstructed = node.deconstruct_keys(preprocessed.keys)
186
+ # in Ident[value: String]
187
+ # in { value: String }
188
+ def compile_hshptn(node)
189
+ compile_error(node) unless node.keyword_rest.nil?
190
+ compiled_constant = compile_node(node.constant) if node.constant
140
191
 
141
- preprocessed.all? do |keyword, matcher|
142
- matcher.call(deconstructed[keyword])
143
- end
192
+ preprocessed =
193
+ node.keywords.to_h do |keyword, value|
194
+ compile_error(node) unless keyword.is_a?(Label)
195
+ [keyword.value.chomp(":").to_sym, compile_node(value)]
144
196
  end
145
197
 
146
- if compiled_constant
147
- combine_and(compiled_constant, compiled_keywords)
148
- else
149
- compiled_keywords
198
+ compiled_keywords = ->(other) do
199
+ deconstructed = other.deconstruct_keys(preprocessed.keys)
200
+
201
+ preprocessed.all? do |keyword, matcher|
202
+ matcher.call(deconstructed[keyword])
150
203
  end
151
- in RegexpLiteral[parts: [TStringContent[value:]]]
204
+ end
205
+
206
+ if compiled_constant
207
+ combine_and(compiled_constant, compiled_keywords)
208
+ else
209
+ compiled_keywords
210
+ end
211
+ end
212
+
213
+ # in /foo/
214
+ def compile_regexp_literal(node)
215
+ if (value = extract_string(node))
152
216
  regexp = /#{value}/
153
217
 
154
- ->(attribute) { regexp.match?(attribute) }
155
- in StringLiteral[parts: []]
156
- ->(attribute) { attribute == "" }
157
- in StringLiteral[parts: [TStringContent[value:]]]
158
- ->(attribute) { attribute == value }
159
- in SymbolLiteral[value:]
160
- symbol = value.value.to_sym
218
+ ->(attribute) { regexp === attribute }
219
+ else
220
+ compile_error(node)
221
+ end
222
+ end
223
+
224
+ # in ""
225
+ # in "foo"
226
+ def compile_string_literal(node)
227
+ if node.parts.empty?
228
+ ->(attribute) { "" === attribute }
229
+ elsif (value = extract_string(node))
230
+ ->(attribute) { value === attribute }
231
+ else
232
+ compile_error(node)
233
+ end
234
+ end
235
+
236
+ # in :+
237
+ # in :foo
238
+ def compile_symbol_literal(node)
239
+ symbol = node.value.value.to_sym
161
240
 
162
- ->(attribute) { attribute == symbol }
163
- in VarRef[value: Const => value]
241
+ ->(attribute) { symbol === attribute }
242
+ end
243
+
244
+ # in Foo
245
+ # in nil
246
+ def compile_var_ref(node)
247
+ value = node.value
248
+
249
+ if value.is_a?(Const)
164
250
  compile_node(value)
165
- in VarRef[value: Kw[value: "nil"]]
166
- ->(attribute) { attribute.nil? }
251
+ elsif value.is_a?(Kw) && value.value.nil?
252
+ ->(attribute) { nil === attribute }
253
+ else
254
+ compile_error(node)
255
+ end
256
+ end
257
+
258
+ # Compile any kind of node. Dispatch out to the individual compilation
259
+ # methods based on the type of node.
260
+ def compile_node(node)
261
+ case node
262
+ when AryPtn
263
+ compile_aryptn(node)
264
+ when Binary
265
+ compile_binary(node)
266
+ when Const
267
+ compile_const(node)
268
+ when ConstPathRef
269
+ compile_const_path_ref(node)
270
+ when DynaSymbol
271
+ compile_dyna_symbol(node)
272
+ when HshPtn
273
+ compile_hshptn(node)
274
+ when RegexpLiteral
275
+ compile_regexp_literal(node)
276
+ when StringLiteral
277
+ compile_string_literal(node)
278
+ when SymbolLiteral
279
+ compile_symbol_literal(node)
280
+ when VarRef
281
+ compile_var_ref(node)
282
+ else
283
+ compile_error(node)
167
284
  end
168
- rescue NoMatchingPatternError
169
- raise CompilationError, PP.pp(root, +"").chomp
170
285
  end
171
286
  end
172
287
  end
@@ -78,7 +78,7 @@ module SyntaxTree
78
78
 
79
79
  arguments << "--ignore-files=#{ignore_files}" if ignore_files != ""
80
80
 
81
- SyntaxTree::CLI.run(arguments + Array(source_files))
81
+ abort if SyntaxTree::CLI.run(arguments + Array(source_files)) != 0
82
82
  end
83
83
  end
84
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "4.2.0"
4
+ VERSION = "4.3.0"
5
5
  end
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.2.0
4
+ version: 4.3.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-25 00:00:00.000000000 Z
11
+ date: 2022-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prettier_print