decode 0.24.3 → 0.24.5
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 +4 -1
- 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 +5 -1
- data/lib/decode/comment/rbs.rb +8 -8
- data/lib/decode/comment/tag.rb +13 -1
- 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 +15 -1
- data/lib/decode/rbs/class.rb +91 -14
- data/lib/decode/rbs/generator.rb +67 -11
- data/lib/decode/rbs/method.rb +394 -68
- data/lib/decode/rbs/module.rb +81 -5
- data/lib/decode/rbs/type.rb +52 -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 +28 -20
- data/lib/decode/version.rb +2 -1
- data/readme.md +6 -0
- data/releases.md +6 -0
- data/sig/decode.rbs +503 -113
- data.tar.gz.sig +0 -0
- metadata +4 -1
- 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: 403ad9626fb4852046e983a7d5c71d1edf6d5d1ab922b8872c46f3b856303291
|
4
|
+
data.tar.gz: ec06989a5e4081c1c23e2f6ed39049faaf449c866370496de19a35a31c1a703a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc8f774f1f9ed21c1d985dc7124fbf2c5c6ea37b3502575186fcbbe6b406d0e62a6914a2c6929d72ef0ec258603b14146c71ac04e2867c3f2ed597f766dededc
|
7
|
+
data.tar.gz: be3291575b35ee43dc03a33a3f8e28f99ed3a792ec156d2a297a0956e00ae569217c5d8afe80f3c5f1bce1b77cc186fe317fdceda19a98d43ec72663014bebab
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bake/decode/rbs.rb
CHANGED
data/context/coverage.md
CHANGED
@@ -60,7 +60,7 @@ end
|
|
60
60
|
|
61
61
|
# Process user data and return formatted results.
|
62
62
|
# @parameter name [String] The user's name.
|
63
|
-
# @returns [
|
63
|
+
# @returns [bool] Success status.
|
64
64
|
def process(name)
|
65
65
|
# Validation logic here:
|
66
66
|
return false if name.empty?
|
data/context/getting-started.md
CHANGED
@@ -71,7 +71,7 @@ source_code = <<~RUBY
|
|
71
71
|
# Initialize a new user.
|
72
72
|
# @parameter email [String] The user's email address.
|
73
73
|
# @parameter options [Hash] Additional options.
|
74
|
-
# @option :active [
|
74
|
+
# @option :active [bool] Whether the account is active.
|
75
75
|
# @raises [ArgumentError] If email is invalid.
|
76
76
|
def initialize(email, options = {})
|
77
77
|
# Validate the email format:
|
@@ -51,7 +51,7 @@ class User
|
|
51
51
|
|
52
52
|
# Authenticate the user with the provided password.
|
53
53
|
# @parameter password [String] The password to verify.
|
54
|
-
# @returns [
|
54
|
+
# @returns [bool] True if authentication succeeds.
|
55
55
|
def authenticate(password)
|
56
56
|
# Hash the password for comparison:
|
57
57
|
hashed = hash_password(password)
|
@@ -64,7 +64,7 @@ class User
|
|
64
64
|
# This method sets the user's status to inactive. Use this instead of
|
65
65
|
# the deprecated {disable!} method. The account status can be checked
|
66
66
|
# using `active?` or by examining the `:active` attribute.
|
67
|
-
# @returns [
|
67
|
+
# @returns [bool] Returns `true` if deactivation was successful.
|
68
68
|
def deactivate!
|
69
69
|
@active = false
|
70
70
|
true
|
@@ -187,7 +187,7 @@ Documents hash options (keyword arguments).
|
|
187
187
|
|
188
188
|
```ruby
|
189
189
|
# @parameter user [User] The user object.
|
190
|
-
# @option :cached [
|
190
|
+
# @option :cached [bool] Whether to cache the result.
|
191
191
|
# @option :timeout [Integer] Request timeout in seconds.
|
192
192
|
def fetch_user_data(user, **options)
|
193
193
|
# ...
|
data/context/types.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Setting Up RBS Types and Steep Type Checking for Ruby Gems
|
2
|
+
|
3
|
+
This guide covers the process for establishing robust type checking in Ruby gems using RBS and Steep, focusing on automated generation from source documentation and proper validation.
|
4
|
+
|
5
|
+
## Core Process
|
6
|
+
|
7
|
+
### Documentation-Driven RBS Generation
|
8
|
+
|
9
|
+
Generate RBS files from documentation:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
bake decode:rbs:generate lib > sig/example/gem.rbs`
|
13
|
+
```
|
14
|
+
|
15
|
+
At a minimum, add `@parameter`, `@attribute` and `@returns` documentation to all public methods.
|
16
|
+
|
17
|
+
#### Parametric Types
|
18
|
+
|
19
|
+
Use `@rbs generic` comments to define type parameters for classes and modules:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# @rbs generic T
|
23
|
+
class Container
|
24
|
+
# @parameter item [T] The item to store
|
25
|
+
def initialize(item)
|
26
|
+
@item = item
|
27
|
+
end
|
28
|
+
|
29
|
+
# @returns [T] The stored item
|
30
|
+
def get
|
31
|
+
@item
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Use `@rbs` comments for parametric method signatures:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# From above:
|
40
|
+
class Container
|
41
|
+
# @rbs () { (T) -> void } -> void
|
42
|
+
def each
|
43
|
+
yield @item
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
#### Interfaces
|
48
|
+
|
49
|
+
Create interfaces in `sig/example/gem/interface.rbs`:
|
50
|
+
|
51
|
+
```rbs
|
52
|
+
module Example
|
53
|
+
module Gem
|
54
|
+
interface _Interface
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
You can use the interface in `@parameter`, `@attribute` and `@returns` types.
|
61
|
+
|
62
|
+
### Testing
|
63
|
+
|
64
|
+
Run tests using the `steep` gem.
|
65
|
+
|
66
|
+
```bash
|
67
|
+
steep check
|
68
|
+
```
|
69
|
+
|
70
|
+
**Process**: Start with basic generation, then refine based on Steep feedback.
|
71
|
+
|
72
|
+
1. Generate initial RBS from documentation
|
73
|
+
2. Run `steep check lib` to identify issues
|
74
|
+
3. Fix structural problems (inheritance, missing docs)
|
75
|
+
4. Iterate until clean validation
|
76
|
+
|
77
|
+
### Deploymnet
|
78
|
+
|
79
|
+
Make sure `bake-test-types` is added to the `test` group in `gems.rb` (or `Gemfile`).
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
group :test do
|
83
|
+
# ...
|
84
|
+
gem "bake-test"
|
85
|
+
gem "bake-test-external"
|
86
|
+
gem "bake-test-types"
|
87
|
+
# ...
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Then, create `.github/workflows/test-types.yaml`:
|
92
|
+
|
93
|
+
```yaml
|
94
|
+
name: Test Types
|
95
|
+
|
96
|
+
on: [push, pull_request]
|
97
|
+
|
98
|
+
permissions:
|
99
|
+
contents: read
|
100
|
+
|
101
|
+
env:
|
102
|
+
CONSOLE_OUTPUT: XTerm
|
103
|
+
|
104
|
+
jobs:
|
105
|
+
test:
|
106
|
+
name: ${{matrix.ruby}} on ${{matrix.os}}
|
107
|
+
runs-on: ${{matrix.os}}-latest
|
108
|
+
|
109
|
+
strategy:
|
110
|
+
matrix:
|
111
|
+
os:
|
112
|
+
- ubuntu
|
113
|
+
|
114
|
+
ruby:
|
115
|
+
- "3.4"
|
116
|
+
|
117
|
+
steps:
|
118
|
+
- uses: actions/checkout@v4
|
119
|
+
- uses: ruby/setup-ruby@v1
|
120
|
+
with:
|
121
|
+
ruby-version: ${{matrix.ruby}}
|
122
|
+
bundler-cache: true
|
123
|
+
|
124
|
+
- name: Run tests
|
125
|
+
timeout-minutes: 10
|
126
|
+
run: bundle exec bake test:types
|
127
|
+
```
|
@@ -13,13 +13,16 @@ module Decode
|
|
13
13
|
# - `@attribute [Integer] The person's age.`
|
14
14
|
#
|
15
15
|
class Attribute < Tag
|
16
|
+
# @constant [Regexp] Pattern for matching attribute declarations.
|
16
17
|
PATTERN = /\A\[#{Tag.bracketed_content(:type)}\](\s+(?<details>.*?))?\Z/
|
17
18
|
|
18
19
|
# Build an attribute from a directive and match.
|
19
20
|
# @parameter directive [String] The original directive text.
|
20
21
|
# @parameter match [MatchData] The regex match data.
|
21
22
|
def self.build(directive, match)
|
22
|
-
|
23
|
+
type = match[:type] or raise "Missing type in attribute match!"
|
24
|
+
|
25
|
+
node = self.new(directive, type)
|
23
26
|
|
24
27
|
if details = match[:details]
|
25
28
|
node.add(Text.new(details))
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "tag"
|
7
|
+
require_relative "text"
|
8
|
+
|
9
|
+
module Decode
|
10
|
+
module Comment
|
11
|
+
# Represents a constant type declaration.
|
12
|
+
#
|
13
|
+
# - `@constant [Regexp] Pattern for matching parameters.`
|
14
|
+
#
|
15
|
+
class Constant < Tag
|
16
|
+
# @constant [Regexp] Pattern for matching constant declarations.
|
17
|
+
PATTERN = /\A\[#{Tag.bracketed_content(:type)}\](\s+(?<details>.*?))?\Z/
|
18
|
+
|
19
|
+
# Build a constant from a directive and regex match.
|
20
|
+
# @parameter directive [String] The original directive text.
|
21
|
+
# @parameter match [MatchData] The regex match data containing type and details.
|
22
|
+
# @returns [Constant] A new constant object.
|
23
|
+
def self.build(directive, match)
|
24
|
+
type = match[:type] or raise "Missing type in constant match!"
|
25
|
+
|
26
|
+
node = self.new(directive, type)
|
27
|
+
|
28
|
+
if details = match[:details]
|
29
|
+
node.add(Text.new(details))
|
30
|
+
end
|
31
|
+
|
32
|
+
return node
|
33
|
+
end
|
34
|
+
|
35
|
+
# Initialize a new constant.
|
36
|
+
# @parameter directive [String] The directive that generated the tag.
|
37
|
+
# @parameter type [String] The type of the constant.
|
38
|
+
def initialize(directive, type)
|
39
|
+
super(directive)
|
40
|
+
@type = type
|
41
|
+
end
|
42
|
+
|
43
|
+
# @attribute [String] The type of the constant.
|
44
|
+
attr :type
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/decode/comment/node.rb
CHANGED
@@ -8,49 +8,67 @@ module Decode
|
|
8
8
|
# Represents a node in a comment tree structure.
|
9
9
|
class Node
|
10
10
|
# Initialize the node.
|
11
|
-
# @parameter children [Array(Node
|
12
|
-
def initialize(children
|
11
|
+
# @parameter children [Array(Node | Text)?] The initial children array containing both structured nodes and text content.
|
12
|
+
def initialize(children)
|
13
13
|
@children = children
|
14
14
|
end
|
15
15
|
|
16
16
|
# Whether this node has any children nodes.
|
17
17
|
# Ignores {Text} instances.
|
18
|
-
# @returns [
|
18
|
+
# @returns [bool]
|
19
19
|
def children?
|
20
|
-
@children&.any?{|child| child.is_a?(Node)}
|
20
|
+
@children&.any?{|child| child.is_a?(Node)} || false
|
21
21
|
end
|
22
22
|
|
23
23
|
# Add a child node to this node.
|
24
|
-
# @parameter child [Node] The node to add.
|
24
|
+
# @parameter child [Node | Text] The node to add.
|
25
25
|
def add(child)
|
26
|
-
|
27
|
-
|
26
|
+
if children = @children
|
27
|
+
children << child
|
28
|
+
else
|
29
|
+
@children = [child]
|
30
|
+
end
|
31
|
+
|
32
|
+
return self
|
28
33
|
end
|
29
34
|
|
30
|
-
#
|
35
|
+
# Contains a mix of Node objects (structured comment tags like `@parameter`, `@returns`) and Text objects (plain comment text and tag descriptions).
|
36
|
+
# @attribute [Array(Node | Text)?] The children of this node.
|
31
37
|
attr :children
|
32
38
|
|
33
39
|
# Enumerate all non-text children nodes.
|
40
|
+
# @yields {|node| process each node}
|
41
|
+
# @parameter node [Node] A structured child node (Text nodes are filtered out).
|
42
|
+
# @returns [Enumerator(Node)] Returns an enumerator if no block given.
|
43
|
+
# @returns [self] Otherwise returns self.
|
34
44
|
def each(&block)
|
35
45
|
return to_enum unless block_given?
|
36
46
|
|
37
47
|
@children&.each do |child|
|
38
48
|
yield child if child.is_a?(Node)
|
39
49
|
end
|
50
|
+
|
51
|
+
return self
|
40
52
|
end
|
41
53
|
|
42
54
|
# Filter children nodes by class type.
|
43
55
|
# @parameter klass [Class] The class to filter by.
|
56
|
+
# @yields {|node| process each filtered node}
|
57
|
+
# @parameter node [Object] A child node that is an instance of klass.
|
58
|
+
# @returns [Enumerator(Node)] Returns an enumerator if no block given.
|
59
|
+
# @returns [self] Otherwise returns self.
|
44
60
|
def filter(klass)
|
45
61
|
return to_enum(:filter, klass) unless block_given?
|
46
62
|
|
47
63
|
@children&.each do |child|
|
48
64
|
yield child if child.is_a?(klass)
|
49
65
|
end
|
66
|
+
|
67
|
+
return self
|
50
68
|
end
|
51
69
|
|
52
70
|
# Any lines of text associated with this node.
|
53
|
-
# @returns [Array(String)
|
71
|
+
# @returns [Array(String)?] The lines of text.
|
54
72
|
def text
|
55
73
|
if text = self.extract_text
|
56
74
|
return text if text.any?
|
@@ -70,11 +88,13 @@ module Decode
|
|
70
88
|
|
71
89
|
protected
|
72
90
|
|
91
|
+
# Extract text lines from Text children of this node.
|
92
|
+
# @returns [Array(String)?] Array of text lines, or nil if no children.
|
73
93
|
def extract_text
|
74
94
|
if children = @children
|
75
|
-
|
76
|
-
|
77
|
-
|
95
|
+
children.filter_map do |child|
|
96
|
+
child.line if child.is_a?(Text)
|
97
|
+
end
|
78
98
|
end
|
79
99
|
end
|
80
100
|
end
|
@@ -12,6 +12,7 @@ module Decode
|
|
12
12
|
# - `@parameter age [Float] The users age.`
|
13
13
|
#
|
14
14
|
class Parameter < Tag
|
15
|
+
# @constant [Regexp] Pattern for matching parameter declarations.
|
15
16
|
PATTERN = /\A(?<name>.*?)\s+\[#{Tag.bracketed_content(:type)}\](\s+(?<details>.*?))?\Z/
|
16
17
|
|
17
18
|
# Build a parameter from a directive and regex match.
|
@@ -19,7 +20,10 @@ module Decode
|
|
19
20
|
# @parameter match [MatchData] The regex match data containing name, type, and details.
|
20
21
|
# @returns [Parameter] A new parameter object.
|
21
22
|
def self.build(directive, match)
|
22
|
-
|
23
|
+
name = match[:name] or raise ArgumentError, "Missing name in parameter match!"
|
24
|
+
type = match[:type] or raise ArgumentError, "Missing type in parameter match!"
|
25
|
+
|
26
|
+
node = self.new(directive, name, type)
|
23
27
|
|
24
28
|
if details = match[:details]
|
25
29
|
node.add(Text.new(details))
|
data/lib/decode/comment/rbs.rb
CHANGED
@@ -34,10 +34,10 @@ module Decode
|
|
34
34
|
|
35
35
|
# Initialize a new RBS pragma.
|
36
36
|
# @parameter directive [String] The directive name.
|
37
|
-
# @parameter text [String] The RBS type annotation text.
|
38
|
-
def initialize(directive, text
|
37
|
+
# @parameter text [String?] The RBS type annotation text.
|
38
|
+
def initialize(directive, text)
|
39
39
|
super(directive)
|
40
|
-
@text = text&.strip
|
40
|
+
@text = text&.strip || ""
|
41
41
|
end
|
42
42
|
|
43
43
|
# The RBS type annotation text.
|
@@ -45,13 +45,13 @@ module Decode
|
|
45
45
|
attr :text
|
46
46
|
|
47
47
|
# Check if this is a generic type declaration.
|
48
|
-
# @returns [
|
48
|
+
# @returns [bool] True if this is a generic declaration.
|
49
49
|
def generic?
|
50
|
-
@text
|
50
|
+
@text.start_with?("generic ")
|
51
51
|
end
|
52
52
|
|
53
53
|
# Extract the generic type parameter name.
|
54
|
-
# @returns [String
|
54
|
+
# @returns [String?] The generic type parameter name, or nil if not a generic.
|
55
55
|
def generic_parameter
|
56
56
|
if generic?
|
57
57
|
# Extract the parameter name from "generic T" or "generic T, U"
|
@@ -61,13 +61,13 @@ module Decode
|
|
61
61
|
end
|
62
62
|
|
63
63
|
# Check if this is a method type signature.
|
64
|
-
# @returns [
|
64
|
+
# @returns [bool] True if this is a method signature.
|
65
65
|
def method_signature?
|
66
66
|
@text && !generic?
|
67
67
|
end
|
68
68
|
|
69
69
|
# Get the method type signature text.
|
70
|
-
# @returns [String
|
70
|
+
# @returns [String?] The method signature text, or nil if not a method signature.
|
71
71
|
def method_signature
|
72
72
|
method_signature? ? @text : nil
|
73
73
|
end
|
data/lib/decode/comment/tag.rb
CHANGED
@@ -8,9 +8,21 @@ require_relative "node"
|
|
8
8
|
module Decode
|
9
9
|
module Comment
|
10
10
|
# Represents a documentation tag parsed from a comment directive.
|
11
|
+
# Subclasses should define a PATTERN constant for matching their specific syntax.
|
11
12
|
class Tag < Node
|
13
|
+
# @constant [Regexp] Abstract pattern constant - subclasses must override this.
|
14
|
+
PATTERN = /(?<never_matches_anything>\A\z)/
|
15
|
+
|
16
|
+
# Abstract method: Build a tag from directive and match data.
|
17
|
+
# Subclasses must implement this method.
|
18
|
+
# @parameter directive [String] The directive that generated the tag.
|
19
|
+
# @parameter match [MatchData] The regex match data.
|
20
|
+
# @returns [Tag] A new tag instance.
|
21
|
+
def self.build(directive, match)
|
22
|
+
raise NotImplementedError, "Subclasses must implement build method"
|
23
|
+
end
|
12
24
|
# Build a pattern for bracketed content, supporting nested brackets.
|
13
|
-
# @parameter name [
|
25
|
+
# @parameter name [Symbol] The name of the group.
|
14
26
|
# @returns [String] The pattern.
|
15
27
|
def self.bracketed_content(name)
|
16
28
|
"(?<#{name}>(?:[^\\[\\]]+|\\[\\g<#{name}>\\])*)"
|
data/lib/decode/comment/tags.rb
CHANGED
@@ -10,7 +10,10 @@ module Decode
|
|
10
10
|
# Represents a collection of documentation tags and their parsing logic.
|
11
11
|
class Tags
|
12
12
|
# Build a tags parser with directive mappings.
|
13
|
-
# @
|
13
|
+
# @yields {|directives| directives['directive'] = Class}
|
14
|
+
# @parameter directives [Hash(String, _Directive)] The directive mappings hash to configure.
|
15
|
+
# @returns [Tags] A new tags parser with the configured directives.
|
16
|
+
# @rbs () { (Hash[String, Class]) -> void } -> Tags
|
14
17
|
def self.build
|
15
18
|
directives = Hash.new
|
16
19
|
|
@@ -20,7 +23,7 @@ module Decode
|
|
20
23
|
end
|
21
24
|
|
22
25
|
# Initialize a new tags parser.
|
23
|
-
# @parameter directives [Hash(String,
|
26
|
+
# @parameter directives [Hash(String, _Directive)] The directive mappings.
|
24
27
|
def initialize(directives)
|
25
28
|
@directives = directives
|
26
29
|
end
|
@@ -32,12 +35,16 @@ module Decode
|
|
32
35
|
line.start_with?(" " * level) || line.start_with?("\t" * level)
|
33
36
|
end
|
34
37
|
|
38
|
+
# @constant [Regexp] Pattern for matching tag directives in comment lines.
|
35
39
|
PATTERN = /\A\s*@(?<directive>.*?)(\s+(?<remainder>.*?))?\Z/
|
36
40
|
|
37
41
|
# Parse documentation tags from lines.
|
38
42
|
# @parameter lines [Array(String)] The lines to parse.
|
39
43
|
# @parameter level [Integer] The indentation level.
|
40
|
-
# @
|
44
|
+
# @yields {|node| process parsed node}
|
45
|
+
# @parameter node [Node | Text] The parsed node (either a structured tag or plain text).
|
46
|
+
# @returns [void] Parses tags from lines and yields them to the block.
|
47
|
+
# @rbs (Array[String] lines, ?Integer level) { (Node | Text) -> void } -> void
|
41
48
|
def parse(lines, level = 0, &block)
|
42
49
|
while line = lines.first
|
43
50
|
# Is it at the right indentation level:
|
@@ -48,9 +55,13 @@ module Decode
|
|
48
55
|
|
49
56
|
# Match it against a tag:
|
50
57
|
if match = PATTERN.match(line)
|
51
|
-
|
58
|
+
directive = match[:directive] #: String
|
59
|
+
remainder = match[:remainder] #: String
|
60
|
+
|
61
|
+
# @type var klass: _Directive?
|
62
|
+
if klass = @directives[directive]
|
52
63
|
yield klass.parse(
|
53
|
-
|
64
|
+
directive, remainder,
|
54
65
|
lines, self, level
|
55
66
|
)
|
56
67
|
else
|
data/lib/decode/comment/text.rb
CHANGED
@@ -13,13 +13,16 @@ module Decode
|
|
13
13
|
#
|
14
14
|
# Should contain nested parameters.
|
15
15
|
class Yields < Tag
|
16
|
+
# @constant [Regexp] Pattern for matching yields declarations.
|
16
17
|
PATTERN = /\A(?<block>{.*?})(\s+(?<details>.*?))?\Z/
|
17
18
|
|
18
19
|
# Build a yields tag from a directive and match.
|
19
20
|
# @parameter directive [String] The directive name.
|
20
21
|
# @parameter match [MatchData] The regex match data.
|
21
22
|
def self.build(directive, match)
|
22
|
-
|
23
|
+
block = match[:block] or raise "Missing block in yields match!"
|
24
|
+
|
25
|
+
node = self.new(directive, block)
|
23
26
|
|
24
27
|
if details = match[:details]
|
25
28
|
node.add(Text.new(details))
|
@@ -34,6 +37,7 @@ module Decode
|
|
34
37
|
def initialize(directive, block)
|
35
38
|
super(directive)
|
36
39
|
|
40
|
+
# @type ivar @block: String?
|
37
41
|
@block = block
|
38
42
|
end
|
39
43
|
|