Skip to content

Commit

Permalink
Add ConstNode and some helper methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Aug 18, 2020
1 parent 5b0eb14 commit f5f9076
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
* [#88](https://github.com/rubocop-hq/rubocop-ast/pull/88): Add `RescueNode`. Add `ResbodyNode#exceptions` and `ResbodyNode#branch_index`. ([@fatkodima][])
* [#89](https://github.com/rubocop-hq/rubocop-ast/pull/89): Support right hand assignment for Ruby 2.8 (3.0) parser. ([@koic][])
* [#93](https://github.com/rubocop-hq/rubocop-ast/pull/93): Add `Node#{left|right}_sibling{s}` ([@marcandre][])
* [#99](https://github.com/rubocop-hq/rubocop-ast/pull/99): Add `ConstNode` and some helper methods. ([@marcandre][])

### Changes

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -29,6 +29,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_node'
require_relative 'ast/node/def_node'
require_relative 'ast/node/defined_node'
require_relative 'ast/node/ensure_node'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast/builder.rb
Expand Up @@ -27,6 +27,7 @@ class Builder < Parser::Builders::Default
case_match: CaseMatchNode,
case: CaseNode,
class: ClassNode,
const: ConstNode,
def: DefNode,
defined?: DefinedNode,
defs: DefNode,
Expand Down
63 changes: 63 additions & 0 deletions lib/rubocop/ast/node/const_node.rb
@@ -0,0 +1,63 @@
# frozen_string_literal: true

module RuboCop
module AST
# A node extension for `const` nodes.
class ConstNode < Node
# The `send` node associated with this block.
#
# @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?
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_given?

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
45 changes: 45 additions & 0 deletions spec/rubocop/ast/const_node_spec.rb
@@ -0,0 +1,45 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::ConstNode do
let(:ast) { parse_source(source).ast }
let(:const_node) { ast }
let(:source) { '::Foo::Bar::BAZ' }

describe '#namespace' do
it { expect(const_node.namespace.source).to eq '::Foo::Bar' }
end

describe '#short_name' do
it { expect(const_node.short_name).to eq :BAZ }
end

describe '#module_name?' do
it { expect(const_node.module_name?).to eq false }

context 'with a constant with a lowercase letter' do
let(:source) { '::Foo::Bar' }

it { expect(const_node.module_name?).to eq true }
end
end

describe '#absolute?' do
it { expect(const_node.absolute?).to eq true }

context 'with a constant not starting with ::' do
let(:source) { 'Foo::Bar::BAZ' }

it { expect(const_node.absolute?).to eq false }
end
end

describe '#each_path' do
let(:source) { 'var = ::Foo::Bar::BAZ' }
let(:const_node) { ast.children.last }

it 'yields all parts of the namespace' do
expect(const_node.each_path.map(&:type)).to eq %i[cbase const const]
expect(const_node.each_path.to_a.last(2).map(&:short_name)).to eq %i[Foo Bar]
end
end
end

0 comments on commit f5f9076

Please sign in to comment.