New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add MasgnNode
class for masgn
nodes
#203
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#203](https://github.com/rubocop-hq/rubocop-ast/pull/203): Add classes for `masgn` and `mlhs` nodes. ([@dvandersluis][]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# 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 | ||
# @return [MlhsNode] the `mlhs` node | ||
def lhs | ||
# The first child is a `mlhs` node | ||
node_parts[0] | ||
end | ||
|
||
# @return [Array<Node>] the assignment nodes of the multiple assignment | ||
def assignments | ||
lhs.assignments | ||
end | ||
|
||
# @return [Array<Symbol>] names of all the variables being assigned | ||
def names | ||
assignments.map do |assignment| | ||
if assignment.send_type? || assignment.indexasgn_type? | ||
assignment.source | ||
else | ||
assignment.name | ||
end | ||
end | ||
end | ||
|
||
# The expression being assigned to the variable. | ||
# | ||
# @return [Node] the expression being assigned. | ||
def expression | ||
node_parts[1] | ||
end | ||
alias rhs expression | ||
|
||
# @return [Array<Node>] values being assigned on the RHS of the multiple assignment | ||
def values | ||
array? ? expression.children : [expression] | ||
end | ||
|
||
# @return [Boolean] whether the expression has multiple values | ||
def array? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have a need for this public method? I find the definition dubious, as |
||
expression.array_type? | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module AST | ||
# A node extension for `mlhs` 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 MlhsNode < Node | ||
# Returns all the assignment nodes on the left hand side (LHS) of a multiple assigment. | ||
# These are generally assigment nodes (`lvasgn`, `ivasgn`, `cvasgn`, `gvasgn`, `casgn`) | ||
# but can also be `send` nodes in case of `foo.bar, ... =` or `foo[:bar], ... =`. | ||
# | ||
# @return [Array<Node>] the assignment nodes of the multiple assignment LHS | ||
def assignments | ||
child_nodes.flat_map do |node| | ||
if node.splat_type? | ||
node.child_nodes.first | ||
elsif node.mlhs_type? | ||
node.assignments | ||
else | ||
node | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::AST::MasgnNode do | ||
let(:masgn_node) { parse_source(source).ast } | ||
let(:source) { 'x, y = z' } | ||
|
||
describe '.new' do | ||
context 'with a `masgn` node' do | ||
it { expect(masgn_node).to be_a(described_class) } | ||
end | ||
end | ||
|
||
describe '#names' do | ||
subject { masgn_node.names } | ||
|
||
let(:source) { 'a, @b, @@c, $d, E, *f = z' } | ||
|
||
it { is_expected.to eq(%i[a @b @@c $d E f]) } | ||
|
||
context 'with nested `mlhs` nodes' do | ||
let(:source) { 'a, (b, c) = z' } | ||
|
||
it { is_expected.to eq(%i[a b c]) } | ||
end | ||
|
||
context 'with nested assignment on LHS' do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong description |
||
let(:source) { 'a, b[c+=1] = z' } | ||
|
||
it { is_expected.to eq([:a, 'b[c+=1]']) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have a use for |
||
end | ||
|
||
context 'with a method chain on LHS' do | ||
let(:source) { 'a, b.c = z' } | ||
|
||
it { is_expected.to eq([:a, 'b.c']) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with |
||
end | ||
end | ||
|
||
describe '#expression' do | ||
include AST::Sexp | ||
|
||
subject { masgn_node.expression } | ||
|
||
context 'with variables' do | ||
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 | ||
|
||
context 'with assignment on RHS' do | ||
let(:source) { 'x, y = 1, z+=2' } | ||
|
||
it { is_expected.to eq(s(:array, s(:int, 1), s(:op_asgn, s(:lvasgn, :z), :+, s(:int, 2)))) } | ||
end | ||
Comment on lines
+44
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is overkill for such a simple method. How about a single test here? |
||
end | ||
|
||
describe '#values' do | ||
include AST::Sexp | ||
|
||
subject { masgn_node.values } | ||
|
||
context 'when the RHS has a single value' do | ||
let(:source) { 'x, y = z' } | ||
|
||
it { is_expected.to eq([s(:send, nil, :z)]) } | ||
end | ||
|
||
Comment on lines
+78
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this method is to remain, then needs a test for |
||
context 'when the RHS has a multiple values' do | ||
let(:source) { 'x, y = u, v' } | ||
|
||
it { is_expected.to eq([s(:send, nil, :u), s(:send, nil, :v)]) } | ||
end | ||
|
||
context 'when the RHS has a splat' do | ||
let(:source) { 'x, y = *z' } | ||
|
||
it { is_expected.to eq([s(:splat, s(:send, nil, :z))]) } | ||
end | ||
end | ||
|
||
describe '#array?' do | ||
subject { masgn_node.array? } | ||
|
||
context 'when the RHS has a single value' do | ||
let(:source) { 'x, y = z' } | ||
|
||
it { is_expected.to eq(false) } | ||
end | ||
|
||
context 'when the RHS has a multiple values' do | ||
let(:source) { 'x, y = u, v' } | ||
|
||
it { is_expected.to eq(true) } | ||
end | ||
|
||
context 'when the RHS has a splat' do | ||
let(:source) { 'x, y = *z' } | ||
|
||
it { is_expected.to eq(true) } | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::AST::MlhsNode do | ||
let(:mlhs_node) { parse_source(source).ast.node_parts[0] } | ||
|
||
describe '.new' do | ||
context 'with a `masgn` node' do | ||
let(:source) { 'x, y = z' } | ||
|
||
it { expect(mlhs_node).to be_a(described_class) } | ||
end | ||
end | ||
|
||
describe '#assignments' do | ||
include AST::Sexp | ||
|
||
subject { mlhs_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 | ||
|
||
context 'with different variable types' do | ||
let(:source) { 'a, @b, @@c, $d, E, *f = z' } | ||
let(:expected_nodes) do | ||
[ | ||
s(:lvasgn, :a), | ||
s(:ivasgn, :@b), | ||
s(:cvasgn, :@@c), | ||
s(:gvasgn, :$d), | ||
s(:casgn, nil, :E), | ||
s(:lvasgn, :f) | ||
] | ||
end | ||
|
||
it { is_expected.to eq(expected_nodes) } | ||
end | ||
|
||
context 'with assignment on RHS' do | ||
let(:source) { 'x, y = 1, z += 2' } | ||
|
||
it { is_expected.to eq([s(:lvasgn, :x), s(:lvasgn, :y)]) } | ||
end | ||
|
||
context 'with nested assignment on LHS' do | ||
let(:source) { 'a, b[c+=1] = z' } | ||
|
||
if RuboCop::AST::Builder.emit_index | ||
let(:expected_nodes) do | ||
[ | ||
s(:lvasgn, :a), | ||
s(:indexasgn, | ||
s(:send, nil, :b), | ||
s(:op_asgn, | ||
s(:lvasgn, :c), :+, s(:int, 1))) | ||
] | ||
end | ||
else | ||
let(:expected_nodes) do | ||
[ | ||
s(:lvasgn, :a), | ||
s(:send, | ||
s(:send, nil, :b), :[]=, | ||
s(:op_asgn, | ||
s(:lvasgn, :c), :+, s(:int, 1))) | ||
] | ||
end | ||
end | ||
|
||
it { is_expected.to eq(expected_nodes) } | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you justify when / how this is useful? I find it a bad idea that
x, y = z
andx, y = [z]
produce the samevalues
even though they are very different