Skip to content

Commit

Permalink
Add MasgnNode class for masgn nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvandersluis committed Aug 12, 2021
1 parent 3aef982 commit ff4472f
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/new_add_masgnnode_class_for_masgn_nodes.md
@@ -0,0 +1 @@
* [#203](https://github.com/rubocop-hq/rubocop-ast/pull/203): Add `MasgnNode` class for `masgn` nodes. ([@dvandersluis][])
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/node_types.adoc
Expand Up @@ -152,7 +152,7 @@ The following fields are given when relevant to nodes in the source code:

|lvasgn|Local variable assignment|Two children: The variable name (symbol) and the expression.|a = some_thing|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode]

|masgn|Multiple assigment.|First set of children are all `mlhs` nodes, and the rest of the children must be expression nodes corresponding to the values in the `mlhs` nodes.|a, b, = [1, 2]|N/A
|masgn|Multiple assigment.|First set of children are all `mlhs` nodes, and the rest of the children must be expression nodes corresponding to the values in the `mlhs` nodes.|a, b, = [1, 2]|a = some_thing|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/MasgnNode[MasgnNode]

|mlhs|Multiple left-hand side. Used inside a `masgn` and block argument destructuring.|Children must all be assignment nodes. Represents the left side of a multiple assignment (`a, b` in the example).|a, b = 5, 6|N/A

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -61,6 +61,7 @@
require_relative 'ast/node/int_node'
require_relative 'ast/node/keyword_splat_node'
require_relative 'ast/node/lambda_node'
require_relative 'ast/node/masgn_node'
require_relative 'ast/node/module_node'
require_relative 'ast/node/next_node'
require_relative 'ast/node/op_asgn_node'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast/builder.rb
Expand Up @@ -64,6 +64,7 @@ class Builder < Parser::Builders::Default
kwargs: HashNode,
kwsplat: KeywordSplatNode,
lambda: LambdaNode,
masgn: MasgnNode,
module: ModuleNode,
next: NextNode,
op_asgn: OpAsgnNode,
Expand Down
54 changes: 54 additions & 0 deletions lib/rubocop/ast/node/masgn_node.rb
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module RuboCop
module AST
# A node extension for `masgn` nodes.
# This will be used in place of a plain node when the builder constructs
# the AST, making its methods available to all assignment nodes within RuboCop.
class MasgnNode < Node
ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].to_set.freeze

# Calls the given block for each assignment node in the `masgn` LHS.
# If no block is given, an `Enumerator` is returned.
#
# @return [self] if a block is given
# @return [Enumerator] if no block is given
def each_assignment
# The first child is a `mlhs` node
assignments = node_parts[0].each_descendant(*ASSIGNMENT_TYPES)
return assignments.to_enum unless block_given?

assignments do |assignment|
yield(*assignment)
end

self
end

# @return Array<Node> the assignment nodes of the multiple assignment
def assignments
each_assignment.to_a
end

# @return Array<Symbol> names of all the variables being assigned
def names
each_assignment.map do |assignment|
unless assignment.is_a?(AsgnNode)
assignment = assignment.each_descendant(*ASSIGNMENT_TYPES).first
end

next if assignment.nil?

assignment.name
end
end

# The expression being assigned to the variable.
#
# @return [Node] the expression being assigned.
def expression
node_parts[1]
end
end
end
end
81 changes: 81 additions & 0 deletions spec/rubocop/ast/masgn_node_spec.rb
@@ -0,0 +1,81 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::MasgnNode do
let(:masgn_node) { parse_source(source).ast }

describe '.new' do
context 'with a `masgn` node' do
let(:source) { 'x, y = z' }

it { expect(masgn_node).to be_a(described_class) }
end
end

describe '#assignments' do
include AST::Sexp

subject { masgn_node.assignments }

context 'with variables' do
let(:source) { 'x, y = z' }

it { is_expected.to eq([s(:lvasgn, :x), s(:lvasgn, :y)]) }
end

context 'with a splat' do
let(:source) { 'x, *y = z' }

it { is_expected.to eq([s(:lvasgn, :x), s(:lvasgn, :y)]) }
end

context 'with nested `mlhs` nodes' do
let(:source) { 'a, (b, c) = z' }

it { is_expected.to eq([s(:lvasgn, :a), s(:lvasgn, :b), s(:lvasgn, :c)]) }
end
end

describe '#names' do
subject { masgn_node.names }

let(:source) { 'a, @b, @@c, $d, *e = z' }

it { is_expected.to eq(%i[a @b @@c $d e]) }

context 'with nested `mlhs` nodes' do
let(:source) { 'a, (b, c) = z' }

it { is_expected.to eq(%i[a b c]) }
end
end

describe '#expression' do
include AST::Sexp

subject { masgn_node.expression }

context 'with variables' do
let(:source) { 'x, y = z' }

it { is_expected.to eq(s(:send, nil, :z)) }
end

context 'with a LHS splat' do
let(:source) { 'x, *y = z' }

it { is_expected.to eq(s(:send, nil, :z)) }
end

context 'with multiple RHS values' do
let(:source) { 'x, y = 1, 2' }

it { is_expected.to eq(s(:array, s(:int, 1), s(:int, 2))) }
end

context 'with an RHS splat' do
let(:source) { 'x, y = *z' }

it { is_expected.to eq(s(:array, s(:splat, s(:send, nil, :z)))) }
end
end
end

0 comments on commit ff4472f

Please sign in to comment.