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 ConstNode and some helper methods. #99

Merged
merged 1 commit into from Aug 22, 2020
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.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