Skip to content

Commit

Permalink
Add ConstAssignNode for type casgn
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Mar 10, 2021
1 parent 19d4b67 commit b130c93
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 54 deletions.
1 change: 1 addition & 0 deletions changelog/change_add_constassignnode_for_type_casgn.md
@@ -0,0 +1 @@
* [#x](https://github.com/rubocop-hq/rubocop-ast/pull/x): Add `ConstAssignNode` for type `casgn`. ([@marcandre][])
2 changes: 2 additions & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -35,6 +35,7 @@
require_relative 'ast/node/mixin/parameterized_node'
require_relative 'ast/node/mixin/predicate_operator_node'
require_relative 'ast/node/mixin/basic_literal_node'
require_relative 'ast/node/mixin/const_access_node'
require_relative 'ast/node/alias_node'
require_relative 'ast/node/and_node'
require_relative 'ast/node/arg_node'
Expand All @@ -45,6 +46,7 @@
require_relative 'ast/node/case_match_node'
require_relative 'ast/node/case_node'
require_relative 'ast/node/class_node'
require_relative 'ast/node/const_assign_node'
require_relative 'ast/node/const_node'
require_relative 'ast/node/def_node'
require_relative 'ast/node/defined_node'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast/builder.rb
Expand Up @@ -38,6 +38,7 @@ class Builder < Parser::Builders::Default
case_match: CaseMatchNode,
case: CaseNode,
class: ClassNode,
casgn: ConstAssignNode,
const: ConstNode,
def: DefNode,
defined?: DefinedNode,
Expand Down
18 changes: 18 additions & 0 deletions lib/rubocop/ast/node/const_assign_node.rb
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module RuboCop
module AST
# A node extension for `casgn` nodes.
# Responds to all methods of `const`
class ConstAssignNode < Node
include ConstAccessNode

# @return [Node, nil] the node associated with the assignment.
# Returns `nil` if is lhs of multiple assignement.
# Should probably be extracted for other assignment nodes
def assignment
children[2] || (parent.mlhs_type? ? nil : parent.children[1])
end
end
end
end
55 changes: 1 addition & 54 deletions lib/rubocop/ast/node/const_node.rb
Expand Up @@ -4,60 +4,7 @@ module RuboCop
module AST
# A node extension for `const` nodes.
class ConstNode < Node
# @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...)
def namespace
children[0]
end

# @return [Symbol] the demodulized name of the constant: "::Foo::Bar" => :Bar
def short_name
children[1]
end

# The body of this block.
#
# @return [Boolean] if the constant is a Module / Class, according to the standard convention.
# Note: some classes might have uppercase in which case this method
# returns false
def module_name?
short_name.match?(/[[:lower:]]/)
end
alias class_name? module_name?

# @return [Boolean] if the constant starts with `::` (aka s(:cbase))
def absolute?
return false unless namespace

each_path.first.cbase_type?
end

# @return [Boolean] if the constant does not start with `::` (aka s(:cbase))
def relative?
!absolute?
end

# Yield nodes for the namespace
#
# For `::Foo::Bar::BAZ` => yields:
# s(:cbase), then
# s(:const, :Foo), then
# s(:const, s(:const, :Foo), :Bar)
def each_path(&block)
return to_enum(__method__) unless block

descendants = []
last = self
loop do
last = last.children.first
break if last.nil?

descendants << last
break unless last.const_type?
end
descendants.reverse_each(&block)

self
end
include ConstAccessNode
end
end
end
64 changes: 64 additions & 0 deletions lib/rubocop/ast/node/mixin/const_access_node.rb
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module RuboCop
module AST
# Common functionality for nodes that access constants:
# `const`, `casgn`
module ConstAccessNode
# @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...)
def namespace
children[0]
end

# @return [Symbol] the demodulized name of the constant: "::Foo::Bar" => :Bar
def short_name
children[1]
end

# The body of this block.
#
# @return [Boolean] if the constant is a Module / Class, according to the standard convention.
# Note: some classes might have uppercase in which case this method
# returns false
def module_name?
short_name.match?(/[[:lower:]]/)
end
alias class_name? module_name?

# @return [Boolean] if the constant starts with `::` (aka s(:cbase))
def absolute?
return false unless namespace

each_path.first.cbase_type?
end

# @return [Boolean] if the constant does not start with `::` (aka s(:cbase))
def relative?
!absolute?
end

# Yield nodes for the namespace
#
# For `::Foo::Bar::BAZ` => yields:
# s(:cbase), then
# s(:const, :Foo), then
# s(:const, s(:const, :Foo), :Bar)
def each_path(&block)
return to_enum(__method__) unless block

descendants = []
last = self
loop do
last = last.children.first
break if last.nil?

descendants << last
break unless last.const_type?
end
descendants.reverse_each(&block)

self
end
end
end
end
29 changes: 29 additions & 0 deletions spec/rubocop/ast/const_assign_node_spec.rb
@@ -0,0 +1,29 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::ConstAssignNode do
subject(:ast) { parse_source(source).ast }

let(:casgn_node) { ast.each_node.find(&:casgn_type?) }
# Relying on `casgn_node_test` for common methods
# Testing only additional behavior

describe '#assignment' do
context 'with a simple assignement' do
let(:source) { '::Foo::Bar::BAZ = 42' }

it { expect(casgn_node.assignment.source).to eq '42' }
end

context 'with a complex assignement' do
let(:source) { '::Foo::Bar::BAZ ||= 42' }

it { expect(casgn_node.assignment.source).to eq '42' }
end

context 'with a multiple assignement' do
let(:source) { '::Foo::Bar::BAZ, = 42' }

it { expect(casgn_node.assignment).to eq nil }
end
end
end

0 comments on commit b130c93

Please sign in to comment.