Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions lib/decode/language/ruby/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,36 @@ def walk_definitions(node, parent = nil, source = nil, &block)

yield definition
when :constant_write_node
definition = Constant.new(node.name,
comments: comments_for(node),
parent: parent,
node: node,
language: @language,
)

store_definition(parent, node.name, definition)
yield definition
if super_class = struct_super_class_for(node.value)
definition = Class.new([node.name],
super_class: super_class,
visibility: :public,
comments: comments_for(node),
parent: parent,
node: node,
language: @language,
source: source,
)

store_definition(parent, node.name, definition)
yield definition

if body = node.value.block&.body
with_visibility do
walk_definitions(body, definition, source, &block)
end
end
else
definition = Constant.new(node.name,
comments: comments_for(node),
parent: parent,
node: node,
language: @language,
)

store_definition(parent, node.name, definition)
yield definition
end
when :call_node
name = node.name

Expand Down Expand Up @@ -506,6 +527,24 @@ def receiver_for(node)
end
end

def struct_super_class_for(node)
return unless node&.type == :call_node
return unless node.block

case node.receiver&.type
when :constant_read_node
receiver_name = node.receiver.name.to_s
when :constant_path_node
receiver_name = nested_name_for(node.receiver)
end

if receiver_name == "Struct" && node.name == :new
return "Struct"
elsif receiver_name == "Data" && node.name == :define
return "Data"
end
end

def singleton_name_for(node)
case node.expression.type
when :self_node
Expand Down
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Add support for indexing methods inside `Struct.new` and `Data.define` assignment blocks.

## v0.27.0

- Add `decode:documentation:markdown` bake task for generating LLM-optimized Markdown documentation.
Expand Down
25 changes: 25 additions & 0 deletions test/decode/language/ruby/.fixtures/data_struct.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

# The context object.
Context = Struct.new(:arguments, keyword_init: true) do
# Build a context.
def self.for(arguments)
end

# The completed words.
def words
end
end

module Types
# The request object.
Request = Data.define(:arguments) do
# Build a request.
def self.for(arguments)
end

# The completed words.
def words
end
end
end
54 changes: 54 additions & 0 deletions test/decode/language/ruby/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,60 @@
end
end

with "Struct.new and Data.define assignments" do
let(:path) {File.expand_path(".fixtures/data_struct.rb", __dir__)}

it "treats Struct.new assignments as class-like containers" do
context = definitions.find{|definition| definition.full_path == [:Context]}

expect(context).to be_a(Decode::Language::Ruby::Class)
expect(context).to have_attributes(
short_form: be == "class Context",
long_form: be == "class Context < Struct",
comments: be == ["The context object."],
)
expect(context).to be(:container?)
end

it "extracts methods from Struct.new assignment blocks" do
methods = definitions.select{|definition| definition.parent&.name == :Context}

expect(methods.collect(&:short_form)).to be == [
"def self.for",
"def words",
]
expect(methods.collect(&:full_path)).to be == [
[:Context, :for],
[:Context, :words],
]
end

it "treats Data.define assignments as class-like containers" do
request = definitions.find{|definition| definition.full_path == [:Types, :Request]}

expect(request).to be_a(Decode::Language::Ruby::Class)
expect(request).to have_attributes(
short_form: be == "class Request",
long_form: be == "class Types::Request < Data",
comments: be == ["The request object."],
)
expect(request).to be(:container?)
end

it "extracts methods from Data.define assignment blocks" do
methods = definitions.select{|definition| definition.parent&.name == :Request}

expect(methods.collect(&:short_form)).to be == [
"def self.for",
"def words",
]
expect(methods.collect(&:full_path)).to be == [
[:Types, :Request, :for],
[:Types, :Request, :words],
]
end
end

with "attributes" do
let(:path) {File.expand_path(".fixtures/attributes.rb", __dir__)}

Expand Down
Loading