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 +4 -4
- data/.github/workflows/main.yml +2 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +14 -1
- data/Gemfile.lock +1 -1
- data/lib/syntax_tree/cli.rb +4 -3
- data/lib/syntax_tree/language_server/inlay_hints.rb +4 -6
- data/lib/syntax_tree/language_server.rb +64 -17
- data/lib/syntax_tree/pattern.rb +179 -64
- data/lib/syntax_tree/rake/task.rb +1 -1
- data/lib/syntax_tree/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ca1eae46ba326b73e129d5b066d144aeac2743947956efc1453a3539c2caac9
|
4
|
+
data.tar.gz: 7c833e7b7bf25df7a82653d9dddb30aa172320a625a0655c52a29640c7a2154f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1720e9ef9dd52399564607a9eca7794875e41f36f8ee2c4af2d97fb973c38ec0bf3a3cc254d26f3d656e67256543afed7c818e274395af122790ae6730ccab8c
|
7
|
+
data.tar.gz: 0f00dbe7739f71bfa1b81ff204fdd4dc505277a65e374810d2f482c6ef3c81078b39f864d55d8deed001a5b7a294491feb0b930948546bcd4194a88058ca9783
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
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.
|
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
data/lib/syntax_tree/cli.rb
CHANGED
@@ -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
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
72
|
+
when Assign, OpAssign
|
73
73
|
parentheses(node.location)
|
74
|
-
|
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
|
-
|
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
|
-
|
86
|
+
when Request[method: "initialize", id: :any]
|
43
87
|
store.clear
|
44
|
-
write(id: id, result: { capabilities: capabilities })
|
45
|
-
|
88
|
+
write(id: request[:id], result: { capabilities: capabilities })
|
89
|
+
when Request[method: "initialized"]
|
46
90
|
# ignored
|
47
|
-
|
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
|
-
|
52
|
-
store[uri] = text
|
53
|
-
|
54
|
-
store[uri] = text
|
55
|
-
|
56
|
-
store.delete(uri)
|
57
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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}"
|
data/lib/syntax_tree/pattern.rb
CHANGED
@@ -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
|
-
->(
|
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
|
-
->(
|
87
|
+
->(other) { left.call(other) || right.call(other) }
|
84
88
|
end
|
85
89
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
103
|
+
if parts.length == 1 && (part = parts.first) && part.is_a?(TStringContent)
|
104
|
+
part.value
|
105
|
+
end
|
106
|
+
end
|
92
107
|
|
93
|
-
|
94
|
-
|
108
|
+
# in [foo, bar, baz]
|
109
|
+
def compile_aryptn(node)
|
110
|
+
compile_error(node) if !node.rest.nil? || node.posts.any?
|
95
111
|
|
96
|
-
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
->(
|
113
|
-
|
148
|
+
->(other) { clazz === other }
|
149
|
+
elsif Object.const_defined?(value)
|
114
150
|
clazz = Object.const_get(value)
|
115
151
|
|
116
|
-
->(
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
->(
|
125
|
-
|
176
|
+
->(other) { symbol === other }
|
177
|
+
elsif (value = extract_string(node))
|
126
178
|
symbol = value.to_sym
|
127
179
|
|
128
|
-
->(
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
139
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
166
|
-
->(attribute) {
|
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
|
data/lib/syntax_tree/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2022-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|