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 authored and marcandre committed Aug 12, 2021
1 parent ffd025c commit 7c0e4f6
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 8 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][])
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

0 comments on commit 7c0e4f6

Please sign in to comment.