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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## [0.3.0]

### Added

- **`ExpressionDeclaration`** — a value-expression primitive wrapping any AST node, with
predicates (`constant?`, `local_variable?`, `method_call?`, `constructor?`, `hash_literal?`,
`symbol?`, `string?`) and accessors (`constant_name`, `method_name`, `name`).
- **`#returns`** on `MethodDeclaration` and `BlockDeclaration` — the points at which it yields a
value (the implicit final expression plus explicit `return`s) as `ReturnDeclaration`s
(`#explicit?`, `#implicit?`, `#expression`), collected in a `ReturnsCollection` (`#expressions`).
`#return_expressions` remains as a shortcut for `returns.expressions` — an `ExpressionsCollection`
(`#hash_literals`, `#constants`). Both are bridged on `MethodsCollection` and `BlocksCollection`.
- **`CallSiteDeclaration#arguments`** — the call's arguments as an `ArgumentsCollection`
(a subclass of `ExpressionsCollection`, so it keeps `#hash_literals`/`#constants`).
- **`CallSiteDeclaration#receiver_expression`** — the receiver modeled structurally (constant /
constructor / local variable). `#receiver` (String const-name) is unchanged.
- **`CallSiteDeclaration#enclosing_blocks`** — the chain of enclosing `do..end`/`{ }` blocks
(innermost first), as a `BlocksCollection`.
- **`#assignments`** on `MethodDeclaration` and `BlockDeclaration` — local-variable assignments
(`AssignmentDeclaration`, with `#name` and `#value`) as an `AssignmentsCollection`. Bridged on
`MethodsCollection` and `BlocksCollection`.

All additions are backward-compatible; no existing API changed.

## [0.2.0]

### Added
Expand Down
20 changes: 16 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Project
│ ├── .all_methods → MethodsCollection
│ │ ├── .parameters → ParametersCollection
│ │ ├── .call_sites → CallSiteCollection
│ │ │ └── (each) .arguments → ArgumentsCollection
│ │ │ .receiver_expression → ExpressionDeclaration
│ │ │ .enclosing_blocks → BlocksCollection
│ │ ├── .returns → ReturnsCollection
│ │ │ └── .expressions → ExpressionsCollection (also via .return_expressions)
│ │ ├── .assignments → AssignmentsCollection
│ │ ├── .if_statements → DeclarationCollection
│ │ ├── .rescues → RescuesCollection
│ │ └── .raises → RaisesCollection
Expand All @@ -52,7 +58,10 @@ Project
├── .modules → ModulesCollection
├── .call_sites → CallSiteCollection
├── .blocks → BlocksCollection
│ └── .call_sites → CallSiteCollection
│ ├── .call_sites → CallSiteCollection
│ ├── .returns → ReturnsCollection
│ │ └── .expressions → ExpressionsCollection (also via .return_expressions)
│ └── .assignments → AssignmentsCollection
├── .constants → ConstantsCollection
└── .requires → RequiresCollection
```
Expand Down Expand Up @@ -221,9 +230,12 @@ Each declaration wraps an AST node and exposes domain-specific methods:
|---|---|---|
| `FileDeclaration` | `name`, `classes`, `modules` | FilePathProvider, LinesOfCodeProvider, ConstantsProvider, RequiresProvider, CallSiteProvider, BlocksProvider |
| `ClassDeclaration` | `name`, `superclass_name`, `instance_methods`, `class_methods`, `top_level_module` | FilePathProvider, ClassNameProvider, LinesOfCodeProvider, ConstantsProvider, AttributesProvider, MacrosProvider, BlocksProvider, IfStatementsProvider, RescuesProvider, RaisesProvider |
| `MethodDeclaration` | `name`, `parameters`, `parameters?` | FilePathProvider, ClassNameProvider, LinesOfCodeProvider, CallSiteProvider, BlocksProvider, IfStatementsProvider, ConstantsProvider, VisibilityProvider, RescuesProvider, RaisesProvider |
| `CallSiteDeclaration` | `name`, `receiver`, `method_name`, `keyword_args`, `keyword_arg_value_pairs`, `symbols`, `strings` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider |
| `BlockDeclaration` | `name`, `method_name` | FilePathProvider, LineNumberProvider, ClassNameProvider, LinesOfCodeProvider, SourceCodeProvider, CallSiteProvider, RescuesProvider, RaisesProvider |
| `MethodDeclaration` | `name`, `parameters`, `parameters?`, `returns`, `return_expressions`, `assignments` | FilePathProvider, ClassNameProvider, LinesOfCodeProvider, CallSiteProvider, BlocksProvider, IfStatementsProvider, ConstantsProvider, VisibilityProvider, RescuesProvider, RaisesProvider, ReturnsProvider, AssignmentsProvider |
| `CallSiteDeclaration` | `name`, `receiver`, `receiver_expression`, `method_name`, `arguments`, `enclosing_blocks`, `keyword_args`, `keyword_arg_value_pairs`, `symbols`, `strings` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider, ArgumentsProvider, EnclosingBlocksProvider |
| `ExpressionDeclaration` | `name`, `constant?`, `local_variable?`, `method_call?`, `constructor?`, `hash_literal?`, `symbol?`, `string?`, `constant_name`, `method_name` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider |
| `AssignmentDeclaration` | `name`, `value` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider |
| `ReturnDeclaration` | `explicit?`, `implicit?`, `expression` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider |
| `BlockDeclaration` | `name`, `method_name`, `returns`, `return_expressions`, `assignments` | FilePathProvider, LineNumberProvider, ClassNameProvider, LinesOfCodeProvider, SourceCodeProvider, CallSiteProvider, RescuesProvider, RaisesProvider, ReturnsProvider, AssignmentsProvider |
| `ParameterDeclaration` | `name`, `default_value` | FilePathProvider, LineNumberProvider, ClassNameProvider |
| `ConstantDeclaration` | `name`, `value`, `assignment?`, `reference?`, `top_level?` | FilePathProvider, LineNumberProvider, ClassNameProvider, SourceCodeProvider |
| `AttributeDeclaration` | `name`, `symbols`, `reader?`, `writer?`, `accessor?` | FilePathProvider, ClassNameProvider, LineNumberProvider, VisibilityProvider |
Expand Down
16 changes: 16 additions & 0 deletions lib/rubyzen/collections/arguments_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Rubyzen
module Collections
# Collection of {Rubyzen::Declarations::ExpressionDeclaration} representing the arguments
# passed at a call site.
#
# A specialization of {ExpressionsCollection}: arguments are value-expressions, so this
# inherits the value-expression filters (`#hash_literals`, `#constants`) and remains a
# drop-in `ExpressionsCollection`. It exists as a distinct type so argument-specific
# filters (e.g. positional vs keyword) can be added later without a breaking change.
#
# @example
# call_site.arguments.first.constant_name
class ArgumentsCollection < ExpressionsCollection
end
end
end
11 changes: 11 additions & 0 deletions lib/rubyzen/collections/assignments_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Rubyzen
module Collections
# Collection of {Rubyzen::Declarations::AssignmentDeclaration} (local-variable assignments).
#
# @example
# method.assignments.with_name('user')
class AssignmentsCollection < BaseCollection
include Rubyzen::Providers::CollectionFilterProvider
end
end
end
21 changes: 21 additions & 0 deletions lib/rubyzen/collections/blocks_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ def call_sites
all_call_sites = flat_map(&:call_sites)
CallSiteCollection.new(all_call_sites)
end

# Returns all return points across every block.
#
# @return [ReturnsCollection]
def returns
ReturnsCollection.new(flat_map(&:returns))
end

# Returns all return expressions across every block.
#
# @return [ExpressionsCollection]
def return_expressions
returns.expressions
end

# Returns all local-variable assignments across every block.
#
# @return [AssignmentsCollection]
def assignments
AssignmentsCollection.new(flat_map(&:assignments))
end
end
end
end
27 changes: 27 additions & 0 deletions lib/rubyzen/collections/expressions_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Rubyzen
module Collections
# Collection of {Rubyzen::Declarations::ExpressionDeclaration} — return values,
# call arguments, and other value-expressions.
#
# @example
# method.return_expressions.hash_literals
class ExpressionsCollection < BaseCollection
include Rubyzen::Providers::CollectionFilterProvider

# Filters to only braced Hash-literal expressions.
#
# @return [ExpressionsCollection]
def hash_literals
filter(&:hash_literal?)
end

# Filters to only constant expressions, including constructors of a constant
# (e.g. both +Repos::Foo+ and +Repos::Foo.new+).
#
# @return [ExpressionsCollection]
def constants
filter(&:constant_name)
end
end
end
end
29 changes: 29 additions & 0 deletions lib/rubyzen/collections/methods_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ def raises
end
)
end

# Returns all return points across every method.
#
# @return [ReturnsCollection]
def returns
ReturnsCollection.new(
flat_map do |method|
method.returns
end
)
end

# Returns all return expressions across every method.
#
# @return [ExpressionsCollection]
def return_expressions
returns.expressions
end

# Returns all local-variable assignments across every method.
#
# @return [AssignmentsCollection]
def assignments
AssignmentsCollection.new(
flat_map do |method|
method.assignments
end
)
end
end
end
end
20 changes: 20 additions & 0 deletions lib/rubyzen/collections/returns_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Rubyzen
module Collections
# Collection of {Rubyzen::Declarations::ReturnDeclaration} — the points at which a
# method or block yields a value.
#
# @example
# method.returns.expressions.hash_literals
class ReturnsCollection < BaseCollection
include Rubyzen::Providers::CollectionFilterProvider

# The value expressions of every return. Bare +return+s (which have no value)
# are omitted.
#
# @return [ExpressionsCollection]
def expressions
ExpressionsCollection.new(filter_map(&:expression))
end
end
end
end
57 changes: 57 additions & 0 deletions lib/rubyzen/declarations/assignment_declaration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module Rubyzen
module Declarations
# Represents a local-variable assignment (an +lvasgn+ node), e.g. +x = Repos::Foo.new+.
#
# @example
# assignment = method.assignments.first
# assignment.name #=> "x"
# assignment.value.constructor? #=> true
# assignment.value.constant_name #=> "Repos::Foo"
#
# NOTE: Multiple assignment (+a, b = ...+) is only partially modelled. Each target is
# surfaced as its own AssignmentDeclaration with a correct {#name}, but {#value} is +nil+:
# in the AST the right-hand side lives on the enclosing +masgn+ node, not on the per-target
# +lvasgn+, and which value each variable receives generally cannot be known statically
# (e.g. +a, b = build_pair+). If a rule ever needs to trace destructured assignments, model
# the shared source explicitly (a +multiple_assignment?+ predicate + +shared_source+) rather
# than attributing the shared right-hand side to each variable's {#value}.
#
class AssignmentDeclaration
include Rubyzen::Providers::FilePathProvider
include Rubyzen::Providers::LineNumberProvider
include Rubyzen::Providers::ClassNameProvider
include Rubyzen::Providers::SourceCodeProvider

# @return [RuboCop::AST::Node]
attr_reader :node

# @return [MethodDeclaration, BlockDeclaration]
attr_reader :parent

# @param node [RuboCop::AST::Node] the +lvasgn+ node
# @param parent [MethodDeclaration, BlockDeclaration] the enclosing declaration
def initialize(node, parent)
@node = node
@parent = parent
end

# Returns the name of the assigned local variable.
#
# @return [String] e.g. +"x"+
def name
node.children.first.to_s
end

# Returns the assigned value as an expression, or +nil+ when there is no value
# node (e.g. the per-variable targets of a multiple assignment, +a, b = foo+).
#
# @return [ExpressionDeclaration, nil]
def value
value_node = node.children[1]
return nil if value_node.nil?

ExpressionDeclaration.new(value_node, self)
end
end
end
end
2 changes: 2 additions & 0 deletions lib/rubyzen/declarations/block_declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class BlockDeclaration
include Rubyzen::Providers::RaisesProvider
include Rubyzen::Providers::SourceCodeProvider
include Rubyzen::Providers::CallSiteProvider
include Rubyzen::Providers::ReturnsProvider
include Rubyzen::Providers::AssignmentsProvider

# @return [RuboCop::AST::Node]
attr_reader :node
Expand Down
14 changes: 14 additions & 0 deletions lib/rubyzen/declarations/call_site_declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class CallSiteDeclaration
include Rubyzen::Providers::LineNumberProvider
include Rubyzen::Providers::ClassNameProvider
include Rubyzen::Providers::SourceCodeProvider
include Rubyzen::Providers::ArgumentsProvider
include Rubyzen::Providers::EnclosingBlocksProvider

# @return [RuboCop::AST::Node]
attr_reader :node
Expand Down Expand Up @@ -41,6 +43,18 @@ def receiver
node.receiver&.type == :const ? node.receiver.const_name : nil
end

# Returns the receiver as an expression, or +nil+ for a receiverless call (e.g. +save+).
#
# Unlike {#receiver} (which returns a constant-name String), this models the receiver
# structurally — constant, constructor, or local variable.
#
# @return [Rubyzen::Declarations::ExpressionDeclaration, nil]
def receiver_expression
return nil if node.receiver.nil?

ExpressionDeclaration.new(node.receiver, self)
end

# Returns the called method name.
#
# @return [String]
Expand Down
Loading