decode 0.24.2 → 0.24.4
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/rbs.rb +1 -1
- data/context/coverage.md +1 -1
- data/context/getting-started.md +1 -1
- data/context/ruby-documentation.md +3 -3
- data/context/types.md +127 -0
- data/lib/decode/comment/attribute.rb +5 -2
- data/lib/decode/comment/constant.rb +47 -0
- data/lib/decode/comment/node.rb +32 -12
- data/lib/decode/comment/option.rb +1 -1
- data/lib/decode/comment/parameter.rb +6 -2
- data/lib/decode/comment/rbs.rb +8 -8
- data/lib/decode/comment/tag.rb +19 -0
- data/lib/decode/comment/tags.rb +16 -5
- data/lib/decode/comment/text.rb +1 -0
- data/lib/decode/comment/yields.rb +5 -1
- data/lib/decode/definition.rb +33 -31
- data/lib/decode/documentation.rb +10 -5
- data/lib/decode/index.rb +12 -7
- data/lib/decode/language/generic.rb +10 -1
- data/lib/decode/language/reference.rb +7 -4
- data/lib/decode/language/ruby/class.rb +2 -2
- data/lib/decode/language/ruby/code.rb +21 -3
- data/lib/decode/language/ruby/definition.rb +15 -3
- data/lib/decode/language/ruby/generic.rb +2 -1
- data/lib/decode/language/ruby/parser.rb +132 -91
- data/lib/decode/language/ruby/reference.rb +4 -1
- data/lib/decode/language/ruby/segment.rb +2 -2
- data/lib/decode/languages.rb +29 -8
- data/lib/decode/location.rb +12 -1
- data/lib/decode/rbs/class.rb +91 -14
- data/lib/decode/rbs/generator.rb +67 -11
- data/lib/decode/rbs/method.rb +394 -65
- data/lib/decode/rbs/module.rb +81 -5
- data/lib/decode/rbs/type.rb +51 -0
- data/lib/decode/rbs/wrapper.rb +10 -3
- data/lib/decode/scope.rb +2 -2
- data/lib/decode/segment.rb +3 -2
- data/lib/decode/source.rb +5 -14
- data/lib/decode/syntax/rewriter.rb +4 -1
- data/lib/decode/trie.rb +29 -21
- data/lib/decode/version.rb +2 -1
- data/readme.md +6 -0
- data/releases.md +6 -0
- data/sig/decode.rbs +1189 -0
- data.tar.gz.sig +0 -0
- metadata +5 -15
- metadata.gz.sig +0 -0
@@ -25,7 +25,7 @@ module Decode
|
|
25
25
|
# The Ruby source code parser.
|
26
26
|
class Parser
|
27
27
|
# Initialize a new Ruby parser.
|
28
|
-
# @parameter language [Language] The language instance.
|
28
|
+
# @parameter language [Language::Generic] The language instance.
|
29
29
|
def initialize(language)
|
30
30
|
@language = language
|
31
31
|
|
@@ -33,7 +33,26 @@ module Decode
|
|
33
33
|
@definitions = Hash.new.compare_by_identity
|
34
34
|
end
|
35
35
|
|
36
|
+
# @attribute [Language::Generic] The language instance.
|
37
|
+
attr :language
|
38
|
+
|
39
|
+
# @attribute [Symbol] The current visibility mode.
|
40
|
+
attr :visibility
|
41
|
+
|
42
|
+
# @attribute [Hash] Cache for definition lookups.
|
43
|
+
attr :definitions
|
44
|
+
|
45
|
+
# Parse the source code using Prism.
|
46
|
+
# @parameter source [Source] The source to parse.
|
47
|
+
# @returns [untyped] The parsed syntax tree.
|
48
|
+
def parse_source(source)
|
49
|
+
text = source.read
|
50
|
+
return ::Prism.parse(text)
|
51
|
+
end
|
52
|
+
|
36
53
|
# Extract definitions from the given input file.
|
54
|
+
# @parameter source [Source] The source file to parse.
|
55
|
+
# @returns [Enumerator[Definition]] An enumerator of definitions.
|
37
56
|
def definitions_for(source, &block)
|
38
57
|
return enum_for(:definitions_for, source) unless block_given?
|
39
58
|
|
@@ -46,6 +65,9 @@ module Decode
|
|
46
65
|
end
|
47
66
|
|
48
67
|
# Walk over the syntax tree and extract relevant definitions with their associated comments.
|
68
|
+
# @parameter node [untyped] The syntax tree node to walk.
|
69
|
+
# @parameter parent [Definition?] The parent definition.
|
70
|
+
# @parameter source [Source?] The source file for location tracking.
|
49
71
|
def walk_definitions(node, parent = nil, source = nil, &block)
|
50
72
|
# Check for scope definitions from comments
|
51
73
|
if node.comments.any?
|
@@ -71,13 +93,13 @@ module Decode
|
|
71
93
|
path = nested_path_for(node.constant_path)
|
72
94
|
|
73
95
|
definition = Module.new(path,
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
96
|
+
visibility: :public,
|
97
|
+
comments: comments_for(node),
|
98
|
+
parent: parent,
|
99
|
+
node: node,
|
100
|
+
language: @language,
|
101
|
+
source: source,
|
102
|
+
)
|
81
103
|
|
82
104
|
store_definition(parent, path.last.to_sym, definition)
|
83
105
|
yield definition
|
@@ -92,14 +114,14 @@ module Decode
|
|
92
114
|
super_class = nested_name_for(node.superclass)
|
93
115
|
|
94
116
|
definition = Class.new(path,
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
117
|
+
super_class: super_class,
|
118
|
+
visibility: :public,
|
119
|
+
comments: comments_for(node),
|
120
|
+
parent: parent,
|
121
|
+
node: node,
|
122
|
+
language: @language,
|
123
|
+
source: source,
|
124
|
+
)
|
103
125
|
|
104
126
|
store_definition(parent, path.last.to_sym, definition)
|
105
127
|
yield definition
|
@@ -112,13 +134,13 @@ module Decode
|
|
112
134
|
when :singleton_class_node
|
113
135
|
if name = singleton_name_for(node)
|
114
136
|
definition = Singleton.new(name,
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
137
|
+
comments: comments_for(node),
|
138
|
+
parent: parent,
|
139
|
+
node: node,
|
140
|
+
language: @language,
|
141
|
+
visibility: :public,
|
142
|
+
source: source
|
143
|
+
)
|
122
144
|
|
123
145
|
yield definition
|
124
146
|
|
@@ -130,23 +152,23 @@ module Decode
|
|
130
152
|
receiver = receiver_for(node.receiver)
|
131
153
|
|
132
154
|
definition = Method.new(node.name,
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
visibility: @visibility,
|
156
|
+
comments: comments_for(node),
|
157
|
+
parent: parent,
|
158
|
+
node: node,
|
159
|
+
language: @language,
|
160
|
+
receiver: receiver,
|
161
|
+
source: source,
|
162
|
+
)
|
141
163
|
|
142
164
|
yield definition
|
143
165
|
when :constant_write_node
|
144
166
|
definition = Constant.new(node.name,
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
167
|
+
comments: comments_for(node),
|
168
|
+
parent: parent,
|
169
|
+
node: node,
|
170
|
+
language: @language,
|
171
|
+
)
|
150
172
|
|
151
173
|
store_definition(parent, node.name, definition)
|
152
174
|
yield definition
|
@@ -165,13 +187,13 @@ module Decode
|
|
165
187
|
receiver = receiver_for(argument_node.receiver)
|
166
188
|
|
167
189
|
definition = Method.new(argument_node.name,
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
190
|
+
visibility: name,
|
191
|
+
comments: comments_for(argument_node),
|
192
|
+
parent: parent,
|
193
|
+
node: argument_node,
|
194
|
+
language: @language,
|
195
|
+
receiver: receiver,
|
196
|
+
)
|
175
197
|
|
176
198
|
yield definition
|
177
199
|
end
|
@@ -195,9 +217,9 @@ module Decode
|
|
195
217
|
end
|
196
218
|
when :attr, :attr_reader, :attr_writer, :attr_accessor
|
197
219
|
definition = Attribute.new(attribute_name_for(node),
|
198
|
-
|
199
|
-
|
200
|
-
|
220
|
+
comments: comments_for(node),
|
221
|
+
parent: parent, language: @language, node: node
|
222
|
+
)
|
201
223
|
|
202
224
|
yield definition
|
203
225
|
when :alias_method
|
@@ -211,13 +233,13 @@ module Decode
|
|
211
233
|
old_name = symbol_name_for(old_name_arg)
|
212
234
|
|
213
235
|
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
236
|
+
comments: comments_for(node),
|
237
|
+
parent: parent,
|
238
|
+
node: node,
|
239
|
+
language: @language,
|
240
|
+
visibility: @visibility,
|
241
|
+
source: source,
|
242
|
+
)
|
221
243
|
|
222
244
|
yield definition
|
223
245
|
end
|
@@ -230,10 +252,10 @@ module Decode
|
|
230
252
|
|
231
253
|
if has_name_comment || has_attribute_comment || has_block
|
232
254
|
definition = Call.new(
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
255
|
+
attribute_name_for(node),
|
256
|
+
comments: comments_for(node),
|
257
|
+
parent: parent, language: @language, node: node
|
258
|
+
)
|
237
259
|
|
238
260
|
yield definition
|
239
261
|
|
@@ -249,13 +271,13 @@ module Decode
|
|
249
271
|
old_name = node.old_name.unescaped
|
250
272
|
|
251
273
|
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
274
|
+
comments: comments_for(node),
|
275
|
+
parent: parent,
|
276
|
+
node: node,
|
277
|
+
language: @language,
|
278
|
+
visibility: @visibility,
|
279
|
+
source: source,
|
280
|
+
)
|
259
281
|
|
260
282
|
yield definition
|
261
283
|
when :if_node
|
@@ -286,13 +308,15 @@ module Decode
|
|
286
308
|
end
|
287
309
|
|
288
310
|
# Extract segments from the given input file.
|
311
|
+
# @parameter source [Source] The source file to parse.
|
312
|
+
# @returns [Enumerator[Segment]] An enumerator of segments.
|
289
313
|
def segments_for(source, &block)
|
290
314
|
result = self.parse_source(source)
|
291
315
|
comments = result.comments.reject do |comment|
|
292
316
|
comment.location.slice.start_with?("#!/") ||
|
293
|
-
|
294
|
-
|
295
|
-
|
317
|
+
comment.location.slice.start_with?("# frozen_string_literal:") ||
|
318
|
+
comment.location.slice.start_with?("# Released under the MIT License.") ||
|
319
|
+
comment.location.slice.start_with?("# Copyright,")
|
296
320
|
end
|
297
321
|
|
298
322
|
# Now we iterate over the syntax tree and generate segments:
|
@@ -304,14 +328,16 @@ module Decode
|
|
304
328
|
# Extract clean comment text from a node by removing leading # symbols and whitespace.
|
305
329
|
# Only returns comments that directly precede the node (i.e., are adjacent to it).
|
306
330
|
# @parameter node [Node] The AST node with comments.
|
307
|
-
#
|
331
|
+
# Extract comments that directly precede a node.
|
332
|
+
# @parameter node [untyped] The syntax tree node.
|
333
|
+
# @returns [Array[String]] Array of cleaned comment strings.
|
308
334
|
def comments_for(node)
|
309
335
|
# Find the node's starting line
|
310
336
|
node_start_line = node.location.start_line
|
311
337
|
|
312
338
|
# Filter comments to only include those that directly precede the node
|
313
339
|
# We work backwards from the line before the node to find consecutive comments
|
314
|
-
adjacent_comments = []
|
340
|
+
adjacent_comments = [] #: Array[String]
|
315
341
|
expected_line = node_start_line - 1
|
316
342
|
|
317
343
|
# Process comments in reverse order to work backwards from the node
|
@@ -337,16 +363,31 @@ module Decode
|
|
337
363
|
end
|
338
364
|
end
|
339
365
|
|
366
|
+
# Assign a definition to a parent scope.
|
367
|
+
# @parameter parent [Definition?] The parent definition.
|
368
|
+
# @parameter definition [Definition] The definition to assign.
|
340
369
|
def assign_definition(parent, definition)
|
341
|
-
|
370
|
+
parent_definitions = @definitions[parent] ||= {}
|
371
|
+
parent_definitions[definition.name] = definition
|
342
372
|
end
|
343
373
|
|
374
|
+
# Look up a definition in the parent scope.
|
375
|
+
# @parameter parent [Definition?] The parent definition.
|
376
|
+
# @parameter name [Symbol] The definition name to look up.
|
377
|
+
# @returns [Definition?] The found definition, if any.
|
344
378
|
def lookup_definition(parent, name)
|
345
|
-
|
379
|
+
if parent_definitions = @definitions[parent]
|
380
|
+
return parent_definitions[name]
|
381
|
+
end
|
346
382
|
end
|
347
383
|
|
384
|
+
# Store a definition in the parent scope.
|
385
|
+
# @parameter parent [Definition?] The parent definition.
|
386
|
+
# @parameter name [Symbol] The definition name.
|
387
|
+
# @parameter definition [Definition] The definition to store.
|
348
388
|
def store_definition(parent, name, definition)
|
349
|
-
|
389
|
+
parent_definitions = @definitions[parent] ||= {}
|
390
|
+
parent_definitions[name] = definition
|
350
391
|
end
|
351
392
|
|
352
393
|
# Parse the given source object, can be a string or a Source instance.
|
@@ -363,8 +404,8 @@ module Decode
|
|
363
404
|
saved_visibility = @visibility
|
364
405
|
@visibility = visibility
|
365
406
|
yield
|
366
|
-
|
367
|
-
|
407
|
+
ensure
|
408
|
+
@visibility = saved_visibility
|
368
409
|
end
|
369
410
|
|
370
411
|
NAME_ATTRIBUTE = /\A@name\s+(?<value>.*?)\Z/
|
@@ -441,9 +482,9 @@ module Decode
|
|
441
482
|
end
|
442
483
|
|
443
484
|
KIND_ATTRIBUTE = /\A
|
444
|
-
|
445
|
-
|
446
|
-
|
485
|
+
(@(?<kind>attribute)\s+(?<value>.*?))|
|
486
|
+
(@define\s+(?<kind>)\s+(?<value>.*?))
|
487
|
+
\Z/x
|
447
488
|
|
448
489
|
def kind_for(node, comments = nil)
|
449
490
|
comments&.each do |comment|
|
@@ -456,8 +497,8 @@ module Decode
|
|
456
497
|
end
|
457
498
|
|
458
499
|
SCOPE_ATTRIBUTE = /\A
|
459
|
-
|
460
|
-
|
500
|
+
@scope\s+(?<names>.*?)
|
501
|
+
\Z/x
|
461
502
|
|
462
503
|
def scope_for(comments, parent = nil, &block)
|
463
504
|
comments&.each do |comment|
|
@@ -509,20 +550,20 @@ module Decode
|
|
509
550
|
# Start a new segment with these comments
|
510
551
|
yield current_segment if current_segment
|
511
552
|
current_segment = Segment.new(
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
553
|
+
preceding_comments.map{|comment| comment.location.slice.sub(/^#[\s\t]?/, "")},
|
554
|
+
@language,
|
555
|
+
statement
|
556
|
+
)
|
516
557
|
elsif current_segment
|
517
558
|
# Extend current segment with this statement
|
518
559
|
current_segment.expand(statement)
|
519
560
|
else
|
520
561
|
# Start a new segment without comments
|
521
562
|
current_segment = Segment.new(
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
563
|
+
[],
|
564
|
+
@language,
|
565
|
+
statement
|
566
|
+
)
|
526
567
|
end
|
527
568
|
end
|
528
569
|
|
@@ -530,10 +571,10 @@ module Decode
|
|
530
571
|
else
|
531
572
|
# One top level segment:
|
532
573
|
segment = Segment.new(
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
574
|
+
[],
|
575
|
+
@language,
|
576
|
+
node
|
577
|
+
)
|
537
578
|
|
538
579
|
yield segment
|
539
580
|
end
|
@@ -12,7 +12,8 @@ module Decode
|
|
12
12
|
class Reference < Language::Reference
|
13
13
|
# Create a reference from a constant node.
|
14
14
|
# @parameter node [Prism::Node] The constant node.
|
15
|
-
# @parameter language [Language] The language instance.
|
15
|
+
# @parameter language [Language::Generic] The language instance.
|
16
|
+
# @returns [Reference] A new reference instance.
|
16
17
|
def self.from_const(node, language)
|
17
18
|
lexical_path = append_const(node)
|
18
19
|
|
@@ -22,6 +23,7 @@ module Decode
|
|
22
23
|
# Append a constant node to the path.
|
23
24
|
# @parameter node [Prism::Node] The constant node.
|
24
25
|
# @parameter path [Array] The path to append to.
|
26
|
+
# @returns [Array] The path with constant information appended.
|
25
27
|
def self.append_const(node, path = [])
|
26
28
|
case node.type
|
27
29
|
when :constant_read_node
|
@@ -50,6 +52,7 @@ module Decode
|
|
50
52
|
|
51
53
|
# Split a Ruby identifier into prefix and name components.
|
52
54
|
# @parameter text [String] The text to split.
|
55
|
+
# @returns [Array] Array of prefix and name pairs.
|
53
56
|
def split(text)
|
54
57
|
text.scan(/(::|\.|#|:)?([^:.#]+)/)
|
55
58
|
end
|
@@ -12,7 +12,7 @@ module Decode
|
|
12
12
|
class Segment < Decode::Segment
|
13
13
|
# Initialize a new Ruby segment.
|
14
14
|
# @parameter comments [Array(String)] The comments for this segment.
|
15
|
-
# @parameter language [
|
15
|
+
# @parameter language [Generic] The language instance.
|
16
16
|
# @parameter node [Prism::Node] The syntax tree node.
|
17
17
|
# @parameter options [Hash] Additional options.
|
18
18
|
def initialize(comments, language, node, **options)
|
@@ -32,7 +32,7 @@ module Decode
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# The source code trailing the comments.
|
35
|
-
# @returns [String
|
35
|
+
# @returns [String?]
|
36
36
|
def code
|
37
37
|
@expression.slice
|
38
38
|
end
|
data/lib/decode/languages.rb
CHANGED
@@ -23,6 +23,12 @@ module Decode
|
|
23
23
|
@extensions = {}
|
24
24
|
end
|
25
25
|
|
26
|
+
# @attribute [Hash[String, Language::Generic]] The named languages.
|
27
|
+
attr :named
|
28
|
+
|
29
|
+
# @attribute [Hash[String, Language::Generic]] The languages by extension.
|
30
|
+
attr :extensions
|
31
|
+
|
26
32
|
# Freeze the languages context to prevent further modifications.
|
27
33
|
def freeze
|
28
34
|
return unless frozen?
|
@@ -35,6 +41,7 @@ module Decode
|
|
35
41
|
|
36
42
|
# Add a language to this context.
|
37
43
|
# @parameter language [Language::Generic] The language to add.
|
44
|
+
# @returns [self]
|
38
45
|
def add(language)
|
39
46
|
# Register by name:
|
40
47
|
language.names.each do |name|
|
@@ -45,22 +52,26 @@ module Decode
|
|
45
52
|
language.extensions.each do |extension|
|
46
53
|
@extensions[extension] = language
|
47
54
|
end
|
55
|
+
|
56
|
+
return self
|
48
57
|
end
|
49
58
|
|
50
59
|
# Fetch a language by name, creating a generic language if needed.
|
51
60
|
# @parameter name [String] The name of the language to fetch.
|
52
|
-
# @returns [Language::Generic] The language instance for the given name.
|
61
|
+
# @returns [Language::Generic?] The language instance for the given name, or nil if frozen and not found.
|
53
62
|
def fetch(name)
|
54
63
|
@named.fetch(name) do
|
55
64
|
unless @named.frozen?
|
56
65
|
@named[name] = Language::Generic.new(name)
|
66
|
+
else
|
67
|
+
nil
|
57
68
|
end
|
58
69
|
end
|
59
70
|
end
|
60
71
|
|
61
72
|
# Create a source object for the given file path.
|
62
73
|
# @parameter path [String] The file system path to create a source for.
|
63
|
-
# @returns [Source
|
74
|
+
# @returns [Source?] A source object if the file extension is supported, nil otherwise.
|
64
75
|
def source_for(path)
|
65
76
|
extension = File.extname(path)
|
66
77
|
|
@@ -69,17 +80,27 @@ module Decode
|
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
83
|
+
# @constant [Regexp] Regular expression for parsing language references.
|
72
84
|
REFERENCE = /\A(?<name>[a-z]+)?\s+(?<identifier>.*?)\z/
|
73
85
|
|
74
86
|
# Parse a language agnostic reference.
|
75
87
|
# @parameter text [String] The text to parse (e.g., "ruby MyModule::MyClass").
|
76
|
-
# @parameter default_language [Language::Generic] The default language to use if none specified.
|
77
|
-
# @returns [Language::Reference
|
88
|
+
# @parameter default_language [Language::Generic?] The default language to use if none specified.
|
89
|
+
# @returns [Language::Reference?] The parsed reference, or nil if parsing fails.
|
78
90
|
def parse_reference(text, default_language: nil)
|
79
91
|
if match = REFERENCE.match(text)
|
80
|
-
|
92
|
+
name = match[:name]
|
93
|
+
identifier = match[:identifier]
|
81
94
|
|
82
|
-
|
95
|
+
if name
|
96
|
+
language = self.fetch(name) || default_language
|
97
|
+
else
|
98
|
+
language = default_language
|
99
|
+
end
|
100
|
+
|
101
|
+
if language && identifier
|
102
|
+
return language.reference_for(identifier)
|
103
|
+
end
|
83
104
|
elsif default_language
|
84
105
|
return default_language.reference_for(text)
|
85
106
|
end
|
@@ -88,9 +109,9 @@ module Decode
|
|
88
109
|
# Create a reference for the given language and identifier.
|
89
110
|
# @parameter name [String] The name of the language.
|
90
111
|
# @parameter identifier [String] The identifier to create a reference for.
|
91
|
-
# @returns [Language::Reference] The created reference.
|
112
|
+
# @returns [Language::Reference?] The created reference, or nil if language not found.
|
92
113
|
def reference_for(name, identifier)
|
93
|
-
self.fetch(name)
|
114
|
+
self.fetch(name)&.reference_for(identifier)
|
94
115
|
end
|
95
116
|
end
|
96
117
|
end
|
data/lib/decode/location.rb
CHANGED
@@ -5,7 +5,18 @@
|
|
5
5
|
|
6
6
|
module Decode
|
7
7
|
# Represents a location in a source file.
|
8
|
-
class Location
|
8
|
+
class Location
|
9
|
+
def initialize(path, line)
|
10
|
+
@path = path
|
11
|
+
@line = line
|
12
|
+
end
|
13
|
+
|
14
|
+
# @attribute [String] The path to the source file.
|
15
|
+
attr :path
|
16
|
+
|
17
|
+
# @attribute [Integer] The line number in the source file.
|
18
|
+
attr :line
|
19
|
+
|
9
20
|
# Generate a string representation of the location.
|
10
21
|
def to_s
|
11
22
|
"#{path}:#{line}"
|