Skip to content
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 Node classes for assignment types #201

Merged
merged 1 commit into from Aug 12, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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][])
16 changes: 8 additions & 8 deletions docs/modules/ROOT/pages/node_types.adoc
Expand Up @@ -56,7 +56,7 @@ The following fields are given when relevant to nodes in the source code:

|and|And operator|Two children are both expression nodes representing the operands.|a and b && c |https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AndNode[AndNode]

|and_asgn|And-assignment (AND the receiver with the argument and assign it back to receiver).|First child must be an assignment node, second child is the expression node.|a &&= b |N/A
|and_asgn|And-assignment (AND the receiver with the argument and assign it back to receiver).|First child must be an assignment node, second child is the expression node.|a &&= b |https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AndAsgnNode[AndAsgnNode]

|arg|Required positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode]

Expand All @@ -76,7 +76,7 @@ The following fields are given when relevant to nodes in the source code:

|case|Case statement.|First child is an expression node for the condition to check. Last child is an expression node for the "else" condition. All middle nodes are `when` nodes.|case a; when 1; b; when 2; c; else d; end|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/CaseNode[CaseNode]

|casgn|Constant assignment|Three children: the parent object (either an expression, `nil` or `cbase`), the constant name (a symbol), and the expression being assigned.|Foo::Bar = 5|N/A
|casgn|Constant assignment|Three children: the parent object (either an expression, `nil` or `cbase`), the constant name (a symbol), and the expression being assigned.|Foo::Bar = 5|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/CasgnNode[CasgnNode]

|cbase|Represents the top-module constant (i.e. the '::' before a constant name). Only occurs inside a `const` node.|None|::Foo|N/A

Expand All @@ -90,7 +90,7 @@ The following fields are given when relevant to nodes in the source code:

|cvar|Class variable access|One child, the variable name `:@@cfoo`|@@cfoo|N/A

|cvasgn|Class variable assignment|Two children: the variable name `:@@foo` and the expression being assigned|@@foo = 5|N/A
|cvasgn|Class variable assignment|Two children: the variable name `:@@foo` and the expression being assigned|@@foo = 5|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode]

|def|Instance method definition (full format)|Three children. First child is the name of the method (symbol); second child is `args` or `forward_args` (only if `emit_forward` is false, and it's true by default), and the last child is a body statement.|def foo(some_arg, kwarg: 1); end|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/DefNode[DefNode]

Expand Down Expand Up @@ -122,7 +122,7 @@ The following fields are given when relevant to nodes in the source code:

|gvar|Global variable access|One child, the variable name as a symbol `:$foo`|$foo|N/A

|gvasgn|Global variable assignment|Two children, the variable name `:$foo` and the expression being assigned|$foo = 5|N/A
|gvasgn|Global variable assignment|Two children, the variable name `:$foo` and the expression being assigned|$foo = 5|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode]

|hash|Hash literal.|`pair` s and/or `kwsplat` s.|{ foo: 'bar' }|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/HashNode[HashNode]

Expand All @@ -132,7 +132,7 @@ The following fields are given when relevant to nodes in the source code:

|ivar|Instance variable access|One child, the variable name `:@foo`|@foo|N/A

|ivasgn|Instance variable assignment|Two children, the variable name `:@foo` and the expression being assigned|@foo = 5|N/A
|ivasgn|Instance variable assignment|Two children, the variable name `:@foo` and the expression being assigned|@foo = 5|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode]

|irange|Inclusive range literal.|Two children, the start and end nodes (including `nil` for beginless/endless)|1..2|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/RangeNode[RangeNode]

Expand All @@ -150,7 +150,7 @@ The following fields are given when relevant to nodes in the source code:

|lvar|Local variable access|One child, the variable name|foo|N/A

|lvasgn|Local variable assignment|Two children: The variable name (symbol) and the expression.|a = some_thing|N/A
|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

Expand All @@ -166,13 +166,13 @@ The following fields are given when relevant to nodes in the source code:

|numblock|Block that has numbered arguments (`_1`) referenced inside it.|Three children. First child is a `send`/`csend` node representing the way the block is created, second child is an `int` (the number of numeric arguments) and the third child is a body statement.|proc { _1 + _3 }|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/BlockNode[BlockNode]

|op_asgn|Operator-assignment - perform an operation and assign the value.|Three children. First child must be an assignment node, second child is the operator (e.g. `:+`) and the third child is the expression node.|a += b|N/A
|op_asgn|Operator-assignment - perform an operation and assign the value.|Three children. First child must be an assignment node, second child is the operator (e.g. `:+`) and the third child is the expression node.|a += b|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/OpAsgnNode[OpAsgnNode]

|optarg|Optional positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar=1)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode]

|or|Or operator|Two children are both expression nodes representing the operands.|a or b|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/OrNode[OrNode]

|or_asgn|Or-assignment (OR the receiver with the argument and assign it back to receiver).|Two children. First child must be an assignment node, second child is the expression node.|a \|\|= b|N/A
|or_asgn|Or-assignment (OR the receiver with the argument and assign it back to receiver).|Two children. First child must be an assignment node, second child is the expression node.|a \|\|= b|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/OrAsgnNode[OrAsgnNode]

|pair|One entry in a hash. |Two children, the key and value nodes.|1 => 2|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/PairNode[PairNode]

Expand Down
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