Skip to content

Commit

Permalink
Add discrete node classes for assignments.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvandersluis committed Aug 12, 2021
1 parent e75733e commit 5c5edc0
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_discrete_node_classes_for.md
@@ -0,0 +1 @@
* [#201](https://github.com/rubocop-hq/rubocop-ast/pull/201): Add discrete node classes for assignments. ([@dvandersluis][])
5 changes: 5 additions & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -39,10 +39,12 @@
require_relative 'ast/node/arg_node'
require_relative 'ast/node/args_node'
require_relative 'ast/node/array_node'
require_relative 'ast/node/asgn_node'
require_relative 'ast/node/block_node'
require_relative 'ast/node/break_node'
require_relative 'ast/node/case_match_node'
require_relative 'ast/node/case_node'
require_relative 'ast/node/casgn_node'
require_relative 'ast/node/class_node'
require_relative 'ast/node/const_node'
require_relative 'ast/node/def_node'
Expand All @@ -61,6 +63,9 @@
require_relative 'ast/node/lambda_node'
require_relative 'ast/node/module_node'
require_relative 'ast/node/next_node'
require_relative 'ast/node/op_asgn_node'
require_relative 'ast/node/and_asgn_node'
require_relative 'ast/node/or_asgn_node'
require_relative 'ast/node/or_node'
require_relative 'ast/node/pair_node'
require_relative 'ast/node/procarg0_node'
Expand Down
8 changes: 8 additions & 0 deletions lib/rubocop/ast/builder.rb
Expand Up @@ -20,6 +20,7 @@ class Builder < Parser::Builders::Default
# @api private
NODE_MAP = {
and: AndNode,
and_asgn: AndAsgnNode,
alias: AliasNode,
arg: ArgNode,
blockarg: ArgNode,
Expand All @@ -32,10 +33,15 @@ class Builder < Parser::Builders::Default
shadowarg: ArgNode,
args: ArgsNode,
array: ArrayNode,
lvasgn: AsgnNode,
ivasgn: AsgnNode,
cvasgn: AsgnNode,
gvasgn: AsgnNode,
block: BlockNode,
numblock: BlockNode,
break: BreakNode,
case_match: CaseMatchNode,
casgn: CasgnNode,
case: CaseNode,
class: ClassNode,
const: ConstNode,
Expand All @@ -60,6 +66,8 @@ class Builder < Parser::Builders::Default
lambda: LambdaNode,
module: ModuleNode,
next: NextNode,
op_asgn: OpAsgnNode,
or_asgn: OrAsgnNode,
or: OrNode,
pair: PairNode,
procarg0: Procarg0Node,
Expand Down
17 changes: 17 additions & 0 deletions lib/rubocop/ast/node/and_asgn_node.rb
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module RuboCop
module AST
# A node extension for `op_asgn` 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 AndAsgnNode < OpAsgnNode
# The operator being used for assignment as a symbol.
#
# @return [Symbol] the assignment operator
def operator
:'&&'
end
end
end
end
24 changes: 24 additions & 0 deletions lib/rubocop/ast/node/asgn_node.rb
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module RuboCop
module AST
# A node extension for `lvasgn`, `ivasgn`, `cvasgn`, and `gvasgn` 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 AsgnNode < Node
# The name of the variable being assigned as a symbol.
#
# @return [Symbol] the name of the variable being assigned
def name
node_parts[0]
end

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

module RuboCop
module AST
# A node extension for `casgn` 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 CasgnNode < Node
# The namespace of the constant being assigned.
#
# @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...)
def namespace
node_parts[0]
end

# The name of the variable being assigned as a symbol.
#
# @return [Symbol] the name of the variable being assigned
def name
node_parts[1]
end

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

module RuboCop
module AST
# A node extension for `op_asgn` 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 OpAsgnNode < Node
# @return [AsgnNode] the assignment node
def assignment_node
node_parts[0]
end

# The name of the variable being assigned as a symbol.
#
# @return [Symbol] the name of the variable being assigned
def name
assignment_node.name
end

# The operator being used for assignment as a symbol.
#
# @return [Symbol] the assignment operator
def operator
node_parts[1]
end

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

module RuboCop
module AST
# A node extension for `op_asgn` 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 OrAsgnNode < OpAsgnNode
# The operator being used for assignment as a symbol.
#
# @return [Symbol] the assignment operator
def operator
:'||'
end
end
end
end
36 changes: 36 additions & 0 deletions spec/rubocop/ast/and_asgn_node_spec.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::AndAsgnNode do
let(:or_asgn_node) { parse_source(source).ast }
let(:source) { 'var &&= value' }

describe '.new' do
it { expect(or_asgn_node).to be_a(described_class) }
end

describe '#assignment_node' do
subject { or_asgn_node.assignment_node }

it { is_expected.to be_a(RuboCop::AST::AsgnNode) }
end

describe '#name' do
subject { or_asgn_node.name }

it { is_expected.to eq(:var) }
end

describe '#operator' do
subject { or_asgn_node.operator }

it { is_expected.to eq(:'&&') }
end

describe '#expression' do
include AST::Sexp

subject { or_asgn_node.expression }

it { is_expected.to eq(s(:send, nil, :value)) }
end
end
89 changes: 89 additions & 0 deletions spec/rubocop/ast/asgn_node_spec.rb
@@ -0,0 +1,89 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::AsgnNode do
let(:asgn_node) { parse_source(source).ast }

describe '.new' do
context 'with a `lvasgn` node' do
let(:source) { 'var = value' }

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

context 'with a `ivasgn` node' do
let(:source) { '@var = value' }

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

context 'with a `cvasgn` node' do
let(:source) { '@@var = value' }

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

context 'with a `gvasgn` node' do
let(:source) { '$var = value' }

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

describe '#name' do
subject { asgn_node.name }

context 'with a `lvasgn` node' do
let(:source) { 'var = value' }

it { is_expected.to eq(:var) }
end

context 'with a `ivasgn` node' do
let(:source) { '@var = value' }

it { is_expected.to eq(:@var) }
end

context 'with a `cvasgn` node' do
let(:source) { '@@var = value' }

it { is_expected.to eq(:@@var) }
end

context 'with a `gvasgn` node' do
let(:source) { '$var = value' }

it { is_expected.to eq(:$var) }
end
end

describe '#expression' do
include AST::Sexp

subject { asgn_node.expression }

context 'with a `lvasgn` node' do
let(:source) { 'var = value' }

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

context 'with a `ivasgn` node' do
let(:source) { '@var = value' }

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

context 'with a `cvasgn` node' do
let(:source) { '@@var = value' }

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

context 'with a `gvasgn` node' do
let(:source) { '$var = value' }

it { is_expected.to eq(s(:send, nil, :value)) }
end
end
end
55 changes: 55 additions & 0 deletions spec/rubocop/ast/casgn_node_spec.rb
@@ -0,0 +1,55 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::CasgnNode do
let(:casgn_node) { parse_source(source).ast }

describe '.new' do
context 'with a `casgn` node' do
let(:source) { 'VAR = value' }

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

describe '#namespace' do
include AST::Sexp

subject { casgn_node.namespace }

context 'when there is no parent' do
let(:source) { 'VAR = value' }

it { is_expected.to eq(nil) }
end

context 'when the parent is a `cbase`' do
let(:source) { '::VAR = value' }

it { is_expected.to eq(s(:cbase)) }
end

context 'when the parent is a `const`' do
let(:source) { 'FOO::VAR = value' }

it { is_expected.to eq(s(:const, nil, :FOO)) }
end
end

describe '#name' do
subject { casgn_node.name }

let(:source) { 'VAR = value' }

it { is_expected.to eq(:VAR) }
end

describe '#expression' do
include AST::Sexp

subject { casgn_node.expression }

let(:source) { 'VAR = value' }

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

0 comments on commit 5c5edc0

Please sign in to comment.