decode 0.22.0 → 0.23.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
- checksums.yaml.gz.sig +0 -0
- data/bake/decode/index.rb +16 -9
- data/context/coverage.md +325 -0
- data/context/getting-started.md +242 -0
- data/context/ruby-documentation.md +363 -0
- data/lib/decode/comment/attribute.rb +9 -3
- data/lib/decode/comment/node.rb +4 -2
- data/lib/decode/comment/option.rb +1 -1
- data/lib/decode/comment/parameter.rb +12 -6
- data/lib/decode/comment/pragma.rb +12 -1
- data/lib/decode/comment/raises.rb +1 -1
- data/lib/decode/comment/returns.rb +3 -4
- data/lib/decode/comment/tag.rb +13 -3
- data/lib/decode/comment/tags.rb +17 -2
- data/lib/decode/comment/text.rb +4 -1
- data/lib/decode/comment/throws.rb +1 -1
- data/lib/decode/comment/yields.rb +7 -1
- data/lib/decode/definition.rb +54 -42
- data/lib/decode/documentation.rb +12 -14
- data/lib/decode/index.rb +29 -14
- data/lib/decode/language/generic.rb +30 -14
- data/lib/decode/language/reference.rb +13 -4
- data/lib/decode/language/ruby/alias.rb +41 -0
- data/lib/decode/language/ruby/attribute.rb +7 -6
- data/lib/decode/language/ruby/block.rb +4 -1
- data/lib/decode/language/ruby/call.rb +16 -6
- data/lib/decode/language/ruby/class.rb +19 -36
- data/lib/decode/language/ruby/code.rb +27 -15
- data/lib/decode/language/ruby/constant.rb +9 -8
- data/lib/decode/language/ruby/definition.rb +27 -19
- data/lib/decode/language/ruby/function.rb +2 -1
- data/lib/decode/language/ruby/generic.rb +17 -7
- data/lib/decode/language/ruby/method.rb +47 -12
- data/lib/decode/language/ruby/module.rb +4 -11
- data/lib/decode/language/ruby/parser.rb +358 -207
- data/lib/decode/language/ruby/reference.rb +26 -17
- data/lib/decode/language/ruby/segment.rb +11 -4
- data/lib/decode/language/ruby.rb +4 -2
- data/lib/decode/language.rb +2 -2
- data/lib/decode/languages.rb +25 -6
- data/lib/decode/location.rb +2 -0
- data/lib/decode/scope.rb +1 -1
- data/lib/decode/segment.rb +6 -5
- data/lib/decode/source.rb +12 -4
- data/lib/decode/syntax/link.rb +9 -1
- data/lib/decode/syntax/match.rb +12 -0
- data/lib/decode/syntax/rewriter.rb +10 -0
- data/lib/decode/trie.rb +27 -22
- data/lib/decode/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +9 -10
- metadata.gz.sig +0 -0
@@ -3,26 +3,29 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2020-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require
|
6
|
+
require "prism"
|
7
7
|
|
8
|
-
require_relative
|
8
|
+
require_relative "../../scope"
|
9
9
|
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
-
require_relative
|
14
|
-
require_relative
|
15
|
-
require_relative
|
16
|
-
require_relative
|
17
|
-
require_relative
|
10
|
+
require_relative "alias"
|
11
|
+
require_relative "attribute"
|
12
|
+
require_relative "block"
|
13
|
+
require_relative "call"
|
14
|
+
require_relative "class"
|
15
|
+
require_relative "constant"
|
16
|
+
require_relative "function"
|
17
|
+
require_relative "method"
|
18
|
+
require_relative "module"
|
18
19
|
|
19
|
-
require_relative
|
20
|
+
require_relative "segment"
|
20
21
|
|
21
22
|
module Decode
|
22
23
|
module Language
|
23
24
|
module Ruby
|
24
25
|
# The Ruby source code parser.
|
25
26
|
class Parser
|
27
|
+
# Initialize a new Ruby parser.
|
28
|
+
# @parameter language [Language] The language instance.
|
26
29
|
def initialize(language)
|
27
30
|
@language = language
|
28
31
|
|
@@ -30,244 +33,385 @@ module Decode
|
|
30
33
|
@definitions = Hash.new.compare_by_identity
|
31
34
|
end
|
32
35
|
|
33
|
-
private def assign_definition(parent, definition)
|
34
|
-
(@definitions[parent] ||= {})[definition.name] = definition
|
35
|
-
end
|
36
|
-
|
37
|
-
private def lookup_definition(parent, name)
|
38
|
-
(@definitions[parent] ||= {})[name]
|
39
|
-
end
|
40
|
-
|
41
|
-
# Parse the given source object, can be a string or a Source instance.
|
42
|
-
# @parameter source [String | Source] The source to parse.
|
43
|
-
private def parse_source(source)
|
44
|
-
if source.is_a?(Source)
|
45
|
-
::Parser::CurrentRuby.parse_with_comments(source.read, source.relative_path)
|
46
|
-
else
|
47
|
-
::Parser::CurrentRuby.parse_with_comments(source)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
36
|
# Extract definitions from the given input file.
|
52
37
|
def definitions_for(source, &block)
|
53
|
-
|
38
|
+
return enum_for(:definitions_for, source) unless block_given?
|
54
39
|
|
55
|
-
|
56
|
-
|
57
|
-
|
40
|
+
result = self.parse_source(source)
|
41
|
+
result.attach_comments!
|
42
|
+
|
43
|
+
# Pass the source to walk_definitions for location tracking
|
44
|
+
source = source.is_a?(Source) ? source : nil
|
45
|
+
walk_definitions(result.value, nil, source, &block)
|
58
46
|
end
|
59
47
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if last_comment = prefix.last
|
67
|
-
if last_comment.location.line != (comment.location.line - 1)
|
68
|
-
prefix.clear
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
prefix << comments.shift
|
48
|
+
# Walk over the syntax tree and extract relevant definitions with their associated comments.
|
49
|
+
def walk_definitions(node, parent = nil, source = nil, &block)
|
50
|
+
# Check for scope definitions from comments
|
51
|
+
if node.comments.any?
|
52
|
+
parent = scope_for(node.comments.map(&:slice), parent, &block) || parent
|
73
53
|
end
|
74
54
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
55
|
+
case node.type
|
56
|
+
when :program_node
|
57
|
+
with_visibility do
|
58
|
+
node.child_nodes.each do |child|
|
59
|
+
walk_definitions(child, parent, source, &block)
|
80
60
|
end
|
81
61
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
when :module
|
97
|
-
definition = Module.new(
|
98
|
-
node, nested_name_for(node.children[0]),
|
99
|
-
comments: extract_comments_for(node, comments),
|
62
|
+
when :statements_node
|
63
|
+
node.child_nodes.each do |child|
|
64
|
+
walk_definitions(child, parent, source, &block)
|
65
|
+
end
|
66
|
+
when :block_node
|
67
|
+
if node.body
|
68
|
+
walk_definitions(node.body, parent, source, &block)
|
69
|
+
end
|
70
|
+
when :module_node
|
71
|
+
path = nested_path_for(node.constant_path)
|
72
|
+
|
73
|
+
definition = Module.new(path,
|
74
|
+
visibility: :public,
|
75
|
+
comments: node.comments.map(&:slice),
|
100
76
|
parent: parent,
|
101
|
-
|
77
|
+
node: node,
|
78
|
+
language: @language,
|
79
|
+
source: source,
|
102
80
|
)
|
103
81
|
|
104
|
-
|
105
|
-
|
82
|
+
store_definition(parent, path.last.to_sym, definition)
|
106
83
|
yield definition
|
107
84
|
|
108
|
-
if
|
85
|
+
if body = node.body
|
109
86
|
with_visibility do
|
110
|
-
walk_definitions(
|
87
|
+
walk_definitions(body, definition, source, &block)
|
111
88
|
end
|
112
89
|
end
|
113
|
-
when :
|
114
|
-
|
115
|
-
|
116
|
-
comments: extract_comments_for(node, comments),
|
117
|
-
parent: parent, language: @language, visibility: :public
|
118
|
-
)
|
90
|
+
when :class_node
|
91
|
+
path = nested_path_for(node.constant_path)
|
92
|
+
super_class = nested_name_for(node.superclass)
|
119
93
|
|
120
|
-
|
94
|
+
definition = Class.new(path,
|
95
|
+
super_class: super_class,
|
96
|
+
visibility: :public,
|
97
|
+
comments: node.comments.map(&:slice),
|
98
|
+
parent: parent,
|
99
|
+
node: node,
|
100
|
+
language: @language,
|
101
|
+
source: source,
|
102
|
+
)
|
121
103
|
|
104
|
+
store_definition(parent, path.last.to_sym, definition)
|
122
105
|
yield definition
|
123
106
|
|
124
|
-
if
|
107
|
+
if body = node.body
|
125
108
|
with_visibility do
|
126
|
-
walk_definitions(
|
109
|
+
walk_definitions(body, definition, source, &block)
|
127
110
|
end
|
128
111
|
end
|
129
|
-
when :
|
130
|
-
if name = singleton_name_for(node
|
131
|
-
definition = Singleton.new(
|
132
|
-
node,
|
133
|
-
|
134
|
-
parent: parent, language: @language, visibility: :public
|
112
|
+
when :singleton_class_node
|
113
|
+
if name = singleton_name_for(node)
|
114
|
+
definition = Singleton.new(name,
|
115
|
+
comments: node.comments.map(&:slice),
|
116
|
+
parent: parent, language: @language, visibility: :public, source: source
|
135
117
|
)
|
136
118
|
|
137
119
|
yield definition
|
138
120
|
|
139
|
-
if
|
140
|
-
walk_definitions(
|
121
|
+
if body = node.body
|
122
|
+
walk_definitions(body, definition, source, &block)
|
141
123
|
end
|
142
124
|
end
|
143
|
-
when :
|
144
|
-
|
145
|
-
node, node.children[0],
|
146
|
-
comments: extract_comments_for(node, comments),
|
147
|
-
parent: parent, language: @language, visibility: @visibility
|
148
|
-
)
|
125
|
+
when :def_node
|
126
|
+
receiver = receiver_for(node.receiver)
|
149
127
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
language: @language, visibility: @visibility
|
128
|
+
definition = Method.new(node.name,
|
129
|
+
visibility: @visibility,
|
130
|
+
comments: node.comments.map(&:slice),
|
131
|
+
parent: parent,
|
132
|
+
node: node,
|
133
|
+
language: @language,
|
134
|
+
receiver: receiver,
|
135
|
+
source: source,
|
159
136
|
)
|
160
137
|
|
161
138
|
yield definition
|
162
|
-
when :
|
163
|
-
definition = Constant.new(
|
164
|
-
|
165
|
-
|
166
|
-
|
139
|
+
when :constant_write_node
|
140
|
+
definition = Constant.new(node.name,
|
141
|
+
comments: node.comments.map(&:slice),
|
142
|
+
parent: parent,
|
143
|
+
node: node,
|
144
|
+
language: @language,
|
167
145
|
)
|
168
146
|
|
147
|
+
store_definition(parent, node.name, definition)
|
169
148
|
yield definition
|
170
|
-
when :
|
171
|
-
name = node.
|
149
|
+
when :call_node
|
150
|
+
name = node.name
|
172
151
|
|
173
152
|
case name
|
174
153
|
when :public, :protected, :private
|
175
|
-
|
154
|
+
# Handle cases like "private def foo" where method definitions are arguments
|
155
|
+
if node.arguments
|
156
|
+
has_method_definitions = false
|
157
|
+
node.arguments.arguments.each do |arg_node|
|
158
|
+
if arg_node.type == :def_node
|
159
|
+
has_method_definitions = true
|
160
|
+
# Process the method definition with the specified visibility
|
161
|
+
receiver = receiver_for(arg_node.receiver)
|
162
|
+
|
163
|
+
definition = Method.new(arg_node.name,
|
164
|
+
visibility: name,
|
165
|
+
comments: arg_node.comments.map(&:slice),
|
166
|
+
parent: parent,
|
167
|
+
node: arg_node,
|
168
|
+
language: @language,
|
169
|
+
receiver: receiver,
|
170
|
+
)
|
171
|
+
|
172
|
+
yield definition
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Only set visibility state if this is NOT an inline method definition
|
177
|
+
unless has_method_definitions
|
178
|
+
@visibility = name
|
179
|
+
end
|
180
|
+
else
|
181
|
+
# No arguments, so this is a standalone visibility modifier
|
182
|
+
@visibility = name
|
183
|
+
end
|
176
184
|
when :private_constant
|
177
|
-
|
178
|
-
|
179
|
-
definition
|
185
|
+
if node.arguments
|
186
|
+
constant_names_for(node.arguments.arguments) do |name|
|
187
|
+
if definition = lookup_definition(parent, name)
|
188
|
+
definition.visibility = :private
|
189
|
+
end
|
180
190
|
end
|
181
191
|
end
|
182
192
|
when :attr, :attr_reader, :attr_writer, :attr_accessor
|
183
|
-
definition = Attribute.new(
|
184
|
-
|
185
|
-
|
186
|
-
parent: parent, language: @language
|
193
|
+
definition = Attribute.new(attribute_name_for(node),
|
194
|
+
comments: node.comments.map(&:slice),
|
195
|
+
parent: parent, language: @language, node: node
|
187
196
|
)
|
188
197
|
|
189
198
|
yield definition
|
199
|
+
when :alias_method
|
200
|
+
# Handle alias_method :new_name, :old_name syntax
|
201
|
+
if node.arguments && node.arguments.arguments.size >= 2
|
202
|
+
new_name_arg = node.arguments.arguments[0]
|
203
|
+
old_name_arg = node.arguments.arguments[1]
|
204
|
+
|
205
|
+
# Extract symbol names from the arguments
|
206
|
+
new_name = symbol_name_for(new_name_arg)
|
207
|
+
old_name = symbol_name_for(old_name_arg)
|
208
|
+
|
209
|
+
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
210
|
+
comments: node.comments.map(&:slice),
|
211
|
+
parent: parent,
|
212
|
+
node: node,
|
213
|
+
language: @language,
|
214
|
+
visibility: @visibility,
|
215
|
+
source: source,
|
216
|
+
)
|
217
|
+
|
218
|
+
yield definition
|
219
|
+
end
|
190
220
|
else
|
191
|
-
|
192
|
-
|
221
|
+
# Check if this call should be treated as a definition
|
222
|
+
# either because it has a @name comment, @attribute comment, or a block
|
223
|
+
has_name_comment = node.comments.any? { |comment| comment.slice.match(NAME_ATTRIBUTE) }
|
224
|
+
has_attribute_comment = kind_for(node, node.comments.map(&:slice))
|
225
|
+
has_block = node.block
|
226
|
+
|
227
|
+
if has_name_comment || has_attribute_comment || has_block
|
193
228
|
definition = Call.new(
|
194
|
-
|
195
|
-
comments:
|
196
|
-
parent: parent, language: @language
|
229
|
+
attribute_name_for(node),
|
230
|
+
comments: node.comments.map(&:slice),
|
231
|
+
parent: parent, language: @language, node: node
|
197
232
|
)
|
198
233
|
|
199
234
|
yield definition
|
235
|
+
|
236
|
+
# Walk into the block body if it exists
|
237
|
+
if node.block
|
238
|
+
walk_definitions(node.block, definition, source, &block)
|
239
|
+
end
|
200
240
|
end
|
201
241
|
end
|
202
|
-
when :
|
203
|
-
|
242
|
+
when :alias_method_node
|
243
|
+
# Handle alias new_name old_name syntax
|
244
|
+
new_name = node.new_name.unescaped
|
245
|
+
old_name = node.old_name.unescaped
|
204
246
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
walk_definitions(children, comments, definition, &block)
|
221
|
-
end
|
247
|
+
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
248
|
+
comments: node.comments.map(&:slice),
|
249
|
+
parent: parent,
|
250
|
+
node: node,
|
251
|
+
language: @language,
|
252
|
+
visibility: @visibility,
|
253
|
+
source: source,
|
254
|
+
)
|
255
|
+
|
256
|
+
yield definition
|
257
|
+
else
|
258
|
+
if node.respond_to?(:statements)
|
259
|
+
walk_definitions(node.statements, parent, source, &block)
|
260
|
+
else
|
261
|
+
# $stderr.puts "Ignoring #{node.type}"
|
222
262
|
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Extract segments from the given input file.
|
267
|
+
def segments_for(source, &block)
|
268
|
+
result = self.parse_source(source)
|
269
|
+
comments = result.comments.reject do |comment|
|
270
|
+
comment.location.slice.start_with?("#!/") ||
|
271
|
+
comment.location.slice.start_with?("# frozen_string_literal:") ||
|
272
|
+
comment.location.slice.start_with?("# Released under the MIT License.") ||
|
273
|
+
comment.location.slice.start_with?("# Copyright,")
|
274
|
+
end
|
275
|
+
|
276
|
+
# Now we iterate over the syntax tree and generate segments:
|
277
|
+
walk_segments(result.value, comments, &block)
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
281
|
+
|
282
|
+
def assign_definition(parent, definition)
|
283
|
+
(@definitions[parent] ||= {})[definition.name] = definition
|
284
|
+
end
|
285
|
+
|
286
|
+
def lookup_definition(parent, name)
|
287
|
+
(@definitions[parent] ||= {})[name]
|
288
|
+
end
|
289
|
+
|
290
|
+
def store_definition(parent, name, definition)
|
291
|
+
(@definitions[parent] ||= {})[name] = definition
|
292
|
+
end
|
293
|
+
|
294
|
+
# Parse the given source object, can be a string or a Source instance.
|
295
|
+
# @parameter source [String | Source] The source to parse.
|
296
|
+
def parse_source(source)
|
297
|
+
if source.is_a?(Source)
|
298
|
+
Prism.parse(source.read, filepath: source.path)
|
223
299
|
else
|
224
|
-
|
225
|
-
|
226
|
-
|
300
|
+
Prism.parse(source)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def extract_comments_for(node, comments)
|
305
|
+
prefix = []
|
306
|
+
|
307
|
+
while comment = comments.first
|
308
|
+
break if comment.location.line >= node.location.line
|
309
|
+
|
310
|
+
if last_comment = prefix.last
|
311
|
+
if last_comment.location.line != (comment.location.line - 1)
|
312
|
+
prefix.clear
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
prefix << comments.shift
|
317
|
+
end
|
318
|
+
|
319
|
+
# The last comment must butt up against the node:
|
320
|
+
if comment = prefix.last
|
321
|
+
if comment.location.line == (node.location.line - 1)
|
322
|
+
return prefix.map do |comment|
|
323
|
+
# Remove # and at most one space/tab to preserve indentation
|
324
|
+
comment.slice.sub(/\A\#[\s\t]?/, "")
|
227
325
|
end
|
228
326
|
end
|
229
327
|
end
|
230
328
|
end
|
231
329
|
|
232
|
-
|
330
|
+
def with_visibility(visibility = :public, &block)
|
331
|
+
saved_visibility = @visibility
|
332
|
+
@visibility = visibility
|
333
|
+
yield
|
334
|
+
ensure
|
335
|
+
@visibility = saved_visibility
|
336
|
+
end
|
233
337
|
|
234
|
-
|
235
|
-
|
236
|
-
|
338
|
+
NAME_ATTRIBUTE = /\A\#\s*@name\s+(?<value>.*?)\Z/
|
339
|
+
|
340
|
+
def attribute_name_for(node)
|
341
|
+
node.comments.each do |comment|
|
342
|
+
text = comment.slice
|
343
|
+
if match = text.match(NAME_ATTRIBUTE)
|
237
344
|
return match[:value].to_sym
|
238
345
|
end
|
239
346
|
end
|
240
347
|
|
348
|
+
if node.arguments && node.arguments.arguments.any?
|
349
|
+
argument = node.arguments.arguments.first
|
350
|
+
case argument.type
|
351
|
+
when :symbol_node
|
352
|
+
return argument.unescaped.to_sym
|
353
|
+
when :call_node
|
354
|
+
return argument.name
|
355
|
+
when :block_node
|
356
|
+
return node.name
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
return node.name
|
361
|
+
end
|
362
|
+
|
363
|
+
def nested_path_for(node, path = [])
|
364
|
+
return nil if node.nil?
|
365
|
+
|
241
366
|
case node.type
|
242
|
-
when :
|
243
|
-
|
244
|
-
when :
|
245
|
-
|
246
|
-
|
247
|
-
return node.children[0].children[1]
|
367
|
+
when :constant_read_node
|
368
|
+
path << node.name
|
369
|
+
when :constant_path_node
|
370
|
+
nested_path_for(node.parent, path)
|
371
|
+
path << node.name
|
248
372
|
end
|
373
|
+
|
374
|
+
return path.empty? ? nil : path
|
249
375
|
end
|
250
376
|
|
251
377
|
def nested_name_for(node)
|
252
|
-
|
253
|
-
|
378
|
+
nested_path_for(node)&.join("::")
|
379
|
+
end
|
380
|
+
|
381
|
+
def symbol_name_for(node)
|
382
|
+
case node.type
|
383
|
+
when :symbol_node
|
384
|
+
node.unescaped
|
254
385
|
else
|
255
|
-
node.
|
386
|
+
node.slice
|
256
387
|
end
|
257
388
|
end
|
258
389
|
|
259
|
-
def
|
390
|
+
def receiver_for(node)
|
391
|
+
return nil unless node
|
392
|
+
|
260
393
|
case node.type
|
261
|
-
when :
|
394
|
+
when :self_node
|
395
|
+
"self"
|
396
|
+
when :constant_read_node
|
397
|
+
node.name.to_s
|
398
|
+
when :constant_path_node
|
262
399
|
nested_name_for(node)
|
263
|
-
when :self
|
264
|
-
:'self'
|
265
400
|
end
|
266
401
|
end
|
267
|
-
|
402
|
+
|
403
|
+
def singleton_name_for(node)
|
404
|
+
case node.expression.type
|
405
|
+
when :self_node
|
406
|
+
"self"
|
407
|
+
when :constant_read_node
|
408
|
+
nested_name_for(node.expression)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
268
412
|
KIND_ATTRIBUTE = /\A
|
269
|
-
(
|
270
|
-
(
|
413
|
+
(\#\s*@(?<kind>attribute)\s+(?<value>.*?))|
|
414
|
+
(\#\s*@define\s+(?<kind>)\s+(?<value>.*?))
|
271
415
|
\Z/x
|
272
416
|
|
273
417
|
def kind_for(node, comments = nil)
|
@@ -281,7 +425,7 @@ module Decode
|
|
281
425
|
end
|
282
426
|
|
283
427
|
SCOPE_ATTRIBUTE = /\A
|
284
|
-
|
428
|
+
\#\s*@scope\s+(?<names>.*?)
|
285
429
|
\Z/x
|
286
430
|
|
287
431
|
def scope_for(comments, parent = nil, &block)
|
@@ -298,59 +442,66 @@ module Decode
|
|
298
442
|
return parent
|
299
443
|
end
|
300
444
|
|
301
|
-
def constant_names_for(
|
302
|
-
|
303
|
-
if node.type == :
|
304
|
-
yield node.
|
445
|
+
def constant_names_for(child_nodes)
|
446
|
+
child_nodes.each do |node|
|
447
|
+
if node.type == :symbol_node
|
448
|
+
yield node.unescaped.to_sym
|
305
449
|
end
|
306
450
|
end
|
307
451
|
end
|
308
452
|
|
309
|
-
# Extract segments from the given input file.
|
310
|
-
def segments_for(source, &block)
|
311
|
-
top, comments = self.parse_source(source)
|
312
|
-
|
313
|
-
# We delete any leading comments:
|
314
|
-
line = 0
|
315
|
-
|
316
|
-
while comment = comments.first
|
317
|
-
if comment.location.line == line
|
318
|
-
comments.pop
|
319
|
-
line += 1
|
320
|
-
else
|
321
|
-
break
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
# Now we iterate over the syntax tree and generate segments:
|
326
|
-
walk_segments(top, comments, &block)
|
327
|
-
end
|
328
|
-
|
329
453
|
def walk_segments(node, comments, &block)
|
330
454
|
case node.type
|
331
|
-
when :
|
332
|
-
|
455
|
+
when :program_node
|
456
|
+
walk_segments(node.statements, comments, &block)
|
457
|
+
when :statements_node
|
458
|
+
statements = node.child_nodes
|
459
|
+
current_segment = nil
|
333
460
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
461
|
+
statements.each_with_index do |stmt, stmt_index|
|
462
|
+
# Find comments that precede this statement and are not inside previous statements
|
463
|
+
preceding_comments = []
|
464
|
+
last_stmt_end_line = stmt_index > 0 ? statements[stmt_index - 1].location.end_line : 0
|
465
|
+
|
466
|
+
comments.each do |comment|
|
467
|
+
comment_line = comment.location.start_line
|
468
|
+
# Comment must be after the previous statement and before this statement
|
469
|
+
if comment_line > last_stmt_end_line && comment_line < stmt.location.start_line
|
470
|
+
preceding_comments << comment
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
# Remove consumed comments
|
475
|
+
comments -= preceding_comments
|
476
|
+
|
477
|
+
if preceding_comments.any?
|
478
|
+
# Start a new segment with these comments
|
479
|
+
yield current_segment if current_segment
|
480
|
+
current_segment = Segment.new(
|
481
|
+
preceding_comments.map { |c| c.location.slice.sub(/^#[\s\t]?/, "") },
|
482
|
+
@language,
|
483
|
+
stmt
|
339
484
|
)
|
340
|
-
elsif
|
341
|
-
|
342
|
-
|
485
|
+
elsif current_segment
|
486
|
+
# Extend current segment with this statement
|
487
|
+
current_segment.expand(stmt)
|
343
488
|
else
|
344
|
-
segment
|
489
|
+
# Start a new segment without comments
|
490
|
+
current_segment = Segment.new(
|
491
|
+
[],
|
492
|
+
@language,
|
493
|
+
stmt
|
494
|
+
)
|
345
495
|
end
|
346
496
|
end
|
347
497
|
|
348
|
-
yield
|
498
|
+
yield current_segment if current_segment
|
349
499
|
else
|
350
500
|
# One top level segment:
|
351
501
|
segment = Segment.new(
|
352
|
-
|
353
|
-
@language,
|
502
|
+
[],
|
503
|
+
@language,
|
504
|
+
node
|
354
505
|
)
|
355
506
|
|
356
507
|
yield segment
|