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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/decode/rbs.rb +1 -1
  4. data/context/coverage.md +1 -1
  5. data/context/getting-started.md +1 -1
  6. data/context/ruby-documentation.md +3 -3
  7. data/context/types.md +127 -0
  8. data/lib/decode/comment/attribute.rb +4 -1
  9. data/lib/decode/comment/constant.rb +47 -0
  10. data/lib/decode/comment/node.rb +32 -12
  11. data/lib/decode/comment/option.rb +1 -1
  12. data/lib/decode/comment/parameter.rb +5 -1
  13. data/lib/decode/comment/rbs.rb +8 -8
  14. data/lib/decode/comment/tag.rb +13 -1
  15. data/lib/decode/comment/tags.rb +16 -5
  16. data/lib/decode/comment/text.rb +1 -0
  17. data/lib/decode/comment/yields.rb +5 -1
  18. data/lib/decode/definition.rb +33 -31
  19. data/lib/decode/documentation.rb +10 -5
  20. data/lib/decode/index.rb +12 -7
  21. data/lib/decode/language/generic.rb +10 -1
  22. data/lib/decode/language/reference.rb +7 -4
  23. data/lib/decode/language/ruby/class.rb +2 -2
  24. data/lib/decode/language/ruby/code.rb +21 -3
  25. data/lib/decode/language/ruby/definition.rb +15 -3
  26. data/lib/decode/language/ruby/generic.rb +2 -1
  27. data/lib/decode/language/ruby/parser.rb +132 -91
  28. data/lib/decode/language/ruby/reference.rb +4 -1
  29. data/lib/decode/language/ruby/segment.rb +2 -2
  30. data/lib/decode/languages.rb +29 -8
  31. data/lib/decode/location.rb +15 -1
  32. data/lib/decode/rbs/class.rb +91 -14
  33. data/lib/decode/rbs/generator.rb +67 -11
  34. data/lib/decode/rbs/method.rb +394 -68
  35. data/lib/decode/rbs/module.rb +81 -5
  36. data/lib/decode/rbs/type.rb +52 -0
  37. data/lib/decode/rbs/wrapper.rb +10 -3
  38. data/lib/decode/scope.rb +2 -2
  39. data/lib/decode/segment.rb +3 -2
  40. data/lib/decode/source.rb +5 -14
  41. data/lib/decode/syntax/rewriter.rb +4 -1
  42. data/lib/decode/trie.rb +28 -20
  43. data/lib/decode/version.rb +2 -1
  44. data/readme.md +6 -0
  45. data/releases.md +6 -0
  46. data/sig/decode.rbs +503 -113
  47. data.tar.gz.sig +0 -0
  48. metadata +4 -1
  49. metadata.gz.sig +0 -0
@@ -5,6 +5,8 @@
5
5
 
6
6
  require "rbs"
7
7
  require_relative "wrapper"
8
+ require_relative "method"
9
+ require_relative "type"
8
10
 
9
11
  module Decode
10
12
  module RBS
@@ -18,18 +20,29 @@ module Decode
18
20
  end
19
21
 
20
22
  # Convert the module definition to RBS AST
21
- def to_rbs_ast(method_definitions = [], index = nil)
23
+ # @parameter method_definitions [Array(Method)] The method definitions to convert.
24
+ # @parameter constant_definitions [Array(Constant)] The constant definitions to convert.
25
+ # @parameter attribute_definitions [Array(Attribute)] The attribute definitions to convert.
26
+ # @parameter index [Index?] The index for resolving references.
27
+ # @returns [RBS::AST::Declarations::Module] The RBS AST for the module.
28
+ def to_rbs_ast(method_definitions = [], constant_definitions = [], attribute_definitions = [], index = nil)
22
29
  name = simple_name_to_rbs(@definition.name)
23
- comment = extract_comment(@definition)
30
+ comment = self.comment
24
31
 
25
32
  # Build method definitions
26
33
  methods = method_definitions.map{|method_def| Method.new(method_def).to_rbs_ast(index)}.compact
27
34
 
35
+ # Build constant definitions:
36
+ constants = constant_definitions.map{|const_def| build_constant_rbs(const_def)}.compact
37
+
38
+ # Build attribute definitions and infer instance variable types:
39
+ attributes, instance_variables = build_attributes_rbs(attribute_definitions)
40
+
28
41
  ::RBS::AST::Declarations::Module.new(
29
42
  name: name,
30
43
  type_params: [],
31
44
  self_types: [],
32
- members: methods,
45
+ members: constants + attributes + instance_variables + methods,
33
46
  annotations: [],
34
47
  location: nil,
35
48
  comment: comment
@@ -38,11 +51,74 @@ module Decode
38
51
 
39
52
  private
40
53
 
41
- # Convert a simple name to RBS TypeName (not qualified)
54
+ # Build a constant RBS declaration.
55
+ def build_constant_rbs(constant_definition)
56
+ # Look for @constant tags in the constant's documentation:
57
+ documentation = constant_definition.documentation
58
+ constant_tags = documentation&.filter(Decode::Comment::Constant)&.to_a
59
+
60
+ if constant_tags&.any?
61
+ type_string = constant_tags.first.type.strip
62
+ type = ::Decode::RBS::Type.parse(type_string)
63
+
64
+ ::RBS::AST::Declarations::Constant.new(
65
+ name: constant_definition.name.to_sym,
66
+ type: type,
67
+ location: nil,
68
+ comment: nil
69
+ )
70
+ end
71
+ end
72
+
73
+ # Convert a simple name to RBS TypeName (not qualified).
42
74
  def simple_name_to_rbs(name)
43
75
  ::RBS::TypeName.new(name: name.to_sym, namespace: ::RBS::Namespace.empty)
44
76
  end
45
77
 
78
+ # Build attribute RBS declarations and infer instance variable types.
79
+ # @parameter attribute_definitions [Array] Array of Attribute definition objects
80
+ # @returns [Array] A tuple of [attribute_declarations, instance_variable_declarations]
81
+ def build_attributes_rbs(attribute_definitions)
82
+ attributes = []
83
+ instance_variables = []
84
+
85
+ # Create a mapping from attribute names to their types:
86
+ attribute_types = {}
87
+
88
+ attribute_definitions.each do |attribute_definition|
89
+ # Extract @attribute type annotation from documentation:
90
+ documentation = attribute_definition.documentation
91
+ attribute_tags = documentation&.filter(Decode::Comment::Attribute)&.to_a
92
+
93
+ if attribute_tags&.any?
94
+ type_string = attribute_tags.first.type.strip
95
+ type = ::Decode::RBS::Type.parse(type_string)
96
+
97
+ attribute_types[attribute_definition.name] = type
98
+
99
+ # Generate attr_reader RBS declaration:
100
+ attributes << ::RBS::AST::Members::AttrReader.new(
101
+ name: attribute_definition.name.to_sym,
102
+ type: type,
103
+ ivar_name: :"@#{attribute_definition.name}",
104
+ kind: :instance,
105
+ annotations: [],
106
+ location: nil,
107
+ comment: nil
108
+ )
109
+
110
+ # Generate instance variable declaration:
111
+ instance_variables << ::RBS::AST::Members::InstanceVariable.new(
112
+ name: :"@#{attribute_definition.name}",
113
+ type: type,
114
+ location: nil,
115
+ comment: nil
116
+ )
117
+ end
118
+ end
119
+
120
+ [attributes, instance_variables]
121
+ end
46
122
  end
47
123
  end
48
- end
124
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require "rbs"
7
+ require "console"
8
+
9
+ module Decode
10
+ module RBS
11
+ # Utilities for working with RBS types.
12
+ module Type
13
+ # Check if an RBS type represents a nullable/optional type
14
+ # This method recursively traverses the type tree to find nil anywhere
15
+ # @parameter rbs_type [untyped] The RBS type to check for nullability.
16
+ # @returns [bool] True if the type can be nil, false otherwise.
17
+ def self.nullable?(rbs_type)
18
+ case rbs_type
19
+ when ::RBS::Types::Optional
20
+ # Type? form - directly optional
21
+ true
22
+ when ::RBS::Types::Union
23
+ # Type | nil form - recursively check all union members
24
+ rbs_type.types.any? {|type| nullable?(type)}
25
+ when ::RBS::Types::Tuple
26
+ # [Type] form - recursively check all tuple elements
27
+ rbs_type.types.any? {|type| nullable?(type)}
28
+ when ::RBS::Types::Bases::Nil
29
+ # Direct nil type
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ # Parse a type string and convert it to RBS type
37
+ # @parameter type_string [String] The type string to parse.
38
+ # @returns [untyped] The parsed RBS type object.
39
+ def self.parse(type_string)
40
+ # This is for backwards compatibility with the old syntax, eventually we will emit warnings for these:
41
+ type_string = type_string.tr("()", "[]")
42
+ type_string.gsub!(/\s*\| Nil/, "?")
43
+ type_string.gsub!("Boolean", "bool")
44
+
45
+ return ::RBS::Parser.parse_type(type_string)
46
+ rescue => error
47
+ warn("Failed to parse type string: #{type_string}") if $DEBUG
48
+ return ::RBS::Parser.parse_type("untyped")
49
+ end
50
+ end
51
+ end
52
+ end
@@ -14,6 +14,7 @@ module Decode
14
14
  def initialize(definition)
15
15
  @definition = definition
16
16
  @tags = nil
17
+ @comment = nil
17
18
  end
18
19
 
19
20
  # Extract RBS tags from the definition's documentation.
@@ -22,6 +23,12 @@ module Decode
22
23
  @tags ||= extract_tags
23
24
  end
24
25
 
26
+ # Extract comment from the definition's documentation.
27
+ # @returns [RBS::AST::Comment?] The RBS comment object.
28
+ def comment
29
+ @comment ||= extract_comment
30
+ end
31
+
25
32
  private
26
33
 
27
34
  # Extract RBS tags from the definition's documentation.
@@ -34,13 +41,13 @@ module Decode
34
41
 
35
42
  # Extract comment from definition documentation.
36
43
  # @parameter definition [Definition] The definition to extract comment from (defaults to @definition).
37
- # @returns [RBS::AST::Comment, nil] The extracted comment or nil if no documentation.
44
+ # @returns [RBS::AST::Comment?] The extracted comment or nil if no documentation.
38
45
  def extract_comment(definition = @definition)
39
46
  documentation = definition.documentation
40
47
  return nil unless documentation
41
48
 
42
49
  # Extract the main description text (non-tag content)
43
- comment_lines = []
50
+ comment_lines = [] #: Array[String]
44
51
 
45
52
  documentation.children&.each do |child|
46
53
  if child.is_a?(Decode::Comment::Text)
@@ -61,4 +68,4 @@ module Decode
61
68
  end
62
69
  end
63
70
  end
64
- end
71
+ end
data/lib/decode/scope.rb CHANGED
@@ -10,11 +10,11 @@ module Decode
10
10
  class Scope < Definition
11
11
  # @returns [String] The name of the scope.
12
12
  def short_form
13
- @name
13
+ name.to_s
14
14
  end
15
15
 
16
16
  # Scopes are always containers.
17
- # @returns [Boolean] Always `true`.
17
+ # @returns [bool] Always `true`.
18
18
  def container?
19
19
  true
20
20
  end
@@ -20,6 +20,7 @@ module Decode
20
20
  def initialize(comments, language)
21
21
  @comments = comments
22
22
  @language = language
23
+ @documentation = nil
23
24
  end
24
25
 
25
26
  # @attribute [Array(String)] The preceeding comments.
@@ -29,7 +30,7 @@ module Decode
29
30
  attr :language
30
31
 
31
32
  # An interface for accsssing the documentation of the definition.
32
- # @returns [Documentation | Nil] A {Documentation} instance if this definition has comments.
33
+ # @returns [Documentation?] A {Documentation} instance if this definition has comments.
33
34
  def documentation
34
35
  if @comments&.any?
35
36
  @documentation ||= Documentation.new(@comments, @language)
@@ -37,7 +38,7 @@ module Decode
37
38
  end
38
39
 
39
40
  # The source code trailing the comments.
40
- # @returns [String | Nil]
41
+ # @returns [String?]
41
42
  def code
42
43
  end
43
44
  end
data/lib/decode/source.rb CHANGED
@@ -13,24 +13,15 @@ module Decode
13
13
  # @parameter language [Language::Generic] The language parser to use.
14
14
  def initialize(path, language)
15
15
  @path = path
16
- @buffer = nil
17
16
  @language = language
17
+
18
+ @buffer = nil
18
19
  end
19
20
 
20
21
  # The path of the source file.
21
- # @attribute [String] A file-system path to the source file.
22
+ # @attribute [StringPath] A file-system path to the source file.
22
23
  attr :path
23
24
 
24
- # The relative path of the source, if it is known.
25
- # @returns [String] The relative path or the full path if relative path is unknown.
26
- def relative_path
27
- if @path.respond_to?(:relative_path)
28
- @path.relative_path
29
- else
30
- @path
31
- end
32
- end
33
-
34
25
  # The language of the source file.
35
26
  # @attribute [Language::Generic] The language parser for this source.
36
27
  attr :language
@@ -62,8 +53,8 @@ module Decode
62
53
  end
63
54
 
64
55
  # Generate code representation with optional index for link resolution.
65
- # @parameter index [Index] Optional index for resolving links.
66
- # @parameter relative_to [Definition] Optional definition to resolve relative references.
56
+ # @parameter index [Index?] Optional index for resolving links.
57
+ # @parameter relative_to [Definition?] Optional definition to resolve relative references.
67
58
  # @returns [String] The formatted code representation.
68
59
  def code(index = nil, relative_to: nil)
69
60
  @language.code_for(self.read, index, relative_to: relative_to)
@@ -14,19 +14,22 @@ module Decode
14
14
  @matches = []
15
15
  end
16
16
 
17
+ # @attribute [String] The text to rewrite.
17
18
  attr :text
18
19
 
20
+ # @attribute [Array[Match]] The matches to apply.
19
21
  attr :matches
20
22
 
21
23
  # Add a match to the rewriter.
22
24
  # @parameter match [Match] The match to add.
23
25
  def << match
24
26
  @matches << match
27
+ return self
25
28
  end
26
29
 
27
30
  # Returns a chunk of raw text with no formatting.
28
31
  def text_for(range)
29
- @text[range]
32
+ @text[range] || ""
30
33
  end
31
34
 
32
35
  # Apply all matches to generate the rewritten output.
data/lib/decode/trie.rb CHANGED
@@ -7,13 +7,14 @@ require_relative "source"
7
7
 
8
8
  module Decode
9
9
  # Represents a prefix-trie data structure for fast lexical lookups.
10
+ # @rbs generic T
10
11
  class Trie
11
12
  # Represents a single node in the trie.
12
13
  class Node
13
- # Initialize a new trie node.
14
+ # Initialize an empty node.
14
15
  def initialize
16
+ @children = {}
15
17
  @values = nil
16
- @children = Hash.new
17
18
  end
18
19
 
19
20
  # Generate a string representation of this node.
@@ -26,17 +27,17 @@ module Decode
26
27
  alias to_s inspect
27
28
 
28
29
  # A mutable array of all values that terminate at this node.
29
- # @attribute [Array | Nil] The values stored at this node, or nil if no values.
30
+ # @attribute [Array[T]?] The values stored at this node, or nil if no values.
30
31
  attr_accessor :values
31
32
 
32
33
  # A hash table of all children nodes, indexed by name.
33
- # @attribute [Hash(String, Node)] Child nodes indexed by their path component.
34
+ # @attribute [Hash(Symbol, Node)] Child nodes indexed by their path component.
34
35
  attr :children
35
36
 
36
37
  # Look up a lexical path starting at this node.
37
- # @parameter path [Array(String)] The path to resolve.
38
+ # @parameter path [Array(Symbol)] The path to resolve.
38
39
  # @parameter index [Integer] The current index in the path (used for recursion).
39
- # @returns [Node | Nil] The node at the specified path, or nil if not found.
40
+ # @returns [Node?] The node at the specified path, or nil if not found.
40
41
  def lookup(path, index = 0)
41
42
  if index < path.size
42
43
  if child = @children[path[index]]
@@ -49,11 +50,12 @@ module Decode
49
50
 
50
51
  # Traverse the trie from this node.
51
52
  # Invoke `descend.call` to traverse the children of the current node.
52
- # @parameter path [Array(String)] The current lexical path.
53
+ # @parameter path [Array(Symbol)] The current lexical path.
53
54
  # @yields {|path, node, descend| ...} Called for each node during traversal.
54
- # @parameter path [Array(String)] The current lexical path.
55
+ # @parameter path [Array(Symbol)] The current lexical path.
55
56
  # @parameter node [Node] The current node which is being traversed.
56
57
  # @parameter descend [Proc] The recursive method for traversing children.
58
+ # @rbs (?Array[Symbol]) { (Array[Symbol], Node, Proc) -> void } -> void
57
59
  def traverse(path = [], &block)
58
60
  descend = lambda do
59
61
  @children.each do |name, node|
@@ -75,8 +77,8 @@ module Decode
75
77
  attr :root
76
78
 
77
79
  # Insert the specified value at the given path into the trie.
78
- # @parameter path [Array(String)] The lexical path where the value will be inserted.
79
- # @parameter value [Object] The value to insert at the specified path.
80
+ # @parameter path [Array(Symbol)] The lexical path where the value will be inserted.
81
+ # @parameter value [T] The value to insert at the specified path.
80
82
  def insert(path, value)
81
83
  node = @root
82
84
 
@@ -86,24 +88,29 @@ module Decode
86
88
  end
87
89
 
88
90
  # Add the value to the target node:
89
- (node.values ||= []) << value
91
+ if node.values
92
+ node.values << value
93
+ else
94
+ node.values = [value]
95
+ end
90
96
  end
91
97
 
92
98
  # Lookup the values at the specified path.
93
- # @parameter path [Array(String)] The lexical path which contains the values.
94
- # @returns [Node | Nil] The node at the specified path, or nil if not found.
99
+ # @parameter path [Array(Symbol)] The lexical path which contains the values.
100
+ # @returns [Node?] The node at the specified path, or nil if not found.
95
101
  def lookup(path)
96
102
  @root.lookup(path)
97
103
  end
98
104
 
99
105
  # Enumerate all lexical scopes under the specified path.
100
- # @parameter path [Array(String)] The starting path to enumerate from.
106
+ # @parameter path [Array(Symbol)] The starting path to enumerate from.
101
107
  # @yields {|path, values| ...} Called for each path with values.
102
- # @parameter path [Array(String)] The lexical path.
103
- # @parameter values [Array(Object) | Nil] The values that exist at the given path.
108
+ # @parameter path [Array(Symbol)] The lexical path.
109
+ # @parameter values [Array[T]?] The values that exist at the given path.
110
+ # @rbs (?Array[Symbol]) { (Array[Symbol], (Array[T] | nil)) -> void } -> void
104
111
  def each(path = [], &block)
105
- if node = @root.lookup(path)
106
- node.traverse do |path, node, descend|
112
+ if node = @root.lookup(path, 0)
113
+ node.traverse(path) do |path, node, descend|
107
114
  yield path, node.values
108
115
 
109
116
  descend.call
@@ -113,11 +120,12 @@ module Decode
113
120
 
114
121
  # Traverse the trie starting from the specified path.
115
122
  # See {Node#traverse} for details.
116
- # @parameter path [Array(String)] The starting path to traverse from.
123
+ # @parameter path [Array(Symbol)] The starting path to traverse from.
117
124
  # @yields {|path, node, descend| ...} Called for each node during traversal.
125
+ # @rbs (?Array[Symbol]) { (Array[Symbol], Node, Proc) -> void } -> void
118
126
  def traverse(path = [], &block)
119
127
  if node = @root.lookup(path)
120
- node.traverse(&block)
128
+ node.traverse(path, &block)
121
129
  end
122
130
  end
123
131
  end
@@ -4,5 +4,6 @@
4
4
  # Copyright, 2020-2025, by Samuel Williams.
5
5
 
6
6
  module Decode
7
- VERSION = "0.24.3"
7
+ # @constant [String] The version of the gem.
8
+ VERSION = "0.24.5"
8
9
  end
data/readme.md CHANGED
@@ -22,6 +22,12 @@ Please see the [project documentation](https://socketry.github.io/decode/) for m
22
22
 
23
23
  Please see the [project releases](https://socketry.github.io/decode/releases/index) for all releases.
24
24
 
25
+ ### v0.24.4
26
+
27
+ - Add support for `@constant [Type] Description.` tags.
28
+ - Add support for instance variable type inference from `@attribute` tags.
29
+ - Add support for method visibility in RBS output.
30
+
25
31
  ### v0.24.0
26
32
 
27
33
  - [Introduce support for RBS signature generation](https://socketry.github.io/decode/releases/index#introduce-support-for-rbs-signature-generation)
data/releases.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Releases
2
2
 
3
+ ## v0.24.4
4
+
5
+ - Add support for `@constant [Type] Description.` tags.
6
+ - Add support for instance variable type inference from `@attribute` tags.
7
+ - Add support for method visibility in RBS output.
8
+
3
9
  ## v0.24.0
4
10
 
5
11
  ### Introduce support for RBS signature generation