diff --git a/changelog/change_add_constassignnode_for_type_casgn.md b/changelog/change_add_constassignnode_for_type_casgn.md new file mode 100644 index 000000000..027998725 --- /dev/null +++ b/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][]) diff --git a/lib/rubocop/ast.rb b/lib/rubocop/ast.rb index be4be80ac..b945935cd 100644 --- a/lib/rubocop/ast.rb +++ b/lib/rubocop/ast.rb @@ -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' @@ -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' diff --git a/lib/rubocop/ast/builder.rb b/lib/rubocop/ast/builder.rb index 51409cbcc..2c3e5ba6a 100644 --- a/lib/rubocop/ast/builder.rb +++ b/lib/rubocop/ast/builder.rb @@ -38,6 +38,7 @@ class Builder < Parser::Builders::Default case_match: CaseMatchNode, case: CaseNode, class: ClassNode, + casgn: ConstAssignNode, const: ConstNode, def: DefNode, defined?: DefinedNode, diff --git a/lib/rubocop/ast/node/const_assign_node.rb b/lib/rubocop/ast/node/const_assign_node.rb new file mode 100644 index 000000000..651b6ba53 --- /dev/null +++ b/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 diff --git a/lib/rubocop/ast/node/const_node.rb b/lib/rubocop/ast/node/const_node.rb index 438b539d7..4494b84f1 100644 --- a/lib/rubocop/ast/node/const_node.rb +++ b/lib/rubocop/ast/node/const_node.rb @@ -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 diff --git a/lib/rubocop/ast/node/mixin/const_access_node.rb b/lib/rubocop/ast/node/mixin/const_access_node.rb new file mode 100644 index 000000000..5acbf30c3 --- /dev/null +++ b/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 diff --git a/spec/rubocop/ast/const_assign_node_spec.rb b/spec/rubocop/ast/const_assign_node_spec.rb new file mode 100644 index 000000000..24ec18d6c --- /dev/null +++ b/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