decode 0.26.0 → 0.28.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/documentation.rb +378 -0
- data/context/documentation-coverage.md +3 -3
- data/context/ruby-documentation.md +33 -18
- data/lib/decode/definition.rb +9 -1
- data/lib/decode/language/ruby/alias.rb +13 -1
- data/lib/decode/language/ruby/class.rb +14 -1
- data/lib/decode/language/ruby/module.rb +19 -1
- data/lib/decode/language/ruby/parser.rb +156 -83
- data/lib/decode/rbs/class.rb +6 -5
- data/lib/decode/rbs/method.rb +11 -11
- data/lib/decode/rbs/module.rb +6 -6
- data/lib/decode/rbs/type.rb +3 -3
- data/lib/decode/version.rb +1 -1
- data/license.md +1 -1
- data/readme.md +24 -0
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e70f7211133fa2733ce29e5f220f6d064d911e969de35fd57c6d11d66d9bacbf
|
|
4
|
+
data.tar.gz: fa6e984ae3633d29b2e8060fd650a9a69800ca561c4f8af3e3a3864b18016a44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3f0b53f4d6f3615aaef8901cd9dbe20cbc61770d3f136786ba4533a4016ef526936da28774ca1cb45d68d4d63219d4b44213f5afd21869f4c72d79ee8580863
|
|
7
|
+
data.tar.gz: 0531b4c5e10fe9aca3abb1ddadaa42eaca20199002786c159f68a8a8f3d60aff53641507a22b0ef6761928fa636b2f3b4fbe3e2296415e04af8f413fbd2c78e9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
def initialize(...)
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
require "decode/index"
|
|
12
|
+
require "yaml"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Generate Markdown documentation for LLM consumption.
|
|
16
|
+
# @parameter root [String] The root path to index (e.g., 'lib').
|
|
17
|
+
# @parameter output_root [String] The root output directory (e.g., 'context').
|
|
18
|
+
# @parameter name [String] The subdirectory name for the generated files (e.g., 'interface').
|
|
19
|
+
def markdown(root, output_root: "context", name: "interface")
|
|
20
|
+
index = Decode::Index.for(root)
|
|
21
|
+
|
|
22
|
+
# Construct full output directory path
|
|
23
|
+
output_directory = File.join(output_root, name)
|
|
24
|
+
|
|
25
|
+
# Track all generated files for index.yaml
|
|
26
|
+
generated_files = []
|
|
27
|
+
|
|
28
|
+
# Group definitions by container (class/module)
|
|
29
|
+
containers = {}
|
|
30
|
+
|
|
31
|
+
# First pass: collect all definitions
|
|
32
|
+
index.definitions.each do |qualified_name, definition|
|
|
33
|
+
# Skip non-public definitions
|
|
34
|
+
next unless definition.public?
|
|
35
|
+
|
|
36
|
+
# If this is a container, register it
|
|
37
|
+
if definition.container?
|
|
38
|
+
containers[qualified_name] ||= {
|
|
39
|
+
definition: definition,
|
|
40
|
+
methods: [],
|
|
41
|
+
aliases: []
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
# This is a method/attribute - add to parent container
|
|
45
|
+
if parent = definition.parent
|
|
46
|
+
# Find the containing class/module
|
|
47
|
+
container_definition = parent
|
|
48
|
+
while container_definition && !container_definition.container?
|
|
49
|
+
container_definition = container_definition.parent
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if container_definition
|
|
53
|
+
container_name = container_definition.qualified_name
|
|
54
|
+
containers[container_name] ||= {
|
|
55
|
+
definition: container_definition,
|
|
56
|
+
methods: [],
|
|
57
|
+
aliases: []
|
|
58
|
+
}
|
|
59
|
+
if definition.respond_to?(:alias?) && definition.alias?
|
|
60
|
+
containers[container_name][:aliases] << definition
|
|
61
|
+
else
|
|
62
|
+
containers[container_name][:methods] << definition
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
$stderr.puts "Found #{containers.size} containers to document"
|
|
70
|
+
|
|
71
|
+
# Generate markdown files for each container
|
|
72
|
+
containers.each do |qualified_name, data|
|
|
73
|
+
container = data[:definition]
|
|
74
|
+
# Preserve original code order as collected by the parser/index:
|
|
75
|
+
methods = data[:methods]
|
|
76
|
+
aliases = data[:aliases]
|
|
77
|
+
|
|
78
|
+
# Generate file path
|
|
79
|
+
file_path = File.join(output_directory, "#{qualified_name.gsub('::', '/')}.md")
|
|
80
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
81
|
+
|
|
82
|
+
# Generate markdown content
|
|
83
|
+
content = generate_container_markdown(container, methods, aliases)
|
|
84
|
+
|
|
85
|
+
File.write(file_path, content)
|
|
86
|
+
generated_files << {
|
|
87
|
+
path: file_path,
|
|
88
|
+
qualified_name: qualified_name,
|
|
89
|
+
kind: container.respond_to?(:container?) && container.container? ? "class/module" : "class/module"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
$stderr.puts "Generated: #{file_path}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
$stderr.puts "Generated #{generated_files.size} files in #{output_directory}"
|
|
96
|
+
|
|
97
|
+
# Generate overview/index file
|
|
98
|
+
overview_path = File.join(output_root, "#{name}.md")
|
|
99
|
+
overview_content = generate_overview(name, containers, index)
|
|
100
|
+
File.write(overview_path, overview_content)
|
|
101
|
+
$stderr.puts "Generated overview: #{overview_path}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# Generate markdown content for a container (class/module) and its methods.
|
|
107
|
+
# @parameter container [Decode::Definition]
|
|
108
|
+
# @parameter methods [Array]
|
|
109
|
+
# @parameter aliases [Array[Decode::Language::Ruby::Alias]]
|
|
110
|
+
def generate_container_markdown(container, methods, aliases)
|
|
111
|
+
lines = []
|
|
112
|
+
|
|
113
|
+
# Title
|
|
114
|
+
lines << "# #{container.qualified_name}"
|
|
115
|
+
lines << ""
|
|
116
|
+
|
|
117
|
+
# Summary from documentation
|
|
118
|
+
if documentation = container.documentation
|
|
119
|
+
if summary = extract_summary(documentation)
|
|
120
|
+
lines << summary
|
|
121
|
+
lines << ""
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Metadata
|
|
126
|
+
kind = case container
|
|
127
|
+
when Decode::Language::Ruby::Class
|
|
128
|
+
"Class"
|
|
129
|
+
when Decode::Language::Ruby::Module
|
|
130
|
+
"Module"
|
|
131
|
+
when Decode::Language::Ruby::Singleton
|
|
132
|
+
"Singleton"
|
|
133
|
+
else
|
|
134
|
+
"Container"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
meta_lines = ["- Kind: #{kind}"]
|
|
138
|
+
if container.respond_to?(:super_class) && container.super_class
|
|
139
|
+
meta_lines << "- Superclass: #{container.super_class}"
|
|
140
|
+
end
|
|
141
|
+
if container.respond_to?(:includes) && container.includes.any?
|
|
142
|
+
meta_lines << "- Includes: #{container.includes.join(', ')}"
|
|
143
|
+
end
|
|
144
|
+
if container.respond_to?(:extends) && container.extends.any?
|
|
145
|
+
meta_lines << "- Extends: #{container.extends.join(', ')}"
|
|
146
|
+
end
|
|
147
|
+
if container.respond_to?(:prepends) && container.prepends.any?
|
|
148
|
+
meta_lines << "- Prepends: #{container.prepends.join(', ')}"
|
|
149
|
+
end
|
|
150
|
+
if container.parent
|
|
151
|
+
meta_lines << "- Namespace: #{container.parent.qualified_name}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if meta_lines.any?
|
|
155
|
+
lines << "## Metadata"
|
|
156
|
+
lines << ""
|
|
157
|
+
lines.concat(meta_lines)
|
|
158
|
+
lines << ""
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Description
|
|
162
|
+
if documentation = container.documentation
|
|
163
|
+
if description = extract_description(documentation)
|
|
164
|
+
lines << "## Overview"
|
|
165
|
+
lines << ""
|
|
166
|
+
lines << description
|
|
167
|
+
lines << ""
|
|
168
|
+
end
|
|
169
|
+
end # Attributes
|
|
170
|
+
attributes = methods.select{|m| m.is_a?(Decode::Language::Ruby::Attribute) rescue false}
|
|
171
|
+
if attributes.any?
|
|
172
|
+
lines << "## Attributes"
|
|
173
|
+
lines << ""
|
|
174
|
+
attributes.each do |attribute|
|
|
175
|
+
lines.concat(generate_method_section(attribute))
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Methods
|
|
180
|
+
non_attributes = methods.reject{|m| m.is_a?(Decode::Language::Ruby::Attribute) rescue false}
|
|
181
|
+
if non_attributes.any?
|
|
182
|
+
lines << "## Methods"
|
|
183
|
+
lines << ""
|
|
184
|
+
non_attributes.each do |method|
|
|
185
|
+
lines.concat(generate_method_section(method, aliases))
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
lines.join("\n")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Generate markdown for a single method.
|
|
193
|
+
# Also annotates any alias names that refer to this method within the same container.
|
|
194
|
+
def generate_method_section(method, aliases = [])
|
|
195
|
+
lines = []
|
|
196
|
+
|
|
197
|
+
# Method heading
|
|
198
|
+
lines << "### `#{method.nested_name}`"
|
|
199
|
+
lines << ""
|
|
200
|
+
|
|
201
|
+
# Also known as (aliases pointing to this method)
|
|
202
|
+
if aliases && !aliases.empty?
|
|
203
|
+
alias_names = aliases.select{|a| a.old_name == method.name}.map(&:name)
|
|
204
|
+
if alias_names.any?
|
|
205
|
+
lines << "_Also known as:_ #{alias_names.map{|n| "`#{n}`"}.join(", ")}"
|
|
206
|
+
lines << ""
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Summary
|
|
211
|
+
if documentation = method.documentation
|
|
212
|
+
if summary = extract_summary(documentation)
|
|
213
|
+
lines << summary
|
|
214
|
+
lines << ""
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Signature
|
|
219
|
+
if signature = method.long_form
|
|
220
|
+
lines << "**Signature:**"
|
|
221
|
+
lines << "```ruby"
|
|
222
|
+
lines << signature
|
|
223
|
+
lines << "```"
|
|
224
|
+
lines << ""
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Parameters
|
|
228
|
+
if documentation = method.documentation
|
|
229
|
+
parameters = documentation.filter(Decode::Comment::Parameter).to_a
|
|
230
|
+
if parameters.any?
|
|
231
|
+
lines << "**Parameters:**"
|
|
232
|
+
parameters.each do |parameter|
|
|
233
|
+
parameter_text = "- `#{parameter.name}` `#{parameter.type}`"
|
|
234
|
+
if description = parameter.text&.join(" ")
|
|
235
|
+
parameter_text << " — #{description}"
|
|
236
|
+
end
|
|
237
|
+
lines << parameter_text
|
|
238
|
+
end
|
|
239
|
+
lines << ""
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns
|
|
243
|
+
returns = documentation.filter(Decode::Comment::Returns).to_a
|
|
244
|
+
if returns.any?
|
|
245
|
+
lines << "**Returns:**"
|
|
246
|
+
returns.each do |return_tag|
|
|
247
|
+
return_text = "- `#{return_tag.type}`"
|
|
248
|
+
if description = return_tag.text&.join(" ")
|
|
249
|
+
return_text << " — #{description}"
|
|
250
|
+
end
|
|
251
|
+
lines << return_text
|
|
252
|
+
end
|
|
253
|
+
lines << ""
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Yields
|
|
257
|
+
yields_tags = documentation.filter(Decode::Comment::Yields).to_a
|
|
258
|
+
if yields_tags.any?
|
|
259
|
+
lines << "**Yields:**"
|
|
260
|
+
yields_tags.each do |yields_tag|
|
|
261
|
+
yield_text = "- `#{yields_tag.block}`"
|
|
262
|
+
if description = yields_tag.text&.join(" ")
|
|
263
|
+
yield_text << " — #{description}"
|
|
264
|
+
end
|
|
265
|
+
lines << yield_text
|
|
266
|
+
end
|
|
267
|
+
lines << ""
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Examples
|
|
271
|
+
examples = documentation.filter(Decode::Comment::Example).to_a
|
|
272
|
+
if examples.any?
|
|
273
|
+
examples.each do |example|
|
|
274
|
+
title = example.title || "Example"
|
|
275
|
+
lines << "**#{title}:**"
|
|
276
|
+
lines << "```ruby"
|
|
277
|
+
lines << example.code if example.code
|
|
278
|
+
lines << "```"
|
|
279
|
+
lines << ""
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Description (longer text after summary)
|
|
284
|
+
if description = extract_description(documentation)
|
|
285
|
+
lines << "**Details:**"
|
|
286
|
+
lines << ""
|
|
287
|
+
lines << description
|
|
288
|
+
lines << ""
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
lines
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Extract summary (first paragraph) from documentation.
|
|
296
|
+
def extract_summary(documentation)
|
|
297
|
+
return nil unless documentation.text
|
|
298
|
+
|
|
299
|
+
lines = documentation.text
|
|
300
|
+
summary_lines = []
|
|
301
|
+
|
|
302
|
+
lines.each do |line|
|
|
303
|
+
line_str = line.to_s.strip
|
|
304
|
+
break if line_str.empty? && summary_lines.any?
|
|
305
|
+
summary_lines << line_str unless line_str.empty?
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
return nil if summary_lines.empty?
|
|
309
|
+
summary_lines.join(" ")
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Extract description (everything after summary) from documentation.
|
|
313
|
+
def extract_description(documentation)
|
|
314
|
+
return nil unless documentation.text
|
|
315
|
+
|
|
316
|
+
lines = documentation.text
|
|
317
|
+
description_lines = []
|
|
318
|
+
found_gap = false
|
|
319
|
+
|
|
320
|
+
lines.each do |line|
|
|
321
|
+
line_str = line.to_s
|
|
322
|
+
if line_str.strip.empty?
|
|
323
|
+
found_gap = true if description_lines.any?
|
|
324
|
+
elsif found_gap
|
|
325
|
+
description_lines << line_str
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
return nil if description_lines.empty?
|
|
330
|
+
description_lines.join("\n")
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Generate an overview/index file for the documentation.
|
|
334
|
+
def generate_overview(name, containers, index)
|
|
335
|
+
lines = []
|
|
336
|
+
|
|
337
|
+
lines << "# #{name.capitalize}"
|
|
338
|
+
lines << ""
|
|
339
|
+
lines << "This directory contains documentation for all public classes and modules."
|
|
340
|
+
lines << ""
|
|
341
|
+
|
|
342
|
+
# Group by top-level namespace
|
|
343
|
+
namespaces = {}
|
|
344
|
+
containers.each do |qualified_name, data|
|
|
345
|
+
parts = qualified_name.split("::")
|
|
346
|
+
top_level = parts.first
|
|
347
|
+
namespaces[top_level] ||= []
|
|
348
|
+
namespaces[top_level] << {name: qualified_name, definition: data[:definition]}
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
lines << "## Namespaces"
|
|
352
|
+
lines << ""
|
|
353
|
+
|
|
354
|
+
namespaces.keys.sort.each do |namespace|
|
|
355
|
+
items = namespaces[namespace].sort_by{|item| item[:name]}
|
|
356
|
+
|
|
357
|
+
lines << "### #{namespace}"
|
|
358
|
+
lines << ""
|
|
359
|
+
|
|
360
|
+
items.each do |item|
|
|
361
|
+
definition = item[:definition]
|
|
362
|
+
relative_path = "#{name}/#{item[:name].gsub('::', '/')}.md"
|
|
363
|
+
|
|
364
|
+
if documentation = definition.documentation
|
|
365
|
+
if summary = extract_summary(documentation)
|
|
366
|
+
lines << "- [#{item[:name]}](#{relative_path}) - #{summary}"
|
|
367
|
+
next
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
lines << "- [#{item[:name]}](#{relative_path})"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
lines << ""
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
lines.join("\n")
|
|
378
|
+
end
|
|
@@ -60,7 +60,7 @@ A definition is considered documented if it has:
|
|
|
60
60
|
- A `@namespace` pragma (for organizational modules).
|
|
61
61
|
|
|
62
62
|
```ruby
|
|
63
|
-
#
|
|
63
|
+
# A user in the system.
|
|
64
64
|
class MyClass
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -130,7 +130,7 @@ Returns true if authentication is successful.
|
|
|
130
130
|
### Document all public APIs
|
|
131
131
|
|
|
132
132
|
```ruby
|
|
133
|
-
#
|
|
133
|
+
# A user account with authentication and email.
|
|
134
134
|
class User
|
|
135
135
|
# @attribute [String] The user's email address.
|
|
136
136
|
attr_reader :email
|
|
@@ -197,7 +197,7 @@ Solution: Add description and pragmas:
|
|
|
197
197
|
# @returns [Array] Processed results.
|
|
198
198
|
def process_data(data)
|
|
199
199
|
# Process the input:
|
|
200
|
-
results = data.map
|
|
200
|
+
results = data.map{|item| transform(item)}
|
|
201
201
|
|
|
202
202
|
# Return processed results:
|
|
203
203
|
results
|
|
@@ -9,7 +9,9 @@ This guide covers documentation practices and pragmas supported by the Decode ge
|
|
|
9
9
|
#### Definition Documentation
|
|
10
10
|
|
|
11
11
|
- Full sentences: All documentation for definitions (classes, modules, methods) should be written as complete sentences with proper grammar and punctuation.
|
|
12
|
-
- Class documentation:
|
|
12
|
+
- Class documentation: Should directly describe what the class *is* or *does*.
|
|
13
|
+
- For data/model classes, "A user account in the system." works well.
|
|
14
|
+
- For functional classes (servers, clients, connections), lead with what the class does: "An HTTP client that manages persistent connections...".
|
|
13
15
|
- Method documentation: Should clearly describe what the method does, not how it does it.
|
|
14
16
|
- Markdown format: All documentation comments are written in Markdown format, allowing for rich formatting including lists, emphasis, code blocks, and links.
|
|
15
17
|
|
|
@@ -32,8 +34,12 @@ This guide covers documentation practices and pragmas supported by the Decode ge
|
|
|
32
34
|
|
|
33
35
|
#### Examples
|
|
34
36
|
|
|
37
|
+
##### Data/Model Classes
|
|
38
|
+
|
|
39
|
+
For classes that model domain concepts, describe what the class is:
|
|
40
|
+
|
|
35
41
|
```ruby
|
|
36
|
-
#
|
|
42
|
+
# A user account in the system.
|
|
37
43
|
class User
|
|
38
44
|
# @attribute [String] The user's email address.
|
|
39
45
|
attr_reader :email
|
|
@@ -70,26 +76,35 @@ class User
|
|
|
70
76
|
true
|
|
71
77
|
end
|
|
72
78
|
end
|
|
79
|
+
```
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
##### Functional/Service Classes
|
|
82
|
+
|
|
83
|
+
For classes that *do* something (clients, servers, processors), lead with what the class does:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# An HTTP client that manages persistent connections to a remote server, with automatic retries for idempotent requests.
|
|
87
|
+
class Client
|
|
88
|
+
# Send a request to the remote server.
|
|
89
|
+
# @parameter request [Protocol::HTTP::Request] The request to send.
|
|
90
|
+
# @returns [Protocol::HTTP::Response] The response from the server.
|
|
91
|
+
def call(request)
|
|
92
|
+
# ...
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Close the client and release all connections.
|
|
96
|
+
def close
|
|
97
|
+
# ...
|
|
89
98
|
end
|
|
90
99
|
end
|
|
100
|
+
|
|
101
|
+
# Raised when a connection to the remote server cannot be established.
|
|
102
|
+
class ConnectionError < StandardError
|
|
103
|
+
end
|
|
91
104
|
```
|
|
92
105
|
|
|
106
|
+
Note the difference: `User` is described as a thing ("A user account..."), while `Client` is described by what it does ("An HTTP client that manages..."), and `ConnectionError` is described by when it occurs ("Raised when...").
|
|
107
|
+
|
|
93
108
|
**Key formatting examples from above:**
|
|
94
109
|
- `{disable!}` - Creates a link to the `disable!` method (relative reference)
|
|
95
110
|
- `active?` - Formats the method name in monospace (backticks for code formatting)
|
|
@@ -155,7 +170,7 @@ Type signatures are used to specify the expected types of parameters, return val
|
|
|
155
170
|
Documents class attributes, instance variables, and `attr_*` declarations. Prefer to have one attribute per line for clarity.
|
|
156
171
|
|
|
157
172
|
```ruby
|
|
158
|
-
#
|
|
173
|
+
# A person with basic attributes.
|
|
159
174
|
class Person
|
|
160
175
|
# @attribute [String] The person's full name.
|
|
161
176
|
attr_reader :name
|
data/lib/decode/definition.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "location"
|
|
7
7
|
|
|
@@ -166,6 +166,14 @@ module Decode
|
|
|
166
166
|
false
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
+
# Whether this definition represents an alias to another definition.
|
|
170
|
+
# Tools can use this to filter aliases from outputs without parsing text.
|
|
171
|
+
#
|
|
172
|
+
# @returns [bool] False by default; specific definition types may override.
|
|
173
|
+
def alias?
|
|
174
|
+
false
|
|
175
|
+
end
|
|
176
|
+
|
|
169
177
|
# Whether this represents a single entity to be documented (along with it's contents).
|
|
170
178
|
#
|
|
171
179
|
# @returns [bool]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "definition"
|
|
7
7
|
|
|
@@ -21,6 +21,18 @@ module Decode
|
|
|
21
21
|
|
|
22
22
|
attr :old_name
|
|
23
23
|
|
|
24
|
+
# Whether this definition represents an alias.
|
|
25
|
+
# @returns [bool] Always true for aliases.
|
|
26
|
+
def alias?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The original name this alias refers to.
|
|
31
|
+
# @returns [Symbol]
|
|
32
|
+
def aliased_name
|
|
33
|
+
@old_name
|
|
34
|
+
end
|
|
35
|
+
|
|
24
36
|
# Aliases don't require separate documentation as they reference existing methods.
|
|
25
37
|
# @returns [bool] Always false for aliases.
|
|
26
38
|
def coverage_relevant?
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "definition"
|
|
7
7
|
|
|
@@ -18,10 +18,23 @@ module Decode
|
|
|
18
18
|
super(*arguments, **options)
|
|
19
19
|
|
|
20
20
|
@super_class = super_class
|
|
21
|
+
@includes = []
|
|
22
|
+
@extends = []
|
|
23
|
+
@prepends = []
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
# @attribute [String?] The super class name.
|
|
23
27
|
attr :super_class
|
|
24
28
|
|
|
29
|
+
# @attribute [Array(Symbol)] Modules included into this class.
|
|
30
|
+
attr :includes
|
|
31
|
+
|
|
32
|
+
# @attribute [Array(Symbol)] Modules extended into this class (adds singleton methods).
|
|
33
|
+
attr :extends
|
|
34
|
+
|
|
35
|
+
# @attribute [Array(Symbol)] Modules prepended into this class (method lookup precedence).
|
|
36
|
+
attr :prepends
|
|
37
|
+
|
|
25
38
|
# A class is a container for other definitions.
|
|
26
39
|
def container?
|
|
27
40
|
true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "definition"
|
|
7
7
|
|
|
@@ -10,6 +10,24 @@ module Decode
|
|
|
10
10
|
module Ruby
|
|
11
11
|
# A Ruby-specific module.
|
|
12
12
|
class Module < Definition
|
|
13
|
+
# Initialize a module with its name and options.
|
|
14
|
+
def initialize(*arguments, **options)
|
|
15
|
+
super(*arguments, **options)
|
|
16
|
+
|
|
17
|
+
@includes = []
|
|
18
|
+
@extends = []
|
|
19
|
+
@prepends = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @attribute [Array(Symbol)] Modules included into this module.
|
|
23
|
+
attr :includes
|
|
24
|
+
|
|
25
|
+
# @attribute [Array(Symbol)] Modules extended into this module (adds singleton methods).
|
|
26
|
+
attr :extends
|
|
27
|
+
|
|
28
|
+
# @attribute [Array(Symbol)] Modules prepended into this module (method lookup precedence).
|
|
29
|
+
attr :prepends
|
|
30
|
+
|
|
13
31
|
# A module is a container for other definitions.
|
|
14
32
|
def container?
|
|
15
33
|
true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "prism"
|
|
7
7
|
|
|
@@ -93,13 +93,13 @@ module Decode
|
|
|
93
93
|
path = nested_path_for(node.constant_path)
|
|
94
94
|
|
|
95
95
|
definition = Module.new(path,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
visibility: :public,
|
|
97
|
+
comments: comments_for(node),
|
|
98
|
+
parent: parent,
|
|
99
|
+
node: node,
|
|
100
|
+
language: @language,
|
|
101
|
+
source: source,
|
|
102
|
+
)
|
|
103
103
|
|
|
104
104
|
store_definition(parent, path.last.to_sym, definition)
|
|
105
105
|
yield definition
|
|
@@ -114,14 +114,14 @@ module Decode
|
|
|
114
114
|
super_class = nested_name_for(node.superclass)
|
|
115
115
|
|
|
116
116
|
definition = Class.new(path,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
)
|
|
125
125
|
|
|
126
126
|
store_definition(parent, path.last.to_sym, definition)
|
|
127
127
|
yield definition
|
|
@@ -134,13 +134,13 @@ module Decode
|
|
|
134
134
|
when :singleton_class_node
|
|
135
135
|
if name = singleton_name_for(node)
|
|
136
136
|
definition = Singleton.new(name,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
comments: comments_for(node),
|
|
138
|
+
parent: parent,
|
|
139
|
+
node: node,
|
|
140
|
+
language: @language,
|
|
141
|
+
visibility: :public,
|
|
142
|
+
source: source
|
|
143
|
+
)
|
|
144
144
|
|
|
145
145
|
yield definition
|
|
146
146
|
|
|
@@ -152,30 +152,85 @@ module Decode
|
|
|
152
152
|
receiver = receiver_for(node.receiver)
|
|
153
153
|
|
|
154
154
|
definition = Method.new(node.name,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
visibility: @visibility,
|
|
156
|
+
comments: comments_for(node),
|
|
157
|
+
parent: parent,
|
|
158
|
+
node: node,
|
|
159
|
+
language: @language,
|
|
160
|
+
receiver: receiver,
|
|
161
|
+
source: source,
|
|
162
|
+
)
|
|
163
163
|
|
|
164
164
|
yield definition
|
|
165
165
|
when :constant_write_node
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
if super_class = struct_super_class_for(node.value)
|
|
167
|
+
definition = Class.new([node.name],
|
|
168
|
+
super_class: super_class,
|
|
169
|
+
visibility: :public,
|
|
170
|
+
comments: comments_for(node),
|
|
171
|
+
parent: parent,
|
|
172
|
+
node: node,
|
|
173
|
+
language: @language,
|
|
174
|
+
source: source,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
store_definition(parent, node.name, definition)
|
|
178
|
+
yield definition
|
|
179
|
+
|
|
180
|
+
if body = node.value.block&.body
|
|
181
|
+
with_visibility do
|
|
182
|
+
walk_definitions(body, definition, source, &block)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
definition = Constant.new(node.name,
|
|
187
|
+
comments: comments_for(node),
|
|
188
|
+
parent: parent,
|
|
189
|
+
node: node,
|
|
190
|
+
language: @language,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
store_definition(parent, node.name, definition)
|
|
194
|
+
yield definition
|
|
195
|
+
end
|
|
175
196
|
when :call_node
|
|
176
197
|
name = node.name
|
|
177
198
|
|
|
178
199
|
case name
|
|
200
|
+
when :include, :extend, :prepend
|
|
201
|
+
# Handle mixins inside classes/modules
|
|
202
|
+
if parent
|
|
203
|
+
if node.arguments
|
|
204
|
+
node.arguments.arguments.each do |arg|
|
|
205
|
+
mod_name = case arg.type
|
|
206
|
+
when :constant_read_node
|
|
207
|
+
# Qualify with enclosing namespace if available (e.g. Mixins::Greeting)
|
|
208
|
+
if parent.parent && parent.parent.respond_to?(:qualified_name)
|
|
209
|
+
"#{parent.parent.qualified_name}::#{arg.name}"
|
|
210
|
+
else
|
|
211
|
+
arg.name.to_s
|
|
212
|
+
end
|
|
213
|
+
when :constant_path_node
|
|
214
|
+
nested_name_for(arg)
|
|
215
|
+
else
|
|
216
|
+
# Skip unsupported argument types (e.g., dynamic expressions)
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
if mod_name
|
|
220
|
+
case name
|
|
221
|
+
when :include
|
|
222
|
+
parent.respond_to?(:includes) && parent.includes << mod_name
|
|
223
|
+
when :extend
|
|
224
|
+
parent.respond_to?(:extends) && parent.extends << mod_name
|
|
225
|
+
when :prepend
|
|
226
|
+
parent.respond_to?(:prepends) && parent.prepends << mod_name
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
# Don't treat include/extend as definitions.
|
|
233
|
+
return
|
|
179
234
|
when :public, :protected, :private
|
|
180
235
|
# Handle cases like "private def foo" where method definitions are arguments
|
|
181
236
|
if node.arguments
|
|
@@ -187,13 +242,13 @@ module Decode
|
|
|
187
242
|
receiver = receiver_for(argument_node.receiver)
|
|
188
243
|
|
|
189
244
|
definition = Method.new(argument_node.name,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
245
|
+
visibility: name,
|
|
246
|
+
comments: comments_for(argument_node),
|
|
247
|
+
parent: parent,
|
|
248
|
+
node: argument_node,
|
|
249
|
+
language: @language,
|
|
250
|
+
receiver: receiver,
|
|
251
|
+
)
|
|
197
252
|
|
|
198
253
|
yield definition
|
|
199
254
|
end
|
|
@@ -217,9 +272,9 @@ module Decode
|
|
|
217
272
|
end
|
|
218
273
|
when :attr, :attr_reader, :attr_writer, :attr_accessor
|
|
219
274
|
definition = Attribute.new(attribute_name_for(node),
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
275
|
+
comments: comments_for(node),
|
|
276
|
+
parent: parent, language: @language, node: node
|
|
277
|
+
)
|
|
223
278
|
|
|
224
279
|
yield definition
|
|
225
280
|
when :alias_method
|
|
@@ -233,29 +288,29 @@ module Decode
|
|
|
233
288
|
old_name = symbol_name_for(old_name_arg)
|
|
234
289
|
|
|
235
290
|
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
291
|
+
comments: comments_for(node),
|
|
292
|
+
parent: parent,
|
|
293
|
+
node: node,
|
|
294
|
+
language: @language,
|
|
295
|
+
visibility: @visibility,
|
|
296
|
+
source: source,
|
|
297
|
+
)
|
|
243
298
|
|
|
244
299
|
yield definition
|
|
245
300
|
end
|
|
246
301
|
else
|
|
247
302
|
# Check if this call should be treated as a definition
|
|
248
303
|
# either because it has a @name comment, @attribute comment, or a block
|
|
249
|
-
has_name_comment = comments_for(node).any?
|
|
304
|
+
has_name_comment = comments_for(node).any?{|comment| comment.match(NAME_ATTRIBUTE)}
|
|
250
305
|
has_attribute_comment = kind_for(node, comments_for(node))
|
|
251
306
|
has_block = node.block
|
|
252
307
|
|
|
253
308
|
if has_name_comment || has_attribute_comment || has_block
|
|
254
309
|
definition = Call.new(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
310
|
+
attribute_name_for(node),
|
|
311
|
+
comments: comments_for(node),
|
|
312
|
+
parent: parent, language: @language, node: node
|
|
313
|
+
)
|
|
259
314
|
|
|
260
315
|
yield definition
|
|
261
316
|
|
|
@@ -265,19 +320,19 @@ module Decode
|
|
|
265
320
|
end
|
|
266
321
|
end
|
|
267
322
|
end
|
|
268
|
-
when :alias_method_node
|
|
269
|
-
# Handle alias new_name old_name syntax
|
|
323
|
+
when :alias_node, :alias_method_node
|
|
324
|
+
# Handle `alias new_name old_name` syntax:
|
|
270
325
|
new_name = node.new_name.unescaped
|
|
271
326
|
old_name = node.old_name.unescaped
|
|
272
327
|
|
|
273
328
|
definition = Alias.new(new_name.to_sym, old_name.to_sym,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
329
|
+
comments: comments_for(node),
|
|
330
|
+
parent: parent,
|
|
331
|
+
node: node,
|
|
332
|
+
language: @language,
|
|
333
|
+
visibility: @visibility,
|
|
334
|
+
source: source,
|
|
335
|
+
)
|
|
281
336
|
|
|
282
337
|
yield definition
|
|
283
338
|
when :if_node
|
|
@@ -472,6 +527,24 @@ module Decode
|
|
|
472
527
|
end
|
|
473
528
|
end
|
|
474
529
|
|
|
530
|
+
def struct_super_class_for(node)
|
|
531
|
+
return unless node&.type == :call_node
|
|
532
|
+
return unless node.block
|
|
533
|
+
|
|
534
|
+
case node.receiver&.type
|
|
535
|
+
when :constant_read_node
|
|
536
|
+
receiver_name = node.receiver.name.to_s
|
|
537
|
+
when :constant_path_node
|
|
538
|
+
receiver_name = nested_name_for(node.receiver)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
if receiver_name == "Struct" && node.name == :new
|
|
542
|
+
return "Struct"
|
|
543
|
+
elsif receiver_name == "Data" && node.name == :define
|
|
544
|
+
return "Data"
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
475
548
|
def singleton_name_for(node)
|
|
476
549
|
case node.expression.type
|
|
477
550
|
when :self_node
|
|
@@ -550,20 +623,20 @@ module Decode
|
|
|
550
623
|
# Start a new segment with these comments
|
|
551
624
|
yield current_segment if current_segment
|
|
552
625
|
current_segment = Segment.new(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
626
|
+
preceding_comments.map{|comment| comment.location.slice.sub(/^#[\s\t]?/, "")},
|
|
627
|
+
@language,
|
|
628
|
+
statement
|
|
629
|
+
)
|
|
557
630
|
elsif current_segment
|
|
558
631
|
# Extend current segment with this statement
|
|
559
632
|
current_segment.expand(statement)
|
|
560
633
|
else
|
|
561
634
|
# Start a new segment without comments
|
|
562
635
|
current_segment = Segment.new(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
636
|
+
[],
|
|
637
|
+
@language,
|
|
638
|
+
statement
|
|
639
|
+
)
|
|
567
640
|
end
|
|
568
641
|
end
|
|
569
642
|
|
|
@@ -571,10 +644,10 @@ module Decode
|
|
|
571
644
|
else
|
|
572
645
|
# One top level segment:
|
|
573
646
|
segment = Segment.new(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
647
|
+
[],
|
|
648
|
+
@language,
|
|
649
|
+
node
|
|
650
|
+
)
|
|
578
651
|
|
|
579
652
|
yield segment
|
|
580
653
|
end
|
data/lib/decode/rbs/class.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "rbs"
|
|
7
7
|
require_relative "wrapper"
|
|
@@ -35,6 +35,7 @@ module Decode
|
|
|
35
35
|
name: generic.to_sym,
|
|
36
36
|
variance: nil,
|
|
37
37
|
upper_bound: nil,
|
|
38
|
+
lower_bound: nil,
|
|
38
39
|
location: nil
|
|
39
40
|
)
|
|
40
41
|
end
|
|
@@ -51,10 +52,10 @@ module Decode
|
|
|
51
52
|
# Extract super class if present:
|
|
52
53
|
super_class = if @definition.super_class
|
|
53
54
|
::RBS::AST::Declarations::Class::Super.new(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
name: qualified_name_to_rbs(@definition.super_class),
|
|
56
|
+
args: [],
|
|
57
|
+
location: nil
|
|
58
|
+
)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
# Create the class declaration with generics:
|
data/lib/decode/rbs/method.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "rbs"
|
|
7
7
|
require "console"
|
|
@@ -103,15 +103,15 @@ module Decode
|
|
|
103
103
|
kind = @definition.receiver ? :singleton : :instance
|
|
104
104
|
|
|
105
105
|
::RBS::AST::Members::MethodDefinition.new(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
name: method_name.to_sym,
|
|
107
|
+
kind: kind,
|
|
108
|
+
overloads: overloads,
|
|
109
|
+
annotations: [],
|
|
110
|
+
location: nil,
|
|
111
|
+
comment: comment,
|
|
112
|
+
overloading: false,
|
|
113
|
+
visibility: @definition.visibility || :public
|
|
114
|
+
)
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
# Build a complete RBS function type from AST information.
|
|
@@ -265,7 +265,7 @@ module Decode
|
|
|
265
265
|
|
|
266
266
|
# Find @parameter tags (but not @option tags, which are handled separately):
|
|
267
267
|
param_tags = documentation.filter(Decode::Comment::Parameter).to_a
|
|
268
|
-
param_tags = param_tags.reject
|
|
268
|
+
param_tags = param_tags.reject{|tag| tag.is_a?(Decode::Comment::Option)}
|
|
269
269
|
return [] if param_tags.empty?
|
|
270
270
|
|
|
271
271
|
param_tags.map do |tag|
|
data/lib/decode/rbs/module.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "rbs"
|
|
7
7
|
require_relative "wrapper"
|
|
@@ -62,11 +62,11 @@ module Decode
|
|
|
62
62
|
type = ::Decode::RBS::Type.parse(type_string)
|
|
63
63
|
|
|
64
64
|
::RBS::AST::Declarations::Constant.new(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
name: constant_definition.name.to_sym,
|
|
66
|
+
type: type,
|
|
67
|
+
location: nil,
|
|
68
|
+
comment: nil
|
|
69
|
+
)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
data/lib/decode/rbs/type.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "rbs"
|
|
7
7
|
require "console"
|
|
@@ -21,10 +21,10 @@ module Decode
|
|
|
21
21
|
true
|
|
22
22
|
when ::RBS::Types::Union
|
|
23
23
|
# Type | nil form - recursively check all union members
|
|
24
|
-
rbs_type.types.any?
|
|
24
|
+
rbs_type.types.any?{|type| nullable?(type)}
|
|
25
25
|
when ::RBS::Types::Tuple
|
|
26
26
|
# [Type] form - recursively check all tuple elements
|
|
27
|
-
rbs_type.types.any?
|
|
27
|
+
rbs_type.types.any?{|type| nullable?(type)}
|
|
28
28
|
when ::RBS::Types::Bases::Nil
|
|
29
29
|
# Direct nil type
|
|
30
30
|
true
|
data/lib/decode/version.rb
CHANGED
data/license.md
CHANGED
data/readme.md
CHANGED
|
@@ -24,6 +24,14 @@ Please see the [project documentation](https://socketry.github.io/decode/) for m
|
|
|
24
24
|
|
|
25
25
|
Please see the [project releases](https://socketry.github.io/decode/releases/index) for all releases.
|
|
26
26
|
|
|
27
|
+
### v0.28.0
|
|
28
|
+
|
|
29
|
+
- Add support for indexing methods inside `Struct.new` and `Data.define` assignment blocks.
|
|
30
|
+
|
|
31
|
+
### v0.27.0
|
|
32
|
+
|
|
33
|
+
- Add `decode:documentation:markdown` bake task for generating LLM-optimized Markdown documentation.
|
|
34
|
+
|
|
27
35
|
### v0.26.0
|
|
28
36
|
|
|
29
37
|
- Add support for `@example` pragmas in Ruby documentation comments.
|
|
@@ -60,6 +68,22 @@ We welcome contributions to this project.
|
|
|
60
68
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
61
69
|
5. Create new Pull Request.
|
|
62
70
|
|
|
71
|
+
### Running Tests
|
|
72
|
+
|
|
73
|
+
To run the test suite:
|
|
74
|
+
|
|
75
|
+
``` shell
|
|
76
|
+
bundle exec sus
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Making Releases
|
|
80
|
+
|
|
81
|
+
To make a new release:
|
|
82
|
+
|
|
83
|
+
``` shell
|
|
84
|
+
bundle exec bake gem:release:patch # or minor or major
|
|
85
|
+
```
|
|
86
|
+
|
|
63
87
|
### Developer Certificate of Origin
|
|
64
88
|
|
|
65
89
|
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.28.0
|
|
4
|
+
|
|
5
|
+
- Add support for indexing methods inside `Struct.new` and `Data.define` assignment blocks.
|
|
6
|
+
|
|
7
|
+
## v0.27.0
|
|
8
|
+
|
|
9
|
+
- Add `decode:documentation:markdown` bake task for generating LLM-optimized Markdown documentation.
|
|
10
|
+
|
|
3
11
|
## v0.26.0
|
|
4
12
|
|
|
5
13
|
- Add support for `@example` pragmas in Ruby documentation comments.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: decode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -71,6 +71,7 @@ extensions: []
|
|
|
71
71
|
extra_rdoc_files: []
|
|
72
72
|
files:
|
|
73
73
|
- agent.md
|
|
74
|
+
- bake/decode/documentation.rb
|
|
74
75
|
- bake/decode/index.rb
|
|
75
76
|
- bake/decode/rbs.rb
|
|
76
77
|
- context/documentation-coverage.md
|
|
@@ -150,14 +151,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
150
151
|
requirements:
|
|
151
152
|
- - ">="
|
|
152
153
|
- !ruby/object:Gem::Version
|
|
153
|
-
version: '3.
|
|
154
|
+
version: '3.3'
|
|
154
155
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
156
|
requirements:
|
|
156
157
|
- - ">="
|
|
157
158
|
- !ruby/object:Gem::Version
|
|
158
159
|
version: '0'
|
|
159
160
|
requirements: []
|
|
160
|
-
rubygems_version:
|
|
161
|
+
rubygems_version: 4.0.10
|
|
161
162
|
specification_version: 4
|
|
162
163
|
summary: Code analysis for documentation generation.
|
|
163
164
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|