From 5df717ce4fcb073f55199b72a340a161e3e813f5 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Sun, 23 Feb 2020 22:46:04 -0500 Subject: [PATCH] Require rubocop-ast gem --- Gemfile | 3 + lib/rubocop.rb | 58 +- lib/rubocop/ast/builder.rb | 85 - lib/rubocop/ast/node.rb | 637 ------ lib/rubocop/ast/node/alias_node.rb | 24 - lib/rubocop/ast/node/and_node.rb | 29 - lib/rubocop/ast/node/args_node.rb | 29 - lib/rubocop/ast/node/array_node.rb | 70 - lib/rubocop/ast/node/block_node.rb | 121 -- lib/rubocop/ast/node/break_node.rb | 17 - lib/rubocop/ast/node/case_match_node.rb | 56 - lib/rubocop/ast/node/case_node.rb | 56 - lib/rubocop/ast/node/class_node.rb | 31 - lib/rubocop/ast/node/def_node.rb | 82 - lib/rubocop/ast/node/defined_node.rb | 17 - lib/rubocop/ast/node/ensure_node.rb | 17 - lib/rubocop/ast/node/float_node.rb | 12 - lib/rubocop/ast/node/for_node.rb | 53 - lib/rubocop/ast/node/forward_args_node.rb | 18 - lib/rubocop/ast/node/hash_node.rb | 109 - lib/rubocop/ast/node/if_node.rb | 175 -- lib/rubocop/ast/node/int_node.rb | 12 - lib/rubocop/ast/node/keyword_splat_node.rb | 45 - .../ast/node/mixin/basic_literal_node.rb | 16 - .../ast/node/mixin/binary_operator_node.rb | 43 - lib/rubocop/ast/node/mixin/collection_node.rb | 15 - .../ast/node/mixin/conditional_node.rb | 45 - .../ast/node/mixin/hash_element_node.rb | 125 -- .../ast/node/mixin/method_dispatch_node.rb | 269 --- .../mixin/method_identifier_predicates.rb | 114 -- lib/rubocop/ast/node/mixin/modifier_node.rb | 17 - lib/rubocop/ast/node/mixin/numeric_node.rb | 21 - .../ast/node/mixin/parameterized_node.rb | 61 - .../ast/node/mixin/predicate_operator_node.rb | 35 - lib/rubocop/ast/node/module_node.rb | 24 - lib/rubocop/ast/node/or_node.rb | 29 - lib/rubocop/ast/node/pair_node.rb | 63 - lib/rubocop/ast/node/range_node.rb | 18 - lib/rubocop/ast/node/regexp_node.rb | 33 - lib/rubocop/ast/node/resbody_node.rb | 24 - lib/rubocop/ast/node/retry_node.rb | 17 - lib/rubocop/ast/node/return_node.rb | 24 - lib/rubocop/ast/node/self_class_node.rb | 24 - lib/rubocop/ast/node/send_node.rb | 17 - lib/rubocop/ast/node/str_node.rb | 16 - lib/rubocop/ast/node/super_node.rb | 21 - lib/rubocop/ast/node/symbol_node.rb | 12 - lib/rubocop/ast/node/until_node.rb | 35 - lib/rubocop/ast/node/when_node.rb | 53 - lib/rubocop/ast/node/while_node.rb | 35 - lib/rubocop/ast/node/yield_node.rb | 21 - lib/rubocop/ast/sexp.rb | 16 - lib/rubocop/ast/traversal.rb | 202 -- lib/rubocop/error.rb | 34 - lib/rubocop/node_pattern.rb | 881 -------- lib/rubocop/processed_source.rb | 211 -- lib/rubocop/token.rb | 114 -- manual/node_pattern.md | 401 ---- rubocop.gemspec | 1 + spec/rubocop/ast/alias_node_spec.rb | 31 - spec/rubocop/ast/and_node_spec.rb | 149 -- spec/rubocop/ast/args_node_spec.rb | 83 - spec/rubocop/ast/array_node_spec.rb | 113 -- spec/rubocop/ast/block_node_spec.rb | 245 --- spec/rubocop/ast/break_node_spec.rb | 13 - spec/rubocop/ast/case_match_node_spec.rb | 130 -- spec/rubocop/ast/case_node_spec.rb | 107 - spec/rubocop/ast/class_node_spec.rb | 65 - spec/rubocop/ast/def_node_spec.rb | 519 ----- spec/rubocop/ast/defined_node_spec.rb | 32 - spec/rubocop/ast/ensure_node_spec.rb | 17 - spec/rubocop/ast/float_node_spec.rb | 25 - spec/rubocop/ast/for_node_spec.rb | 63 - spec/rubocop/ast/forward_args_node_spec.rb | 19 - spec/rubocop/ast/hash_node_spec.rb | 236 --- spec/rubocop/ast/if_node_spec.rb | 495 ----- spec/rubocop/ast/int_node_spec.rb | 25 - spec/rubocop/ast/keyword_splat_node_spec.rb | 363 ---- spec/rubocop/ast/module_node_spec.rb | 47 - spec/rubocop/ast/node_spec.rb | 350 ---- spec/rubocop/ast/or_node_spec.rb | 149 -- spec/rubocop/ast/pair_node_spec.rb | 728 ------- spec/rubocop/ast/range_node_spec.rb | 45 - spec/rubocop/ast/regexp_node_spec.rb | 137 -- spec/rubocop/ast/resbody_node_spec.rb | 41 - spec/rubocop/ast/retry_node_spec.rb | 13 - spec/rubocop/ast/return_node_spec.rb | 57 - spec/rubocop/ast/self_class_node_spec.rb | 47 - spec/rubocop/ast/send_node_spec.rb | 1257 ------------ spec/rubocop/ast/str_node_spec.rb | 59 - spec/rubocop/ast/super_node_spec.rb | 413 ---- spec/rubocop/ast/symbol_node_spec.rb | 23 - spec/rubocop/ast/until_node_spec.rb | 45 - spec/rubocop/ast/when_node_spec.rb | 120 -- spec/rubocop/ast/while_node_spec.rb | 45 - spec/rubocop/ast/yield_node_spec.rb | 353 ---- spec/rubocop/node_pattern_spec.rb | 1770 ----------------- spec/rubocop/processed_source_spec.rb | 419 ---- spec/rubocop/token_spec.rb | 361 ---- 99 files changed, 5 insertions(+), 14047 deletions(-) delete mode 100644 lib/rubocop/ast/builder.rb delete mode 100644 lib/rubocop/ast/node.rb delete mode 100644 lib/rubocop/ast/node/alias_node.rb delete mode 100644 lib/rubocop/ast/node/and_node.rb delete mode 100644 lib/rubocop/ast/node/args_node.rb delete mode 100644 lib/rubocop/ast/node/array_node.rb delete mode 100644 lib/rubocop/ast/node/block_node.rb delete mode 100644 lib/rubocop/ast/node/break_node.rb delete mode 100644 lib/rubocop/ast/node/case_match_node.rb delete mode 100644 lib/rubocop/ast/node/case_node.rb delete mode 100644 lib/rubocop/ast/node/class_node.rb delete mode 100644 lib/rubocop/ast/node/def_node.rb delete mode 100644 lib/rubocop/ast/node/defined_node.rb delete mode 100644 lib/rubocop/ast/node/ensure_node.rb delete mode 100644 lib/rubocop/ast/node/float_node.rb delete mode 100644 lib/rubocop/ast/node/for_node.rb delete mode 100644 lib/rubocop/ast/node/forward_args_node.rb delete mode 100644 lib/rubocop/ast/node/hash_node.rb delete mode 100644 lib/rubocop/ast/node/if_node.rb delete mode 100644 lib/rubocop/ast/node/int_node.rb delete mode 100644 lib/rubocop/ast/node/keyword_splat_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/basic_literal_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/binary_operator_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/collection_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/conditional_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/hash_element_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/method_dispatch_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/method_identifier_predicates.rb delete mode 100644 lib/rubocop/ast/node/mixin/modifier_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/numeric_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/parameterized_node.rb delete mode 100644 lib/rubocop/ast/node/mixin/predicate_operator_node.rb delete mode 100644 lib/rubocop/ast/node/module_node.rb delete mode 100644 lib/rubocop/ast/node/or_node.rb delete mode 100644 lib/rubocop/ast/node/pair_node.rb delete mode 100644 lib/rubocop/ast/node/range_node.rb delete mode 100644 lib/rubocop/ast/node/regexp_node.rb delete mode 100644 lib/rubocop/ast/node/resbody_node.rb delete mode 100644 lib/rubocop/ast/node/retry_node.rb delete mode 100644 lib/rubocop/ast/node/return_node.rb delete mode 100644 lib/rubocop/ast/node/self_class_node.rb delete mode 100644 lib/rubocop/ast/node/send_node.rb delete mode 100644 lib/rubocop/ast/node/str_node.rb delete mode 100644 lib/rubocop/ast/node/super_node.rb delete mode 100644 lib/rubocop/ast/node/symbol_node.rb delete mode 100644 lib/rubocop/ast/node/until_node.rb delete mode 100644 lib/rubocop/ast/node/when_node.rb delete mode 100644 lib/rubocop/ast/node/while_node.rb delete mode 100644 lib/rubocop/ast/node/yield_node.rb delete mode 100644 lib/rubocop/ast/sexp.rb delete mode 100644 lib/rubocop/ast/traversal.rb delete mode 100644 lib/rubocop/error.rb delete mode 100644 lib/rubocop/node_pattern.rb delete mode 100644 lib/rubocop/processed_source.rb delete mode 100644 lib/rubocop/token.rb delete mode 100644 manual/node_pattern.md delete mode 100644 spec/rubocop/ast/alias_node_spec.rb delete mode 100644 spec/rubocop/ast/and_node_spec.rb delete mode 100644 spec/rubocop/ast/args_node_spec.rb delete mode 100644 spec/rubocop/ast/array_node_spec.rb delete mode 100644 spec/rubocop/ast/block_node_spec.rb delete mode 100644 spec/rubocop/ast/break_node_spec.rb delete mode 100644 spec/rubocop/ast/case_match_node_spec.rb delete mode 100644 spec/rubocop/ast/case_node_spec.rb delete mode 100644 spec/rubocop/ast/class_node_spec.rb delete mode 100644 spec/rubocop/ast/def_node_spec.rb delete mode 100644 spec/rubocop/ast/defined_node_spec.rb delete mode 100644 spec/rubocop/ast/ensure_node_spec.rb delete mode 100644 spec/rubocop/ast/float_node_spec.rb delete mode 100644 spec/rubocop/ast/for_node_spec.rb delete mode 100644 spec/rubocop/ast/forward_args_node_spec.rb delete mode 100644 spec/rubocop/ast/hash_node_spec.rb delete mode 100644 spec/rubocop/ast/if_node_spec.rb delete mode 100644 spec/rubocop/ast/int_node_spec.rb delete mode 100644 spec/rubocop/ast/keyword_splat_node_spec.rb delete mode 100644 spec/rubocop/ast/module_node_spec.rb delete mode 100644 spec/rubocop/ast/node_spec.rb delete mode 100644 spec/rubocop/ast/or_node_spec.rb delete mode 100644 spec/rubocop/ast/pair_node_spec.rb delete mode 100644 spec/rubocop/ast/range_node_spec.rb delete mode 100644 spec/rubocop/ast/regexp_node_spec.rb delete mode 100644 spec/rubocop/ast/resbody_node_spec.rb delete mode 100644 spec/rubocop/ast/retry_node_spec.rb delete mode 100644 spec/rubocop/ast/return_node_spec.rb delete mode 100644 spec/rubocop/ast/self_class_node_spec.rb delete mode 100644 spec/rubocop/ast/send_node_spec.rb delete mode 100644 spec/rubocop/ast/str_node_spec.rb delete mode 100644 spec/rubocop/ast/super_node_spec.rb delete mode 100644 spec/rubocop/ast/symbol_node_spec.rb delete mode 100644 spec/rubocop/ast/until_node_spec.rb delete mode 100644 spec/rubocop/ast/when_node_spec.rb delete mode 100644 spec/rubocop/ast/while_node_spec.rb delete mode 100644 spec/rubocop/ast/yield_node_spec.rb delete mode 100644 spec/rubocop/node_pattern_spec.rb delete mode 100644 spec/rubocop/processed_source_spec.rb delete mode 100644 spec/rubocop/token_spec.rb diff --git a/Gemfile b/Gemfile index 0551942c2b06..2bbea51a6296 100644 --- a/Gemfile +++ b/Gemfile @@ -22,5 +22,8 @@ group :test do gem 'webmock', require: false end +local_ast = File.expand_path('../rubocop-ast', __dir__) +gem 'rubocop-ast', path: local_ast if Dir.exist? local_ast + local_gemfile = File.expand_path('Gemfile.local', __dir__) eval_gemfile local_gemfile if File.exist?(local_gemfile) diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 94057ee7d473..99f723a8c146 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true -require 'parser' require 'rainbow' require 'English' require 'set' -require 'forwardable' require 'unicode/display_width/no_string_ext' +require 'rubocop-ast' require_relative 'rubocop/version' @@ -15,60 +14,7 @@ require_relative 'rubocop/file_finder' require_relative 'rubocop/platform' require_relative 'rubocop/name_similarity' -require_relative 'rubocop/node_pattern' require_relative 'rubocop/string_interpreter' -require_relative 'rubocop/ast/sexp' -require_relative 'rubocop/ast/node' -require_relative 'rubocop/ast/node/mixin/method_identifier_predicates' -require_relative 'rubocop/ast/node/mixin/binary_operator_node' -require_relative 'rubocop/ast/node/mixin/collection_node' -require_relative 'rubocop/ast/node/mixin/conditional_node' -require_relative 'rubocop/ast/node/mixin/hash_element_node' -require_relative 'rubocop/ast/node/mixin/method_dispatch_node' -require_relative 'rubocop/ast/node/mixin/modifier_node' -require_relative 'rubocop/ast/node/mixin/numeric_node' -require_relative 'rubocop/ast/node/mixin/parameterized_node' -require_relative 'rubocop/ast/node/mixin/predicate_operator_node' -require_relative 'rubocop/ast/node/mixin/basic_literal_node' -require_relative 'rubocop/ast/node/alias_node' -require_relative 'rubocop/ast/node/and_node' -require_relative 'rubocop/ast/node/args_node' -require_relative 'rubocop/ast/node/array_node' -require_relative 'rubocop/ast/node/block_node' -require_relative 'rubocop/ast/node/break_node' -require_relative 'rubocop/ast/node/case_match_node' -require_relative 'rubocop/ast/node/case_node' -require_relative 'rubocop/ast/node/class_node' -require_relative 'rubocop/ast/node/def_node' -require_relative 'rubocop/ast/node/defined_node' -require_relative 'rubocop/ast/node/ensure_node' -require_relative 'rubocop/ast/node/for_node' -require_relative 'rubocop/ast/node/forward_args_node' -require_relative 'rubocop/ast/node/float_node' -require_relative 'rubocop/ast/node/hash_node' -require_relative 'rubocop/ast/node/if_node' -require_relative 'rubocop/ast/node/int_node' -require_relative 'rubocop/ast/node/keyword_splat_node' -require_relative 'rubocop/ast/node/module_node' -require_relative 'rubocop/ast/node/or_node' -require_relative 'rubocop/ast/node/pair_node' -require_relative 'rubocop/ast/node/range_node' -require_relative 'rubocop/ast/node/regexp_node' -require_relative 'rubocop/ast/node/resbody_node' -require_relative 'rubocop/ast/node/retry_node' -require_relative 'rubocop/ast/node/return_node' -require_relative 'rubocop/ast/node/self_class_node' -require_relative 'rubocop/ast/node/send_node' -require_relative 'rubocop/ast/node/str_node' -require_relative 'rubocop/ast/node/super_node' -require_relative 'rubocop/ast/node/symbol_node' -require_relative 'rubocop/ast/node/until_node' -require_relative 'rubocop/ast/node/when_node' -require_relative 'rubocop/ast/node/while_node' -require_relative 'rubocop/ast/node/yield_node' -require_relative 'rubocop/ast/builder' -require_relative 'rubocop/ast/traversal' -require_relative 'rubocop/error' require_relative 'rubocop/warning' require_relative 'rubocop/cop/util' @@ -612,10 +558,8 @@ require_relative 'rubocop/config_store' require_relative 'rubocop/config_validator' require_relative 'rubocop/target_finder' -require_relative 'rubocop/token' require_relative 'rubocop/comment_config' require_relative 'rubocop/magic_comment' -require_relative 'rubocop/processed_source' require_relative 'rubocop/result_cache' require_relative 'rubocop/runner' require_relative 'rubocop/cli' diff --git a/lib/rubocop/ast/builder.rb b/lib/rubocop/ast/builder.rb deleted file mode 100644 index 84ef5abcbde1..000000000000 --- a/lib/rubocop/ast/builder.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # `RuboCop::AST::Builder` is an AST builder that is utilized to let `Parser` - # generate ASTs with {RuboCop::AST::Node}. - # - # @example - # buffer = Parser::Source::Buffer.new('(string)') - # buffer.source = 'puts :foo' - # - # builder = RuboCop::AST::Builder.new - # require 'parser/ruby25' - # parser = Parser::Ruby25.new(builder) - # root_node = parser.parse(buffer) - class Builder < Parser::Builders::Default - NODE_MAP = { - and: AndNode, - alias: AliasNode, - args: ArgsNode, - array: ArrayNode, - block: BlockNode, - numblock: BlockNode, - break: BreakNode, - case_match: CaseMatchNode, - case: CaseNode, - class: ClassNode, - def: DefNode, - defined?: DefinedNode, - defs: DefNode, - ensure: EnsureNode, - for: ForNode, - forward_args: ForwardArgsNode, - float: FloatNode, - hash: HashNode, - if: IfNode, - int: IntNode, - irange: RangeNode, - erange: RangeNode, - kwsplat: KeywordSplatNode, - module: ModuleNode, - or: OrNode, - pair: PairNode, - regexp: RegexpNode, - resbody: ResbodyNode, - retry: RetryNode, - return: ReturnNode, - csend: SendNode, - send: SendNode, - str: StrNode, - dstr: StrNode, - xstr: StrNode, - sclass: SelfClassNode, - super: SuperNode, - zsuper: SuperNode, - sym: SymbolNode, - until: UntilNode, - until_post: UntilNode, - when: WhenNode, - while: WhileNode, - while_post: WhileNode, - yield: YieldNode - }.freeze - - # Generates {Node} from the given information. - # - # @return [Node] the generated node - def n(type, children, source_map) - node_klass(type).new(type, children, location: source_map) - end - - # TODO: Figure out what to do about literal encoding handling... - # More details here https://github.com/whitequark/parser/issues/283 - def string_value(token) - value(token) - end - - private - - def node_klass(type) - NODE_MAP[type] || Node - end - end - end -end diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb deleted file mode 100644 index 7e4d8e01fdc8..000000000000 --- a/lib/rubocop/ast/node.rb +++ /dev/null @@ -1,637 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # `RuboCop::AST::Node` is a subclass of `Parser::AST::Node`. It provides - # access to parent nodes and an object-oriented way to traverse an AST with - # the power of `Enumerable`. - # - # It has predicate methods for every node type, like this: - # - # @example - # node.send_type? # Equivalent to: `node.type == :send` - # node.op_asgn_type? # Equivalent to: `node.type == :op_asgn` - # - # # Non-word characters (other than a-zA-Z0-9_) in type names are omitted. - # node.defined_type? # Equivalent to: `node.type == :defined?` - # - # # Find the first lvar node under the receiver node. - # lvar_node = node.each_descendant.find(&:lvar_type?) - # - class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength - include RuboCop::AST::Sexp - extend NodePattern::Macros - - # <=> isn't included here, because it doesn't return a boolean. - COMPARISON_OPERATORS = %i[== === != <= >= > <].freeze - - TRUTHY_LITERALS = %i[str dstr xstr int float sym dsym array - hash regexp true irange erange complex - rational regopt].freeze - FALSEY_LITERALS = %i[false nil].freeze - LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze - COMPOSITE_LITERALS = %i[dstr xstr dsym array hash irange - erange regexp].freeze - BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze - MUTABLE_LITERALS = %i[str dstr xstr array hash - regexp irange erange].freeze - IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze - - EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn - casgn masgn].freeze - SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].freeze - ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze - - BASIC_CONDITIONALS = %i[if while until].freeze - CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze - VARIABLES = %i[ivar gvar cvar lvar].freeze - REFERENCES = %i[nth_ref back_ref].freeze - KEYWORDS = %i[alias and break case class def defs defined? - kwbegin do else ensure for if module next - not or postexe redo rescue retry return self - super zsuper then undef until when while - yield].freeze - OPERATOR_KEYWORDS = %i[and or].freeze - SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze - - # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize - def initialize(type, children = [], properties = {}) - @mutable_attributes = {} - - # ::AST::Node#initialize freezes itself. - super - - # #parent= may be invoked multiple times for a node because there are - # pending nodes while constructing AST and they are replaced later. - # For example, `lvar` and `send` type nodes are initially created as an - # `ident` type node and fixed to the appropriate type later. - # So, the #parent attribute needs to be mutable. - each_child_node do |child_node| - child_node.parent = self unless child_node.complete? - end - end - - Parser::Meta::NODE_TYPES.each do |node_type| - method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?" - define_method(method_name) do - type == node_type - end - end - - # Returns the parent node, or `nil` if the receiver is a root node. - # - # @return [Node, nil] the parent node or `nil` - def parent - @mutable_attributes[:parent] - end - - def parent=(node) - @mutable_attributes[:parent] = node - end - - def complete! - @mutable_attributes.freeze - each_child_node(&:complete!) - end - - def complete? - @mutable_attributes.frozen? - end - - protected :parent= - - # Override `AST::Node#updated` so that `AST::Processor` does not try to - # mutate our ASTs. Since we keep references from children to parents and - # not just the other way around, we cannot update an AST and share - # identical subtrees. Rather, the entire AST must be copied any time any - # part of it is changed. - def updated(type = nil, children = nil, properties = {}) - properties[:location] ||= @location - klass = RuboCop::AST::Builder::NODE_MAP[type || @type] || Node - klass.new(type || @type, children || @children, properties) - end - - # Returns the index of the receiver node in its siblings. (Sibling index - # uses zero based numbering.) - # - # @return [Integer] the index of the receiver node in its siblings - def sibling_index - parent&.children&.index { |sibling| sibling.equal?(self) } - end - - # Common destructuring method. This can be used to normalize - # destructuring for different variations of the node. - # Some node types override this with their own custom - # destructuring method. - # - # @return [Array] the different parts of the ndde - def node_parts - to_a - end - - # Calls the given block for each ancestor node from parent to root. - # If no block is given, an `Enumerator` is returned. - # - # @overload each_ancestor - # Yield all nodes. - # @overload each_ancestor(type) - # Yield only nodes matching the type. - # @param [Symbol] type a node type - # @overload each_ancestor(type_a, type_b, ...) - # Yield only nodes matching any of the types. - # @param [Symbol] type_a a node type - # @param [Symbol] type_b a node type - # @yieldparam [Node] node each ancestor node - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_ancestor(*types, &block) - return to_enum(__method__, *types) unless block_given? - - visit_ancestors(types, &block) - - self - end - - # Returns an array of ancestor nodes. - # This is a shorthand for `node.each_ancestor.to_a`. - # - # @return [Array] an array of ancestor nodes - def ancestors - each_ancestor.to_a - end - - # Calls the given block for each child node. - # If no block is given, an `Enumerator` is returned. - # - # Note that this is different from `node.children.each { |child| ... }` - # which yields all children including non-node elements. - # - # @overload each_child_node - # Yield all nodes. - # @overload each_child_node(type) - # Yield only nodes matching the type. - # @param [Symbol] type a node type - # @overload each_child_node(type_a, type_b, ...) - # Yield only nodes matching any of the types. - # @param [Symbol] type_a a node type - # @param [Symbol] type_b a node type - # @yieldparam [Node] node each child node - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_child_node(*types) - return to_enum(__method__, *types) unless block_given? - - children.each do |child| - next unless child.is_a?(Node) - - yield child if types.empty? || types.include?(child.type) - end - - self - end - - # Returns an array of child nodes. - # This is a shorthand for `node.each_child_node.to_a`. - # - # @return [Array] an array of child nodes - def child_nodes - each_child_node.to_a - end - - # Calls the given block for each descendant node with depth first order. - # If no block is given, an `Enumerator` is returned. - # - # @overload each_descendant - # Yield all nodes. - # @overload each_descendant(type) - # Yield only nodes matching the type. - # @param [Symbol] type a node type - # @overload each_descendant(type_a, type_b, ...) - # Yield only nodes matching any of the types. - # @param [Symbol] type_a a node type - # @param [Symbol] type_b a node type - # @yieldparam [Node] node each descendant node - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_descendant(*types, &block) - return to_enum(__method__, *types) unless block_given? - - visit_descendants(types, &block) - - self - end - - # Returns an array of descendant nodes. - # This is a shorthand for `node.each_descendant.to_a`. - # - # @return [Array] an array of descendant nodes - def descendants - each_descendant.to_a - end - - # Calls the given block for the receiver and each descendant node in - # depth-first order. - # If no block is given, an `Enumerator` is returned. - # - # This method would be useful when you treat the receiver node as the root - # of a tree and want to iterate over all nodes in the tree. - # - # @overload each_node - # Yield all nodes. - # @overload each_node(type) - # Yield only nodes matching the type. - # @param [Symbol] type a node type - # @overload each_node(type_a, type_b, ...) - # Yield only nodes matching any of the types. - # @param [Symbol] type_a a node type - # @param [Symbol] type_b a node type - # @yieldparam [Node] node each node - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_node(*types, &block) - return to_enum(__method__, *types) unless block_given? - - yield self if types.empty? || types.include?(type) - - visit_descendants(types, &block) - - self - end - - def source - loc.expression.source - end - - def source_range - loc.expression - end - - def first_line - loc.line - end - - def last_line - loc.last_line - end - - def line_count - return 0 unless source_range - - source_range.last_line - source_range.first_line + 1 - end - - def nonempty_line_count - source.lines.grep(/\S/).size - end - - def source_length - source_range ? source_range.size : 0 - end - - ## Destructuring - - def_node_matcher :receiver, <<~PATTERN - {(send $_ ...) ({block numblock} (send $_ ...) ...)} - PATTERN - - def_node_matcher :str_content, '(str $_)' - - def const_name - return unless const_type? - - namespace, name = *self - if namespace && !namespace.cbase_type? - "#{namespace.const_name}::#{name}" - else - name.to_s - end - end - - def_node_matcher :defined_module0, <<~PATTERN - {(class (const $_ $_) ...) - (module (const $_ $_) ...) - (casgn $_ $_ (send (const nil? {:Class :Module}) :new ...)) - (casgn $_ $_ (block (send (const nil? {:Class :Module}) :new ...) ...))} - PATTERN - - private :defined_module0 - - def defined_module - namespace, name = *defined_module0 - s(:const, namespace, name) if name - end - - def defined_module_name - (const = defined_module) && const.const_name - end - - ## Searching the AST - - def parent_module_name - # what class or module is this method/constant/etc definition in? - # returns nil if answer cannot be determined - ancestors = each_ancestor(:class, :module, :sclass, :casgn, :block) - result = ancestors.map do |ancestor| - parent_module_name_part(ancestor) { |full_name| return full_name } - end.compact.reverse.join('::') - result.empty? ? 'Object' : result - end - - ## Predicates - - def multiline? - line_count > 1 - end - - def single_line? - line_count == 1 - end - - def empty_source? - source_length.zero? - end - - # Some cops treat the shovel operator as a kind of assignment. - def_node_matcher :assignment_or_similar?, <<~PATTERN - {assignment? (send _recv :<< ...)} - PATTERN - - def literal? - LITERALS.include?(type) - end - - def basic_literal? - BASIC_LITERALS.include?(type) - end - - def truthy_literal? - TRUTHY_LITERALS.include?(type) - end - - def falsey_literal? - FALSEY_LITERALS.include?(type) - end - - def mutable_literal? - MUTABLE_LITERALS.include?(type) - end - - def immutable_literal? - IMMUTABLE_LITERALS.include?(type) - end - - %i[literal basic_literal].each do |kind| - recursive_kind = :"recursive_#{kind}?" - kind_filter = :"#{kind}?" - define_method(recursive_kind) do - case type - when :send - [*COMPARISON_OPERATORS, :!, :<=>].include?(method_name) && - receiver.send(recursive_kind) && - arguments.all?(&recursive_kind) - when :begin, :pair, *OPERATOR_KEYWORDS, *COMPOSITE_LITERALS - children.compact.all?(&recursive_kind) - else - send(kind_filter) - end - end - end - - def variable? - VARIABLES.include?(type) - end - - def reference? - REFERENCES.include?(type) - end - - def equals_asgn? - EQUALS_ASSIGNMENTS.include?(type) - end - - def shorthand_asgn? - SHORTHAND_ASSIGNMENTS.include?(type) - end - - def assignment? - ASSIGNMENTS.include?(type) - end - - def basic_conditional? - BASIC_CONDITIONALS.include?(type) - end - - def conditional? - CONDITIONALS.include?(type) - end - - def keyword? - return true if special_keyword? || send_type? && prefix_not? - return false unless KEYWORDS.include?(type) - - !OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s) - end - - def special_keyword? - SPECIAL_KEYWORDS.include?(source) - end - - def operator_keyword? - OPERATOR_KEYWORDS.include?(type) - end - - def parenthesized_call? - loc.respond_to?(:begin) && loc.begin && loc.begin.is?('(') - end - - def call_type? - send_type? || csend_type? - end - - def chained? - parent&.call_type? && eql?(parent.receiver) - end - - def argument? - parent&.send_type? && parent.arguments.include?(self) - end - - def boolean_type? - true_type? || false_type? - end - - def numeric_type? - int_type? || float_type? - end - - def range_type? - irange_type? || erange_type? - end - - def guard_clause? - node = and_type? || or_type? ? rhs : self - - node.match_guard_clause? - end - - def_node_matcher :match_guard_clause?, <<~PATTERN - [${(send nil? {:raise :fail} ...) return break next} single_line?] - PATTERN - - def_node_matcher :proc?, <<~PATTERN - {(block (send nil? :proc) ...) - (block (send (const nil? :Proc) :new) ...) - (send (const nil? :Proc) :new)} - PATTERN - - def_node_matcher :lambda?, '({block numblock} (send nil? :lambda) ...)' - def_node_matcher :lambda_or_proc?, '{lambda? proc?}' - - def_node_matcher :class_constructor?, <<~PATTERN - { (send (const nil? {:Class :Module}) :new ...) - (block (send (const nil? {:Class :Module}) :new ...) ...)} - PATTERN - - # Some expressions are evaluated for their value, some for their side - # effects, and some for both - # If we know that an expression is useful only for its side effects, that - # means we can transform it in ways which preserve the side effects, but - # change the return value - # So, does the return value of this node matter? If we changed it to - # `(...; nil)`, might that affect anything? - # - # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity - def value_used? - # Be conservative and return true if we're not sure. - return false if parent.nil? - - case parent.type - when :array, :defined?, :dstr, :dsym, :eflipflop, :erange, :float, - :hash, :iflipflop, :irange, :not, :pair, :regexp, :str, :sym, - :when, :xstr - parent.value_used? - when :begin, :kwbegin - begin_value_used? - when :for - for_value_used? - when :case, :if - case_if_value_used? - when :while, :until, :while_post, :until_post - while_until_value_used? - else - true - end - end - # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity - - # Some expressions are evaluated for their value, some for their side - # effects, and some for both. - # If we know that expressions are useful only for their return values, - # and have no side effects, that means we can reorder them, change the - # number of times they are evaluated, or replace them with other - # expressions which are equivalent in value. - # So, is evaluation of this node free of side effects? - # - def pure? - # Be conservative and return false if we're not sure - case type - when :__FILE__, :__LINE__, :const, :cvar, :defined?, :false, :float, - :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true, :regopt - true - when :and, :array, :begin, :case, :dstr, :dsym, :eflipflop, :ensure, - :erange, :for, :hash, :if, :iflipflop, :irange, :kwbegin, :not, - :or, :pair, :regexp, :until, :until_post, :when, :while, - :while_post - child_nodes.all?(&:pure?) - else - false - end - end - - protected - - def visit_descendants(types, &block) - each_child_node do |child| - yield child if types.empty? || types.include?(child.type) - child.visit_descendants(types, &block) - end - end - - private - - def visit_ancestors(types) - last_node = self - - while (current_node = last_node.parent) - yield current_node if types.empty? || - types.include?(current_node.type) - last_node = current_node - end - end - - def begin_value_used? - # the last child node determines the value of the parent - sibling_index == parent.children.size - 1 ? parent.value_used? : false - end - - def for_value_used? - # `for var in enum; body; end` - # (for ) - sibling_index == 2 ? parent.value_used? : true - end - - def case_if_value_used? - # (case ) - # (if ) - sibling_index.zero? ? true : parent.value_used? - end - - def while_until_value_used? - # (while ) -> always evaluates to `nil` - sibling_index.zero? - end - - def parent_module_name_part(node) - case node.type - when :class, :module, :casgn - # TODO: if constant name has cbase (leading ::), then we don't need - # to keep traversing up through nested classes/modules - node.defined_module_name - when :sclass - yield parent_module_name_for_sclass(node) - else # block - parent_module_name_for_block(node) { yield nil } - end - end - - def parent_module_name_for_sclass(sclass_node) - # TODO: look for constant definition and see if it is nested - # inside a class or module - subject = sclass_node.children[0] - - if subject.const_type? - "#" - elsif subject.self_type? - "#" - end - end - - def parent_module_name_for_block(ancestor) - if ancestor.method?(:class_eval) - # `class_eval` with no receiver applies to whatever module or class - # we are currently in - return unless (receiver = ancestor.receiver) - - yield unless receiver.const_type? - receiver.const_name - elsif !new_class_or_module_block?(ancestor) - yield - end - end - - def_node_matcher :new_class_or_module_block?, <<~PATTERN - ^(casgn _ _ (block (send (const _ {:Class :Module}) :new) ...)) - PATTERN - end - end -end diff --git a/lib/rubocop/ast/node/alias_node.rb b/lib/rubocop/ast/node/alias_node.rb deleted file mode 100644 index 841b8fc21d18..000000000000 --- a/lib/rubocop/ast/node/alias_node.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `alias` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `alias` nodes within RuboCop. - class AliasNode < Node - # Returns the old identifier as specified by the `alias`. - # - # @return [SymbolNode] the old identifier - def old_identifier - node_parts[1] - end - - # Returns the new identifier as specified by the `alias`. - # - # @return [SymbolNode] the new identifier - def new_identifier - node_parts[0] - end - end - end -end diff --git a/lib/rubocop/ast/node/and_node.rb b/lib/rubocop/ast/node/and_node.rb deleted file mode 100644 index c7fe9a97d23e..000000000000 --- a/lib/rubocop/ast/node/and_node.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `until` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `until` nodes within RuboCop. - class AndNode < Node - include BinaryOperatorNode - include PredicateOperatorNode - - # Returns the alternate operator of the `and` as a string. - # Returns `and` for `&&` and vice versa. - # - # @return [String] the alternate of the `and` operator - def alternate_operator - logical_operator? ? SEMANTIC_AND : LOGICAL_AND - end - - # Returns the inverse keyword of the `and` node as a string. - # Returns `||` for `&&` and `or` for `and`. - # - # @return [String] the inverse of the `and` operator - def inverse_operator - logical_operator? ? LOGICAL_OR : SEMANTIC_OR - end - end - end -end diff --git a/lib/rubocop/ast/node/args_node.rb b/lib/rubocop/ast/node/args_node.rb deleted file mode 100644 index 4e3d7689f817..000000000000 --- a/lib/rubocop/ast/node/args_node.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `args` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `args` nodes within RuboCop. - class ArgsNode < Node - include CollectionNode - - # It returns true if arguments are empty and delimiters do not exist. - # @example: - # # true - # def x; end - # x { } - # -> {} - # - # # false - # def x(); end - # def x a; end - # x { || } - # -> () {} - # -> a {} - def empty_and_without_delimiters? - loc.expression.nil? - end - end - end -end diff --git a/lib/rubocop/ast/node/array_node.rb b/lib/rubocop/ast/node/array_node.rb deleted file mode 100644 index f8f2f44b46f4..000000000000 --- a/lib/rubocop/ast/node/array_node.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `array` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `array` nodes within RuboCop. - class ArrayNode < Node - PERCENT_LITERAL_TYPES = { - string: /^%[wW]/, - symbol: /^%[iI]/ - }.freeze - - # Returns an array of all value nodes in the `array` literal. - # - # @return [Array] an array of value nodes - def values - each_child_node.to_a - end - - # Calls the given block for all values in the `array` literal. - # - # @yieldparam [Node] node each node - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_value(&block) - return to_enum(__method__) unless block_given? - - values.each(&block) - - self - end - - # Checks whether the `array` literal is delimited by square brackets. - # - # @return [Boolean] whether the array is enclosed in square brackets - def square_brackets? - loc.begin&.is?('[') - end - - # Checks whether the `array` literal is delimited by percent brackets. - # - # @overload percent_literal? - # Check for any percent literal. - # - # @overload percent_literal?(type) - # Check for percent literal of type `type`. - # - # @param type [Symbol] an optional percent literal type - # - # @return [Boolean] whether the array is enclosed in percent brackets - def percent_literal?(type = nil) - if type - loc.begin && loc.begin.source =~ PERCENT_LITERAL_TYPES[type] - else - loc.begin&.source&.start_with?('%') - end - end - - # Checks whether the `array` literal is delimited by either percent or - # square brackets - # - # @return [Boolean] whether the array is enclosed in percent or square - # brackets - def bracketed? - square_brackets? || percent_literal? - end - end - end -end diff --git a/lib/rubocop/ast/node/block_node.rb b/lib/rubocop/ast/node/block_node.rb deleted file mode 100644 index 1af323acdf98..000000000000 --- a/lib/rubocop/ast/node/block_node.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `block` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `send` nodes within RuboCop. - # - # A `block` node is essentially a method send with a block. Parser nests - # the `send` node inside the `block` node. - class BlockNode < Node - include MethodIdentifierPredicates - - VOID_CONTEXT_METHODS = %i[each tap].freeze - - # The `send` node associated with this block. - # - # @return [SendNode] the `send` node associated with the `block` node - def send_node - node_parts[0] - end - - # The arguments of this block. - # - # @return [Array] - def arguments - if numblock_type? - [] # Numbered parameters have no block arguments. - else - node_parts[1] - end - end - - # The body of this block. - # - # @return [Node, nil] the body of the `block` node or `nil` - def body - node_parts[2] - end - - # The name of the dispatched method as a symbol. - # - # @return [Symbol] the name of the dispatched method - def method_name - send_node.method_name - end - - # Checks whether this block takes any arguments. - # - # @return [Boolean] whether this `block` node takes any arguments - def arguments? - !arguments.empty? - end - - # Checks whether the `block` literal is delimited by curly braces. - # - # @return [Boolean] whether the `block` literal is enclosed in braces - def braces? - loc.end&.is?('}') - end - - # Checks whether the `block` literal is delimited by `do`-`end` keywords. - # - # @return [Boolean] whether the `block` literal is enclosed in `do`-`end` - def keywords? - loc.end&.is?('end') - end - - # The delimiters for this `block` literal. - # - # @return [Array] the delimiters for the `block` literal - def delimiters - [loc.begin.source, loc.end.source].freeze - end - - # The opening delimiter for this `block` literal. - # - # @return [String] the opening delimiter for the `block` literal - def opening_delimiter - delimiters.first - end - - # The closing delimiter for this `block` literal. - # - # @return [String] the closing delimiter for the `block` literal - def closing_delimiter - delimiters.last - end - - # Checks whether this is a single line block. This is overridden here - # because the general version in `Node` does not work for `block` nodes. - # - # @return [Boolean] whether the `block` literal is on a single line - def single_line? - loc.begin.line == loc.end.line - end - - # Checks whether this is a multiline block. This is overridden here - # because the general version in `Node` does not work for `block` nodes. - # - # @return [Boolean] whether the `block` literal is on a several lines - def multiline? - !single_line? - end - - # Checks whether this `block` literal belongs to a lambda. - # - # @return [Boolean] whether the `block` literal belongs to a lambda - def lambda? - send_node.method?(:lambda) - end - - # Checks whether this node body is a void context. - # - # @return [Boolean] whether the `block` node body is a void context - def void_context? - VOID_CONTEXT_METHODS.include?(method_name) - end - end - end -end diff --git a/lib/rubocop/ast/node/break_node.rb b/lib/rubocop/ast/node/break_node.rb deleted file mode 100644 index 032aded7ea8d..000000000000 --- a/lib/rubocop/ast/node/break_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `break` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `break` nodes within RuboCop. - class BreakNode < Node - include MethodDispatchNode - include ParameterizedNode - - def arguments - [] - end - end - end -end diff --git a/lib/rubocop/ast/node/case_match_node.rb b/lib/rubocop/ast/node/case_match_node.rb deleted file mode 100644 index 8f558d23cc36..000000000000 --- a/lib/rubocop/ast/node/case_match_node.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `case_match` nodes. This will be used in place of - # a plain node when the builder constructs the AST, making its methods - # available to all `case_match` nodes within RuboCop. - class CaseMatchNode < Node - include ConditionalNode - - # Returns the keyword of the `case` statement as a string. - # - # @return [String] the keyword of the `case` statement - def keyword - 'case' - end - - # Calls the given block for each `in_pattern` node in the `in` statement. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_in_pattern - return in_pattern_branches.to_enum(__method__) unless block_given? - - in_pattern_branches.each do |condition| - yield condition - end - - self - end - - # Returns an array of all the when branches in the `case` statement. - # - # @return [Array] an array of `in_pattern` nodes - def in_pattern_branches - node_parts[1...-1] - end - - # Returns the else branch of the `case` statement, if any. - # - # @return [Node] the else branch node of the `case` statement - # @return [nil] if the case statement does not have an else branch. - def else_branch - node_parts[-1] - end - - # Checks whether this case statement has an `else` branch. - # - # @return [Boolean] whether the `case` statement has an `else` branch - def else? - !loc.else.nil? - end - end - end -end diff --git a/lib/rubocop/ast/node/case_node.rb b/lib/rubocop/ast/node/case_node.rb deleted file mode 100644 index 42b75185a4a8..000000000000 --- a/lib/rubocop/ast/node/case_node.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `case` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `case` nodes within RuboCop. - class CaseNode < Node - include ConditionalNode - - # Returns the keyword of the `case` statement as a string. - # - # @return [String] the keyword of the `case` statement - def keyword - 'case' - end - - # Calls the given block for each `when` node in the `case` statement. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_when - return when_branches.to_enum(__method__) unless block_given? - - when_branches.each do |condition| - yield condition - end - - self - end - - # Returns an array of all the when branches in the `case` statement. - # - # @return [Array] an array of `when` nodes - def when_branches - node_parts[1...-1] - end - - # Returns the else branch of the `case` statement, if any. - # - # @return [Node] the else branch node of the `case` statement - # @return [nil] if the case statement does not have an else branch. - def else_branch - node_parts[-1] - end - - # Checks whether this case statement has an `else` branch. - # - # @return [Boolean] whether the `case` statement has an `else` branch - def else? - loc.else - end - end - end -end diff --git a/lib/rubocop/ast/node/class_node.rb b/lib/rubocop/ast/node/class_node.rb deleted file mode 100644 index aa3623b92a62..000000000000 --- a/lib/rubocop/ast/node/class_node.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `class` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `class` nodes within RuboCop. - class ClassNode < Node - # The identifer for this `class` node. - # - # @return [Node] the identifer of the class - def identifier - node_parts[0] - end - - # The parent class for this `class` node. - # - # @return [Node, nil] the parent class of the class - def parent_class - node_parts[1] - end - - # The body of this `class` node. - # - # @return [Node, nil] the body of the class - def body - node_parts[2] - end - end - end -end diff --git a/lib/rubocop/ast/node/def_node.rb b/lib/rubocop/ast/node/def_node.rb deleted file mode 100644 index ee2d7561531f..000000000000 --- a/lib/rubocop/ast/node/def_node.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `def` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `def` nodes within RuboCop. - class DefNode < Node - include ParameterizedNode - include MethodIdentifierPredicates - - # Checks whether this node body is a void context. - # - # @return [Boolean] whether the `def` node body is a void context - def void_context? - method?(:initialize) || assignment_method? - end - - # Checks whether this method definition node forwards its arguments - # as per the feature added in Ruby 2.7. - # - # @note This is written in a way that may support lead arguments - # which are rumored to be added in a later version of Ruby. - # - # @return [Boolean] whether the `def` node uses argument forwarding - def argument_forwarding? - arguments.any?(&:forward_args_type?) - end - - # The name of the defined method as a symbol. - # - # @return [Symbol] the name of the defined method - def method_name - node_parts[2] - end - - # An array containing the arguments of the method definition. - # - # @return [Array] the arguments of the method definition - def arguments - node_parts[1] - end - - # The body of the method definition. - # - # @note this can be either a `begin` node, if the method body contains - # multiple expressions, or any other node, if it contains a single - # expression. - # - # @return [Node] the body of the method definition - def body - node_parts[0] - end - - # The receiver of the method definition, if any. - # - # @return [Node, nil] the receiver of the method definition, or `nil`. - def receiver - node_parts[3] - end - - # Custom destructuring method. This can be used to normalize - # destructuring for different variations of the node. - # - # In this case, the `def` node destructures into: - # - # `method_name, arguments, body` - # - # while the `defs` node destructures into: - # - # `receiver, method_name, arguments, body` - # - # so we reverse the destructured array to get the optional receiver - # at the end, where it can be discarded. - # - # @return [Array] the different parts of the `def` or `defs` node - def node_parts - to_a.reverse - end - end - end -end diff --git a/lib/rubocop/ast/node/defined_node.rb b/lib/rubocop/ast/node/defined_node.rb deleted file mode 100644 index cf5229474d70..000000000000 --- a/lib/rubocop/ast/node/defined_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `defined?` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `send` nodes within RuboCop. - class DefinedNode < Node - include ParameterizedNode - include MethodDispatchNode - - def node_parts - [nil, :defined?, *to_a] - end - end - end -end diff --git a/lib/rubocop/ast/node/ensure_node.rb b/lib/rubocop/ast/node/ensure_node.rb deleted file mode 100644 index a9be864496bb..000000000000 --- a/lib/rubocop/ast/node/ensure_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `ensure` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `ensure` nodes within RuboCop. - class EnsureNode < Node - # Returns the body of the `ensure` clause. - # - # @return [Node, nil] The body of the `ensure`. - def body - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/float_node.rb b/lib/rubocop/ast/node/float_node.rb deleted file mode 100644 index 6f892c23f80c..000000000000 --- a/lib/rubocop/ast/node/float_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `float` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available to - # all `float` nodes within RuboCop. - class FloatNode < Node - include NumericNode - end - end -end diff --git a/lib/rubocop/ast/node/for_node.rb b/lib/rubocop/ast/node/for_node.rb deleted file mode 100644 index 18c64530de52..000000000000 --- a/lib/rubocop/ast/node/for_node.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `for` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `for` nodes within RuboCop. - class ForNode < Node - # Returns the keyword of the `for` statement as a string. - # - # @return [String] the keyword of the `until` statement - def keyword - 'for' - end - - # Checks whether the `for` node has a `do` keyword. - # - # @return [Boolean] whether the `for` node has a `do` keyword - def do? - loc.begin&.is?('do') - end - - # Checks whether this node body is a void context. - # Always `true` for `for`. - # - # @return [true] whether the `for` node body is a void context - def void_context? - true - end - - # Returns the iteration variable of the `for` loop. - # - # @return [Node] The iteration variable of the `for` loop - def variable - node_parts[0] - end - - # Returns the collection the `for` loop is iterating over. - # - # @return [Node] The collection the `for` loop is iterating over - def collection - node_parts[1] - end - - # Returns the body of the `for` loop. - # - # @return [Node, nil] The body of the `for` loop. - def body - node_parts[2] - end - end - end -end diff --git a/lib/rubocop/ast/node/forward_args_node.rb b/lib/rubocop/ast/node/forward_args_node.rb deleted file mode 100644 index 8a42434c0884..000000000000 --- a/lib/rubocop/ast/node/forward_args_node.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `forward-args` nodes. This will be used in place - # of a plain node when the builder constructs the AST, making its methods - # available to all `forward-args` nodes within RuboCop. - class ForwardArgsNode < Node - include CollectionNode - - # Node wraps itself in an array to be compatible with other - # enumerable argument types. - def to_a - [self] - end - end - end -end diff --git a/lib/rubocop/ast/node/hash_node.rb b/lib/rubocop/ast/node/hash_node.rb deleted file mode 100644 index 661cb73554f1..000000000000 --- a/lib/rubocop/ast/node/hash_node.rb +++ /dev/null @@ -1,109 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `hash` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `hash` nodes within RuboCop. - class HashNode < Node - # Returns an array of all the key value pairs in the `hash` literal. - # - # @return [Array] an array of `pair` nodes - def pairs - each_pair.to_a - end - - # Checks whether the `hash` node contains any `pair`- or `kwsplat` nodes. - # - # @return[Boolean] whether the `hash` is empty - def empty? - children.empty? - end - - # Calls the given block for each `pair` node in the `hash` literal. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_pair - return each_child_node(:pair).to_enum unless block_given? - - each_child_node(:pair) do |pair| - yield(*pair) - end - - self - end - - # Returns an array of all the keys in the `hash` literal. - # - # @return [Array] an array of keys in the `hash` literal - def keys - each_key.to_a - end - - # Calls the given block for each `key` node in the `hash` literal. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_key - return pairs.map(&:key).to_enum unless block_given? - - pairs.map(&:key).each do |key| - yield key - end - - self - end - - # Returns an array of all the values in the `hash` literal. - # - # @return [Array] an array of values in the `hash` literal - def values - each_pair.map(&:value) - end - - # Calls the given block for each `value` node in the `hash` literal. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_value - return pairs.map(&:value).to_enum unless block_given? - - pairs.map(&:value).each do |value| - yield value - end - - self - end - - # Checks whether any of the key value pairs in the `hash` literal are on - # the same line. - # - # @note A multiline `pair` is considered to be on the same line if it - # shares any of its lines with another `pair` - # - # @return [Boolean] whether any `pair` nodes are on the same line - def pairs_on_same_line? - pairs.each_cons(2).any? { |first, second| first.same_line?(second) } - end - - # Checks whether this `hash` uses a mix of hash rocket and colon - # delimiters for its pairs. - # - # @return [Boolean] whether the `hash` uses mixed delimiters - def mixed_delimiters? - pairs.map(&:delimiter).uniq.size > 1 - end - - # Checks whether the `hash` literal is delimited by curly braces. - # - # @return [Boolean] whether the `hash` literal is enclosed in braces - def braces? - loc.end&.is?('}') - end - end - end -end diff --git a/lib/rubocop/ast/node/if_node.rb b/lib/rubocop/ast/node/if_node.rb deleted file mode 100644 index 45c79657a7c2..000000000000 --- a/lib/rubocop/ast/node/if_node.rb +++ /dev/null @@ -1,175 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `if` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `if` nodes within RuboCop. - class IfNode < Node - include ConditionalNode - include ModifierNode - - # Checks whether this node is an `if` statement. (This is not true of - # ternary operators and `unless` statements.) - # - # @return [Boolean] whether the node is an `if` statement - def if? - keyword == 'if' - end - - # Checks whether this node is an `unless` statement. (This is not true - # of ternary operators and `if` statements.) - # - # @return [Boolean] whether the node is an `unless` statement - def unless? - keyword == 'unless' - end - - # Checks whether the `if` is an `elsif`. Parser handles these by nesting - # `if` nodes in the `else` branch. - # - # @return [Boolean] whether the node is an `elsif` - def elsif? - keyword == 'elsif' - end - - # Checks whether the `if` node has an `else` clause. - # - # @note This returns `true` for nodes containing an `elsif` clause. - # This is legacy behavior, and many cops rely on it. - # - # @return [Boolean] whether the node has an `else` clause - def else? - loc.respond_to?(:else) && loc.else - end - - # Checks whether the `if` node is a ternary operator. - # - # @return [Boolean] whether the `if` node is a ternary operator - def ternary? - loc.respond_to?(:question) - end - - # Returns the keyword of the `if` statement as a string. Returns an empty - # string for ternary operators. - # - # @return [String] the keyword of the `if` statement - def keyword - ternary? ? '' : loc.keyword.source - end - - # Returns the inverse keyword of the `if` node as a string. Returns `if` - # for `unless` nodes and vice versa. Returns an empty string for ternary - # operators. - # - # @return [String] the inverse keyword of the `if` statement - def inverse_keyword - if keyword == 'if' - 'unless' - elsif keyword == 'unless' - 'if' - else - '' - end - end - - # Checks whether the `if` node is in a modifier form, i.e. a condition - # trailing behind an expression. Only `if` and `unless` nodes without - # other branches can be modifiers. - # - # @return [Boolean] whether the `if` node is a modifier - def modifier_form? - (if? || unless?) && super - end - - # Chacks whether the `if` node has nested `if` nodes in any of its - # branches. - # - # @note This performs a shallow search. - # - # @return [Boolean] whether the `if` node contains nested conditionals - def nested_conditional? - node_parts[1..2].compact.each do |branch| - branch.each_node(:if) do |nested| - return true unless nested.elsif? - end - end - - false - end - - # Checks whether the `if` node has at least one `elsif` branch. Returns - # true if this `if` node itself is an `elsif`. - # - # @return [Boolean] whether the `if` node has at least one `elsif` branch - def elsif_conditional? - else_branch&.if_type? && else_branch&.elsif? - end - - # Returns the branch of the `if` node that gets evaluated when its - # condition is truthy. - # - # @note This is normalized for `unless` nodes. - # - # @return [Node] the truthy branch node of the `if` node - # @return [nil] if the truthy branch is empty - def if_branch - node_parts[1] - end - - # Returns the branch of the `if` node that gets evaluated when its - # condition is falsey. - # - # @note This is normalized for `unless` nodes. - # - # @return [Node] the falsey branch node of the `if` node - # @return [nil] when there is no else branch - def else_branch - node_parts[2] - end - - # Custom destructuring method. This is used to normalize the branches - # for `if` and `unless` nodes, to aid comparisons and conversions. - # - # @return [Array] the different parts of the `if` statement - def node_parts - if unless? - condition, false_branch, true_branch = *self - else - condition, true_branch, false_branch = *self - end - - [condition, true_branch, false_branch] - end - - # Returns an array of all the branches in the conditional statement. - # - # @return [Array] an array of branch nodes - def branches - branches = [if_branch] - - return branches unless else_branch - - other_branches = if elsif_conditional? - else_branch.branches - else - [else_branch] - end - branches.concat(other_branches) - end - - # Calls the given block for each branch node in the conditional statement. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_branch - return branches.to_enum(__method__) unless block_given? - - branches.each do |branch| - yield branch - end - end - end - end -end diff --git a/lib/rubocop/ast/node/int_node.rb b/lib/rubocop/ast/node/int_node.rb deleted file mode 100644 index 178270bbc739..000000000000 --- a/lib/rubocop/ast/node/int_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `int` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available to - # all `int` nodes within RuboCop. - class IntNode < Node - include NumericNode - end - end -end diff --git a/lib/rubocop/ast/node/keyword_splat_node.rb b/lib/rubocop/ast/node/keyword_splat_node.rb deleted file mode 100644 index 2c7c663045b3..000000000000 --- a/lib/rubocop/ast/node/keyword_splat_node.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `kwsplat` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `kwsplat` nodes within RuboCop. - class KeywordSplatNode < Node - include HashElementNode - - DOUBLE_SPLAT = '**' - - # This is used for duck typing with `pair` nodes which also appear as - # `hash` elements. - # - # @return [false] - def hash_rocket? - false - end - - # This is used for duck typing with `pair` nodes which also appear as - # `hash` elements. - # - # @return [false] - def colon? - false - end - - # Returns the operator for the `kwsplat` as a string. - # - # @return [String] the double splat operator - def operator - DOUBLE_SPLAT - end - - # Custom destructuring method. This is used to normalize the branches - # for `pair` and `kwsplat` nodes, to add duck typing to `hash` elements. - # - # @return [Array] the different parts of the `kwsplat` - def node_parts - [self, self] - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/basic_literal_node.rb b/lib/rubocop/ast/node/mixin/basic_literal_node.rb deleted file mode 100644 index 8354e6100a80..000000000000 --- a/lib/rubocop/ast/node/mixin/basic_literal_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for primitive literal nodes: `sym`, `str`, - # `int`, `float`, ... - module BasicLiteralNode - # Returns the value of the literal. - # - # @return [mixed] the value of the literal - def value - node_parts[0] - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/binary_operator_node.rb b/lib/rubocop/ast/node/mixin/binary_operator_node.rb deleted file mode 100644 index b5cc6800c40e..000000000000 --- a/lib/rubocop/ast/node/mixin/binary_operator_node.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that are binary operations: - # `or`, `and` ... - module BinaryOperatorNode - # Returns the left hand side node of the binary operation. - # - # @return [Node] the left hand side of the binary operation - def lhs - node_parts[0] - end - - # Returns the right hand side node of the binary operation. - # - # @return [Node] the right hand side of the binary operation - def rhs - node_parts[1] - end - - # Returns all of the conditions, including nested conditions, - # of the binary operation. - # - # @return [Array] the left and right hand side of the binary - # operation and the let and right hand side of any nested binary - # operators - def conditions - lhs, rhs = *self - lhs = lhs.children.first if lhs.begin_type? - rhs = rhs.children.first if rhs.begin_type? - - [lhs, rhs].each_with_object([]) do |side, collection| - if side.operator_keyword? - collection.concat(side.conditions) - else - collection << side - end - end - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/collection_node.rb b/lib/rubocop/ast/node/mixin/collection_node.rb deleted file mode 100644 index acb12de7a4df..000000000000 --- a/lib/rubocop/ast/node/mixin/collection_node.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A mixin that helps give collection nodes array polymorphism. - module CollectionNode - extend Forwardable - - ARRAY_METHODS = - (Array.instance_methods - Object.instance_methods - [:to_a]).freeze - - def_delegators :to_a, *ARRAY_METHODS - end - end -end diff --git a/lib/rubocop/ast/node/mixin/conditional_node.rb b/lib/rubocop/ast/node/mixin/conditional_node.rb deleted file mode 100644 index 656004a9654b..000000000000 --- a/lib/rubocop/ast/node/mixin/conditional_node.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that have conditions: - # `if`, `while`, `until`, `case`. - # This currently doesn't include `when` nodes, because they have multiple - # conditions, and need to be checked for that. - module ConditionalNode - # Checks whether the condition of the node is written on a single line. - # - # @return [Boolean] whether the condition is on a single line - def single_line_condition? - loc.keyword.line == condition.source_range.line - end - - # Checks whether the condition of the node is written on more than - # one line. - # - # @return [Boolean] whether the condition is on more than one line - def multiline_condition? - !single_line_condition? - end - - # Returns the condition of the node. This works together with each node's - # custom destructuring method to select the correct part of the node. - # - # @return [Node, nil] the condition of the node - def condition - node_parts[0] - end - - # Returns the body associated with the condition. This works together with - # each node's custom destructuring method to select the correct part of - # the node. - # - # @note For `if` nodes, this is the truthy branch. - # - # @return [Node, nil] the body of the node - def body - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/hash_element_node.rb b/lib/rubocop/ast/node/mixin/hash_element_node.rb deleted file mode 100644 index 930a3c9dbb34..000000000000 --- a/lib/rubocop/ast/node/mixin/hash_element_node.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that can be used as hash elements: - # `pair`, `kwsplat` - module HashElementNode - # Returns the key of this `hash` element. - # - # @note For keyword splats, this returns the whole node - # - # @return [Node] the key of the hash element - def key - node_parts[0] - end - - # Returns the value of this `hash` element. - # - # @note For keyword splats, this returns the whole node - # - # @return [Node] the value of the hash element - def value - node_parts[1] - end - - # Checks whether this `hash` element is on the same line as `other`. - # - # @note A multiline element is considered to be on the same line if it - # shares any of its lines with `other` - # - # @return [Boolean] whether this element is on the same line as `other` - def same_line?(other) - loc.last_line == other.loc.line || loc.line == other.loc.last_line - end - - # Returns the delta between this pair's key and the argument pair's. - # - # @note Keys on the same line always return a delta of 0 - # @note Keyword splats always return a delta of 0 for right alignment - # - # @param [Symbol] alignment whether to check the left or right side - # @return [Integer] the delta between the two keys - def key_delta(other, alignment = :left) - HashElementDelta.new(self, other).key_delta(alignment) - end - - # Returns the delta between this element's value and the argument's. - # - # @note Keyword splats always return a delta of 0 - # - # @return [Integer] the delta between the two values - def value_delta(other) - HashElementDelta.new(self, other).value_delta - end - - # Returns the delta between this element's delimiter and the argument's. - # - # @note Pairs with different delimiter styles return a delta of 0 - # - # @return [Integer] the delta between the two delimiters - def delimiter_delta(other) - HashElementDelta.new(self, other).delimiter_delta - end - - # A helper class for comparing the positions of different parts of a - # `pair` node. - class HashElementDelta - def initialize(first, second) - @first = first - @second = second - - raise ArgumentError unless valid_argument_types? - end - - def key_delta(alignment = :left) - return 0 if first.same_line?(second) - return 0 if keyword_splat? && alignment == :right - - delta(first.key.loc, second.key.loc, alignment) - end - - def value_delta - return 0 if first.same_line?(second) - return 0 if keyword_splat? - - delta(first.value.loc, second.value.loc) - end - - def delimiter_delta - return 0 if first.same_line?(second) - return 0 if first.delimiter != second.delimiter - - delta(first.loc.operator, second.loc.operator) - end - - private - - attr_reader :first, :second - - def valid_argument_types? - [first, second].all? do |argument| - argument.pair_type? || argument.kwsplat_type? - end - end - - def delta(first, second, alignment = :left) - case alignment - when :left - first.column - second.column - when :right - first.last_column - second.last_column - else - 0 - end - end - - def keyword_splat? - [first, second].any?(&:kwsplat_type?) - end - end - - private_constant :HashElementDelta - end - end -end diff --git a/lib/rubocop/ast/node/mixin/method_dispatch_node.rb b/lib/rubocop/ast/node/mixin/method_dispatch_node.rb deleted file mode 100644 index bf2bd3b4f732..000000000000 --- a/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +++ /dev/null @@ -1,269 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that are a kind of method dispatch: - # `send`, `csend`, `super`, `zsuper`, `yield`, `defined?` - module MethodDispatchNode - extend NodePattern::Macros - include MethodIdentifierPredicates - - ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze - SPECIAL_MODIFIERS = %w[private protected].freeze - - # The receiving node of the method dispatch. - # - # @return [Node, nil] the receiver of the dispatched method or `nil` - def receiver - node_parts[0] - end - - # The name of the dispatched method as a symbol. - # - # @return [Symbol] the name of the dispatched method - def method_name - node_parts[1] - end - - # An array containing the arguments of the dispatched method. - # - # @return [Array] the arguments of the dispatched method - def arguments - node_parts[2..-1] - end - - # The `block` node associated with this method dispatch, if any. - # - # @return [BlockNode, nil] the `block` node associated with this method - # call or `nil` - def block_node - parent if block_literal? - end - - # Checks whether the dispatched method is a macro method. A macro method - # is defined as a method that sits in a class, module, or block body and - # has an implicit receiver. - # - # @note This does not include DSLs that use nested blocks, like RSpec - # - # @return [Boolean] whether the dispatched method is a macro method - def macro? - !receiver && macro_scope? - end - - # Checks whether the dispatched method is an access modifier. - # - # @return [Boolean] whether the dispatched method is an access modifier - def access_modifier? - bare_access_modifier? || non_bare_access_modifier? - end - - # Checks whether the dispatched method is a bare access modifier that - # affects all methods defined after the macro. - # - # @return [Boolean] whether the dispatched method is a bare - # access modifier - def bare_access_modifier? - macro? && bare_access_modifier_declaration? - end - - # Checks whether the dispatched method is a non-bare access modifier that - # affects only the method it receives. - # - # @return [Boolean] whether the dispatched method is a non-bare - # access modifier - def non_bare_access_modifier? - macro? && non_bare_access_modifier_declaration? - end - - # Checks whether the dispatched method is a bare `private` or `protected` - # access modifier that affects all methods defined after the macro. - # - # @return [Boolean] whether the dispatched method is a bare - # `private` or `protected` access modifier - def special_modifier? - bare_access_modifier? && SPECIAL_MODIFIERS.include?(source) - end - - # Checks whether the name of the dispatched method matches the argument - # and has an implicit receiver. - # - # @param [Symbol, String] name the method name to check for - # @return [Boolean] whether the method name matches the argument - def command?(name) - !receiver && method?(name) - end - - # Checks whether the dispatched method is a setter method. - # - # @return [Boolean] whether the dispatched method is a setter - def setter_method? - loc.respond_to?(:operator) && loc.operator - end - alias assignment? setter_method? - - # Checks whether the dispatched method uses a dot to connect the - # receiver and the method name. - # - # This is useful for comparison operators, which can be called either - # with or without a dot, i.e. `foo == bar` or `foo.== bar`. - # - # @return [Boolean] whether the method was called with a connecting dot - def dot? - loc.respond_to?(:dot) && loc.dot && loc.dot.is?('.') - end - - # Checks whether the dispatched method uses a double colon to connect the - # receiver and the method name. - # - # @return [Boolean] whether the method was called with a connecting dot - def double_colon? - loc.respond_to?(:dot) && loc.dot && loc.dot.is?('::') - end - - # Checks whether the dispatched method uses a safe navigation operator to - # connect the receiver and the method name. - # - # @return [Boolean] whether the method was called with a connecting dot - def safe_navigation? - loc.respond_to?(:dot) && loc.dot && loc.dot.is?('&.') - end - - # Checks whether the *explicit* receiver of this method dispatch is - # `self`. - # - # @return [Boolean] whether the receiver of this method dispatch is `self` - def self_receiver? - receiver&.self_type? - end - - # Checks whether the *explicit* receiver of this method dispatch is a - # `const` node. - # - # @return [Boolean] whether the receiver of this method dispatch - # is a `const` node - def const_receiver? - receiver&.const_type? - end - - # Checks whether the method dispatch is the implicit form of `#call`, - # e.g. `foo.(bar)`. - # - # @return [Boolean] whether the method is the implicit form of `#call` - def implicit_call? - method?(:call) && !loc.selector - end - - # Whether this method dispatch has an explicit block. - # - # @return [Boolean] whether the dispatched method has a block - def block_literal? - parent&.block_type? && eql?(parent.send_node) - end - - # Checks whether this node is an arithmetic operation - # - # @return [Boolean] whether the dispatched method is an arithmetic - # operation - def arithmetic_operation? - ARITHMETIC_OPERATORS.include?(method_name) - end - - # Checks if this node is part of a chain of `def` modifiers. - # - # @example - # - # private def foo; end - # - # @return [Boolean] whether the dispatched method is a `def` modifier - def def_modifier? - send_type? && - [self, *each_descendant(:send)].any?(&:adjacent_def_modifier?) - end - - # Checks whether this is a lambda. Some versions of parser parses - # non-literal lambdas as a method send. - # - # @return [Boolean] whether this method is a lambda - def lambda? - block_literal? && command?(:lambda) - end - - # Checks whether this is a lambda literal (stabby lambda.) - # - # @example - # - # -> (foo) { bar } - # - # @return [Boolean] whether this method is a lambda literal - def lambda_literal? - block_literal? && loc.expression && loc.expression.source == '->' - end - - # Checks whether this is a unary operation. - # - # @example - # - # -foo - # - # @return [Boolean] whether this method is a unary operation - def unary_operation? - return false unless loc.selector - - operator_method? && loc.expression.begin_pos == loc.selector.begin_pos - end - - # Checks whether this is a binary operation. - # - # @example - # - # foo + bar - # - # @return [Bookean] whether this method is a binary operation - def binary_operation? - return false unless loc.selector - - operator_method? && loc.expression.begin_pos != loc.selector.begin_pos - end - - private - - def_node_matcher :macro_scope?, <<~PATTERN - {^{({sclass class module block} ...) class_constructor?} - ^^{({sclass class module block} ... ({begin if} ...)) class_constructor?} - ^#macro_kwbegin_wrapper? - #root_node?} - PATTERN - - # Check if a node's parent is a kwbegin wrapper within a macro scope - # - # @param parent [Node] parent of the node being checked - # - # @return [Boolean] true if the parent is a kwbegin in a macro scope - def macro_kwbegin_wrapper?(parent) - parent.kwbegin_type? && macro_scope?(parent) - end - - # Check if a node does not have a parent - # - # @param node [Node] - # - # @return [Boolean] if the parent is nil - def root_node?(node) - node.parent.nil? - end - - def_node_matcher :adjacent_def_modifier?, <<~PATTERN - (send nil? _ ({def defs} ...)) - PATTERN - - def_node_matcher :bare_access_modifier_declaration?, <<~PATTERN - (send nil? {:public :protected :private :module_function}) - PATTERN - - def_node_matcher :non_bare_access_modifier_declaration?, <<~PATTERN - (send nil? {:public :protected :private :module_function} _) - PATTERN - end - end -end diff --git a/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb b/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb deleted file mode 100644 index 7ddca670a09b..000000000000 --- a/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common predicates for nodes that reference method identifiers: - # `send`, `csend`, `def`, `defs`, `super`, `zsuper` - # - # @note this mixin expects `#method_name` and `#receiver` to be implemented - module MethodIdentifierPredicates - ENUMERATOR_METHODS = %i[collect collect_concat detect downto each - find find_all find_index inject loop map! - map reduce reject reject! reverse_each select - select! times upto].freeze - - # http://phrogz.net/programmingruby/language.html#table_18.4 - OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / - % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].freeze - - # Checks whether the method name matches the argument. - # - # @param [Symbol, String] name the method name to check for - # @return [Boolean] whether the method name matches the argument - def method?(name) - method_name == name.to_sym - end - - # Checks whether the method is an operator method. - # - # @return [Boolean] whether the method is an operator - def operator_method? - OPERATOR_METHODS.include?(method_name) - end - - # Checks whether the method is a comparison method. - # - # @return [Boolean] whether the method is a comparison - def comparison_method? - Node::COMPARISON_OPERATORS.include?(method_name) - end - - # Checks whether the method is an assignment method. - # - # @return [Boolean] whether the method is an assignment - def assignment_method? - !comparison_method? && method_name.to_s.end_with?('=') - end - - # Checks whether the method is an enumerator method. - # - # @return [Boolean] whether the method is an enumerator - def enumerator_method? - ENUMERATOR_METHODS.include?(method_name) || - method_name.to_s.start_with?('each_') - end - - # Checks whether the method is a predicate method. - # - # @return [Boolean] whether the method is a predicate method - def predicate_method? - method_name.to_s.end_with?('?') - end - - # Checks whether the method is a bang method. - # - # @return [Boolean] whether the method is a bang method - def bang_method? - method_name.to_s.end_with?('!') - end - - # Checks whether the method is a camel case method, - # e.g. `Integer()`. - # - # @return [Boolean] whether the method is a camel case method - def camel_case_method? - method_name.to_s =~ /\A[A-Z]/ - end - - # Checks whether the *explicit* receiver of this node is `self`. - # - # @return [Boolean] whether the receiver of this node is `self` - def self_receiver? - receiver&.self_type? - end - - # Checks whether the *explicit* receiver of node is a `const` node. - # - # @return [Boolean] whether the receiver of this node is a `const` node - def const_receiver? - receiver&.const_type? - end - - # Checks whether this is a negation method, i.e. `!` or keyword `not`. - # - # @return [Boolean] whether this method is a negation method - def negation_method? - receiver && method_name == :! - end - - # Checks whether this is a prefix not method, e.g. `not foo`. - # - # @return [Boolean] whether this method is a prefix not - def prefix_not? - negation_method? && loc.selector.is?('not') - end - - # Checks whether this is a prefix bang method, e.g. `!foo`. - # - # @return [Boolean] whether this method is a prefix bang - def prefix_bang? - negation_method? && loc.selector.is?('!') - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/modifier_node.rb b/lib/rubocop/ast/node/mixin/modifier_node.rb deleted file mode 100644 index 55ffa80787ab..000000000000 --- a/lib/rubocop/ast/node/mixin/modifier_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that can be used as modifiers: - # `if`, `while`, `until` - module ModifierNode - # Checks whether the node is in a modifier form, i.e. a condition - # trailing behind an expression. - # - # @return [Boolean] whether the node is a modifier - def modifier_form? - loc.end.nil? - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/numeric_node.rb b/lib/rubocop/ast/node/mixin/numeric_node.rb deleted file mode 100644 index a5406d32620e..000000000000 --- a/lib/rubocop/ast/node/mixin/numeric_node.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for primitive numeric nodes: `int`, `float`, ... - module NumericNode - SIGN_REGEX = /\A[+-]/.freeze - - # Checks whether this is literal has a sign. - # - # @example - # - # +42 - # - # @return [Boolean] whether this literal has a sign. - def sign? - source.match(SIGN_REGEX) - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/parameterized_node.rb b/lib/rubocop/ast/node/mixin/parameterized_node.rb deleted file mode 100644 index 9b26aca7ed04..000000000000 --- a/lib/rubocop/ast/node/mixin/parameterized_node.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that are parameterized: - # `send`, `super`, `zsuper`, `def`, `defs` - module ParameterizedNode - # Checks whether this node's arguments are wrapped in parentheses. - # - # @return [Boolean] whether this node's arguments are - # wrapped in parentheses - def parenthesized? - loc.end&.is?(')') - end - - # A shorthand for getting the first argument of the node. - # Equivalent to `arguments.first`. - # - # @return [Node, nil] the first argument of the node, - # or `nil` if there are no arguments - def first_argument - arguments[0] - end - - # A shorthand for getting the last argument of the node. - # Equivalent to `arguments.last`. - # - # @return [Node, nil] the last argument of the node, - # or `nil` if there are no arguments - def last_argument - arguments[-1] - end - - # Checks whether this node has any arguments. - # - # @return [Boolean] whether this node has any arguments - def arguments? - !arguments.empty? - end - - # Checks whether any argument of the node is a splat - # argument, i.e. `*splat`. - # - # @return [Boolean] whether the node is a splat argument - def splat_argument? - arguments? && - (arguments.any?(&:splat_type?) || arguments.any?(&:restarg_type?)) - end - alias rest_argument? splat_argument? - - # Whether the last argument of the node is a block pass, - # i.e. `&block`. - # - # @return [Boolean] whether the last argument of the node is a block pass - def block_argument? - arguments? && - (last_argument.block_pass_type? || last_argument.blockarg_type?) - end - end - end -end diff --git a/lib/rubocop/ast/node/mixin/predicate_operator_node.rb b/lib/rubocop/ast/node/mixin/predicate_operator_node.rb deleted file mode 100644 index 43d635c34edd..000000000000 --- a/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # Common functionality for nodes that are predicates: - # `or`, `and` ... - module PredicateOperatorNode - LOGICAL_AND = '&&' - SEMANTIC_AND = 'and' - LOGICAL_OR = '||' - SEMANTIC_OR = 'or' - - # Returns the operator as a string. - # - # @return [String] the operator - def operator - loc.operator.source - end - - # Checks whether this is a logical operator. - # - # @return [Boolean] whether this is a logical operator - def logical_operator? - operator == LOGICAL_AND || operator == LOGICAL_OR - end - - # Checks whether this is a semantic operator. - # - # @return [Boolean] whether this is a semantic operator - def semantic_operator? - operator == SEMANTIC_AND || operator == SEMANTIC_OR - end - end - end -end diff --git a/lib/rubocop/ast/node/module_node.rb b/lib/rubocop/ast/node/module_node.rb deleted file mode 100644 index 1c0fbe228f19..000000000000 --- a/lib/rubocop/ast/node/module_node.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `module` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `module` nodes within RuboCop. - class ModuleNode < Node - # The identifer for this `module` node. - # - # @return [Node] the identifer of the module - def identifier - node_parts[0] - end - - # The body of this `module` node. - # - # @return [Node, nil] the body of the module - def body - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/or_node.rb b/lib/rubocop/ast/node/or_node.rb deleted file mode 100644 index 31c65ac481c1..000000000000 --- a/lib/rubocop/ast/node/or_node.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `or` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `or` nodes within RuboCop. - class OrNode < Node - include BinaryOperatorNode - include PredicateOperatorNode - - # Returns the alternate operator of the `or` as a string. - # Returns `or` for `||` and vice versa. - # - # @return [String] the alternate of the `or` operator - def alternate_operator - logical_operator? ? SEMANTIC_OR : LOGICAL_OR - end - - # Returns the inverse keyword of the `or` node as a string. - # Returns `and` for `or` and `&&` for `||`. - # - # @return [String] the inverse of the `or` operator - def inverse_operator - logical_operator? ? LOGICAL_AND : SEMANTIC_AND - end - end - end -end diff --git a/lib/rubocop/ast/node/pair_node.rb b/lib/rubocop/ast/node/pair_node.rb deleted file mode 100644 index da2e34bea272..000000000000 --- a/lib/rubocop/ast/node/pair_node.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `pair` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `pair` nodes within RuboCop. - class PairNode < Node - include HashElementNode - - HASH_ROCKET = '=>' - SPACED_HASH_ROCKET = ' => ' - COLON = ':' - SPACED_COLON = ': ' - - # Checks whether the `pair` uses a hash rocket delimiter. - # - # @return [Boolean] whether this `pair` uses a hash rocket delimiter - def hash_rocket? - loc.operator.is?(HASH_ROCKET) - end - - # Checks whether the `pair` uses a colon delimiter. - # - # @return [Boolean] whether this `pair` uses a colon delimiter - def colon? - loc.operator.is?(COLON) - end - - # Returns the delimiter of the `pair` as a string. Returns `=>` for a - # colon delimited `pair` and `:` for a hash rocket delimited `pair`. - # - # @param [Boolean] with_spacing whether to include spacing - # @return [String] the delimiter of the `pair` - def delimiter(with_spacing = false) - if with_spacing - hash_rocket? ? SPACED_HASH_ROCKET : SPACED_COLON - else - hash_rocket? ? HASH_ROCKET : COLON - end - end - - # Returns the inverse delimiter of the `pair` as a string. - # - # @param [Boolean] with_spacing whether to include spacing - # @return [String] the inverse delimiter of the `pair` - def inverse_delimiter(with_spacing = false) - if with_spacing - hash_rocket? ? SPACED_COLON : SPACED_HASH_ROCKET - else - hash_rocket? ? COLON : HASH_ROCKET - end - end - - # Checks whether the value starts on its own line. - # - # @return [Boolean] whether the value in the `pair` starts its own line - def value_on_new_line? - key.loc.line != value.loc.line - end - end - end -end diff --git a/lib/rubocop/ast/node/range_node.rb b/lib/rubocop/ast/node/range_node.rb deleted file mode 100644 index 7168afc6e0c6..000000000000 --- a/lib/rubocop/ast/node/range_node.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `irange` and `erange` nodes. This will be used in - # place of a plain node when the builder constructs the AST, making its - # methods available to all `irange` and `erange` nodes within RuboCop. - class RangeNode < Node - def begin - node_parts[0] - end - - def end - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/regexp_node.rb b/lib/rubocop/ast/node/regexp_node.rb deleted file mode 100644 index 3a84a113bb90..000000000000 --- a/lib/rubocop/ast/node/regexp_node.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `regexp` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `regexp` nodes within RuboCop. - class RegexpNode < Node - OPTIONS = { - x: Regexp::EXTENDED, - i: Regexp::IGNORECASE, - m: Regexp::MULTILINE, - n: Regexp::NOENCODING - }.freeze - - # @return [Regexp] a regexp of this node - def to_regexp - option = regopt.children.map { |opt| OPTIONS[opt] }.inject(:|) - Regexp.new(content, option) - end - - # @return [RuboCop::AST::Node] a regopt node - def regopt - children.last - end - - # @return [String] a string of regexp content - def content - children.select(&:str_type?).map(&:str_content).join - end - end - end -end diff --git a/lib/rubocop/ast/node/resbody_node.rb b/lib/rubocop/ast/node/resbody_node.rb deleted file mode 100644 index 62c7cebc8361..000000000000 --- a/lib/rubocop/ast/node/resbody_node.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `resbody` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `resbody` nodes within RuboCop. - class ResbodyNode < Node - # Returns the body of the `rescue` clause. - # - # @return [Node, nil] The body of the `resbody`. - def body - node_parts[2] - end - - # Returns the exception variable of the `rescue` clause. - # - # @return [Node, nil] The exception variable of the `resbody`. - def exception_variable - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/retry_node.rb b/lib/rubocop/ast/node/retry_node.rb deleted file mode 100644 index e1aa96046dc3..000000000000 --- a/lib/rubocop/ast/node/retry_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `retry` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `retry` nodes within RuboCop. - class RetryNode < Node - include MethodDispatchNode - include ParameterizedNode - - def arguments - [] - end - end - end -end diff --git a/lib/rubocop/ast/node/return_node.rb b/lib/rubocop/ast/node/return_node.rb deleted file mode 100644 index 922736e999ed..000000000000 --- a/lib/rubocop/ast/node/return_node.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `return` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `return` nodes within RuboCop. - class ReturnNode < Node - include MethodDispatchNode - include ParameterizedNode - - # Returns the arguments of the `return`. - # - # @return [Array] The arguments of the `return`. - def arguments - if node_parts.one? && node_parts.first.begin_type? - node_parts.first.children - else - node_parts - end - end - end - end -end diff --git a/lib/rubocop/ast/node/self_class_node.rb b/lib/rubocop/ast/node/self_class_node.rb deleted file mode 100644 index 1400ade1ddd8..000000000000 --- a/lib/rubocop/ast/node/self_class_node.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `sclass` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `sclass` nodes within RuboCop. - class SelfClassNode < Node - # The identifer for this `sclass` node. (Always `self`.) - # - # @return [Node] the identifer of the class - def identifier - node_parts[0] - end - - # The body of this `sclass` node. - # - # @return [Node, nil] the body of the class - def body - node_parts[1] - end - end - end -end diff --git a/lib/rubocop/ast/node/send_node.rb b/lib/rubocop/ast/node/send_node.rb deleted file mode 100644 index 1895ed6a5677..000000000000 --- a/lib/rubocop/ast/node/send_node.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `send` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `send` nodes within RuboCop. - class SendNode < Node - include ParameterizedNode - include MethodDispatchNode - - def_node_matcher :attribute_accessor?, <<~PATTERN - (send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...) - PATTERN - end - end -end diff --git a/lib/rubocop/ast/node/str_node.rb b/lib/rubocop/ast/node/str_node.rb deleted file mode 100644 index 37b95bd73993..000000000000 --- a/lib/rubocop/ast/node/str_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `str`, `dstr`, and `xstr` nodes. This will be used - # in place of a plain node when the builder constructs the AST, making - # its methods available to all `str` nodes within RuboCop. - class StrNode < Node - include BasicLiteralNode - - def heredoc? - loc.is_a?(Parser::Source::Map::Heredoc) - end - end - end -end diff --git a/lib/rubocop/ast/node/super_node.rb b/lib/rubocop/ast/node/super_node.rb deleted file mode 100644 index db7a01c40831..000000000000 --- a/lib/rubocop/ast/node/super_node.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `super`- and `zsuper` nodes. This will be used in - # place of a plain node when the builder constructs the AST, making its - # methods available to all `super`- and `zsuper` nodes within RuboCop. - class SuperNode < Node - include ParameterizedNode - include MethodDispatchNode - - # Custom destructuring method. This can be used to normalize - # destructuring for different variations of the node. - # - # @return [Array] the different parts of the `super` node - def node_parts - [nil, :super, *to_a] - end - end - end -end diff --git a/lib/rubocop/ast/node/symbol_node.rb b/lib/rubocop/ast/node/symbol_node.rb deleted file mode 100644 index 9f3c4d064197..000000000000 --- a/lib/rubocop/ast/node/symbol_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `sym` nodes. This will be used in place of a - # plain node when the builder constructs the AST, making its methods - # available to all `sym` nodes within RuboCop. - class SymbolNode < Node - include BasicLiteralNode - end - end -end diff --git a/lib/rubocop/ast/node/until_node.rb b/lib/rubocop/ast/node/until_node.rb deleted file mode 100644 index 54f1783c8362..000000000000 --- a/lib/rubocop/ast/node/until_node.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `until` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `until` nodes within RuboCop. - class UntilNode < Node - include ConditionalNode - include ModifierNode - - # Returns the keyword of the `until` statement as a string. - # - # @return [String] the keyword of the `until` statement - def keyword - 'until' - end - - # Returns the inverse keyword of the `until` node as a string. - # Returns `while` for `until` nodes and vice versa. - # - # @return [String] the inverse keyword of the `until` statement - def inverse_keyword - 'while' - end - - # Checks whether the `until` node has a `do` keyword. - # - # @return [Boolean] whether the `until` node has a `do` keyword - def do? - loc.begin&.is?('do') - end - end - end -end diff --git a/lib/rubocop/ast/node/when_node.rb b/lib/rubocop/ast/node/when_node.rb deleted file mode 100644 index f94ab44024db..000000000000 --- a/lib/rubocop/ast/node/when_node.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `when` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `when` nodes within RuboCop. - class WhenNode < Node - # Returns an array of all the conditions in the `when` branch. - # - # @return [Array] an array of condition nodes - def conditions - node_parts[0...-1] - end - - # Calls the given block for each condition node in the `when` branch. - # If no block is given, an `Enumerator` is returned. - # - # @return [self] if a block is given - # @return [Enumerator] if no block is given - def each_condition - return conditions.to_enum(__method__) unless block_given? - - conditions.each do |condition| - yield condition - end - - self - end - - # Returns the index of the `when` branch within the `case` statement. - # - # @return [Integer] the index of the `when` branch - def branch_index - parent.when_branches.index(self) - end - - # Checks whether the `when` node has a `then` keyword. - # - # @return [Boolean] whether the `when` node has a `then` keyword - def then? - loc.begin&.is?('then') - end - - # Returns the body of the `when` node. - # - # @return [Node, nil] the body of the `when` node - def body - node_parts[-1] - end - end - end -end diff --git a/lib/rubocop/ast/node/while_node.rb b/lib/rubocop/ast/node/while_node.rb deleted file mode 100644 index 61f46e95e633..000000000000 --- a/lib/rubocop/ast/node/while_node.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `while` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `while` nodes within RuboCop. - class WhileNode < Node - include ConditionalNode - include ModifierNode - - # Returns the keyword of the `while` statement as a string. - # - # @return [String] the keyword of the `while` statement - def keyword - 'while' - end - - # Returns the inverse keyword of the `while` node as a string. - # Returns `until` for `while` nodes and vice versa. - # - # @return [String] the inverse keyword of the `while` statement - def inverse_keyword - 'until' - end - - # Checks whether the `until` node has a `do` keyword. - # - # @return [Boolean] whether the `until` node has a `do` keyword - def do? - loc.begin&.is?('do') - end - end - end -end diff --git a/lib/rubocop/ast/node/yield_node.rb b/lib/rubocop/ast/node/yield_node.rb deleted file mode 100644 index 1ae1fd68a89b..000000000000 --- a/lib/rubocop/ast/node/yield_node.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # A node extension for `yield` nodes. This will be used in place of a plain - # node when the builder constructs the AST, making its methods available - # to all `yield` nodes within RuboCop. - class YieldNode < Node - include ParameterizedNode - include MethodDispatchNode - - # Custom destructuring method. This can be used to normalize - # destructuring for different variations of the node. - # - # @return [Array] the different parts of the `send` node - def node_parts - [nil, :yield, *to_a] - end - end - end -end diff --git a/lib/rubocop/ast/sexp.rb b/lib/rubocop/ast/sexp.rb deleted file mode 100644 index 36c878cf43a0..000000000000 --- a/lib/rubocop/ast/sexp.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module AST - # This module provides a shorthand method to create a {Node} like - # `Parser::AST::Sexp`. - # - # @see https://www.rubydoc.info/gems/ast/AST/Sexp - module Sexp - # Creates a {Node} with type `type` and children `children`. - def s(type, *children) - Node.new(type, children) - end - end - end -end diff --git a/lib/rubocop/ast/traversal.rb b/lib/rubocop/ast/traversal.rb deleted file mode 100644 index 91de20a3bfa4..000000000000 --- a/lib/rubocop/ast/traversal.rb +++ /dev/null @@ -1,202 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Metrics/ModuleLength -module RuboCop - module AST - # Provides methods for traversing an AST. - # Does not transform an AST; for that, use Parser::AST::Processor. - # Override methods to perform custom processing. Remember to call `super` - # if you want to recursively process descendant nodes. - module Traversal - def walk(node) - return if node.nil? - - send(:"on_#{node.type}", node) - nil - end - - NO_CHILD_NODES = %i[true false nil int float complex - rational str sym regopt self lvar - ivar cvar gvar nth_ref back_ref cbase - arg restarg blockarg shadowarg - kwrestarg zsuper lambda redo retry - forward_args forwarded_args - match_var match_nil_pattern empty_else].freeze - ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next - preexe postexe match_current_line defined? - arg_expr pin match_rest if_guard unless_guard - match_with_trailing_comma].freeze - MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair - mlhs masgn or_asgn and_asgn - undef alias args super yield or and - while_post until_post iflipflop eflipflop - match_with_lvasgn begin kwbegin return - in_match match_alt - match_as array_pattern array_pattern_with_tail - hash_pattern const_pattern].freeze - SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg - kwoptarg].freeze - - NO_CHILD_NODES.each do |type| - module_eval("def on_#{type}(node); end", __FILE__, __LINE__) - end - - ONE_CHILD_NODE.each do |type| - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - if (child = node.children[0]) - send(:"on_\#{child.type}", child) - end - end - RUBY - end - - MANY_CHILD_NODES.each do |type| - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - node.children.each { |child| send(:"on_\#{child.type}", child) } - nil - end - RUBY - end - - SECOND_CHILD_ONLY.each do |type| - # Guard clause is for nodes nested within mlhs - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - if (child = node.children[1]) - send(:"on_\#{child.type}", child) - end - end - RUBY - end - - def on_const(node) - return unless (child = node.children[0]) - - send(:"on_#{child.type}", child) - end - - def on_casgn(node) - children = node.children - if (child = children[0]) # always const??? - send(:"on_#{child.type}", child) - end - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - - def on_class(node) - children = node.children - child = children[0] # always const??? - send(:"on_#{child.type}", child) - if (child = children[1]) - send(:"on_#{child.type}", child) - end - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - - def on_def(node) - children = node.children - on_args(children[1]) - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - - def on_send(node) - node.children.each_with_index do |child, i| - next if i == 1 - - send(:"on_#{child.type}", child) if child - end - nil - end - - alias on_csend on_send - - def on_op_asgn(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) - child = children[2] - send(:"on_#{child.type}", child) - end - - def on_defs(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) - on_args(children[2]) - return unless (child = children[3]) - - send(:"on_#{child.type}", child) - end - - def on_if(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) - if (child = children[1]) - send(:"on_#{child.type}", child) - end - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - - def on_while(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) - return unless (child = children[1]) - - send(:"on_#{child.type}", child) - end - - alias on_until on_while - alias on_module on_while - alias on_sclass on_while - - def on_block(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) # can be send, zsuper... - on_args(children[1]) - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - - def on_case(node) - node.children.each do |child| - send(:"on_#{child.type}", child) if child - end - nil - end - - alias on_rescue on_case - alias on_resbody on_case - alias on_ensure on_case - alias on_for on_case - alias on_when on_case - alias on_case_match on_case - alias on_in_pattern on_case - alias on_irange on_case - alias on_erange on_case - - def on_numblock(node) - children = node.children - child = children[0] - send(:"on_#{child.type}", child) - return unless (child = children[2]) - - send(:"on_#{child.type}", child) - end - end - end -end -# rubocop:enable Metrics/ModuleLength diff --git a/lib/rubocop/error.rb b/lib/rubocop/error.rb deleted file mode 100644 index e989b23cc123..000000000000 --- a/lib/rubocop/error.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - # An Error exception is different from an Offense with severity 'error' - # When this exception is raised, it means that RuboCop is unable to perform - # a requested action (probably due to misconfiguration) and must stop - # immediately, rather than carrying on - class Error < StandardError; end - - class ValidationError < Error; end - - # A wrapper to display errored location of analyzed file. - class ErrorWithAnalyzedFileLocation < Error - def initialize(cause:, node:, cop:) - @cause = cause - @cop = cop - @location = node.is_a?(RuboCop::AST::Node) ? node.loc : node - end - - attr_reader :cause, :cop - - def line - @location&.line - end - - def column - @location&.column - end - - def message - "cause: #{cause.inspect}" - end - end -end diff --git a/lib/rubocop/node_pattern.rb b/lib/rubocop/node_pattern.rb deleted file mode 100644 index 6167992a1e48..000000000000 --- a/lib/rubocop/node_pattern.rb +++ /dev/null @@ -1,881 +0,0 @@ -# frozen_string_literal: true - -require 'delegate' -require 'erb' - -# rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity -module RuboCop - # This class performs a pattern-matching operation on an AST node. - # - # Initialize a new `NodePattern` with `NodePattern.new(pattern_string)`, then - # pass an AST node to `NodePattern#match`. Alternatively, use one of the class - # macros in `NodePattern::Macros` to define your own pattern-matching method. - # - # If the match fails, `nil` will be returned. If the match succeeds, the - # return value depends on whether a block was provided to `#match`, and - # whether the pattern contained any "captures" (values which are extracted - # from a matching AST.) - # - # - With block: #match yields the captures (if any) and passes the return - # value of the block through. - # - With no block, but one capture: the capture is returned. - # - With no block, but multiple captures: captures are returned as an array. - # - With no block and no captures: #match returns `true`. - # - # ## Pattern string format examples - # - # ':sym' # matches a literal symbol - # '1' # matches a literal integer - # 'nil' # matches a literal nil - # 'send' # matches (send ...) - # '(send)' # matches (send) - # '(send ...)' # matches (send ...) - # '(op-asgn)' # node types with hyphenated names also work - # '{send class}' # matches (send ...) or (class ...) - # '({send class})' # matches (send) or (class) - # '(send const)' # matches (send (const ...)) - # '(send _ :new)' # matches (send :new) - # '(send $_ :new)' # as above, but whatever matches the $_ is captured - # '(send $_ $_)' # you can use as many captures as you want - # '(send !const ...)' # ! negates the next part of the pattern - # '$(send const ...)' # arbitrary matching can be performed on a capture - # '(send _recv _msg)' # wildcards can be named (for readability) - # '(send ... :new)' # you can match against the last children - # '(array )' # you can match children in any order. This - # # would match `['x', :y]` as well as `[:y, 'x'] - # '(_ )' # will match if arguments have at least a `str` and - # # a `sym` node, but can have more. - # '(array <$str $_>)' # captures are in the order of the pattern, - # # irrespective of the actual order of the children - # '(array int*)' # will match an array of 0 or more integers - # '(array int ?)' # will match 0 or 1 integer. - # # Note: Space needed to distinguish from int? - # '(array int+)' # will match an array of 1 or more integers - # '(array (int $_)+)' # as above and will capture the numbers in an array - # '(send $...)' # capture all the children as an array - # '(send $... int)' # capture all children but the last as an array - # '(send _x :+ _x)' # unification is performed on named wildcards - # # (like Prolog variables...) - # # (#== is used to see if values unify) - # '(int odd?)' # words which end with a ? are predicate methods, - # # are are called on the target to see if it matches - # # any Ruby method which the matched object supports - # # can be used - # # if a truthy value is returned, the match succeeds - # '(int [!1 !2])' # [] contains multiple patterns, ALL of which must - # # match in that position - # # in other words, while {} is pattern union (logical - # # OR), [] is intersection (logical AND) - # '(send %1 _)' # % stands for a parameter which must be supplied to - # # #match at matching time - # # it will be compared to the corresponding value in - # # the AST using #== - # # a bare '%' is the same as '%1' - # # the number of extra parameters passed to #match - # # must equal the highest % value in the pattern - # # for consistency, %0 is the 'root node' which is - # # passed as the 1st argument to #match, where the - # # matching process starts - # '^^send' # each ^ ascends one level in the AST - # # so this matches against the grandparent node - # '`send' # descends any number of level in the AST - # # so this matches against any descendant node - # '#method' # we call this a 'funcall'; it calls a method in the - # # context where a pattern-matching method is defined - # # if that returns a truthy value, the match succeeds - # 'equal?(%1)' # predicates can be given 1 or more extra args - # '#method(%0, 1)' # funcalls can also be given 1 or more extra args - # - # You can nest arbitrarily deep: - # - # # matches node parsed from 'Const = Class.new' or 'Const = Module.new': - # '(casgn nil? :Const (send (const nil? {:Class :Module}) :new))' - # # matches a node parsed from an 'if', with a '==' comparison, - # # and no 'else' branch: - # '(if (send _ :== _) _ nil?)' - # - # Note that patterns like 'send' are implemented by calling `#send_type?` on - # the node being matched, 'const' by `#const_type?`, 'int' by `#int_type?`, - # and so on. Therefore, if you add methods which are named like - # `#prefix_type?` to the AST node class, then 'prefix' will become usable as - # a pattern. - # - # Also note that if you need a "guard clause" to protect against possible nils - # in a certain place in the AST, you can do it like this: `[!nil ]` - # - # The compiler code is very simple; don't be afraid to read through it! - class NodePattern - # @private - Invalid = Class.new(StandardError) - - # @private - # Builds Ruby code which implements a pattern - class Compiler - SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze - IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze - META = Regexp.union( - %w"( ) { } [ ] $< < > $... $ ! ^ ` ... + * ?" - ).freeze - NUMBER = /-?\d+(?:\.\d+)?/.freeze - STRING = /".+?"/.freeze - METHOD_NAME = /\#?#{IDENTIFIER}[\!\?]?\(?/.freeze - PARAM_NUMBER = /%\d*/.freeze - - SEPARATORS = /[\s]+/.freeze - TOKENS = Regexp.union(META, PARAM_NUMBER, NUMBER, - METHOD_NAME, SYMBOL, STRING) - - TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze - - NODE = /\A#{IDENTIFIER}\Z/.freeze - PREDICATE = /\A#{IDENTIFIER}\?\(?\Z/.freeze - WILDCARD = /\A_(?:#{IDENTIFIER})?\Z/.freeze - - FUNCALL = /\A\##{METHOD_NAME}/.freeze - LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze - PARAM = /\A#{PARAM_NUMBER}\Z/.freeze - CLOSING = /\A(?:\)|\}|\])\Z/.freeze - - REST = '...' - CAPTURED_REST = '$...' - - attr_reader :match_code, :tokens, :captures - - SEQ_HEAD_INDEX = -1 - - # Placeholders while compiling, see with_..._context methods - CUR_PLACEHOLDER = '@@@cur' - CUR_NODE = "#{CUR_PLACEHOLDER} node@@@" - CUR_ELEMENT = "#{CUR_PLACEHOLDER} element@@@" - SEQ_HEAD_GUARD = '@@@seq guard head@@@' - - line = __LINE__ - ANY_ORDER_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>') - <% if capture_rest %>(<%= capture_rest %> = []) && <% end -%> - <% if capture_all %>(<%= capture_all %> = <% end -%> - <%= CUR_NODE %>.children[<%= range %>]<% if capture_all %>)<% end -%> - .each_with_object({}) { |<%= child %>, <%= matched %>| - case - <% patterns.each_with_index do |pattern, i| -%> - when !<%= matched %>[<%= i %>] && <%= - with_context(pattern, child, use_temp_node: false) - %> then <%= matched %>[<%= i %>] = true - <% end -%> - <% if !rest %> else break({}) - <% elsif capture_rest %> else <%= capture_rest %> << <%= child %> - <% end -%> - end - }.size == <%= patterns.size -%> - RUBY - ANY_ORDER_TEMPLATE.location = [__FILE__, line + 1] - - line = __LINE__ - REPEATED_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>') - <% if captured %>(<%= accumulate %> = Array.new) && <% end %> - <%= CUR_NODE %>.children[<%= range %>].all? do |<%= child %>| - <%= with_context(expr, child, use_temp_node: false) %><% if captured %>&& - <%= accumulate %>.push(<%= captured %>)<% end %> - end <% if captured %>&& - (<%= captured %> = if <%= accumulate %>.empty? - <%= captured %>.map{[]} # Transpose hack won't work for empty case - else - <%= accumulate %>.transpose - end) <% end -%> - RUBY - REPEATED_TEMPLATE.location = [__FILE__, line + 1] - - def initialize(str, node_var = 'node0') - @string = str - @root = node_var - - @temps = 0 # avoid name clashes between temp variables - @captures = 0 # number of captures seen - @unify = {} # named wildcard -> temp variable - @params = 0 # highest % (param) number seen - run(node_var) - end - - def run(node_var) - @tokens = Compiler.tokens(@string) - - @match_code = with_context(compile_expr, node_var, use_temp_node: false) - @match_code.prepend("(captures = Array.new(#{@captures})) && ") \ - if @captures.positive? - - fail_due_to('unbalanced pattern') unless tokens.empty? - end - - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - def compile_expr(token = tokens.shift) - # read a single pattern-matching expression from the token stream, - # return Ruby code which performs the corresponding matching operation - # - # the 'pattern-matching' expression may be a composite which - # contains an arbitrary number of sub-expressions, but that composite - # must all have precedence higher or equal to that of `&&` - # - # Expressions may use placeholders like: - # CUR_NODE: Ruby code that evaluates to an AST node - # CUR_ELEMENT: Either the node or the type if in first element of - # a sequence (aka seq_head, e.g. "(seq_head first_node_arg ...") - case token - when '(' then compile_seq - when '{' then compile_union - when '[' then compile_intersect - when '!' then compile_negation - when '$' then compile_capture - when '^' then compile_ascend - when '`' then compile_descend - when WILDCARD then compile_wildcard(token[1..-1]) - when FUNCALL then compile_funcall(token) - when LITERAL then compile_literal(token) - when PREDICATE then compile_predicate(token) - when NODE then compile_nodetype(token) - when PARAM then compile_param(token[1..-1]) - when CLOSING then fail_due_to("#{token} in invalid position") - when nil then fail_due_to('pattern ended prematurely') - else fail_due_to("invalid token #{token.inspect}") - end - end - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize - - def tokens_until(stop, what) - return to_enum __method__, stop, what unless block_given? - - fail_due_to("empty #{what}") if tokens.first == stop && what - yield until tokens.first == stop - tokens.shift - end - - def compile_seq - terms = tokens_until(')', 'sequence').map { variadic_seq_term } - Sequence.new(self, *terms).compile - end - - def compile_guard_clause - "#{CUR_NODE}.is_a?(RuboCop::AST::Node)" - end - - def variadic_seq_term - token = tokens.shift - case token - when CAPTURED_REST then compile_captured_ellipsis - when REST then compile_ellipsis - when '$<' then compile_any_order(next_capture) - when '<' then compile_any_order - else compile_repeated_expr(token) - end - end - - def compile_repeated_expr(token) - before = @captures - expr = compile_expr(token) - min, max = parse_repetition_token - return [1, expr] if min.nil? - - if @captures != before - captured = "captures[#{before}...#{@captures}]" - accumulate = next_temp_variable(:accumulate) - end - arity = min..max || Float::INFINITY - - [arity, repeated_generator(expr, captured, accumulate)] - end - - def repeated_generator(expr, captured, accumulate) - with_temp_variables do |child| - lambda do |range| - fail_due_to 'repeated pattern at beginning of sequence' if range.begin == SEQ_HEAD_INDEX - REPEATED_TEMPLATE.result(binding) - end - end - end - - def parse_repetition_token - case tokens.first - when '*' then min = 0 - when '+' then min = 1 - when '?' then min = 0 - max = 1 - else return - end - tokens.shift - [min, max] - end - - # @private - # Builds Ruby code for a sequence - # (head *first_terms variadic_term *last_terms) - class Sequence < SimpleDelegator - def initialize(compiler, *arity_term_list) - @arities, @terms = arity_term_list.transpose - - super(compiler) - @variadic_index = @arities.find_index { |a| a.is_a?(Range) } - fail_due_to 'multiple variable patterns in same sequence' \ - if @variadic_index && !@arities.one? { |a| a.is_a?(Range) } - end - - def compile - [ - compile_guard_clause, - compile_child_nb_guard, - compile_seq_head, - *compile_first_terms, - compile_variadic_term, - *compile_last_terms - ].compact.join(" &&\n") << SEQ_HEAD_GUARD - end - - private - - def first_terms_arity - first_terms_range { |r| @arities[r].inject(0, :+) } || 0 - end - - def last_terms_arity - last_terms_range { |r| @arities[r].inject(0, :+) } || 0 - end - - def variadic_term_min_arity - @variadic_index ? @arities[@variadic_index].begin : 0 - end - - def first_terms_range - yield 1..(@variadic_index || @terms.size) - 1 if seq_head? - end - - def last_terms_range - yield @variadic_index + 1...@terms.size if @variadic_index - end - - def seq_head? - @variadic_index != 0 - end - - def compile_child_nb_guard - fixed = first_terms_arity + last_terms_arity - min = fixed + variadic_term_min_arity - op = if @variadic_index - max_variadic = @arities[@variadic_index].end - if max_variadic != Float::INFINITY - range = min..fixed + max_variadic - return "(#{range}).cover?(#{CUR_NODE}.children.size)" - end - '>=' - else - '==' - end - "#{CUR_NODE}.children.size #{op} #{min}" - end - - def term(index, range) - t = @terms[index] - if t.respond_to? :call - t.call(range) - else - with_child_context(t, range.begin) - end - end - - def compile_seq_head - return unless seq_head? - - fail_due_to 'sequences cannot start with <' \ - if @terms[0].respond_to? :call - - with_seq_head_context(@terms[0]) - end - - def compile_first_terms - first_terms_range { |range| compile_terms(range, 0) } - end - - def compile_last_terms - last_terms_range { |r| compile_terms(r, -last_terms_arity) } - end - - def compile_terms(index_range, start) - index_range.map do |i| - current = start - start += @arities.fetch(i) - term(i, current..start - 1) - end - end - - def compile_variadic_term - variadic_arity { |arity| term(@variadic_index, arity) } - end - - def variadic_arity - return unless @variadic_index - - first = @variadic_index.positive? ? first_terms_arity : SEQ_HEAD_INDEX - yield first..-last_terms_arity - 1 - end - end - private_constant :Sequence - - def compile_captured_ellipsis - capture = next_capture - block = lambda { |range| - # Consider ($...) like (_ $...): - range = 0..range.end if range.begin == SEQ_HEAD_INDEX - "(#{capture} = #{CUR_NODE}.children[#{range}])" - } - [0..Float::INFINITY, block] - end - - def compile_ellipsis - [0..Float::INFINITY, 'true'] - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def compile_any_order(capture_all = nil) - rest = capture_rest = nil - patterns = [] - with_temp_variables do |child, matched| - tokens_until('>', 'any child') do - fail_due_to 'ellipsis must be at the end of <>' if rest - token = tokens.shift - case token - when CAPTURED_REST then rest = capture_rest = next_capture - when REST then rest = true - else patterns << compile_expr(token) - end - end - [rest ? patterns.size..Float::INFINITY : patterns.size, - ->(range) { ANY_ORDER_TEMPLATE.result(binding) }] - end - end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/AbcSize - - def insure_same_captures(enum, what) - return to_enum __method__, enum, what unless block_given? - - captures_before = captures_after = nil - enum.each do - captures_before ||= @captures - @captures = captures_before - yield - captures_after ||= @captures - fail_due_to("each #{what} must have same # of captures") if captures_after != @captures - end - end - - def access_unify(name) - var = @unify[name] - - if var == :forbidden_unification - fail_due_to "Wildcard #{name} was first seen in a subset of a" \ - " union and can't be used outside that union" - end - var - end - - def forbid_unification(*names) - names.each do |name| - @unify[name] = :forbidden_unification - end - end - - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - def unify_in_union(enum) - # We need to reset @unify before each branch is processed. - # Moreover we need to keep track of newly encountered wildcards. - # Var `new_unify_intersection` will hold those that are encountered - # in all branches; these are not a problem. - # Var `partial_unify` will hold those encountered in only a subset - # of the branches; these can't be used outside of the union. - - return to_enum __method__, enum unless block_given? - - new_unify_intersection = nil - partial_unify = [] - unify_before = @unify.dup - - result = enum.each do |e| - @unify = unify_before.dup if new_unify_intersection - yield e - new_unify = @unify.keys - unify_before.keys - if new_unify_intersection.nil? - # First iteration - new_unify_intersection = new_unify - else - union = new_unify_intersection | new_unify - new_unify_intersection &= new_unify - partial_unify |= union - new_unify_intersection - end - end - - # At this point, all members of `new_unify_intersection` can be used - # for unification outside of the union, but partial_unify may not - - forbid_unification(*partial_unify) - - result - end - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize - - def compile_union - # we need to ensure that each branch of the {} contains the same - # number of captures (since only one branch of the {} can actually - # match, the same variables are used to hold the captures for each - # branch) - enum = tokens_until('}', 'union') - enum = unify_in_union(enum) - terms = insure_same_captures(enum, 'branch of {}') - .map { compile_expr } - - "(#{terms.join(' || ')})" - end - - def compile_intersect - tokens_until(']', 'intersection') - .map { compile_expr } - .join(' && ') - end - - def compile_capture - "(#{next_capture} = #{CUR_ELEMENT}; #{compile_expr})" - end - - def compile_negation - "!(#{compile_expr})" - end - - def compile_ascend - with_context("#{CUR_NODE} && #{compile_expr}", "#{CUR_NODE}.parent") - end - - def compile_descend - with_temp_variables do |descendant| - pattern = with_context(compile_expr, descendant, - use_temp_node: false) - [ - "RuboCop::NodePattern.descend(#{CUR_ELEMENT}).", - "any? do |#{descendant}|", - " #{pattern}", - 'end' - ].join("\n") - end - end - - def compile_wildcard(name) - if name.empty? - 'true' - elsif @unify.key?(name) - # we have already seen a wildcard with this name before - # so the value it matched the first time will already be stored - # in a temp. check if this value matches the one stored in the temp - "#{CUR_ELEMENT} == #{access_unify(name)}" - else - n = @unify[name] = "unify_#{name.gsub('-', '__')}" - # double assign to avoid "assigned but unused variable" - "(#{n} = #{CUR_ELEMENT}; " \ - "#{n} = #{n}; true)" - end - end - - def compile_literal(literal) - "#{CUR_ELEMENT} == #{literal}" - end - - def compile_predicate(predicate) - if predicate.end_with?('(') # is there an arglist? - args = compile_args(tokens) - predicate = predicate[0..-2] # drop the trailing ( - "#{CUR_ELEMENT}.#{predicate}(#{args.join(',')})" - else - "#{CUR_ELEMENT}.#{predicate}" - end - end - - def compile_funcall(method) - # call a method in the context which this pattern-matching - # code is used in. pass target value as an argument - method = method[1..-1] # drop the leading # - if method.end_with?('(') # is there an arglist? - args = compile_args(tokens) - method = method[0..-2] # drop the trailing ( - "#{method}(#{CUR_ELEMENT},#{args.join(',')})" - else - "#{method}(#{CUR_ELEMENT})" - end - end - - def compile_nodetype(type) - "#{compile_guard_clause} && #{CUR_NODE}.#{type.tr('-', '_')}_type?" - end - - def compile_param(number) - "#{CUR_ELEMENT} == #{get_param(number)}" - end - - def compile_args(tokens) - index = tokens.find_index { |token| token == ')' } - - tokens.slice!(0..index).each_with_object([]) do |token, args| - next if [')', ','].include?(token) - - args << compile_arg(token) - end - end - - def compile_arg(token) - case token - when WILDCARD then - name = token[1..-1] - access_unify(name) || fail_due_to('invalid in arglist: ' + token) - when LITERAL then token - when PARAM then get_param(token[1..-1]) - when CLOSING then fail_due_to("#{token} in invalid position") - when nil then fail_due_to('pattern ended prematurely') - else fail_due_to("invalid token in arglist: #{token.inspect}") - end - end - - def next_capture - index = @captures - @captures += 1 - "captures[#{index}]" - end - - def get_param(number) - number = number.empty? ? 1 : Integer(number) - @params = number if number > @params - number.zero? ? @root : "param#{number}" - end - - def emit_yield_capture(when_no_capture = '') - yield_val = if @captures.zero? - when_no_capture - elsif @captures == 1 - 'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710 - else - '*captures' - end - "yield(#{yield_val})" - end - - def emit_retval - if @captures.zero? - 'true' - elsif @captures == 1 - 'captures[0]' - else - 'captures' - end - end - - def emit_param_list - (1..@params).map { |n| "param#{n}" }.join(',') - end - - def emit_trailing_params - params = emit_param_list - params.empty? ? '' : ",#{params}" - end - - def emit_method_code - <<~RUBY - return unless #{@match_code} - block_given? ? #{emit_yield_capture} : (return #{emit_retval}) - RUBY - end - - def fail_due_to(message) - raise Invalid, "Couldn't compile due to #{message}. Pattern: #{@string}" - end - - def with_temp_node(cur_node) - with_temp_variables do |node| - yield "(#{node} = #{cur_node})", node - end - .gsub("\n", "\n ") # Nicer indent for debugging - end - - def with_temp_variables(&block) - names = block.parameters.map { |_, name| next_temp_variable(name) } - yield(*names) - end - - def next_temp_variable(name) - "#{name}#{next_temp_value}" - end - - def next_temp_value - @temps += 1 - end - - def auto_use_temp_node?(code) - code.scan(CUR_PLACEHOLDER).count > 1 - end - - # with_<...>_context methods are used whenever the context, - # i.e the current node or the current element can be determined. - - def with_child_context(code, child_index) - with_context(code, "#{CUR_NODE}.children[#{child_index}]") - end - - def with_context(code, cur_node, - use_temp_node: auto_use_temp_node?(code)) - if use_temp_node - with_temp_node(cur_node) do |init, temp_var| - substitute_cur_node(code, temp_var, first_cur_node: init) - end - else - substitute_cur_node(code, cur_node) - end - end - - def with_seq_head_context(code) - fail_due_to('parentheses at sequence head') if code.include?(SEQ_HEAD_GUARD) - - code.gsub CUR_ELEMENT, "#{CUR_NODE}.type" - end - - def substitute_cur_node(code, cur_node, first_cur_node: cur_node) - iter = 0 - code - .gsub(CUR_ELEMENT, CUR_NODE) - .gsub(CUR_NODE) do - iter += 1 - iter == 1 ? first_cur_node : cur_node - end - .gsub(SEQ_HEAD_GUARD, '') - end - - def self.tokens(pattern) - pattern.scan(TOKEN).reject { |token| token =~ /\A#{SEPARATORS}\Z/ } - end - end - private_constant :Compiler - - # Helpers for defining methods based on a pattern string - module Macros - # Define a method which applies a pattern to an AST node - # - # The new method will return nil if the node does not match - # If the node matches, and a block is provided, the new method will - # yield to the block (passing any captures as block arguments). - # If the node matches, and no block is provided, the new method will - # return the captures, or `true` if there were none. - def def_node_matcher(method_name, pattern_str) - compiler = Compiler.new(pattern_str, 'node') - src = "def #{method_name}(node = self" \ - "#{compiler.emit_trailing_params});" \ - "#{compiler.emit_method_code};end" - - location = caller_locations(1, 1).first - class_eval(src, location.path, location.lineno) - end - - # Define a method which recurses over the descendants of an AST node, - # checking whether any of them match the provided pattern - # - # If the method name ends with '?', the new method will return `true` - # as soon as it finds a descendant which matches. Otherwise, it will - # yield all descendants which match. - def def_node_search(method_name, pattern_str) - compiler = Compiler.new(pattern_str, 'node') - called_from = caller(1..1).first.split(':') - - if method_name.to_s.end_with?('?') - node_search_first(method_name, compiler, called_from) - else - node_search_all(method_name, compiler, called_from) - end - end - - def node_search_first(method_name, compiler, called_from) - node_search(method_name, compiler, 'return true', '', called_from) - end - - def node_search_all(method_name, compiler, called_from) - yield_code = compiler.emit_yield_capture('node') - prelude = "return enum_for(:#{method_name}, node0" \ - "#{compiler.emit_trailing_params}) unless block_given?" - - node_search(method_name, compiler, yield_code, prelude, called_from) - end - - def node_search(method_name, compiler, on_match, prelude, called_from) - src = node_search_body(method_name, compiler.emit_trailing_params, - prelude, compiler.match_code, on_match) - filename, lineno = *called_from - class_eval(src, filename, lineno.to_i) - end - - def node_search_body(method_name, trailing_params, prelude, match_code, - on_match) - <<~RUBY - def #{method_name}(node0#{trailing_params}) - #{prelude} - node0.each_node do |node| - if #{match_code} - #{on_match} - end - end - nil - end - RUBY - end - end - - attr_reader :pattern - - def initialize(str) - @pattern = str - compiler = Compiler.new(str) - src = "def match(node0#{compiler.emit_trailing_params});" \ - "#{compiler.emit_method_code}end" - instance_eval(src, __FILE__, __LINE__ + 1) - end - - def match(*args) - # If we're here, it's because the singleton method has not been defined, - # either because we've been dup'ed or serialized through YAML - initialize(pattern) - match(*args) - end - - def marshal_load(pattern) - initialize pattern - end - - def marshal_dump - pattern - end - - def ==(other) - other.is_a?(NodePattern) && - Compiler.tokens(other.pattern) == Compiler.tokens(pattern) - end - alias eql? == - - def to_s - "#<#{self.class} #{pattern}>" - end - - # Yields its argument and any descendants, depth-first. - # - def self.descend(element, &block) - return to_enum(__method__, element) unless block_given? - - yield element - - if element.is_a?(::RuboCop::AST::Node) - element.children.each do |child| - descend(child, &block) - end - end - - nil - end - end -end -# rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity diff --git a/lib/rubocop/processed_source.rb b/lib/rubocop/processed_source.rb deleted file mode 100644 index 10ca66a02190..000000000000 --- a/lib/rubocop/processed_source.rb +++ /dev/null @@ -1,211 +0,0 @@ -# frozen_string_literal: true - -require 'digest/sha1' - -module RuboCop - # ProcessedSource contains objects which are generated by Parser - # and other information such as disabled lines for cops. - # It also provides a convenient way to access source lines. - class ProcessedSource - STRING_SOURCE_NAME = '(string)' - - attr_reader :path, :buffer, :ast, :comments, :tokens, :diagnostics, - :parser_error, :raw_source, :ruby_version - - def self.from_file(path, ruby_version) - file = File.read(path, mode: 'rb') - new(file, ruby_version, path) - rescue Errno::ENOENT - raise RuboCop::Error, "No such file or directory: #{path}" - end - - def initialize(source, ruby_version, path = nil) - # Defaults source encoding to UTF-8, regardless of the encoding it has - # been read with, which could be non-utf8 depending on the default - # external encoding. - source.force_encoding(Encoding::UTF_8) unless source.encoding == Encoding::UTF_8 - - @raw_source = source - @path = path - @diagnostics = [] - @ruby_version = ruby_version - @parser_error = nil - - parse(source, ruby_version) - end - - def comment_config - @comment_config ||= CommentConfig.new(self) - end - - def disabled_line_ranges - comment_config.cop_disabled_line_ranges - end - - def ast_with_comments - return if !ast || !comments - - @ast_with_comments ||= Parser::Source::Comment.associate(ast, comments) - end - - # Returns the source lines, line break characters removed, excluding a - # possible __END__ and everything that comes after. - def lines - @lines ||= begin - all_lines = @buffer.source_lines - last_token_line = tokens.any? ? tokens.last.line : all_lines.size - result = [] - all_lines.each_with_index do |line, ix| - break if ix >= last_token_line && line == '__END__' - - result << line - end - result - end - end - - def [](*args) - lines[*args] - end - - def valid_syntax? - return false if @parser_error - - @diagnostics.none? { |d| %i[error fatal].include?(d.level) } - end - - # Raw source checksum for tracking infinite loops. - def checksum - Digest::SHA1.hexdigest(@raw_source) - end - - def each_comment - comments.each { |comment| yield comment } - end - - def find_comment - comments.find { |comment| yield comment } - end - - def each_token - tokens.each { |token| yield token } - end - - def find_token - tokens.find { |token| yield token } - end - - def file_path - buffer.name - end - - def blank? - ast.nil? - end - - def commented?(source_range) - comment_lines.include?(source_range.line) - end - - def comments_before_line(line) - comments.select { |c| c.location.line <= line } - end - - def start_with?(string) - return false if self[0].nil? - - self[0].start_with?(string) - end - - def preceding_line(token) - lines[token.line - 2] - end - - def current_line(token) - lines[token.line - 1] - end - - def following_line(token) - lines[token.line] - end - - def line_indentation(line_number) - lines[line_number - 1] - .match(/^(\s*)/)[1] - .to_s - .length - end - - private - - def comment_lines - @comment_lines ||= comments.map { |c| c.location.line } - end - - def parse(source, ruby_version) - buffer_name = @path || STRING_SOURCE_NAME - @buffer = Parser::Source::Buffer.new(buffer_name, 1) - - begin - @buffer.source = source - rescue EncodingError => e - @parser_error = e - return - end - - @ast, @comments, @tokens = tokenize(create_parser(ruby_version)) - end - - def tokenize(parser) - begin - ast, comments, tokens = parser.tokenize(@buffer) - - ast.respond_to?(:complete!) && ast.complete! - rescue Parser::SyntaxError - # All errors are in diagnostics. No need to handle exception. - end - - tokens = tokens.map { |t| Token.from_parser_token(t) } if tokens - - [ast, comments, tokens] - end - - # rubocop:disable Metrics/MethodLength - def parser_class(ruby_version) - case ruby_version - when 2.4 - require 'parser/ruby24' - Parser::Ruby24 - when 2.5 - require 'parser/ruby25' - Parser::Ruby25 - when 2.6 - require 'parser/ruby26' - Parser::Ruby26 - when 2.7 - require 'parser/ruby27' - Parser::Ruby27 - else - raise ArgumentError, - "RuboCop found unknown Ruby version: #{ruby_version.inspect}" - end - end - # rubocop:enable Metrics/MethodLength - - def create_parser(ruby_version) - builder = RuboCop::AST::Builder.new - - parser_class(ruby_version).new(builder).tap do |parser| - # On JRuby there's a risk that we hang in tokenize() if we - # don't set the all errors as fatal flag. The problem is caused by a bug - # in Racc that is discussed in issue #93 of the whitequark/parser - # project on GitHub. - parser.diagnostics.all_errors_are_fatal = (RUBY_ENGINE != 'ruby') - parser.diagnostics.ignore_warnings = false - parser.diagnostics.consumer = lambda do |diagnostic| - @diagnostics << diagnostic - end - end - end - end -end diff --git a/lib/rubocop/token.rb b/lib/rubocop/token.rb deleted file mode 100644 index 41ffab12020e..000000000000 --- a/lib/rubocop/token.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - # A basic wrapper around Parser's tokens. - class Token - attr_reader :pos, :type, :text - - def self.from_parser_token(parser_token) - type, details = parser_token - text, range = details - new(range, type, text) - end - - def initialize(pos, type, text) - @pos = pos - @type = type - # Parser token "text" may be an Integer - @text = text.to_s - end - - def line - @pos.line - end - - def column - @pos.column - end - - def begin_pos - @pos.begin_pos - end - - def end_pos - @pos.end_pos - end - - def to_s - "[[#{line}, #{column}], #{type}, #{text.inspect}]" - end - - # Checks if there is whitespace after token - def space_after? - pos.source_buffer.source.match(/\G\s/, end_pos) - end - - # Checks if there is whitespace before token - def space_before? - position = begin_pos.zero? ? begin_pos : begin_pos - 1 - pos.source_buffer.source.match(/\G\s/, position) - end - - ## Type Predicates - - def comment? - type == :tCOMMENT - end - - def semicolon? - type == :tSEMI - end - - def left_array_bracket? - type == :tLBRACK - end - - def left_ref_bracket? - type == :tLBRACK2 - end - - def left_bracket? - %i[tLBRACK tLBRACK2].include?(type) - end - - def right_bracket? - type == :tRBRACK - end - - def left_brace? - type == :tLBRACE - end - - def left_curly_brace? - type == :tLCURLY - end - - def right_curly_brace? - type == :tRCURLY - end - - def left_parens? - %i[tLPAREN tLPAREN2].include?(type) - end - - def right_parens? - type == :tRPAREN - end - - def comma? - type == :tCOMMA - end - - def rescue_modifier? - type == :kRESCUE_MOD - end - - def end? - type == :kEND - end - - def equal_sign? - %i[tEQL tOP_ASGN].include?(type) - end - end -end diff --git a/manual/node_pattern.md b/manual/node_pattern.md deleted file mode 100644 index ce5237025e3f..000000000000 --- a/manual/node_pattern.md +++ /dev/null @@ -1,401 +0,0 @@ -# Node Pattern - -Node pattern is a DSL to help find specific nodes in the Abstract Syntax Tree -using a simple string. - -It reminds the simplicity of regular expressions but used to find specific -nodes of Ruby code. - -## History - -The Node Pattern was introduced by [Alex Dowad](https://github.com/alexdowad) -and solves a problem that RuboCop contributors were facing for a long time: - -- Ability to declaratively define rules for node search, matching, and capture. - -The code below belongs to [Style/ArrayJoin](https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ArrayJoin) -cop and it's in favor of `Array#join` over `Array#*`. Then it tries to find -code like `%w(one two three) * ", "` and suggest to use `#join` instead. - -It can also be an array of integers, and the code doesn't check it. However, -it checks if the argument sent is a string. - -```ruby -def on_send(node) - receiver_node, method_name, *arg_nodes = *node - return unless receiver_node && receiver_node.array_type? && - method_name == :* && arg_nodes.first.str_type? - - add_offense(node, location: :selector) -end -``` - -This code was replaced in the cop defining a new matcher that does the same as the code above: - -```ruby -def_node_matcher :join_candidate?, '(send $array :* $str)' -``` - -And the `on_send` method is simplified to a method usage: - -```ruby -def on_send(node) - join_candidate?(node) { add_offense(node, location: :selector) } -end -``` - -## `(` and `)` Navigate deeply with Parens - -Parens delimits navigation inside node and its children. - -A simple integer like `1` is represented by `(int 1)` in the AST. - -```sh -$ ruby-parse -e '1' -(int 1) -``` - -- `int` will match exactly the node, looking only the node type. -- `(int 1)` will match precisely the node - -## `_` for any single node - -`_` will check if there's something present in the specific position, no matter the -value: - -- `(int _)` will match any number -- `(int _ _)` will not match because `int` types have just one child that - contains the value. - - -## `...` for several subsequent nodes - -Where `_` matches any single node, `...` matches any number of nodes. - -Say for example you want to find instances of calls to the method `sum` with any -number of arguments, be it `sum(1, 2)` or `sum(1, 2, 3, n)`. -First, let's check how it looks like in the AST: - -```sh -$ ruby-parse -e 'sum(1, 2)' -(send nil :sum - (int 1) - (int 2)) -``` - -Or with more children: - -```sh -$ ruby-parse -e 'sum(1, 2, 3, n)' -(send nil :sum - (int 1) - (int 2) - (int 3) - (send nil :n)) -``` - -The following expression would only match a call with 2 arguments: - -``` -(send nil? :sum _ _) -``` - -Instead, the following expression will any number of arguments (and thus both examples above): - -``` -(send nil? :sum ...) -``` - -Note that `...` can be appear anywhere in a sequence, for example `(send nil? :sum ... int)` -would no longer match the second example, as the last argument is not an integer. - -Nesting `...` is also supported; the only limitation is that `...` and -other "variable length" patterns can only appear once within a sequence. -For example `(send ... :sum ...)` is not supported. - -## `*`, `+`, `?` for repetitions - -Another way to handle a variable number of nodes is by using `*`, `+`, `?` to signify -a particular pattern should match any number of times, at least once and at most once respectively. - -Following on the previous example, to find sums of integer literals, we could use: - -``` -(send nil? :sum int*) -``` - -This would match our first example `sum(1, 2)` but not the other `sum(1, 2, 3, n)` - -This pattern would also match a call to `sum` without any argument, which might not be desirable. - -Using `+` would insure that only sums with at least one argument would be matched. - -``` -(send nil? :sum int+) -``` - -The `?` can limit the match only 0 or 1 nodes. -The following example would match any sum of three integer literals -optionally followed by a method call: - -``` -(send nil? :sum int int int send ?) -``` - -Note that we have to put a space between `send` and `?`, -since `send?` would be considered as a predicate (described below). - -## `<>` for match in any order - -You may not care about the exact order of the nodes you want to match. -In this case you can put the nodes without brackets: - -``` -(send nil? :sum <(int 2) int>) -``` - -This will match our first example (`sum(1, 2)`). - -It won't match our second example though, as it specifies that there must be -exactly two arguments to the method call `sum`. - -You can add `...` before the closing bracket to allow for additional parameters: - -``` -(send nil? :sum <(int 2) int ...>) -``` - -This will match both our examples, but not `sum(1.0, 2)` or `sum(2)`, -since the first node in the brackets is found, but not the second (`int`). - -## `{}` for "OR" - -Lets make it a bit more complex and introduce floats: - -```sh -$ ruby-parse -e '1' -(int 1) -$ ruby-parse -e '1.0' -(float 1.0) -``` - -- `({int float} _)` - int or float types, no matter the value - -## `$` for captures - -You can capture elements or nodes along with your search, prefixing the expression -with `$`. For example, in a tuple like `(int 1)`, you can capture the value using `(int $_)`. - -You can also capture multiple things like: - -``` -(${int float} $_) -``` - -The tuple can be entirely captured using the `$` before the open parens: - -``` -$({int float} _) -``` - -Or remove the parens and match directly from node head: - -``` -${int float} -``` - -All variable length patterns (`...`, `*`, `+`, `?`, `<>`) are captured as arrays. - -The following pattern will have two captures, both arrays: - -``` -(send nil? $int+ (send $...)) -``` - -## `^` for parent - -One may use the `^` character to check against a parent. - -For example, the following pattern would find any node with two children and -with a parent that is a hash: - -``` -(^hash _key $_value) -``` - -It is possible to use `^` somewhere else than the head of a sequnece; in that -case it is relative to that child (i.e. the current node). One case also use -multiple `^` to go up multiple levels. -For example, the previous example is basically the same as: - -``` -(pair ^^hash $_value) -``` - -## `` ` `` for descendants - -The `` ` `` character can be used to search a node and all its descendants. -For example if looking for a `return` statement anywhere within a method definition, -we can write: - -``` -(def _method_name _args `return) -``` - -This would match both of these methods `foo` and `bar`, even though -these `return` for `foo` and `bar` are not at the same level. - -``` -def foo # (def :foo - return 42 # (args) -end # (return - # (int 42))) - -def bar # (def :bar - return 42 if foo # (args) - nil # (begin -end # (if - # (send nil :foo) - # (return - # (int 42)) nil) - # (nil))) -``` - -## Predicate methods - -Words which end with a `?` are predicate methods, are called on the target -to see if it matches any Ruby method which the matched object supports can be -used. - -Example: - -- `int_type?` can be used herein replacement of `(int _)`. - -And refactoring the expression to allow both int or float types: - -- `{int_type? float_type?}` can be used herein replacement of `({int float} _)` - -You can also use it at the node level, asking for each child: - -- `(int odd?)` will match only with odd numbers, asking it to the current - number. - -## `[]` for "AND" - -Imagine you want to check if the number is `odd?` and also positive numbers: - -`(int [odd? positive?])` - is an int and the value should be odd and positive. - - -## `#` to call external methods - -Sometimes, we want to add extra logic. Let's imagine we're searching for -prime numbers, so we have a method to detect it: - -```ruby -def prime?(n) - if n <= 1 - false - elsif n == 2 - true - else - (2..n/2).none? { |i| n % i == 0 } - end -end -``` - -We can use the `#prime?` method directly in the expression: - -``` -(int #prime?) -``` - -## Using node matcher macros - -The RuboCop base includes two useful methods to use the node pattern with Ruby in a -simple way. You can use the macros to define methods. The basics are -[def_node_matcher](https://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern/Macros#def_node_matcher-instance_method) -and [def_node_search](https://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern/Macros#def_node_search-instance_method). - -When you define a pattern, it creates a method that accepts a node and tries to match. - -Lets create an example where we're trying to find the symbols `user` and -`current_user` in expressions like: `user: current_user` or -`current_user: User.first`, so the objective here is pick all keys: - -```sh -$ ruby-parse -e ':current_user' -(sym :current_user) -$ ruby-parse -e ':user' -(sym :user) -$ ruby-parse -e '{ user: current_user }' -(hash - (pair - (sym :user) - (send nil :current_user))) -``` - -Our minimal matcher can get it in the simple node `sym`: - -```ruby -def_node_matcher :user_symbol?, '(sym {:current_user :user})' -``` - -### Composing complex expressions with multiple matchers - -Now let's go deeply combining the previous expression and also match if the -current symbol is being called from an initialization method, like: - -```sh -$ ruby-parse -e 'Comment.new(user: current_user)' -(send - (const nil :Comment) :new - (hash - (pair - (sym :user) - (send nil :current_user)))) -``` - -And we can also reuse this and check if it's a constructor: - -```ruby -def_node_matcher :initializing_with_user?, <<~PATTERN - (send _ :new (hash (pair #user_symbol?))) -PATTERN -``` - -## `nil` or `nil?` - -Take a special attention to nil behavior: - -```sh -$ ruby-parse -e 'nil' -(nil) -``` -In this case, the `nil` implicit matches with expressions like: `nil`, `(nil)`, or `nil_type?`. - -But, nil is also used to represent a call from `nothing` from a simple method call: - -```sh -$ ruby-parse -e 'method' -(send nil :method) -``` - -Then, for such case you can use the predicate `nil?`. And the code can be -matched with an expression like: - -``` -(send nil? :method) -``` - -## More resources - -Curious about how it works? - -Check more details in the -[documentation](https://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern) -or browse the [source code](https://github.com/rubocop-hq/rubocop/blob/master/lib/rubocop/node_pattern.rb) -directly. It's easy to read and hack on. - -The [specs](https://github.com/rubocop-hq/rubocop/blob/master/spec/rubocop/node_pattern_spec.rb) -are also very useful to comprehend each feature. diff --git a/rubocop.gemspec b/rubocop.gemspec index 0bc2bf4e449b..4b973157ad68 100644 --- a/rubocop.gemspec +++ b/rubocop.gemspec @@ -37,6 +37,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('parser', '>= 2.7.0.1') s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0') s.add_runtime_dependency('rexml') + s.add_runtime_dependency('rubocop-ast', '~> 0.0') s.add_runtime_dependency('ruby-progressbar', '~> 1.7') s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 2.0') diff --git a/spec/rubocop/ast/alias_node_spec.rb b/spec/rubocop/ast/alias_node_spec.rb deleted file mode 100644 index dd2c03c444f9..000000000000 --- a/spec/rubocop/ast/alias_node_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::AliasNode do - let(:alias_node) { parse_source(source).ast } - - describe '.new' do - let(:source) do - 'alias foo bar' - end - - it { expect(alias_node.is_a?(described_class)).to be(true) } - end - - describe '#new_identifier' do - let(:source) do - 'alias foo bar' - end - - it { expect(alias_node.new_identifier.sym_type?).to be(true) } - it { expect(alias_node.new_identifier.children.first).to eq(:foo) } - end - - describe '#old_identifier' do - let(:source) do - 'alias foo bar' - end - - it { expect(alias_node.old_identifier.sym_type?).to be(true) } - it { expect(alias_node.old_identifier.children.first).to eq(:bar) } - end -end diff --git a/spec/rubocop/ast/and_node_spec.rb b/spec/rubocop/ast/and_node_spec.rb deleted file mode 100644 index b6eccee6fbac..000000000000 --- a/spec/rubocop/ast/and_node_spec.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::AndNode do - let(:and_node) { parse_source(source).ast } - - describe '.new' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.is_a?(described_class)).to be(true) } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.is_a?(described_class)).to be(true) } - end - end - - describe '#logical_operator?' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.logical_operator?).to be(true) } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.logical_operator?).to be(false) } - end - end - - describe '#semantic_operator?' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.semantic_operator?).to be(false) } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.semantic_operator?).to be(true) } - end - end - - describe '#operator' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.operator).to eq('&&') } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.operator).to eq('and') } - end - end - - describe '#alternate_operator' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.alternate_operator).to eq('and') } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.alternate_operator).to eq('&&') } - end - end - - describe '#inverse_operator' do - context 'with a logical and node' do - let(:source) do - ':foo && :bar' - end - - it { expect(and_node.inverse_operator).to eq('||') } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and :bar' - end - - it { expect(and_node.inverse_operator).to eq('or') } - end - end - - describe '#lhs' do - context 'with a logical and node' do - let(:source) do - ':foo && 42' - end - - it { expect(and_node.lhs.sym_type?).to be(true) } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and 42' - end - - it { expect(and_node.lhs.sym_type?).to be(true) } - end - end - - describe '#rhs' do - context 'with a logical and node' do - let(:source) do - ':foo && 42' - end - - it { expect(and_node.rhs.int_type?).to be(true) } - end - - context 'with a semantic and node' do - let(:source) do - ':foo and 42' - end - - it { expect(and_node.rhs.int_type?).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/args_node_spec.rb b/spec/rubocop/ast/args_node_spec.rb deleted file mode 100644 index f4b335a05ee7..000000000000 --- a/spec/rubocop/ast/args_node_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ArgsNode do - let(:args_node) { parse_source(source).ast.arguments } - - describe '.new' do - context 'with a method definition' do - let(:source) { 'def foo(x) end' } - - it { expect(args_node.is_a?(described_class)).to be(true) } - end - - context 'with a block' do - let(:source) { 'foo { |x| bar }' } - - it { expect(args_node.is_a?(described_class)).to be(true) } - end - - context 'with a lambda literal' do - let(:source) { '-> (x) { bar }' } - - it { expect(args_node.is_a?(described_class)).to be(true) } - end - end - - describe '#empty_and_without_delimiters?' do - subject { args_node.empty_and_without_delimiters? } - - context 'with empty arguments' do - context 'with a method definition' do - let(:source) { 'def x; end' } - - it { is_expected.to be(true) } - end - - context 'with a block' do - let(:source) { 'x { }' } - - it { is_expected.to be(true) } - end - - context 'with a lambda literal' do - let(:source) { '-> { }' } - - it { is_expected.to be(true) } - end - end - - context 'with delimiters' do - context 'with a method definition' do - let(:source) { 'def x(); end' } - - it { is_expected.to be(false) } - end - - context 'with a block' do - let(:source) { 'x { || }' } - - it { is_expected.to be(false) } - end - - context 'with a lambda literal' do - let(:source) { '-> () { }' } - - it { is_expected.to be(false) } - end - end - - context 'with arguments' do - context 'with a method definition' do - let(:source) { 'def x a; end' } - - it { is_expected.to be(false) } - end - - context 'with a lambda literal' do - let(:source) { '-> a { }' } - - it { is_expected.to be(false) } - end - end - end -end diff --git a/spec/rubocop/ast/array_node_spec.rb b/spec/rubocop/ast/array_node_spec.rb deleted file mode 100644 index 128462cbf821..000000000000 --- a/spec/rubocop/ast/array_node_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ArrayNode do - let(:array_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { '[]' } - - it { expect(array_node.is_a?(described_class)).to be(true) } - end - - describe '#values' do - context 'with an empty array' do - let(:source) { '[]' } - - it { expect(array_node.values.empty?).to be(true) } - end - - context 'with an array of literals' do - let(:source) { '[1, 2, 3]' } - - it { expect(array_node.values.size).to eq(3) } - it { expect(array_node.values).to all(be_literal) } - end - - context 'with an array of variables' do - let(:source) { '[foo, bar]' } - - it { expect(array_node.values.size).to eq(2) } - it { expect(array_node.values).to all(be_send_type) } - end - end - - describe '#each_value' do - let(:source) { '[1, 2, 3]' } - - context 'with block' do - it { expect(array_node.each_value {}.is_a?(described_class)).to be(true) } - it do - ret = [] - array_node.each_value { |i| ret << i.to_s } - - expect(ret).to eq(['(int 1)', '(int 2)', '(int 3)']) - end - end - - context 'without block' do - it { expect(array_node.each_value.is_a?(Enumerator)).to be(true) } - end - end - - describe '#square_brackets?' do - context 'with square brackets' do - let(:source) { '[1, 2, 3]' } - - it { expect(array_node.square_brackets?).to be_truthy } - end - - context 'with a percent literal' do - let(:source) { '%w(foo bar)' } - - it { expect(array_node.square_brackets?).to be_falsey } - end - end - - describe '#percent_literal?' do - context 'with square brackets' do - let(:source) { '[1, 2, 3]' } - - it { expect(array_node.percent_literal?).to be_falsey } - it { expect(array_node.percent_literal?(:string)).to be_falsey } - it { expect(array_node.percent_literal?(:symbol)).to be_falsey } - end - - context 'with a string percent literal' do - let(:source) { '%w(foo bar)' } - - it { expect(array_node.percent_literal?).to be_truthy } - it { expect(array_node.percent_literal?(:string)).to be_truthy } - it { expect(array_node.percent_literal?(:symbol)).to be_falsey } - end - - context 'with a symbol percent literal' do - let(:source) { '%i(foo bar)' } - - it { expect(array_node.percent_literal?).to be_truthy } - it { expect(array_node.percent_literal?(:string)).to be_falsey } - it { expect(array_node.percent_literal?(:symbol)).to be_truthy } - end - end - - describe '#bracketed?' do - context 'with square brackets' do - let(:source) { '[1, 2, 3]' } - - it { expect(array_node.bracketed?).to be(true) } - end - - context 'with a percent literal' do - let(:source) { '%w(foo bar)' } - - it { expect(array_node.bracketed?).to be(true) } - end - - context 'unbracketed' do - let(:array_node) do - parse_source('foo = 1, 2, 3').ast.to_a.last - end - - it { expect(array_node.bracketed?).to be(nil) } - end - end -end diff --git a/spec/rubocop/ast/block_node_spec.rb b/spec/rubocop/ast/block_node_spec.rb deleted file mode 100644 index 3acc38e59a75..000000000000 --- a/spec/rubocop/ast/block_node_spec.rb +++ /dev/null @@ -1,245 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::BlockNode do - let(:block_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { 'foo { |q| bar(q) }' } - - it { expect(block_node.is_a?(described_class)).to be(true) } - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.arguments.empty?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'foo { |q| bar(q) }' } - - it { expect(block_node.arguments.size).to eq(1) } - end - - context 'with a single splat argument' do - let(:source) { 'foo { |*q| bar(q) }' } - - it { expect(block_node.arguments.size).to eq(1) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo { |q, *z| bar(q, z) }' } - - it { expect(block_node.arguments.size).to eq(2) } - end - - context '>= Ruby 2.7', :ruby27 do - context 'using numbered parameters' do - let(:source) { 'foo { _1 }' } - - it { expect(block_node.arguments.empty?).to be(true) } - end - end - end - - describe '#arguments?' do - context 'with no arguments' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.arguments?).to be_falsey } - end - - context 'with a single argument' do - let(:source) { 'foo { |q| bar(q) }' } - - it { expect(block_node.arguments?).to be_truthy } - end - - context 'with a single splat argument' do - let(:source) { 'foo { |*q| bar(q) }' } - - it { expect(block_node.arguments?).to be_truthy } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo { |q, *z| bar(q, z) }' } - - it { expect(block_node.arguments?).to be_truthy } - end - - context '>= Ruby 2.7', :ruby27 do - context 'using numbered parameters' do - let(:source) { 'foo { _1 }' } - - it { expect(block_node.arguments?).to be false } - end - end - end - - describe '#braces?' do - context 'when enclosed in braces' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.braces?).to be_truthy } - end - - context 'when enclosed in do-end keywords' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.braces?).to be_falsey } - end - end - - describe '#keywords?' do - context 'when enclosed in braces' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.keywords?).to be_falsey } - end - - context 'when enclosed in do-end keywords' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.keywords?).to be_truthy } - end - end - - describe '#lambda?' do - context 'when block belongs to a stabby lambda' do - let(:source) { '-> { bar }' } - - it { expect(block_node.lambda?).to be_truthy } - end - - context 'when block belongs to a method lambda' do - let(:source) { 'lambda { bar }' } - - it { expect(block_node.lambda?).to be_truthy } - end - - context 'when block belongs to a non-lambda method' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.lambda?).to be_falsey } - end - end - - describe '#delimiters' do - context 'when enclosed in braces' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.delimiters).to eq(%w[{ }]) } - end - - context 'when enclosed in do-end keywords' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.delimiters).to eq(%w[do end]) } - end - end - - describe '#opening_delimiter' do - context 'when enclosed in braces' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.opening_delimiter).to eq('{') } - end - - context 'when enclosed in do-end keywords' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.opening_delimiter).to eq('do') } - end - end - - describe '#closing_delimiter' do - context 'when enclosed in braces' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.closing_delimiter).to eq('}') } - end - - context 'when enclosed in do-end keywords' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.closing_delimiter).to eq('end') } - end - end - - describe '#single_line?' do - context 'when block is on a single line' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.single_line?).to be_truthy } - end - - context 'when block is on several lines' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.single_line?).to be_falsey } - end - end - - describe '#multiline?' do - context 'when block is on a single line' do - let(:source) { 'foo { bar }' } - - it { expect(block_node.multiline?).to be_falsey } - end - - context 'when block is on several lines' do - let(:source) do - ['foo do', - ' bar', - 'end'].join("\n") - end - - it { expect(block_node.multiline?).to be_truthy } - end - end - - describe '#void_context?' do - context 'when block method is each' do - let(:source) { 'each { bar }' } - - it { expect(block_node.void_context?).to be_truthy } - end - - context 'when block method is tap' do - let(:source) { 'tap { bar }' } - - it { expect(block_node.void_context?).to be_truthy } - end - - context 'when block method is not each' do - let(:source) { 'map { bar }' } - - it { expect(block_node.void_context?).to be_falsey } - end - end -end diff --git a/spec/rubocop/ast/break_node_spec.rb b/spec/rubocop/ast/break_node_spec.rb deleted file mode 100644 index 0fbf64360d77..000000000000 --- a/spec/rubocop/ast/break_node_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::BreakNode do - let(:break_node) { parse_source(source).ast } - - describe '.new' do - context 'with a break node' do - let(:source) { 'break' } - - it { expect(break_node.is_a?(described_class)).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/case_match_node_spec.rb b/spec/rubocop/ast/case_match_node_spec.rb deleted file mode 100644 index 62b1a45b670d..000000000000 --- a/spec/rubocop/ast/case_match_node_spec.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::CaseMatchNode do - let(:case_match_node) { parse_source(source).ast } - - context 'when using Ruby 2.7 or newer', :ruby27 do - describe '.new' do - let(:source) do - <<~RUBY - case expr - in pattern - end - RUBY - end - - it { expect(case_match_node.is_a?(described_class)).to be(true) } - end - - describe '#keyword' do - let(:source) do - <<~RUBY - case expr - in pattern - end - RUBY - end - - it { expect(case_match_node.keyword).to eq('case') } - end - - describe '#in_pattern_branches' do - let(:source) do - <<~RUBY - case expr - in pattern - in pattern - in pattern - end - RUBY - end - - it { expect(case_match_node.in_pattern_branches.size).to eq(3) } - it { - expect(case_match_node.in_pattern_branches).to all(be_in_pattern_type) - } - end - - describe '#each_in_pattern' do - let(:source) do - <<~RUBY - case expr - in pattern - in pattern - in pattern - end - RUBY - end - - context 'when not passed a block' do - it { - expect(case_match_node.each_in_pattern.is_a?(Enumerator)).to be(true) - } - end - - context 'when passed a block' do - it 'yields all the conditions' do - expect { |b| case_match_node.each_in_pattern(&b) } - .to yield_successive_args(*case_match_node.in_pattern_branches) - end - end - end - - describe '#else?' do - context 'without an else statement' do - let(:source) do - <<~RUBY - case expr - in pattern - end - RUBY - end - - it { expect(case_match_node.else?).to be(false) } - end - - context 'with an else statement' do - let(:source) do - <<~RUBY - case expr - in pattern - else - end - RUBY - end - - it { expect(case_match_node.else?).to be(true) } - end - end - - describe '#else_branch' do - describe '#else?' do - context 'without an else statement' do - let(:source) do - <<~RUBY - case expr - in pattern - end - RUBY - end - - it { expect(case_match_node.else_branch.nil?).to be(true) } - end - - context 'with an else statement' do - let(:source) do - <<~RUBY - case expr - in pattern - else - :foo - end - RUBY - end - - it { expect(case_match_node.else_branch.sym_type?).to be(true) } - end - end - end - end -end diff --git a/spec/rubocop/ast/case_node_spec.rb b/spec/rubocop/ast/case_node_spec.rb deleted file mode 100644 index 20eb587addf2..000000000000 --- a/spec/rubocop/ast/case_node_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::CaseNode do - let(:case_node) { parse_source(source).ast } - - describe '.new' do - let(:source) do - ['case', - 'when :foo then bar', - 'end'].join("\n") - end - - it { expect(case_node.is_a?(described_class)).to be(true) } - end - - describe '#keyword' do - let(:source) do - ['case', - 'when :foo then bar', - 'end'].join("\n") - end - - it { expect(case_node.keyword).to eq('case') } - end - - describe '#when_branches' do - let(:source) do - ['case', - 'when :foo then 1', - 'when :bar then 2', - 'when :baz then 3', - 'end'].join("\n") - end - - it { expect(case_node.when_branches.size).to eq(3) } - it { expect(case_node.when_branches).to all(be_when_type) } - end - - describe '#each_when' do - let(:source) do - ['case', - 'when :foo then 1', - 'when :bar then 2', - 'when :baz then 3', - 'end'].join("\n") - end - - context 'when not passed a block' do - it { expect(case_node.each_when.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - it 'yields all the conditions' do - expect { |b| case_node.each_when(&b) } - .to yield_successive_args(*case_node.when_branches) - end - end - end - - describe '#else?' do - context 'without an else statement' do - let(:source) do - ['case', - 'when :foo then :bar', - 'end'].join("\n") - end - - it { expect(case_node.else?).to be_falsey } - end - - context 'with an else statement' do - let(:source) do - ['case', - 'when :foo then :bar', - 'else :baz', - 'end'].join("\n") - end - - it { expect(case_node.else?).to be_truthy } - end - end - - describe '#else_branch' do - describe '#else?' do - context 'without an else statement' do - let(:source) do - ['case', - 'when :foo then :bar', - 'end'].join("\n") - end - - it { expect(case_node.else_branch.nil?).to be(true) } - end - - context 'with an else statement' do - let(:source) do - ['case', - 'when :foo then :bar', - 'else :baz', - 'end'].join("\n") - end - - it { expect(case_node.else_branch.sym_type?).to be(true) } - end - end - end -end diff --git a/spec/rubocop/ast/class_node_spec.rb b/spec/rubocop/ast/class_node_spec.rb deleted file mode 100644 index 8eb5c19ef5ed..000000000000 --- a/spec/rubocop/ast/class_node_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ClassNode do - let(:class_node) { parse_source(source).ast } - - describe '.new' do - let(:source) do - 'class Foo; end' - end - - it { expect(class_node.is_a?(described_class)).to be(true) } - end - - describe '#identifier' do - let(:source) do - 'class Foo; end' - end - - it { expect(class_node.identifier.const_type?).to be(true) } - end - - describe '#parent_class' do - context 'when a parent class is specified' do - let(:source) do - 'class Foo < Bar; end' - end - - it { expect(class_node.parent_class.const_type?).to be(true) } - end - - context 'when no parent class is specified' do - let(:source) do - 'class Foo; end' - end - - it { expect(class_node.parent_class).to be(nil) } - end - end - - describe '#body' do - context 'with a single expression body' do - let(:source) do - 'class Foo; bar; end' - end - - it { expect(class_node.body.send_type?).to be(true) } - end - - context 'with a multi-expression body' do - let(:source) do - 'class Foo; bar; baz; end' - end - - it { expect(class_node.body.begin_type?).to be(true) } - end - - context 'with an empty body' do - let(:source) do - 'class Foo; end' - end - - it { expect(class_node.body).to be(nil) } - end - end -end diff --git a/spec/rubocop/ast/def_node_spec.rb b/spec/rubocop/ast/def_node_spec.rb deleted file mode 100644 index 3aba8082e138..000000000000 --- a/spec/rubocop/ast/def_node_spec.rb +++ /dev/null @@ -1,519 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::DefNode do - let(:def_node) { parse_source(source).ast } - - describe '.new' do - context 'with a def node' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.is_a?(described_class)).to be(true) } - end - - context 'with a defs node' do - let(:source) { 'def self.foo(bar); end' } - - it { expect(def_node.is_a?(described_class)).to be(true) } - end - end - - describe '#method_name' do - context 'with a plain method' do - let(:source) { 'def foo; end' } - - it { expect(def_node.method_name).to eq(:foo) } - end - - context 'with a setter method' do - let(:source) { 'def foo=(bar); end' } - - it { expect(def_node.method_name).to eq(:foo=) } - end - - context 'with an operator method' do - let(:source) { 'def ==(bar); end' } - - it { expect(def_node.method_name).to eq(:==) } - end - - context 'with a unary method' do - let(:source) { 'def -@; end' } - - it { expect(def_node.method_name).to eq(:-@) } - end - end - - describe '#method?' do - context 'when message matches' do - context 'when argument is a symbol' do - let(:source) { 'bar(:baz)' } - - it { expect(def_node.method?(:bar)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'bar(:baz)' } - - it { expect(def_node.method?('bar')).to be_truthy } - end - end - - context 'when message does not match' do - context 'when argument is a symbol' do - let(:source) { 'bar(:baz)' } - - it { expect(def_node.method?(:foo)).to be_falsey } - end - - context 'when argument is a string' do - let(:source) { 'bar(:baz)' } - - it { expect(def_node.method?('foo')).to be_falsey } - end - end - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.arguments.empty?).to be(true) } - end - - context 'with a single regular argument' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.arguments.size).to eq(1) } - end - - context 'with a single rest argument' do - let(:source) { 'def foo(*baz); end' } - - it { expect(def_node.arguments.size).to eq(1) } - end - - context 'with multiple regular arguments' do - let(:source) { 'def foo(bar, baz); end' } - - it { expect(def_node.arguments.size).to eq(2) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'def foo(bar, *baz); end' } - - it { expect(def_node.arguments.size).to eq(2) } - end - - context 'with argument forwarding', :ruby27 do - let(:source) { 'def foo(...); end' } - - it { expect(def_node.arguments.size).to eq(1) } - end - end - - describe '#first_argument' do - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.first_argument.nil?).to be(true) } - end - - context 'with a single regular argument' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.first_argument.arg_type?).to be(true) } - end - - context 'with a single rest argument' do - let(:source) { 'def foo(*bar); end' } - - it { expect(def_node.first_argument.restarg_type?).to be(true) } - end - - context 'with a single keyword argument' do - let(:source) { 'def foo(bar: :baz); end' } - - it { expect(def_node.first_argument.kwoptarg_type?).to be(true) } - end - - context 'with multiple regular arguments' do - let(:source) { 'def foo(bar, baz); end' } - - it { expect(def_node.first_argument.arg_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'def foo(bar, *baz); end' } - - it { expect(def_node.first_argument.arg_type?).to be(true) } - end - end - - describe '#last_argument' do - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.last_argument.nil?).to be(true) } - end - - context 'with a single regular argument' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.last_argument.arg_type?).to be(true) } - end - - context 'with a single rest argument' do - let(:source) { 'def foo(*bar); end' } - - it { expect(def_node.last_argument.restarg_type?).to be(true) } - end - - context 'with a single keyword argument' do - let(:source) { 'def foo(bar: :baz); end' } - - it { expect(def_node.last_argument.kwoptarg_type?).to be(true) } - end - - context 'with multiple regular arguments' do - let(:source) { 'def foo(bar, baz); end' } - - it { expect(def_node.last_argument.arg_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'def foo(bar, *baz); end' } - - it { expect(def_node.last_argument.restarg_type?).to be(true) } - end - end - - describe '#arguments?' do - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.arguments?).to be_falsey } - end - - context 'with a single regular argument' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.arguments?).to be_truthy } - end - - context 'with a single rest argument' do - let(:source) { 'def foo(*bar); end' } - - it { expect(def_node.arguments?).to be_truthy } - end - - context 'with a single keyword argument' do - let(:source) { 'def foo(bar: :baz); end' } - - it { expect(def_node.arguments?).to be_truthy } - end - - context 'with multiple regular arguments' do - let(:source) { 'def foo(bar, baz); end' } - - it { expect(def_node.arguments?).to be_truthy } - end - - context 'with multiple mixed arguments' do - let(:source) { 'def foo(bar, *baz); end' } - - it { expect(def_node.arguments?).to be_truthy } - end - end - - describe '#rest_argument?' do - context 'with a rest argument' do - let(:source) { 'def foo(*bar); end' } - - it { expect(def_node.rest_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.rest_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.rest_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'def foo(bar, *baz); end' } - - it { expect(def_node.rest_argument?).to be_truthy } - end - end - - describe '#operator_method?' do - context 'with a binary operator method' do - let(:source) { 'def ==(bar); end' } - - it { expect(def_node.operator_method?).to be_truthy } - end - - context 'with a unary operator method' do - let(:source) { 'def -@; end' } - - it { expect(def_node.operator_method?).to be_truthy } - end - - context 'with a setter method' do - let(:source) { 'def foo=(bar); end' } - - it { expect(def_node.operator_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.operator_method?).to be_falsey } - end - end - - describe '#comparison_method?' do - context 'with a comparison method' do - let(:source) { 'def <=(bar); end' } - - it { expect(def_node.comparison_method?).to be_truthy } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.comparison_method?).to be_falsey } - end - end - - describe '#assignment_method?' do - context 'with an assignment method' do - let(:source) { 'def foo=(bar); end' } - - it { expect(def_node.assignment_method?).to be_truthy } - end - - context 'with a bracket assignment method' do - let(:source) { 'def []=(bar); end' } - - it { expect(def_node.assignment_method?).to be_truthy } - end - - context 'with a comparison method' do - let(:source) { 'def ==(bar); end' } - - it { expect(def_node.assignment_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.assignment_method?).to be_falsey } - end - end - - describe '#void_context?' do - context 'with an initializer method' do - let(:source) { 'def initialize(bar); end' } - - it { expect(def_node.void_context?).to be_truthy } - end - - context 'with a regular assignment method' do - let(:source) { 'def foo=(bar); end' } - - it { expect(def_node.void_context?).to be_truthy } - end - - context 'with a bracket assignment method' do - let(:source) { 'def []=(bar); end' } - - it { expect(def_node.void_context?).to be_truthy } - end - - context 'with a comparison method' do - let(:source) { 'def ==(bar); end' } - - it { expect(def_node.void_context?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.void_context?).to be_falsey } - end - end - - context 'when using Ruby 2.7 or newer', :ruby27 do - describe '#argument_forwarding?' do - let(:source) { 'def foo(...); end' } - - it { expect(def_node.argument_forwarding?).to be_truthy } - end - end - - describe '#receiver' do - context 'with an instance method definition' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.receiver.nil?).to be(true) } - end - - context 'with a class method definition' do - let(:source) { 'def self.foo(bar); end' } - - it { expect(def_node.receiver.self_type?).to be(true) } - end - - context 'with a singleton method definition' do - let(:source) { 'def Foo.bar(baz); end' } - - it { expect(def_node.receiver.const_type?).to be(true) } - end - end - - describe '#self_receiver?' do - context 'with an instance method definition' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.self_receiver?).to be_falsey } - end - - context 'with a class method definition' do - let(:source) { 'def self.foo(bar); end' } - - it { expect(def_node.self_receiver?).to be_truthy } - end - - context 'with a singleton method definition' do - let(:source) { 'def Foo.bar(baz); end' } - - it { expect(def_node.self_receiver?).to be_falsey } - end - end - - describe '#const_receiver?' do - context 'with an instance method definition' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.const_receiver?).to be_falsey } - end - - context 'with a class method definition' do - let(:source) { 'def self.foo(bar); end' } - - it { expect(def_node.const_receiver?).to be_falsey } - end - - context 'with a singleton method definition' do - let(:source) { 'def Foo.bar(baz); end' } - - it { expect(def_node.const_receiver?).to be_truthy } - end - end - - describe '#predicate_method?' do - context 'with a predicate method' do - let(:source) { 'def foo?(bar); end' } - - it { expect(def_node.predicate_method?).to be_truthy } - end - - context 'with a bang method' do - let(:source) { 'def foo!(bar); end' } - - it { expect(def_node.predicate_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.predicate_method?).to be_falsey } - end - end - - describe '#bang_method?' do - context 'with a bang method' do - let(:source) { 'def foo!(bar); end' } - - it { expect(def_node.bang_method?).to be_truthy } - end - - context 'with a predicate method' do - let(:source) { 'def foo?(bar); end' } - - it { expect(def_node.bang_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.bang_method?).to be_falsey } - end - end - - describe '#camel_case_method?' do - context 'with a camel case method' do - let(:source) { 'def Foo(bar); end' } - - it { expect(def_node.camel_case_method?).to be_truthy } - end - - context 'with a regular method' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.camel_case_method?).to be_falsey } - end - end - - describe '#block_argument?' do - context 'with a block argument' do - let(:source) { 'def foo(&bar); end' } - - it { expect(def_node.block_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'def foo; end' } - - it { expect(def_node.block_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.block_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'def foo(bar, &baz); end' } - - it { expect(def_node.block_argument?).to be_truthy } - end - end - - describe '#body' do - context 'with no body' do - let(:source) { 'def foo(bar); end' } - - it { expect(def_node.body.nil?).to be(true) } - end - - context 'with a single expression body' do - let(:source) { 'def foo(bar); baz; end' } - - it { expect(def_node.body.send_type?).to be(true) } - end - - context 'with a multi-expression body' do - let(:source) { 'def foo(bar); baz; qux; end' } - - it { expect(def_node.body.begin_type?).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/defined_node_spec.rb b/spec/rubocop/ast/defined_node_spec.rb deleted file mode 100644 index 14e16fed143d..000000000000 --- a/spec/rubocop/ast/defined_node_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::DefinedNode do - let(:defined_node) { parse_source(source).ast } - - describe '.new' do - context 'with a defined? node' do - let(:source) { 'defined? :foo' } - - it { expect(defined_node.is_a?(described_class)).to be(true) } - end - end - - describe '#receiver' do - let(:source) { 'defined? :foo' } - - it { expect(defined_node.receiver).to eq(nil) } - end - - describe '#method_name' do - let(:source) { 'defined? :foo' } - - it { expect(defined_node.method_name).to eq(:defined?) } - end - - describe '#arguments' do - let(:source) { 'defined? :foo' } - - it { expect(defined_node.arguments.size).to eq(1) } - it { expect(defined_node.arguments).to all(be_sym_type) } - end -end diff --git a/spec/rubocop/ast/ensure_node_spec.rb b/spec/rubocop/ast/ensure_node_spec.rb deleted file mode 100644 index c50aed9c03cb..000000000000 --- a/spec/rubocop/ast/ensure_node_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::EnsureNode do - let(:ensure_node) { parse_source(source).ast.children.first } - - describe '.new' do - let(:source) { 'begin; beginbody; ensure; ensurebody; end' } - - it { expect(ensure_node.is_a?(described_class)).to be(true) } - end - - describe '#body' do - let(:source) { 'begin; beginbody; ensure; :ensurebody; end' } - - it { expect(ensure_node.body.sym_type?).to be(true) } - end -end diff --git a/spec/rubocop/ast/float_node_spec.rb b/spec/rubocop/ast/float_node_spec.rb deleted file mode 100644 index 12635938359b..000000000000 --- a/spec/rubocop/ast/float_node_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::FloatNode do - let(:int_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { '42.0' } - - it { expect(int_node.is_a?(described_class)).to be_truthy } - end - - describe '#sign?' do - context 'explicit positive float' do - let(:source) { '+42.0' } - - it { expect(int_node.sign?).to be_truthy } - end - - context 'explicit negative float' do - let(:source) { '-42.0' } - - it { expect(int_node.sign?).to be_truthy } - end - end -end diff --git a/spec/rubocop/ast/for_node_spec.rb b/spec/rubocop/ast/for_node_spec.rb deleted file mode 100644 index 94b84bb7538b..000000000000 --- a/spec/rubocop/ast/for_node_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ForNode do - let(:for_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { 'for foo in bar; baz; end' } - - it { expect(for_node.is_a?(described_class)).to be(true) } - end - - describe '#keyword' do - let(:source) { 'for foo in bar; baz; end' } - - it { expect(for_node.keyword).to eq('for') } - end - - describe '#do?' do - context 'with a do keyword' do - let(:source) { 'for foo in bar do baz; end' } - - it { expect(for_node.do?).to be_truthy } - end - - context 'without a do keyword' do - let(:source) { 'for foo in bar; baz; end' } - - it { expect(for_node.do?).to be_falsey } - end - end - - describe '#void_context?' do - context 'with a do keyword' do - let(:source) { 'for foo in bar do baz; end' } - - it { expect(for_node.void_context?).to be_truthy } - end - - context 'without a do keyword' do - let(:source) { 'for foo in bar; baz; end' } - - it { expect(for_node.void_context?).to be_truthy } - end - end - - describe '#variable' do - let(:source) { 'for foo in :bar; :baz; end' } - - it { expect(for_node.variable.lvasgn_type?).to be(true) } - end - - describe '#collection' do - let(:source) { 'for foo in :bar; baz; end' } - - it { expect(for_node.collection.sym_type?).to be(true) } - end - - describe '#body' do - let(:source) { 'for foo in bar; :baz; end' } - - it { expect(for_node.body.sym_type?).to be(true) } - end -end diff --git a/spec/rubocop/ast/forward_args_node_spec.rb b/spec/rubocop/ast/forward_args_node_spec.rb deleted file mode 100644 index ef27a906b1ac..000000000000 --- a/spec/rubocop/ast/forward_args_node_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ForwardArgsNode do - let(:args_node) { parse_source(source).ast.arguments } - - context 'when using Ruby 2.7 or newer', :ruby27 do - describe '.new' do - let(:source) { 'def foo(...); end' } - - it { expect(args_node.is_a?(described_class)).to be(true) } - end - - describe '#to_a' do - let(:source) { 'def foo(...); end' } - - it { expect(args_node.to_a).to contain_exactly(args_node) } - end - end -end diff --git a/spec/rubocop/ast/hash_node_spec.rb b/spec/rubocop/ast/hash_node_spec.rb deleted file mode 100644 index 29ee2620055c..000000000000 --- a/spec/rubocop/ast/hash_node_spec.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::HashNode do - let(:hash_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { '{}' } - - it { expect(hash_node.is_a?(described_class)).to be(true) } - end - - describe '#pairs' do - context 'with an empty hash' do - let(:source) { '{}' } - - it { expect(hash_node.pairs.empty?).to be(true) } - end - - context 'with a hash of literals' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - it { expect(hash_node.pairs.size).to eq(3) } - it { expect(hash_node.pairs).to all(be_pair_type) } - end - - context 'with a hash of variables' do - let(:source) { '{ a: foo, b: bar }' } - - it { expect(hash_node.pairs.size).to eq(2) } - it { expect(hash_node.pairs).to all(be_pair_type) } - end - end - - describe '#empty?' do - context 'with an empty hash' do - let(:source) { '{}' } - - it { expect(hash_node.empty?).to be(true) } - end - - context 'with a hash containing pairs' do - let(:source) { '{ a: 1, b: 2 }' } - - it { expect(hash_node.empty?).to be(false) } - end - - context 'with a hash containing a keyword splat' do - let(:source) { '{ **foo }' } - - it { expect(hash_node.empty?).to be(false) } - end - end - - describe '#keys' do - context 'with an empty hash' do - let(:source) { '{}' } - - it { expect(hash_node.keys.empty?).to be(true) } - end - - context 'with a hash with symbol keys' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - it { expect(hash_node.keys.size).to eq(3) } - it { expect(hash_node.keys).to all(be_sym_type) } - end - - context 'with a hash with string keys' do - let(:source) { "{ 'a' => foo,'b' => bar }" } - - it { expect(hash_node.keys.size).to eq(2) } - it { expect(hash_node.keys).to all(be_str_type) } - end - end - - describe '#each_key' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - context 'when not passed a block' do - it { expect(hash_node.each_key.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - let(:expected) do - [ - hash_node.pairs[0].key, - hash_node.pairs[1].key, - hash_node.pairs[2].key - ] - end - - it 'yields all the pairs' do - expect { |b| hash_node.each_key(&b) } - .to yield_successive_args(*expected) - end - end - end - - describe '#values' do - context 'with an empty hash' do - let(:source) { '{}' } - - it { expect(hash_node.values.empty?).to be(true) } - end - - context 'with a hash with literal values' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - it { expect(hash_node.values.size).to eq(3) } - it { expect(hash_node.values).to all(be_literal) } - end - - context 'with a hash with string keys' do - let(:source) { '{ a: foo, b: bar }' } - - it { expect(hash_node.values.size).to eq(2) } - it { expect(hash_node.values).to all(be_send_type) } - end - end - - describe '#each_value' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - context 'when not passed a block' do - it { expect(hash_node.each_value.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - let(:expected) do - [ - hash_node.pairs[0].value, - hash_node.pairs[1].value, - hash_node.pairs[2].value - ] - end - - it 'yields all the pairs' do - expect { |b| hash_node.each_value(&b) } - .to yield_successive_args(*expected) - end - end - end - - describe '#each_pair' do - let(:source) { '{ a: 1, b: 2, c: 3 }' } - - context 'when not passed a block' do - it { expect(hash_node.each_pair.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - let(:expected) do - [ - [*hash_node.pairs[0]], - [*hash_node.pairs[1]], - [*hash_node.pairs[2]] - ] - end - - it 'yields all the pairs' do - expect { |b| hash_node.each_pair(&b) } - .to yield_successive_args(*expected) - end - end - end - - describe '#pairs_on_same_line?' do - context 'with all pairs on the same line' do - let(:source) { '{ a: 1, b: 2 }' } - - it { expect(hash_node.pairs_on_same_line?).to be_truthy } - end - - context 'with no pairs on the same line' do - let(:source) do - ['{ a: 1,', - ' b: 2 }'].join("\n") - end - - it { expect(hash_node.pairs_on_same_line?).to be_falsey } - end - - context 'with some pairs on the same line' do - let(:source) do - ['{ a: 1,', - ' b: 2, c: 3 }'].join("\n") - end - - it { expect(hash_node.pairs_on_same_line?).to be_truthy } - end - end - - describe '#mixed_delimiters?' do - context 'when all pairs are using a colon delimiter' do - let(:source) { '{ a: 1, b: 2 }' } - - it { expect(hash_node.mixed_delimiters?).to be_falsey } - end - - context 'when all pairs are using a hash rocket delimiter' do - let(:source) { '{ :a => 1, :b => 2 }' } - - it { expect(hash_node.mixed_delimiters?).to be_falsey } - end - - context 'when pairs are using different delimiters' do - let(:source) { '{ :a => 1, b: 2 }' } - - it { expect(hash_node.mixed_delimiters?).to be_truthy } - end - end - - describe '#braces?' do - context 'with braces' do - let(:source) { '{ a: 1, b: 2 }' } - - it { expect(hash_node.braces?).to be_truthy } - end - - context 'as an argument with no braces' do - let(:source) { 'foo(:bar, a: 1, b: 2)' } - - let(:hash_argument) { hash_node.children.last } - - it { expect(hash_argument.braces?).to be_falsey } - end - - context 'as an argument with braces' do - let(:source) { 'foo(:bar, { a: 1, b: 2 })' } - - let(:hash_argument) { hash_node.children.last } - - it { expect(hash_argument.braces?).to be_truthy } - end - end -end diff --git a/spec/rubocop/ast/if_node_spec.rb b/spec/rubocop/ast/if_node_spec.rb deleted file mode 100644 index e78caa446532..000000000000 --- a/spec/rubocop/ast/if_node_spec.rb +++ /dev/null @@ -1,495 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::IfNode do - let(:if_node) { parse_source(source).ast } - - describe '.new' do - context 'with a regular if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.is_a?(described_class)).to be(true) } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.is_a?(described_class)).to be(true) } - end - - context 'with a modifier statement' do - let(:source) { ':foo if bar?' } - - it { expect(if_node.is_a?(described_class)).to be(true) } - end - end - - describe '#keyword' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.keyword).to eq('if') } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.keyword).to eq('unless') } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.keyword).to eq('') } - end - end - - describe '#inverse_keyword?' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.inverse_keyword).to eq('unless') } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.inverse_keyword).to eq('if') } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.inverse_keyword).to eq('') } - end - end - - describe '#if?' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.if?).to be_truthy } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.if?).to be_falsey } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.if?).to be_falsey } - end - end - - describe '#unless?' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.unless?).to be_falsey } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.unless?).to be_truthy } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.unless?).to be_falsey } - end - end - - describe '#ternary?' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.ternary?).to be_falsey } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.ternary?).to be_falsey } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.ternary?).to be_truthy } - end - end - - describe '#elsif?' do - context 'with an elsif statement' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'end'].join("\n") - end - - let(:elsif_node) { if_node.else_branch } - - it { expect(elsif_node.elsif?).to be_truthy } - end - - context 'with an if statement comtaining an elsif' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'end'].join("\n") - end - - it { expect(if_node.elsif?).to be_falsey } - end - - context 'without an elsif statement' do - let(:source) do - ['if foo?', - ' 1', - 'end'].join("\n") - end - - it { expect(if_node.elsif?).to be_falsey } - end - end - - describe '#else?' do - context 'with an elsif statement' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'end'].join("\n") - end - - # Note: This is a legacy behavior. - it { expect(if_node.else?).to be_truthy } - end - - context 'without an else statement' do - let(:source) do - ['if foo?', - ' 1', - 'else', - ' 2', - 'end'].join("\n") - end - - it { expect(if_node.elsif?).to be_falsey } - end - end - - describe '#modifier_form?' do - context 'with a non-modifier if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.modifier_form?).to be_falsey } - end - - context 'with a non-modifier unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.modifier_form?).to be_falsey } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :bar : :baz' } - - it { expect(if_node.modifier_form?).to be_falsey } - end - - context 'with a modifier if statement' do - let(:source) { ':bar if foo?' } - - it { expect(if_node.modifier_form?).to be_truthy } - end - - context 'with a modifier unless statement' do - let(:source) { ':bar unless foo?' } - - it { expect(if_node.modifier_form?).to be_truthy } - end - end - - describe '#nested_conditional?' do - context 'with no nested conditionals' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'else', - ' 3', - 'end'].join("\n") - end - - it { expect(if_node.nested_conditional?).to be_falsey } - end - - context 'with nested conditionals in if clause' do - let(:source) do - ['if foo?', - ' if baz; 4; end', - 'elsif bar?', - ' 2', - 'else', - ' 3', - 'end'].join("\n") - end - - it { expect(if_node.nested_conditional?).to be_truthy } - end - - context 'with nested conditionals in elsif clause' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' if baz; 4; end', - 'else', - ' 3', - 'end'].join("\n") - end - - it { expect(if_node.nested_conditional?).to be_truthy } - end - - context 'with nested conditionals in else clause' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'else', - ' if baz; 4; end', - 'end'].join("\n") - end - - it { expect(if_node.nested_conditional?).to be_truthy } - end - - context 'with nested ternary operators' do - context 'when nested in the truthy branch' do - let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } - - it { expect(if_node.nested_conditional?).to be_truthy } - end - - context 'when nested in the falsey branch' do - let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } - - it { expect(if_node.nested_conditional?).to be_truthy } - end - end - end - - describe '#elsif_conditional?' do - context 'with one elsif conditional' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'else', - ' 3', - 'end'].join("\n") - end - - it { expect(if_node.elsif_conditional?).to be_truthy } - end - - context 'with multiple elsif conditionals' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'elsif baz?', - ' 3', - 'else', - ' 4', - 'end'].join("\n") - end - - it { expect(if_node.elsif_conditional?).to be_truthy } - end - - context 'with nested conditionals in if clause' do - let(:source) do - ['if foo?', - ' if baz; 1; end', - 'else', - ' 2', - 'end'].join("\n") - end - - it { expect(if_node.elsif_conditional?).to be_falsey } - end - - context 'with nested conditionals in else clause' do - let(:source) do - ['if foo?', - ' 1', - 'else', - ' if baz; 2; end', - 'end'].join("\n") - end - - it { expect(if_node.elsif_conditional?).to be_falsey } - end - - context 'with nested ternary operators' do - context 'when nested in the truthy branch' do - let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } - - it { expect(if_node.elsif_conditional?).to be_falsey } - end - - context 'when nested in the falsey branch' do - let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } - - it { expect(if_node.elsif_conditional?).to be_falsey } - end - end - end - - describe '#if_branch' do - context 'with an if statement' do - let(:source) do - ['if foo?', - ' :foo', - 'else', - ' 42', - 'end'].join("\n") - end - - it { expect(if_node.if_branch.sym_type?).to be(true) } - end - - context 'with an unless statement' do - let(:source) do - ['unless foo?', - ' :foo', - 'else', - ' 42', - 'end'].join("\n") - end - - it { expect(if_node.if_branch.sym_type?).to be(true) } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :foo : 42' } - - it { expect(if_node.if_branch.sym_type?).to be(true) } - end - end - - describe '#else_branch' do - context 'with an if statement' do - let(:source) do - ['if foo?', - ' :foo', - 'else', - ' 42', - 'end'].join("\n") - end - - it { expect(if_node.else_branch.int_type?).to be(true) } - end - - context 'with an unless statement' do - let(:source) do - ['unless foo?', - ' :foo', - 'else', - ' 42', - 'end'].join("\n") - end - - it { expect(if_node.else_branch.int_type?).to be(true) } - end - - context 'with a ternary operator' do - let(:source) { 'foo? ? :foo : 42' } - - it { expect(if_node.else_branch.int_type?).to be(true) } - end - end - - describe '#branches' do - context 'with an if statement' do - let(:source) { 'if foo?; :bar; end' } - - it { expect(if_node.branches.size).to eq(1) } - it { expect(if_node.branches).to all(be_literal) } - end - - context 'with an unless statement' do - let(:source) { 'unless foo?; :bar; end' } - - it { expect(if_node.branches.size).to eq(1) } - it { expect(if_node.branches).to all(be_literal) } - end - - context 'with an else statement' do - let(:source) do - ['if foo?', - ' 1', - 'else', - ' 2', - 'end'].join("\n") - end - - it { expect(if_node.branches.size).to eq(2) } - it { expect(if_node.branches).to all(be_literal) } - end - - context 'with an elsif statement' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'else', - ' 3', - 'end'].join("\n") - end - - it { expect(if_node.branches.size).to eq(3) } - it { expect(if_node.branches).to all(be_literal) } - end - end - - describe '#each_branch' do - let(:source) do - ['if foo?', - ' 1', - 'elsif bar?', - ' 2', - 'else', - ' 3', - 'end'].join("\n") - end - - context 'when not passed a block' do - it { expect(if_node.each_branch.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - it 'yields all the branches' do - expect { |b| if_node.each_branch(&b) } - .to yield_successive_args(*if_node.branches) - end - end - end -end diff --git a/spec/rubocop/ast/int_node_spec.rb b/spec/rubocop/ast/int_node_spec.rb deleted file mode 100644 index 69971760513a..000000000000 --- a/spec/rubocop/ast/int_node_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::IntNode do - let(:int_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { '42' } - - it { expect(int_node.is_a?(described_class)).to be_truthy } - end - - describe '#sign?' do - context 'explicit positive int' do - let(:source) { '+42' } - - it { expect(int_node.sign?).to be_truthy } - end - - context 'explicit negative int' do - let(:source) { '-42' } - - it { expect(int_node.sign?).to be_truthy } - end - end -end diff --git a/spec/rubocop/ast/keyword_splat_node_spec.rb b/spec/rubocop/ast/keyword_splat_node_spec.rb deleted file mode 100644 index aad7070089b3..000000000000 --- a/spec/rubocop/ast/keyword_splat_node_spec.rb +++ /dev/null @@ -1,363 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::KeywordSplatNode do - let(:kwsplat_node) { parse_source(source).ast.children.last } - - describe '.new' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.is_a?(described_class)).to be(true) } - end - - describe '#hash_rocket?' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.hash_rocket?).to be_falsey } - end - - describe '#colon?' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.colon?).to be_falsey } - end - - describe '#key' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.key).to eq(kwsplat_node) } - end - - describe '#value' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.value).to eq(kwsplat_node) } - end - - describe '#operator' do - let(:source) { '{ a: 1, **foo }' } - - it { expect(kwsplat_node.operator).to eq('**') } - end - - describe '#same_line?' do - let(:first_pair) { parse_source(source).ast.children[0] } - let(:second_pair) { parse_source(source).ast.children[1] } - - context 'when both pairs are on the same line' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - end - - context 'when a multiline pair shares the same line' do - let(:source) do - ['{', - ' a: (', - ' ), **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - it { expect(second_pair.same_line?(first_pair)).to be_truthy } - end - - context 'when pairs are on separate lines' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_falsey } - end - end - - describe '#key_delta' do - let(:pair_node) { parse_source(source).ast.children[0] } - let(:kwsplat_node) { parse_source(source).ast.children[1] } - - context 'with alignment set to :left' do - context 'when using colon delimiters' do - context 'when keyword splat is aligned' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(2) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(-2) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } - end - end - - context 'when using hash rocket delimiters' do - context 'when keyword splat is aligned' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(2) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(-2) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a => 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } - end - end - end - - context 'with alignment set to :right' do - context 'when using colon delimiters' do - context 'when keyword splat is aligned' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - end - - context 'when using hash rocket delimiters' do - context 'when keyword splat is aligned' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a => 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } - end - end - end - end - - describe '#value_delta' do - let(:pair_node) { parse_source(source).ast.children[0] } - let(:kwsplat_node) { parse_source(source).ast.children[1] } - - context 'when using colon delimiters' do - context 'when keyword splat is left aligned' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - end - - context 'when using hash rocket delimiters' do - context 'when keyword splat is left aligned' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is ahead' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is behind' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - - context 'when keyword splat is on the same line' do - let(:source) do - ['{', - ' a => 1, **foo', - '}'].join("\n") - end - - it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } - end - end - end -end diff --git a/spec/rubocop/ast/module_node_spec.rb b/spec/rubocop/ast/module_node_spec.rb deleted file mode 100644 index 0b03860e7328..000000000000 --- a/spec/rubocop/ast/module_node_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ModuleNode do - let(:module_node) { parse_source(source).ast } - - describe '.new' do - let(:source) do - 'module Foo; end' - end - - it { expect(module_node.is_a?(described_class)).to be(true) } - end - - describe '#identifier' do - let(:source) do - 'module Foo; end' - end - - it { expect(module_node.identifier.const_type?).to be(true) } - end - - describe '#body' do - context 'with a single expression body' do - let(:source) do - 'module Foo; bar; end' - end - - it { expect(module_node.body.send_type?).to be(true) } - end - - context 'with a multi-expression body' do - let(:source) do - 'module Foo; bar; baz; end' - end - - it { expect(module_node.body.begin_type?).to be(true) } - end - - context 'with an empty body' do - let(:source) do - 'module Foo; end' - end - - it { expect(module_node.body).to be(nil) } - end - end -end diff --git a/spec/rubocop/ast/node_spec.rb b/spec/rubocop/ast/node_spec.rb deleted file mode 100644 index 6c4e759eaea1..000000000000 --- a/spec/rubocop/ast/node_spec.rb +++ /dev/null @@ -1,350 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::Node do - let(:node) { RuboCop::ProcessedSource.new(src, ruby_version).ast } - - describe '#value_used?' do - before :all do - module RuboCop - module AST - class Node - # Let's make our predicate matchers read better - def used? - value_used? - end - end - end - end - end - - context 'at the top level' do - let(:src) { 'expr' } - - it 'is false' do - expect(node.used?).to be(false) - end - end - - context 'within a method call node' do - let(:src) { 'obj.method(arg1, arg2, arg3)' } - - it 'is always true' do - expect(node.child_nodes).to all(be_used) - end - end - - context 'at the end of a block' do - let(:src) { 'obj.method { blah; expr }' } - - it 'is always true' do - expect(node.children.last.used?).to be(true) - end - end - - context 'within a class definition node' do - let(:src) { 'class C < Super; def a; 1; end; self; end' } - - it 'is always true' do - expect(node.child_nodes).to all(be_used) - end - end - - context 'within a module definition node' do - let(:src) { 'module M; def method; end; 1; end' } - - it 'is always true' do - expect(node.child_nodes).to all(be_used) - end - end - - context 'within a singleton class node' do - let(:src) { 'class << obj; 1; 2; end' } - - it 'is always true' do - expect(node.child_nodes).to all(be_used) - end - end - - context 'within an if...else..end node' do - context 'nested in a method call' do - let(:src) { 'obj.method(if a then b else c end)' } - - it 'is always true' do - if_node = node.children[2] - expect(if_node.child_nodes).to all(be_used) - end - end - - context 'at the top level' do - let(:src) { 'if a then b else c end' } - - it 'is true only for the condition' do - expect(node.condition.used?).to be(true) - expect(node.if_branch.used?).to be(false) - expect(node.else_branch.used?).to be(false) - end - end - end - - context 'within an array literal' do - context 'assigned to an ivar' do - let(:src) { '@var = [a, b, c]' } - - it 'is always true' do - ary_node = node.children[1] - expect(ary_node.child_nodes).to all(be_used) - end - end - - context 'at the top level' do - let(:src) { '[a, b, c]' } - - it 'is always false' do - expect(node.child_nodes.map(&:used?)).to all(be false) - end - end - end - - context 'within a while node' do - let(:src) { 'while a; b; end' } - - it 'is true only for the condition' do - expect(node.condition.used?).to be(true) - expect(node.body.used?).to be(false) - end - end - end - - describe '#recursive_basic_literal?' do - shared_examples 'literal' do |source| - let(:src) { source } - - it "returns true for `#{source}`" do - expect(node.recursive_literal?).to be(true) - end - end - - it_behaves_like 'literal', '!true' - it_behaves_like 'literal', '"#{2}"' - it_behaves_like 'literal', '(1)' - it_behaves_like 'literal', '(false && true)' - it_behaves_like 'literal', '(false <=> true)' - it_behaves_like 'literal', '(false or true)' - it_behaves_like 'literal', '[1, 2, 3]' - it_behaves_like 'literal', '{ :a => 1, :b => 2 }' - it_behaves_like 'literal', '{ a: 1, b: 2 }' - it_behaves_like 'literal', '/./' - it_behaves_like 'literal', '%r{abx}ixo' - it_behaves_like 'literal', '1.0' - it_behaves_like 'literal', '1' - it_behaves_like 'literal', 'false' - it_behaves_like 'literal', 'nil' - it_behaves_like 'literal', "'str'" - - shared_examples 'non literal' do |source| - let(:src) { source } - - it "returns false for `#{source}`" do - expect(node.recursive_literal?).to be(false) - end - end - - it_behaves_like 'non literal', '(x && false)' - it_behaves_like 'non literal', '(x == false)' - it_behaves_like 'non literal', '(x or false)' - it_behaves_like 'non literal', '[some_method_call]' - it_behaves_like 'non literal', '{ :sym => some_method_call }' - it_behaves_like 'non literal', '{ some_method_call => :sym }' - it_behaves_like 'non literal', '/.#{some_method_call}/' - it_behaves_like 'non literal', '%r{abx#{foo}}ixo' - it_behaves_like 'non literal', 'some_method_call' - it_behaves_like 'non literal', 'some_method_call(x, y)' - end - - describe '#pure?' do - context 'for a method call' do - let(:src) { 'obj.method(arg1, arg2)' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for an integer literal' do - let(:src) { '100' } - - it 'returns true' do - expect(node.pure?).to be(true) - end - end - - context 'for an array literal' do - context 'with only literal children' do - let(:src) { '[1..100, false, :symbol, "string", 1.0]' } - - it 'returns true' do - expect(node.pure?).to be(true) - end - end - - context 'which contains a method call' do - let(:src) { '[1, 2, 3, 3 + 4]' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - end - - context 'for a hash literal' do - context 'with only literal children' do - let(:src) { '{range: 1..100, bool: false, str: "string", float: 1.0}' } - - it 'returns true' do - expect(node.pure?).to be(true) - end - end - - context 'which contains a method call' do - let(:src) { '{a: 1, b: 2, c: Kernel.exit}' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - end - - context 'for a nested if' do - context 'where the innermost descendants are local vars and literals' do - let(:src) do - ['lvar1, lvar2 = method1, method2', - 'if $global', - ' if @initialized', - ' [lvar1, lvar2, true]', - ' else', - ' :symbol', - ' end', - 'else', - ' lvar1', - 'end'].join("\n") - end - - it 'returns true' do - if_node = node.children[1] - expect(if_node.type).to be :if - expect(if_node.pure?).to be(true) - end - end - - context 'where one branch contains a method call' do - let(:src) { 'if $DEBUG then puts "hello" else nil end' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'where one branch contains an assignment statement' do - let(:src) { 'if @a then 1 else $global = "str" end' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - end - - context 'for an ivar assignment' do - let(:src) { '@var = 1' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for a gvar assignment' do - let(:src) { '$var = 1' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for a cvar assignment' do - let(:src) { '@@var = 1' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for an lvar assignment' do - let(:src) { 'var = 1' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for a class definition' do - let(:src) { 'class C < Super; def method; end end' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for a module definition' do - let(:src) { 'module M; def method; end end' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'for a regexp' do - let(:opts) { '' } - let(:body) { '' } - let(:src) { "/#{body}/#{opts}" } - - context 'with interpolated segments' do - let(:body) { '#{x}' } - - it 'returns false' do - expect(node.pure?).to be(false) - end - end - - context 'with no interpolation' do - let(:src) { URI::DEFAULT_PARSER.make_regexp.inspect } - - it 'returns true' do - expect(node.pure?).to be(true) - end - end - - context 'with options' do - let(:opts) { 'oix' } - - it 'returns true' do - expect(node.pure?).to be(true) - end - end - end - end - - describe '#sibling_index' do - let(:src) do - [ - 'def foo; end', - 'def bar; end', - 'def baz; end' - ].join("\n") - end - - it 'returns its sibling index' do - (0..2).each do |n| - expect(node.children[n].sibling_index).to eq(n) - end - end - end -end diff --git a/spec/rubocop/ast/or_node_spec.rb b/spec/rubocop/ast/or_node_spec.rb deleted file mode 100644 index b171babae9b0..000000000000 --- a/spec/rubocop/ast/or_node_spec.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::OrNode do - let(:or_node) { parse_source(source).ast } - - describe '.new' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.is_a?(described_class)).to be(true) } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.is_a?(described_class)).to be(true) } - end - end - - describe '#logical_operator?' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.logical_operator?).to be(true) } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.logical_operator?).to be(false) } - end - end - - describe '#semantic_operator?' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.semantic_operator?).to be(false) } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.semantic_operator?).to be(true) } - end - end - - describe '#operator' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.operator).to eq('||') } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.operator).to eq('or') } - end - end - - describe '#alternate_operator' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.alternate_operator).to eq('or') } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.alternate_operator).to eq('||') } - end - end - - describe '#inverse_operator' do - context 'with a logical or node' do - let(:source) do - ':foo || :bar' - end - - it { expect(or_node.inverse_operator).to eq('&&') } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or :bar' - end - - it { expect(or_node.inverse_operator).to eq('and') } - end - end - - describe '#lhs' do - context 'with a logical or node' do - let(:source) do - ':foo || 42' - end - - it { expect(or_node.lhs.sym_type?).to be(true) } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or 42' - end - - it { expect(or_node.lhs.sym_type?).to be(true) } - end - end - - describe '#rhs' do - context 'with a logical or node' do - let(:source) do - ':foo || 42' - end - - it { expect(or_node.rhs.int_type?).to be(true) } - end - - context 'with a semantic or node' do - let(:source) do - ':foo or 42' - end - - it { expect(or_node.rhs.int_type?).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/pair_node_spec.rb b/spec/rubocop/ast/pair_node_spec.rb deleted file mode 100644 index 4defb07c2485..000000000000 --- a/spec/rubocop/ast/pair_node_spec.rb +++ /dev/null @@ -1,728 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::PairNode do - let(:pair_node) { parse_source(source).ast.children.first } - - describe '.new' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.is_a?(described_class)).to be(true) } - end - - describe '#hash_rocket?' do - context 'when using a hash rocket delimiter' do - let(:source) { '{ a => 1 }' } - - it { expect(pair_node.hash_rocket?).to be_truthy } - end - - context 'when using a colon delimiter' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.hash_rocket?).to be_falsey } - end - end - - describe '#colon?' do - context 'when using a hash rocket delimiter' do - let(:source) { '{ a => 1 }' } - - it { expect(pair_node.colon?).to be_falsey } - end - - context 'when using a colon delimiter' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.colon?).to be_truthy } - end - end - - describe '#colon?' do - context 'when using a hash rocket delimiter' do - let(:source) { '{ a => 1 }' } - - it { expect(pair_node.colon?).to be_falsey } - end - - context 'when using a colon delimiter' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.colon?).to be_truthy } - end - end - - describe '#delimiter' do - context 'when using a hash rocket delimiter' do - let(:source) { '{ a => 1 }' } - - it { expect(pair_node.delimiter).to eq('=>') } - it { expect(pair_node.delimiter(true)).to eq(' => ') } - end - - context 'when using a colon delimiter' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.delimiter).to eq(':') } - it { expect(pair_node.delimiter(true)).to eq(': ') } - end - end - - describe '#inverse_delimiter' do - context 'when using a hash rocket delimiter' do - let(:source) { '{ a => 1 }' } - - it { expect(pair_node.inverse_delimiter).to eq(':') } - it { expect(pair_node.inverse_delimiter(true)).to eq(': ') } - end - - context 'when using a colon delimiter' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.inverse_delimiter).to eq('=>') } - it { expect(pair_node.inverse_delimiter(true)).to eq(' => ') } - end - end - - describe '#key' do - context 'when using a symbol key' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.key.sym_type?).to be(true) } - end - - context 'when using a string key' do - let(:source) { "{ 'a' => 1 }" } - - it { expect(pair_node.key.str_type?).to be(true) } - end - end - - describe '#value' do - let(:source) { '{ a: 1 }' } - - it { expect(pair_node.value.int_type?).to be(true) } - end - - describe '#value_on_new_line?' do - let(:pair) { parse_source(source).ast.children[0] } - - context 'when value starts on a new line' do - let(:source) do - ['{', - ' a:', - ' 1', - '}'].join("\n") - end - - it { expect(pair.value_on_new_line?).to be_truthy } - end - - context 'when value spans multiple lines' do - let(:source) do - ['{', - ' a: (', - ' )', - '}'].join("\n") - end - - it { expect(pair.value_on_new_line?).to be_falsey } - end - - context 'when pair is on a single line' do - let(:source) { "{ 'a' => 1 }" } - - it { expect(pair.value_on_new_line?).to be_falsey } - end - end - - describe '#same_line?' do - let(:first_pair) { parse_source(source).ast.children[0] } - let(:second_pair) { parse_source(source).ast.children[1] } - - context 'when both pairs are on the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1, b: 2', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - end - - context 'when both pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - end - end - - context 'when a multiline pair shares the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: (', - ' ), b: 2', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - it { expect(second_pair.same_line?(first_pair)).to be_truthy } - end - - context 'when last pair is a keyword splat' do - let(:source) do - ['{', - ' a: (', - ' ), **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_truthy } - it { expect(second_pair.same_line?(first_pair)).to be_truthy } - end - end - - context 'when pairs are on separate lines' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_falsey } - end - - context 'when last pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.same_line?(second_pair)).to be_falsey } - end - end - end - - describe '#key_delta' do - let(:first_pair) { parse_source(source).ast.children[0] } - let(:second_pair) { parse_source(source).ast.children[1] } - - context 'with alignment set to :left' do - context 'when using colon delimiters' do - context 'when keys are aligned' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - end - - context 'when receiver key is behind' do - context 'when both pairs are reail pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(-2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(-2) } - end - end - - context 'when receiver key is ahead' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(2) } - end - end - - context 'when both keys are on the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1, b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - end - end - - context 'when using hash rocket delimiters' do - context 'when keys are aligned' do - context 'when both keys are explicit keys' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - - context 'when second key is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - end - - context 'when receiver key is behind' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(-2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(-2) } - end - end - - context 'when receiver key is ahead' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(2) } - end - end - - context 'when both keys are on the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1, b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair)).to eq(0) } - end - end - end - end - - context 'with alignment set to :right' do - context 'when using colon delimiters' do - context 'when keys are aligned' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when receiver key is behind' do - context 'when both pairs are reail pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(-2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when receiver key is ahead' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when both keys are on the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1, b: 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - end - - context 'when using hash rocket delimiters' do - context 'when keys are aligned' do - context 'when both keys are explicit keys' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - - context 'when second key is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when receiver key is behind' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(-2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when receiver key is ahead' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(2) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - - context 'when both keys are on the same line' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1, b => 2', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1, **foo', - '}'].join("\n") - end - - it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } - end - end - end - end - end - - describe '#value_delta' do - let(:first_pair) { parse_source(source).ast.children[0] } - let(:second_pair) { parse_source(source).ast.children[1] } - - context 'when using colon delimiters' do - context 'when values are aligned' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a: 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - end - - context 'when receiver value is behind' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(-2) } - end - - context 'when receiver value is ahead' do - let(:source) do - ['{', - ' a: 1,', - ' b: 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(2) } - end - - context 'when both pairs are on the same line' do - let(:source) do - ['{', - ' a: 1, b: 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - end - - context 'when using hash rocket delimiters' do - context 'when values are aligned' do - context 'when both pairs are explicit pairs' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - - context 'when second pair is a keyword splat' do - let(:source) do - ['{', - ' a => 1,', - ' **foo', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - end - - context 'when receiver value is behind' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(-2) } - end - - context 'when receiver value is ahead' do - let(:source) do - ['{', - ' a => 1,', - ' b => 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(2) } - end - - context 'when both pairs are on the same line' do - let(:source) do - ['{', - ' a => 1, b => 2', - '}'].join("\n") - end - - it { expect(first_pair.value_delta(second_pair)).to eq(0) } - end - end - end -end diff --git a/spec/rubocop/ast/range_node_spec.rb b/spec/rubocop/ast/range_node_spec.rb deleted file mode 100644 index e478cc26d775..000000000000 --- a/spec/rubocop/ast/range_node_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::RangeNode do - let(:range_node) { parse_source(source).ast } - - describe '.new' do - context 'with an inclusive range' do - let(:source) do - '1..2' - end - - it { expect(range_node.is_a?(described_class)).to be(true) } - it { expect(range_node.range_type?).to be(true) } - end - - context 'with an exclusive range' do - let(:source) do - '1...2' - end - - it { expect(range_node.is_a?(described_class)).to be(true) } - it { expect(range_node.range_type?).to be(true) } - end - - context 'with an infinite range' do - let(:ruby_version) { 2.6 } - let(:source) do - '1..' - end - - it { expect(range_node.is_a?(described_class)).to be(true) } - it { expect(range_node.range_type?).to be(true) } - end - - context 'with a beignless range' do - let(:ruby_version) { 2.7 } - let(:source) do - '..42' - end - - it { expect(range_node.is_a?(described_class)).to be(true) } - it { expect(range_node.range_type?).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/regexp_node_spec.rb b/spec/rubocop/ast/regexp_node_spec.rb deleted file mode 100644 index a6dbe5f6c4df..000000000000 --- a/spec/rubocop/ast/regexp_node_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::RegexpNode do - let(:regexp_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { '/re/' } - - it { expect(regexp_node.is_a?(described_class)).to be(true) } - end - - describe '#to_regexp' do - # rubocop:disable Security/Eval - context 'with an empty regexp' do - let(:source) { '//' } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - - context 'with a regexp without option' do - let(:source) { '/.+/' } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - - context 'with a multi-line regexp without option' do - let(:source) { "/\n.+\n/" } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - - context 'with an empty regexp with option' do - let(:source) { '//ix' } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - - context 'with a regexp with option' do - let(:source) { '/.+/imx' } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - - context 'with a multi-line regexp with option' do - let(:source) { "/\n.+\n/ix" } - - it { expect(regexp_node.to_regexp).to eq(eval(source)) } - end - # rubocop:enable Security/Eval - end - - describe '#regopt' do - let(:regopt) { regexp_node.regopt } - - context 'with an empty regexp' do - let(:source) { '//' } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children.empty?).to be(true) } - end - - context 'with a regexp without option' do - let(:source) { '/.+/' } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children.empty?).to be(true) } - end - - context 'with a multi-line regexp without option' do - let(:source) { "/\n.+\n/" } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children.empty?).to be(true) } - end - - context 'with an empty regexp with option' do - let(:source) { '//ix' } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children).to eq(%i[i x]) } - end - - context 'with a regexp with option' do - let(:source) { '/.+/imx' } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children).to eq(%i[i m x]) } - end - - context 'with a multi-line regexp with option' do - let(:source) { "/\n.+\n/imx" } - - it { expect(regopt.regopt_type?).to be(true) } - it { expect(regopt.children).to eq(%i[i m x]) } - end - end - - describe '#content' do - let(:content) { regexp_node.content } - - context 'with an empty regexp' do - let(:source) { '//' } - - it { expect(content).to eq('') } - end - - context 'with a regexp without option' do - let(:source) { '/.+/' } - - it { expect(content).to eq('.+') } - end - - context 'with a multi-line regexp without option' do - let(:source) { "/\n.+\n/" } - - it { expect(content).to eq("\n.+\n") } - end - - context 'with an empty regexp with option' do - let(:source) { '//ix' } - - it { expect(content).to eq('') } - end - - context 'with a regexp with option' do - let(:source) { '/.+/imx' } - - it { expect(content).to eq('.+') } - end - - context 'with a multi-line regexp with option' do - let(:source) { "/\n.+\n/imx" } - - it { expect(content).to eq("\n.+\n") } - end - end -end diff --git a/spec/rubocop/ast/resbody_node_spec.rb b/spec/rubocop/ast/resbody_node_spec.rb deleted file mode 100644 index a37bc697f0d1..000000000000 --- a/spec/rubocop/ast/resbody_node_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ResbodyNode do - let(:resbody_node) do - begin_node = parse_source(source).ast - rescue_node, = *begin_node - rescue_node.children[1] - end - - describe '.new' do - let(:source) { 'begin; beginbody; rescue; rescuebody; end' } - - it { expect(resbody_node.is_a?(described_class)).to be(true) } - end - - describe '#exception_variable' do - context 'for an explicit rescue' do - let(:source) { 'begin; beginbody; rescue Error => ex; rescuebody; end' } - - it { expect(resbody_node.exception_variable.source).to eq('ex') } - end - - context 'for an implicit rescue' do - let(:source) { 'begin; beginbody; rescue => ex; rescuebody; end' } - - it { expect(resbody_node.exception_variable.source).to eq('ex') } - end - - context 'when an exception variable is not given' do - let(:source) { 'begin; beginbody; rescue; rescuebody; end' } - - it { expect(resbody_node.exception_variable).to be(nil) } - end - end - - describe '#body' do - let(:source) { 'begin; beginbody; rescue Error => ex; :rescuebody; end' } - - it { expect(resbody_node.body.sym_type?).to be(true) } - end -end diff --git a/spec/rubocop/ast/retry_node_spec.rb b/spec/rubocop/ast/retry_node_spec.rb deleted file mode 100644 index dbf651abdb0b..000000000000 --- a/spec/rubocop/ast/retry_node_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::RetryNode do - let(:retry_node) { parse_source(source).ast } - - describe '.new' do - context 'with a retry node' do - let(:source) { 'retry' } - - it { expect(retry_node.is_a?(described_class)).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/return_node_spec.rb b/spec/rubocop/ast/return_node_spec.rb deleted file mode 100644 index 8b0e178ace50..000000000000 --- a/spec/rubocop/ast/return_node_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::ReturnNode do - let(:return_node) { parse_source(source).ast } - - describe '.new' do - context 'without arguments' do - let(:source) { 'return' } - - it { expect(return_node.is_a?(described_class)).to be(true) } - end - - context 'with arguments' do - let(:source) { 'return "foo"' } - - it { expect(return_node.is_a?(described_class)).to be(true) } - end - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'return' } - - it { expect(return_node.arguments.empty?).to be(true) } - end - - context 'with no arguments and braces' do - let(:source) { 'return()' } - - it { expect(return_node.arguments.empty?).to be(true) } - end - - context 'with a single argument' do - let(:source) { 'return "foo"' } - - it { expect(return_node.arguments.size).to eq(1) } - end - - context 'with a single argument and braces' do - let(:source) { 'return("foo")' } - - it { expect(return_node.arguments.size).to eq(1) } - end - - context 'with a single splat argument' do - let(:source) { 'return *baz' } - - it { expect(return_node.arguments.size).to eq(1) } - end - - context 'with multiple literal arguments' do - let(:source) { 'return "foo", "bar"' } - - it { expect(return_node.arguments.size).to eq(2) } - end - end -end diff --git a/spec/rubocop/ast/self_class_node_spec.rb b/spec/rubocop/ast/self_class_node_spec.rb deleted file mode 100644 index 977d6dc8623b..000000000000 --- a/spec/rubocop/ast/self_class_node_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::SelfClassNode do - let(:self_class_node) { parse_source(source).ast } - - describe '.new' do - let(:source) do - 'class << self; end' - end - - it { expect(self_class_node.is_a?(described_class)).to be(true) } - end - - describe '#identifier' do - let(:source) do - 'class << self; end' - end - - it { expect(self_class_node.identifier.self_type?).to be(true) } - end - - describe '#body' do - context 'with a single expression body' do - let(:source) do - 'class << self; bar; end' - end - - it { expect(self_class_node.body.send_type?).to be(true) } - end - - context 'with a multi-expression body' do - let(:source) do - 'class << self; bar; baz; end' - end - - it { expect(self_class_node.body.begin_type?).to be(true) } - end - - context 'with an empty body' do - let(:source) do - 'class << self; end' - end - - it { expect(self_class_node.body).to be(nil) } - end - end -end diff --git a/spec/rubocop/ast/send_node_spec.rb b/spec/rubocop/ast/send_node_spec.rb deleted file mode 100644 index a7c2cfdc7bc0..000000000000 --- a/spec/rubocop/ast/send_node_spec.rb +++ /dev/null @@ -1,1257 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::SendNode do - let(:send_node) { parse_source(source).ast } - - describe '.new' do - context 'with a regular method send' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.is_a?(described_class)).to be(true) } - end - - context 'with a safe navigation method send' do - let(:source) { 'foo&.bar(:baz)' } - - it { expect(send_node.is_a?(described_class)).to be(true) } - end - end - - describe '#receiver' do - context 'with no receiver' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.receiver.nil?).to be(true) } - end - - context 'with a literal receiver' do - let(:source) { "'foo'.bar(:baz)" } - - it { expect(send_node.receiver.str_type?).to be(true) } - end - - context 'with a variable receiver' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.receiver.send_type?).to be(true) } - end - end - - describe '#method_name' do - context 'with a plain method' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.method_name).to eq(:bar) } - end - - context 'with a setter method' do - let(:source) { 'foo.bar = :baz' } - - it { expect(send_node.method_name).to eq(:bar=) } - end - - context 'with an operator method' do - let(:source) { 'foo == bar' } - - it { expect(send_node.method_name).to eq(:==) } - end - - context 'with an implicit call method' do - let(:source) { 'foo.(:baz)' } - - it { expect(send_node.method_name).to eq(:call) } - end - end - - describe '#method?' do - context 'when message matches' do - context 'when argument is a symbol' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.method?(:bar)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.method?('bar')).to be_truthy } - end - end - - context 'when message does not match' do - context 'when argument is a symbol' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.method?(:foo)).to be_falsey } - end - - context 'when argument is a string' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.method?('foo')).to be_falsey } - end - end - end - - describe '#access_modifier?' do - let(:send_node) { parse_source(source).ast.children[1] } - - context 'when node is a bare `module_function`' do - let(:source) do - <<~RUBY - module Foo - module_function - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `module_function`' do - let(:source) do - <<~RUBY - module Foo - module_function :foo - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a bare `private`' do - let(:source) do - <<~RUBY - module Foo - private - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `private`' do - let(:source) do - <<~RUBY - module Foo - private :foo - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a bare `protected`' do - let(:source) do - <<~RUBY - module Foo - protected - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `protected`' do - let(:source) do - <<~RUBY - module Foo - protected :foo - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a bare `public`' do - let(:source) do - <<~RUBY - module Foo - public - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `public`' do - let(:source) do - <<~RUBY - module Foo - public :foo - end - RUBY - end - - it { expect(send_node.access_modifier?).to be_truthy } - end - - context 'when node is not an access modifier' do - let(:source) do - <<~RUBY - module Foo - some_command - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_falsey } - end - end - - describe '#bare_access_modifier?' do - let(:send_node) { parse_source(source).ast.children[1] } - - context 'when node is a bare `module_function`' do - let(:source) do - <<~RUBY - module Foo - module_function - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_truthy } - end - - context 'when node is a bare `private`' do - let(:source) do - <<~RUBY - module Foo - private - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_truthy } - end - - context 'when node is a bare `protected`' do - let(:source) do - <<~RUBY - module Foo - protected - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_truthy } - end - - context 'when node is a bare `public`' do - let(:source) do - <<~RUBY - module Foo - public - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_truthy } - end - - context 'when node has an argument' do - let(:source) do - <<~RUBY - module Foo - private :foo - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_falsey } - end - - context 'when node is not an access modifier' do - let(:source) do - <<~RUBY - module Foo - some_command - end - RUBY - end - - it { expect(send_node.bare_access_modifier?).to be_falsey } - end - end - - describe '#non_bare_access_modifier?' do - let(:send_node) { parse_source(source).ast.children[1] } - - context 'when node is a non-bare `module_function`' do - let(:source) do - <<~RUBY - module Foo - module_function :foo - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `private`' do - let(:source) do - <<~RUBY - module Foo - private :foo - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `protected`' do - let(:source) do - <<~RUBY - module Foo - protected :foo - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_truthy } - end - - context 'when node is a non-bare `public`' do - let(:source) do - <<~RUBY - module Foo - public :foo - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_truthy } - end - - context 'when node does not have an argument' do - let(:source) do - <<~RUBY - module Foo - private - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_falsey } - end - - context 'when node is not an access modifier' do - let(:source) do - <<~RUBY - module Foo - some_command - end - RUBY - end - - it { expect(send_node.non_bare_access_modifier?).to be_falsey } - end - end - - describe '#macro?' do - context 'without a receiver' do - context 'when parent is a class' do - let(:send_node) { parse_source(source).ast.children[2].children[0] } - - let(:source) do - ['class Foo', - ' bar :baz', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a module' do - let(:send_node) { parse_source(source).ast.children[1].children[0] } - - let(:source) do - ['module Foo', - ' bar :baz', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a class constructor' do - let(:send_node) { parse_source(source).ast.children[2].children[0] } - - let(:source) do - ['Module.new do', - ' bar :baz', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a singleton class' do - let(:send_node) { parse_source(source).ast.children[1].children[0] } - - let(:source) do - ['class << self', - ' bar :baz', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a block' do - let(:send_node) { parse_source(source).ast.children[2].children[0] } - - let(:source) do - ['concern :Auth do', - ' bar :baz', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a keyword begin inside of an class' do - let(:send_node) { parse_source(source).ast.children[2].children[0] } - - let(:source) do - ['class Foo', - ' begin', - ' bar :qux', - ' end', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'without a parent' do - let(:source) { 'bar :baz' } - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a begin without a parent' do - let(:send_node) { parse_source(source).ast.children[0] } - - let(:source) do - ['begin', - ' bar :qux', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_truthy } - end - - context 'when parent is a method definition' do - let(:send_node) { parse_source(source).ast.children[2] } - - let(:source) do - ['def foo', - ' bar :baz', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_falsey } - end - end - - context 'with a receiver' do - context 'when parent is a class' do - let(:send_node) { parse_source(source).ast.children[2] } - - let(:source) do - ['class Foo', - ' qux.bar :baz', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_falsey } - end - - context 'when parent is a module' do - let(:send_node) { parse_source(source).ast.children[1] } - - let(:source) do - ['module Foo', - ' qux.bar :baz', - 'end'].join("\n") - end - - it { expect(send_node.macro?).to be_falsey } - end - end - end - - describe '#command?' do - context 'when argument is a symbol' do - context 'with an explicit receiver' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.command?(:bar)).to be_falsey } - end - - context 'with an implicit receiver' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.command?(:bar)).to be_truthy } - end - end - - context 'when argument is a string' do - context 'with an explicit receiver' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.command?('bar')).to be_falsey } - end - - context 'with an implicit receiver' do - let(:source) { 'bar(:baz)' } - - it { expect(send_node.command?('bar')).to be_truthy } - end - end - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.arguments.empty?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.arguments.size).to eq(1) } - end - - context 'with a single splat argument' do - let(:source) { 'foo.bar(*baz)' } - - it { expect(send_node.arguments.size).to eq(1) } - end - - context 'with multiple literal arguments' do - let(:source) { 'foo.bar(:baz, :qux)' } - - it { expect(send_node.arguments.size).to eq(2) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo.bar(:baz, *qux)' } - - it { expect(send_node.arguments.size).to eq(2) } - end - end - - describe '#first_argument' do - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.first_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.first_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'foo.bar(*baz)' } - - it { expect(send_node.first_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'foo.bar(:baz, :qux)' } - - it { expect(send_node.first_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo.bar(:baz, *qux)' } - - it { expect(send_node.first_argument.sym_type?).to be(true) } - end - end - - describe '#last_argument' do - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.last_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.last_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'foo.bar(*baz)' } - - it { expect(send_node.last_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'foo.bar(:baz, :qux)' } - - it { expect(send_node.last_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo.bar(:baz, *qux)' } - - it { expect(send_node.last_argument.splat_type?).to be(true) } - end - end - - describe '#arguments?' do - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.arguments?).to be_falsey } - end - - context 'with a single literal argument' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.arguments?).to be_truthy } - end - - context 'with a single splat argument' do - let(:source) { 'foo.bar(*baz)' } - - it { expect(send_node.arguments?).to be_truthy } - end - - context 'with multiple literal arguments' do - let(:source) { 'foo.bar(:baz, :qux)' } - - it { expect(send_node.arguments?).to be_truthy } - end - - context 'with multiple mixed arguments' do - let(:source) { 'foo.bar(:baz, *qux)' } - - it { expect(send_node.arguments?).to be_truthy } - end - end - - describe '#parenthesized?' do - context 'with no arguments' do - context 'when not using parentheses' do - let(:source) { 'foo.bar' } - - it { expect(send_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'foo.bar()' } - - it { expect(send_node.parenthesized?).to be_truthy } - end - end - - context 'with arguments' do - context 'when not using parentheses' do - let(:source) { 'foo.bar :baz' } - - it { expect(send_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.parenthesized?).to be_truthy } - end - end - end - - describe '#setter_method?' do - context 'with a setter method' do - let(:source) { 'foo.bar = :baz' } - - it { expect(send_node.setter_method?).to be_truthy } - end - - context 'with an indexed setter method' do - let(:source) { 'foo.bar[:baz] = :qux' } - - it { expect(send_node.setter_method?).to be_truthy } - end - - context 'with an operator method' do - let(:source) { 'foo.bar + 1' } - - it { expect(send_node.setter_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.setter_method?).to be_falsey } - end - end - - describe '#operator_method?' do - context 'with a binary operator method' do - let(:source) { 'foo.bar + :baz' } - - it { expect(send_node.operator_method?).to be_truthy } - end - - context 'with a unary operator method' do - let(:source) { '!foo.bar' } - - it { expect(send_node.operator_method?).to be_truthy } - end - - context 'with a setter method' do - let(:source) { 'foo.bar = :baz' } - - it { expect(send_node.operator_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.operator_method?).to be_falsey } - end - end - - describe '#comparison_method?' do - context 'with a comparison method' do - let(:source) { 'foo.bar >= :baz' } - - it { expect(send_node.comparison_method?).to be_truthy } - end - - context 'with a regular method' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.comparison_method?).to be_falsey } - end - - context 'with a negation method' do - let(:source) { '!foo' } - - it { expect(send_node.comparison_method?).to be_falsey } - end - end - - describe '#assignment_method?' do - context 'with an assignment method' do - let(:source) { 'foo.bar = :baz' } - - it { expect(send_node.assignment_method?).to be_truthy } - end - - context 'with a bracket assignment method' do - let(:source) { 'foo.bar[:baz] = :qux' } - - it { expect(send_node.assignment_method?).to be_truthy } - end - - context 'with a comparison method' do - let(:source) { 'foo.bar == :qux' } - - it { expect(send_node.assignment_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.assignment_method?).to be_falsey } - end - end - - describe '#dot?' do - context 'with a dot' do - let(:source) { 'foo.+ 1' } - - it { expect(send_node.dot?).to be_truthy } - end - - context 'without a dot' do - let(:source) { 'foo + 1' } - - it { expect(send_node.dot?).to be_falsey } - end - - context 'with a double colon' do - let(:source) { 'Foo::bar' } - - it { expect(send_node.dot?).to be_falsey } - end - - context 'with a unary method' do - let(:source) { '!foo.bar' } - - it { expect(send_node.dot?).to be_falsey } - end - end - - describe '#double_colon?' do - context 'with a double colon' do - let(:source) { 'Foo::bar' } - - it { expect(send_node.double_colon?).to be_truthy } - end - - context 'with a dot' do - let(:source) { 'foo.+ 1' } - - it { expect(send_node.double_colon?).to be_falsey } - end - - context 'without a dot' do - let(:source) { 'foo + 1' } - - it { expect(send_node.double_colon?).to be_falsey } - end - - context 'with a unary method' do - let(:source) { '!foo.bar' } - - it { expect(send_node.double_colon?).to be_falsey } - end - end - - describe '#self_receiver?' do - context 'with a self receiver' do - let(:source) { 'self.bar' } - - it { expect(send_node.self_receiver?).to be_truthy } - end - - context 'with a non-self receiver' do - let(:source) { 'foo.bar' } - - it { expect(send_node.self_receiver?).to be_falsey } - end - - context 'with an implicit receiver' do - let(:source) { 'bar' } - - it { expect(send_node.self_receiver?).to be_falsey } - end - end - - describe '#const_receiver?' do - context 'with a self receiver' do - let(:source) { 'self.bar' } - - it { expect(send_node.const_receiver?).to be_falsey } - end - - context 'with a non-constant receiver' do - let(:source) { 'foo.bar' } - - it { expect(send_node.const_receiver?).to be_falsey } - end - - context 'with a constant receiver' do - let(:source) { 'Foo.bar' } - - it { expect(send_node.const_receiver?).to be_truthy } - end - end - - describe '#implicit_call?' do - context 'with an implicit call method' do - let(:source) { 'foo.(:bar)' } - - it { expect(send_node.implicit_call?).to be_truthy } - end - - context 'with an explicit call method' do - let(:source) { 'foo.call(:bar)' } - - it { expect(send_node.implicit_call?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.implicit_call?).to be_falsey } - end - end - - describe '#predicate_method?' do - context 'with a predicate method' do - let(:source) { 'foo.bar?' } - - it { expect(send_node.predicate_method?).to be_truthy } - end - - context 'with a bang method' do - let(:source) { 'foo.bar!' } - - it { expect(send_node.predicate_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.predicate_method?).to be_falsey } - end - end - - describe '#bang_method?' do - context 'with a bang method' do - let(:source) { 'foo.bar!' } - - it { expect(send_node.bang_method?).to be_truthy } - end - - context 'with a predicate method' do - let(:source) { 'foo.bar?' } - - it { expect(send_node.bang_method?).to be_falsey } - end - - context 'with a regular method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.bang_method?).to be_falsey } - end - end - - describe '#camel_case_method?' do - context 'with a camel case method' do - let(:source) { 'Integer(1.0)' } - - it { expect(send_node.camel_case_method?).to be_truthy } - end - - context 'with a regular method' do - let(:source) { 'integer(1.0)' } - - it { expect(send_node.camel_case_method?).to be_falsey } - end - end - - describe '#block_argument?' do - context 'with a block argument' do - let(:source) { 'foo.bar(&baz)' } - - it { expect(send_node.block_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.block_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.block_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'foo.bar(:baz, &qux)' } - - it { expect(send_node.block_argument?).to be_truthy } - end - end - - describe '#block_literal?' do - context 'with a block literal' do - let(:send_node) { parse_source(source).ast.children[0] } - - let(:source) { 'foo.bar { |q| baz(q) }' } - - it { expect(send_node.block_literal?).to be_truthy } - end - - context 'with a block argument' do - let(:source) { 'foo.bar(&baz)' } - - it { expect(send_node.block_literal?).to be_falsey } - end - - context 'with no block' do - let(:source) { 'foo.bar' } - - it { expect(send_node.block_literal?).to be_falsey } - end - end - - describe '#arithmetic_operation?' do - context 'with a binary arithmetic operation' do - let(:source) { 'foo + bar' } - - it { expect(send_node.arithmetic_operation?).to be_truthy } - end - - context 'with a unary numeric operation' do - let(:source) { '+foo' } - - it { expect(send_node.arithmetic_operation?).to be_falsey } - end - - context 'with a regular method call' do - let(:source) { 'foo.bar' } - - it { expect(send_node.arithmetic_operation?).to be_falsey } - end - end - - describe '#block_node' do - context 'with a block literal' do - let(:send_node) { parse_source(source).ast.children[0] } - - let(:source) { 'foo.bar { |q| baz(q) }' } - - it { expect(send_node.block_node.block_type?).to be(true) } - end - - context 'with a block argument' do - let(:source) { 'foo.bar(&baz)' } - - it { expect(send_node.block_node.nil?).to be(true) } - end - - context 'with no block' do - let(:source) { 'foo.bar' } - - it { expect(send_node.block_node.nil?).to be(true) } - end - end - - describe '#splat_argument?' do - context 'with a splat argument' do - let(:source) { 'foo.bar(*baz)' } - - it { expect(send_node.splat_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'foo.bar' } - - it { expect(send_node.splat_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(send_node.splat_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'foo.bar(:baz, *qux)' } - - it { expect(send_node.splat_argument?).to be_truthy } - end - end - - describe '#def_modifier?' do - context 'with a prefixed def modifier' do - let(:source) { 'foo def bar; end' } - - it { expect(send_node.def_modifier?).to be_truthy } - end - - context 'with several prefixed def modifiers' do - let(:source) { 'foo bar def baz; end' } - - it { expect(send_node.def_modifier?).to be_truthy } - end - end - - describe '#negation_method?' do - context 'with prefix `not`' do - let(:source) { 'not foo' } - - it { expect(send_node.negation_method?).to be_truthy } - end - - context 'with suffix `not`' do - let(:source) { 'foo.not' } - - it { expect(send_node.negation_method?).to be_falsey } - end - - context 'with prefix bang' do - let(:source) { '!foo' } - - it { expect(send_node.negation_method?).to be_truthy } - end - - context 'with a non-negated method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.negation_method?).to be_falsey } - end - end - - describe '#prefix_not?' do - context 'with keyword `not`' do - let(:source) { 'not foo' } - - it { expect(send_node.prefix_not?).to be_truthy } - end - - context 'with a bang method' do - let(:source) { '!foo' } - - it { expect(send_node.prefix_not?).to be_falsey } - end - - context 'with a non-negated method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.prefix_not?).to be_falsey } - end - end - - describe '#prefix_bang?' do - context 'with keyword `not`' do - let(:source) { 'not foo' } - - it { expect(send_node.prefix_bang?).to be_falsey } - end - - context 'with a bang method' do - let(:source) { '!foo' } - - it { expect(send_node.prefix_bang?).to be_truthy } - end - - context 'with a non-negated method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.prefix_bang?).to be_falsey } - end - end - - describe '#lambda?' do - context 'with a lambda method' do - let(:source) { 'lambda { |foo| bar(foo) }' } - let(:send_node) { parse_source(source).ast.send_node } - - it { expect(send_node.lambda?).to be_truthy } - end - - context 'with a method named lambda in a class' do - let(:source) { 'foo.lambda { |bar| baz }' } - let(:send_node) { parse_source(source).ast.send_node } - - it { expect(send_node.lambda?).to be_falsey } - end - - context 'with a stabby lambda method' do - let(:source) { '-> (foo) { do_something(foo) }' } - let(:send_node) { parse_source(source).ast.send_node } - - it { expect(send_node.lambda?).to be_truthy } - end - - context 'with a non-lambda method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.lambda?).to be_falsey } - end - end - - describe '#lambda_literal?' do - context 'with a stabby lambda' do - let(:send_node) { parse_source(source).ast.send_node } - let(:source) { '-> (foo) { do_something(foo) }' } - - it { expect(send_node.lambda_literal?).to be(true) } - end - - context 'with a lambda method' do - let(:send_node) { parse_source(source).ast.send_node } - let(:source) { 'lambda { |foo| bar(foo) }' } - - it { expect(send_node.lambda_literal?).to be(false) } - end - - context 'with a non-lambda method' do - let(:source) { 'foo.bar' } - - it { expect(send_node.lambda?).to be_falsey } - end - - # Regression test https://github.com/rubocop-hq/rubocop/pull/5194 - context 'with `a.() {}` style method' do - let(:send_node) { parse_source(source).ast.send_node } - let(:source) { 'a.() {}' } - - it { expect(send_node.lambda?).to be_falsey } - end - end - - describe '#unary_operation?' do - context 'with a unary operation' do - let(:source) { '-foo' } - - it { expect(send_node.unary_operation?).to be(true) } - end - - context 'with a binary operation' do - let(:source) { 'foo + bar' } - - it { expect(send_node.unary_operation?).to be(false) } - end - - context 'with a regular method call' do - let(:source) { 'foo(bar)' } - - it { expect(send_node.unary_operation?).to be(false) } - end - - context 'with an implicit call method' do - let(:source) { 'foo.(:baz)' } - - it { expect(send_node.unary_operation?).to be(false) } - end - end - - describe '#binary_operation??' do - context 'with a unary operation' do - let(:source) { '-foo' } - - it { expect(send_node.binary_operation?).to be(false) } - end - - context 'with a binary operation' do - let(:source) { 'foo + bar' } - - it { expect(send_node.binary_operation?).to be(true) } - end - - context 'with a regular method call' do - let(:source) { 'foo(bar)' } - - it { expect(send_node.binary_operation?).to be(false) } - end - - context 'with an implicit call method' do - let(:source) { 'foo.(:baz)' } - - it { expect(send_node.binary_operation?).to be(false) } - end - end -end diff --git a/spec/rubocop/ast/str_node_spec.rb b/spec/rubocop/ast/str_node_spec.rb deleted file mode 100644 index 893966e58639..000000000000 --- a/spec/rubocop/ast/str_node_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::StrNode do - let(:str_node) { parse_source(source).ast } - - describe '.new' do - context 'with a normal string' do - let(:source) { "'foo'" } - - it { expect(str_node.is_a?(described_class)).to be(true) } - end - - context 'with a string with interpolation' do - let(:source) { '"#{foo}"' } - - it { expect(str_node.is_a?(described_class)).to be(true) } - end - - context 'with a heredoc' do - let(:source) do - <<~RUBY - <<-CODE - foo - bar - CODE - RUBY - end - - it { expect(str_node.is_a?(described_class)).to be(true) } - end - end - - describe '#heredoc?' do - context 'with a normal string' do - let(:source) { "'foo'" } - - it { expect(str_node.heredoc?).to be(false) } - end - - context 'with a string with interpolation' do - let(:source) { '"#{foo}"' } - - it { expect(str_node.heredoc?).to be(false) } - end - - context 'with a heredoc' do - let(:source) do - <<~RUBY - <<-CODE - foo - bar - CODE - RUBY - end - - it { expect(str_node.heredoc?).to be(true) } - end - end -end diff --git a/spec/rubocop/ast/super_node_spec.rb b/spec/rubocop/ast/super_node_spec.rb deleted file mode 100644 index 71b33bdf6ff3..000000000000 --- a/spec/rubocop/ast/super_node_spec.rb +++ /dev/null @@ -1,413 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::SuperNode do - let(:super_node) { parse_source(source).ast } - - describe '.new' do - context 'with a super node' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.is_a?(described_class)).to be(true) } - end - - context 'with a zsuper node' do - let(:source) { 'super' } - - it { expect(super_node.is_a?(described_class)).to be(true) } - end - end - - describe '#receiver' do - let(:source) { 'super(foo)' } - - it { expect(super_node.receiver.nil?).to be(true) } - end - - describe '#method_name' do - let(:source) { 'super(foo)' } - - it { expect(super_node.method_name).to eq(:super) } - end - - describe '#method?' do - context 'when message matches' do - context 'when argument is a symbol' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.method?(:super)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.method?('super')).to be_truthy } - end - end - - context 'when message does not match' do - context 'when argument is a symbol' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.method?(:foo)).to be_falsey } - end - - context 'when argument is a string' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.method?('foo')).to be_falsey } - end - end - end - - describe '#macro?' do - let(:super_node) { parse_source(source).ast.children[2] } - - let(:source) do - ['def initialize', - ' super(foo)', - 'end'].join("\n") - end - - it { expect(super_node.macro?).to be_falsey } - end - - describe '#command?' do - context 'when argument is a symbol' do - let(:source) { 'super(foo)' } - - it { expect(super_node.command?(:super)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'super(foo)' } - - it { expect(super_node.command?('super')).to be_truthy } - end - end - - describe '#setter_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.setter_method?).to be_falsey } - end - - describe '#operator_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.operator_method?).to be_falsey } - end - - describe '#comparison_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.comparison_method?).to be_falsey } - end - - describe '#assignment_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.assignment_method?).to be_falsey } - end - - describe '#dot?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.dot?).to be_falsey } - end - - describe '#double_colon?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.double_colon?).to be_falsey } - end - - describe '#self_receiver?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.self_receiver?).to be_falsey } - end - - describe '#const_receiver?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.const_receiver?).to be_falsey } - end - - describe '#implicit_call?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.implicit_call?).to be_falsey } - end - - describe '#predicate_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.predicate_method?).to be_falsey } - end - - describe '#bang_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.bang_method?).to be_falsey } - end - - describe '#camel_case_method?' do - let(:source) { 'super(foo)' } - - it { expect(super_node.camel_case_method?).to be_falsey } - end - - describe '#parenthesized?' do - context 'with no arguments' do - context 'when not using parentheses' do - let(:source) { 'super' } - - it { expect(super_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'foo.bar()' } - - it { expect(super_node.parenthesized?).to be_truthy } - end - end - - context 'with arguments' do - context 'when not using parentheses' do - let(:source) { 'foo.bar :baz' } - - it { expect(super_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'foo.bar(:baz)' } - - it { expect(super_node.parenthesized?).to be_truthy } - end - end - end - - describe '#block_argument?' do - context 'with a block argument' do - let(:source) { 'super(&baz)' } - - it { expect(super_node.block_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.block_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.block_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'super(:baz, &qux)' } - - it { expect(super_node.block_argument?).to be_truthy } - end - end - - describe '#block_literal?' do - context 'with a block literal' do - let(:super_node) { parse_source(source).ast.children[0] } - - let(:source) { 'super { |q| baz(q) }' } - - it { expect(super_node.block_literal?).to be_truthy } - end - - context 'with a block argument' do - let(:source) { 'super(&baz)' } - - it { expect(super_node.block_literal?).to be_falsey } - end - - context 'with no block' do - let(:source) { 'super' } - - it { expect(super_node.block_literal?).to be_falsey } - end - end - - describe '#block_node' do - context 'with a block literal' do - let(:super_node) { parse_source(source).ast.children[0] } - - let(:source) { 'super { |q| baz(q) }' } - - it { expect(super_node.block_node.block_type?).to be(true) } - end - - context 'with a block argument' do - let(:source) { 'super(&baz)' } - - it { expect(super_node.block_node.nil?).to be(true) } - end - - context 'with no block' do - let(:source) { 'super' } - - it { expect(super_node.block_node.nil?).to be(true) } - end - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.arguments.empty?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.arguments.size).to eq(1) } - end - - context 'with a single splat argument' do - let(:source) { 'super(*baz)' } - - it { expect(super_node.arguments.size).to eq(1) } - end - - context 'with multiple literal arguments' do - let(:source) { 'super(:baz, :qux)' } - - it { expect(super_node.arguments.size).to eq(2) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'super(:baz, *qux)' } - - it { expect(super_node.arguments.size).to eq(2) } - end - end - - describe '#first_argument' do - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.first_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.first_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'super(*baz)' } - - it { expect(super_node.first_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'super(:baz, :qux)' } - - it { expect(super_node.first_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'superr(:baz, *qux)' } - - it { expect(super_node.first_argument.sym_type?).to be(true) } - end - end - - describe '#last_argument' do - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.last_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.last_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'super(*baz)' } - - it { expect(super_node.last_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'super(:baz, :qux)' } - - it { expect(super_node.last_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'super(:baz, *qux)' } - - it { expect(super_node.last_argument.splat_type?).to be(true) } - end - end - - describe '#arguments?' do - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.arguments?).to be_falsey } - end - - context 'with a single literal argument' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.arguments?).to be_truthy } - end - - context 'with a single splat argument' do - let(:source) { 'super(*baz)' } - - it { expect(super_node.arguments?).to be_truthy } - end - - context 'with multiple literal arguments' do - let(:source) { 'super(:baz, :qux)' } - - it { expect(super_node.arguments?).to be_truthy } - end - - context 'with multiple mixed arguments' do - let(:source) { 'super(:baz, *qux)' } - - it { expect(super_node.arguments?).to be_truthy } - end - end - - describe '#splat_argument?' do - context 'with a splat argument' do - let(:source) { 'super(*baz)' } - - it { expect(super_node.splat_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'super' } - - it { expect(super_node.splat_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'super(:baz)' } - - it { expect(super_node.splat_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'super(:baz, *qux)' } - - it { expect(super_node.splat_argument?).to be_truthy } - end - end -end diff --git a/spec/rubocop/ast/symbol_node_spec.rb b/spec/rubocop/ast/symbol_node_spec.rb deleted file mode 100644 index 36a68e73289f..000000000000 --- a/spec/rubocop/ast/symbol_node_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::SymbolNode do - let(:sym_node) { parse_source(source).ast } - - describe '.new' do - context 'with a symbol node' do - let(:source) do - ':foo' - end - - it { expect(sym_node.is_a?(described_class)).to be(true) } - end - end - - describe '#value' do - let(:source) do - ':foo' - end - - it { expect(sym_node.value).to eq(:foo) } - end -end diff --git a/spec/rubocop/ast/until_node_spec.rb b/spec/rubocop/ast/until_node_spec.rb deleted file mode 100644 index e2d4679ad5ae..000000000000 --- a/spec/rubocop/ast/until_node_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::UntilNode do - let(:until_node) { parse_source(source).ast } - - describe '.new' do - context 'with a statement until' do - let(:source) { 'until foo; bar; end' } - - it { expect(until_node.is_a?(described_class)).to be(true) } - end - - context 'with a modifier until' do - let(:source) { 'begin foo; end until bar' } - - it { expect(until_node.is_a?(described_class)).to be(true) } - end - end - - describe '#keyword' do - let(:source) { 'until foo; bar; end' } - - it { expect(until_node.keyword).to eq('until') } - end - - describe '#inverse_keyword' do - let(:source) { 'until foo; bar; end' } - - it { expect(until_node.inverse_keyword).to eq('while') } - end - - describe '#do?' do - context 'with a do keyword' do - let(:source) { 'until foo do; bar; end' } - - it { expect(until_node.do?).to be_truthy } - end - - context 'without a do keyword' do - let(:source) { 'until foo; bar; end' } - - it { expect(until_node.do?).to be_falsey } - end - end -end diff --git a/spec/rubocop/ast/when_node_spec.rb b/spec/rubocop/ast/when_node_spec.rb deleted file mode 100644 index 4b2461c4f87c..000000000000 --- a/spec/rubocop/ast/when_node_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::WhenNode do - let(:when_node) { parse_source(source).ast.children[1] } - - describe '.new' do - let(:source) do - ['case', - 'when :foo then bar', - 'end'].join("\n") - end - - it { expect(when_node.is_a?(described_class)).to be(true) } - end - - describe '#conditions' do - context 'with a single condition' do - let(:source) do - ['case', - 'when :foo then bar', - 'end'].join("\n") - end - - it { expect(when_node.conditions.size).to eq(1) } - it { expect(when_node.conditions).to all(be_literal) } - end - - context 'with a multiple conditions' do - let(:source) do - ['case', - 'when :foo, :bar, :baz then bar', - 'end'].join("\n") - end - - it { expect(when_node.conditions.size).to eq(3) } - it { expect(when_node.conditions).to all(be_literal) } - end - end - - describe '#each_condition' do - let(:source) do - ['case', - 'when :foo, :bar, :baz then bar', - 'end'].join("\n") - end - - context 'when not passed a block' do - it { expect(when_node.each_condition.is_a?(Enumerator)).to be(true) } - end - - context 'when passed a block' do - it 'yields all the conditions' do - expect { |b| when_node.each_condition(&b) } - .to yield_successive_args(*when_node.conditions) - end - end - end - - describe '#then?' do - context 'with a then keyword' do - let(:source) do - ['case', - 'when :foo then bar', - 'end'].join("\n") - end - - it { expect(when_node.then?).to be_truthy } - end - - context 'without a then keyword' do - let(:source) do - ['case', - 'when :foo', - ' bar', - 'end'].join("\n") - end - - it { expect(when_node.then?).to be_falsey } - end - end - - describe '#body' do - context 'with a then keyword' do - let(:source) do - ['case', - 'when :foo then :bar', - 'end'].join("\n") - end - - it { expect(when_node.body.sym_type?).to be(true) } - end - - context 'without a then keyword' do - let(:source) do - ['case', - 'when :foo', - ' [:bar, :baz]', - 'end'].join("\n") - end - - it { expect(when_node.body.array_type?).to be(true) } - end - end - - describe '#branch_index' do - let(:source) do - ['case', - 'when :foo then 1', - 'when :bar then 2', - 'when :baz then 3', - 'end'].join("\n") - end - - let(:whens) { parse_source(source).ast.children[1...-1] } - - it { expect(whens[0].branch_index).to eq(0) } - it { expect(whens[1].branch_index).to eq(1) } - it { expect(whens[2].branch_index).to eq(2) } - end -end diff --git a/spec/rubocop/ast/while_node_spec.rb b/spec/rubocop/ast/while_node_spec.rb deleted file mode 100644 index 4933203b8889..000000000000 --- a/spec/rubocop/ast/while_node_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::WhileNode do - let(:while_node) { parse_source(source).ast } - - describe '.new' do - context 'with a statement while' do - let(:source) { 'while foo; bar; end' } - - it { expect(while_node.is_a?(described_class)).to be(true) } - end - - context 'with a modifier while' do - let(:source) { 'begin foo; end while bar' } - - it { expect(while_node.is_a?(described_class)).to be(true) } - end - end - - describe '#keyword' do - let(:source) { 'while foo; bar; end' } - - it { expect(while_node.keyword).to eq('while') } - end - - describe '#inverse_keyword' do - let(:source) { 'while foo; bar; end' } - - it { expect(while_node.inverse_keyword).to eq('until') } - end - - describe '#do?' do - context 'with a do keyword' do - let(:source) { 'while foo do; bar; end' } - - it { expect(while_node.do?).to be_truthy } - end - - context 'without a do keyword' do - let(:source) { 'while foo; bar; end' } - - it { expect(while_node.do?).to be_falsey } - end - end -end diff --git a/spec/rubocop/ast/yield_node_spec.rb b/spec/rubocop/ast/yield_node_spec.rb deleted file mode 100644 index 190776d22da6..000000000000 --- a/spec/rubocop/ast/yield_node_spec.rb +++ /dev/null @@ -1,353 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::AST::YieldNode do - let(:yield_node) { parse_source(source).ast } - - describe '.new' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.is_a?(described_class)).to be(true) } - end - - describe '#receiver' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.receiver.nil?).to be(true) } - end - - describe '#method_name' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.method_name).to eq(:yield) } - end - - describe '#method?' do - context 'when message matches' do - context 'when argument is a symbol' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.method?(:yield)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.method?('yield')).to be_truthy } - end - end - - context 'when message does not match' do - context 'when argument is a symbol' do - let(:source) { 'yield :bar' } - - it { expect(yield_node.method?(:foo)).to be_falsey } - end - - context 'when argument is a string' do - let(:source) { 'yield :bar' } - - it { expect(yield_node.method?('foo')).to be_falsey } - end - end - end - - describe '#macro?' do - let(:yield_node) { parse_source(source).ast.children[2] } - - let(:source) do - ['def give_me_bar', - ' yield :bar', - 'end'].join("\n") - end - - it { expect(yield_node.macro?).to be_falsey } - end - - describe '#command?' do - context 'when argument is a symbol' do - let(:source) { 'yield :bar' } - - it { expect(yield_node.command?(:yield)).to be_truthy } - end - - context 'when argument is a string' do - let(:source) { 'yield :bar' } - - it { expect(yield_node.command?('yield')).to be_truthy } - end - end - - describe '#arguments' do - context 'with no arguments' do - let(:source) { 'yield' } - - it { expect(yield_node.arguments.empty?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.arguments.size).to eq(1) } - end - - context 'with a single splat argument' do - let(:source) { 'yield *foo' } - - it { expect(yield_node.arguments.size).to eq(1) } - end - - context 'with multiple literal arguments' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.arguments.size).to eq(2) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'yield :foo, *bar' } - - it { expect(yield_node.arguments.size).to eq(2) } - end - end - - describe '#first_argument' do - context 'with no arguments' do - let(:source) { 'yield' } - - it { expect(yield_node.first_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.first_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'yield *foo' } - - it { expect(yield_node.first_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.first_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'yield :foo, *bar' } - - it { expect(yield_node.first_argument.sym_type?).to be(true) } - end - end - - describe '#last_argument' do - context 'with no arguments' do - let(:source) { 'yield' } - - it { expect(yield_node.last_argument.nil?).to be(true) } - end - - context 'with a single literal argument' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.last_argument.sym_type?).to be(true) } - end - - context 'with a single splat argument' do - let(:source) { 'yield *foo' } - - it { expect(yield_node.last_argument.splat_type?).to be(true) } - end - - context 'with multiple literal arguments' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.last_argument.sym_type?).to be(true) } - end - - context 'with multiple mixed arguments' do - let(:source) { 'yield :foo, *bar' } - - it { expect(yield_node.last_argument.splat_type?).to be(true) } - end - end - - describe '#arguments?' do - context 'with no arguments' do - let(:source) { 'yield' } - - it { expect(yield_node.arguments?).to be_falsey } - end - - context 'with a single literal argument' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.arguments?).to be_truthy } - end - - context 'with a single splat argument' do - let(:source) { 'yield *foo' } - - it { expect(yield_node.arguments?).to be_truthy } - end - - context 'with multiple literal arguments' do - let(:source) { 'yield :foo, :bar' } - - it { expect(yield_node.arguments?).to be_truthy } - end - - context 'with multiple mixed arguments' do - let(:source) { 'yield :foo, *bar' } - - it { expect(yield_node.arguments?).to be_truthy } - end - end - - describe '#parenthesized?' do - context 'with no arguments' do - context 'when not using parentheses' do - let(:source) { 'yield' } - - it { expect(yield_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'yield()' } - - it { expect(yield_node.parenthesized?).to be_truthy } - end - end - - context 'with arguments' do - context 'when not using parentheses' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.parenthesized?).to be_falsey } - end - - context 'when using parentheses' do - let(:source) { 'yield(:foo)' } - - it { expect(yield_node.parenthesized?).to be_truthy } - end - end - end - - describe '#setter_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.setter_method?).to be_falsey } - end - - describe '#operator_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.operator_method?).to be_falsey } - end - - describe '#comparison_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.comparison_method?).to be_falsey } - end - - describe '#assignment_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.assignment_method?).to be_falsey } - end - - describe '#dot?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.dot?).to be_falsey } - end - - describe '#double_colon?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.double_colon?).to be_falsey } - end - - describe '#self_receiver?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.self_receiver?).to be_falsey } - end - - describe '#const_receiver?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.const_receiver?).to be_falsey } - end - - describe '#implicit_call?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.implicit_call?).to be_falsey } - end - - describe '#predicate_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.predicate_method?).to be_falsey } - end - - describe '#bang_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.bang_method?).to be_falsey } - end - - describe '#camel_case_method?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.camel_case_method?).to be_falsey } - end - - describe '#block_argument?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.block_argument?).to be_falsey } - end - - describe '#block_literal?' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.block_literal?).to be_falsey } - end - - describe '#block_node' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.block_node.nil?).to be(true) } - end - - describe '#splat_argument?' do - context 'with a splat argument' do - let(:source) { 'yield *foo' } - - it { expect(yield_node.splat_argument?).to be_truthy } - end - - context 'with no arguments' do - let(:source) { 'yield' } - - it { expect(yield_node.splat_argument?).to be_falsey } - end - - context 'with regular arguments' do - let(:source) { 'yield :foo' } - - it { expect(yield_node.splat_argument?).to be_falsey } - end - - context 'with mixed arguments' do - let(:source) { 'yield :foo, *bar' } - - it { expect(yield_node.splat_argument?).to be_truthy } - end - end -end diff --git a/spec/rubocop/node_pattern_spec.rb b/spec/rubocop/node_pattern_spec.rb deleted file mode 100644 index 1e39c0471bee..000000000000 --- a/spec/rubocop/node_pattern_spec.rb +++ /dev/null @@ -1,1770 +0,0 @@ -# frozen_string_literal: true - -require 'parser/current' - -RSpec.describe RuboCop::NodePattern do - before { $VERBOSE = true } - - after { $VERBOSE = false } - - let(:root_node) do - buffer = Parser::Source::Buffer.new('(string)', 1) - buffer.source = ruby - builder = RuboCop::AST::Builder.new - Parser::CurrentRuby.new(builder).parse(buffer) - end - - let(:node) { root_node } - let(:params) { [] } - let(:instance) { described_class.new(pattern) } - - shared_examples 'matching' do - include RuboCop::AST::Sexp - it 'matches' do - expect(instance.match(node, *params)).to be true - end - end - - shared_examples 'nonmatching' do - it "doesn't match" do - expect(instance.match(node, *params).nil?).to be(true) - end - end - - shared_examples 'invalid' do - it 'is invalid' do - expect { instance } - .to raise_error(RuboCop::NodePattern::Invalid) - end - end - - shared_examples 'single capture' do - include RuboCop::AST::Sexp - it 'yields captured value(s) and returns true if there is a block' do - expect do |probe| - compiled = instance - retval = compiled.match(node, *params) do |capture| - probe.to_proc.call(capture) - :retval_from_block - end - expect(retval).to be :retval_from_block - end.to yield_with_args(captured_val) - end - - it 'returns captured values if there is no block' do - retval = instance.match(node, *params) - expect(retval).to eq captured_val - end - end - - shared_examples 'multiple capture' do - include RuboCop::AST::Sexp - it 'yields captured value(s) and returns true if there is a block' do - expect do |probe| - compiled = instance - retval = compiled.match(node, *params) do |*captures| - probe.to_proc.call(captures) - :retval_from_block - end - expect(retval).to be :retval_from_block - end.to yield_with_args(captured_vals) - end - - it 'returns captured values if there is no block' do - retval = instance.match(node, *params) - expect(retval).to eq captured_vals - end - end - - describe 'bare node type' do - let(:pattern) { 'send' } - - context 'on a node with the same type' do - let(:ruby) { 'obj.method' } - - it_behaves_like 'matching' - end - - context 'on a node with a different type' do - let(:ruby) { '@ivar' } - - it_behaves_like 'nonmatching' - end - - context 'on a node with a matching, hyphenated type' do - let(:pattern) { 'op-asgn' } - let(:ruby) { 'a += 1' } - - # this is an (op-asgn ...) node - it_behaves_like 'matching' - end - - describe '#pattern' do - it 'returns the pattern' do - expect(instance.pattern).to eq pattern - end - end - - describe '#to_s' do - it 'is instructive' do - expect(instance.to_s).to include pattern - end - end - - describe 'marshal compatibility' do - let(:instance) { Marshal.load(Marshal.dump(super())) } - let(:ruby) { 'obj.method' } - - it_behaves_like 'matching' - end - - describe '#dup' do - let(:instance) { super().dup } - let(:ruby) { 'obj.method' } - - it_behaves_like 'matching' - end - - describe 'yaml compatibility' do - let(:instance) do - $VERBOSE = false - YAML.safe_load(YAML.dump(super()), [described_class]) - end - let(:ruby) { 'obj.method' } - - it_behaves_like 'matching' - end - - describe '#==' do - let(:pattern) { " (send 42 \n :to_s ) " } - - it 'returns true iff the patterns are similar' do - expect(instance == instance.dup).to eq true - expect(instance == 42).to eq false - expect(instance == described_class.new('(send)')).to eq false - expect(instance == described_class.new('(send 42 :to_s)')).to eq true - end - end - end - - describe 'node type' do - describe 'in seq head' do - let(:pattern) { '(send ...)' } - - context 'on a node with the same type' do - let(:ruby) { '@ivar + 2' } - - it_behaves_like 'matching' - end - - context 'on a child with a different type' do - let(:ruby) { '@ivar += 2' } - - it_behaves_like 'nonmatching' - end - end - - describe 'for a child' do - let(:pattern) { '(_ send ...)' } - - context 'on a child with the same type' do - let(:ruby) { 'foo.bar' } - - it_behaves_like 'matching' - end - - context 'on a child with a different type' do - let(:ruby) { '@ivar.bar' } - - it_behaves_like 'nonmatching' - end - - context 'on a child litteral' do - let(:pattern) { '(_ _ send)' } - let(:ruby) { '42.bar' } - - it_behaves_like 'nonmatching' - end - end - end - - describe 'literals' do - context 'negative integer literals' do - let(:pattern) { '(int -100)' } - let(:ruby) { '-100' } - - it_behaves_like 'matching' - end - - context 'positive float literals' do - let(:pattern) { '(float 1.0)' } - let(:ruby) { '1.0' } - - it_behaves_like 'matching' - end - - context 'negative float literals' do - let(:pattern) { '(float -2.5)' } - let(:ruby) { '-2.5' } - - it_behaves_like 'matching' - end - - context 'single quoted string literals' do - let(:pattern) { '(str "foo")' } - let(:ruby) { '"foo"' } - - it_behaves_like 'matching' - end - - context 'double quoted string literals' do - let(:pattern) { '(str "foo")' } - let(:ruby) { "'foo'" } - - it_behaves_like 'matching' - end - - context 'symbol literals' do - let(:pattern) { '(sym :foo)' } - let(:ruby) { ':foo' } - - it_behaves_like 'matching' - end - - describe 'bare literal' do - let(:ruby) { ':bar' } - let(:pattern) { ':bar' } - - context 'on a node' do - it_behaves_like 'nonmatching' - end - - context 'on a matching literal' do - let(:node) { root_node.children[0] } - - it_behaves_like 'matching' - end - end - end - - describe 'nil' do - context 'nil literals' do - let(:pattern) { '(nil)' } - let(:ruby) { 'nil' } - - it_behaves_like 'matching' - end - - context 'nil value in AST' do - let(:pattern) { '(send nil :foo)' } - let(:ruby) { 'foo' } - - it_behaves_like 'nonmatching' - end - - context 'nil value in AST, use nil? method' do - let(:pattern) { '(send nil? :foo)' } - let(:ruby) { 'foo' } - - it_behaves_like 'matching' - end - - context 'against a node pattern (bug #5470)' do - let(:pattern) { '(:send (:const ...) ...)' } - let(:ruby) { 'foo' } - - it_behaves_like 'nonmatching' - end - end - - describe 'simple sequence' do - let(:pattern) { '(send int :+ int)' } - - context 'on a node with the same type and matching children' do - let(:ruby) { '1 + 1' } - - it_behaves_like 'matching' - end - - context 'on a node with a different type' do - let(:ruby) { 'a = 1' } - - it_behaves_like 'nonmatching' - end - - context 'on a node with the same type and non-matching children' do - context 'with non-matching selector' do - let(:ruby) { '1 - 1' } - - it_behaves_like 'nonmatching' - end - - context 'with non-matching receiver type' do - let(:ruby) { '1.0 + 1' } - - it_behaves_like 'nonmatching' - end - end - - context 'on a node with too many children' do - let(:pattern) { '(send int :blah int)' } - let(:ruby) { '1.blah(1, 2)' } - - it_behaves_like 'nonmatching' - end - - context 'with a nested sequence in head position' do - let(:pattern) { '((send) int :blah)' } - - it_behaves_like 'invalid' - end - - context 'with a nested sequence in non-head position' do - let(:pattern) { '(send (send _ :a) :b)' } - let(:ruby) { 'obj.a.b' } - - it_behaves_like 'matching' - end - end - - describe 'sequence with trailing ...' do - let(:pattern) { '(send int :blah ...)' } - - context 'on a node with the same type and exact number of children' do - let(:ruby) { '1.blah' } - - it_behaves_like 'matching' - end - - context 'on a node with the same type and more children' do - context 'with 1 child more' do - let(:ruby) { '1.blah(1)' } - - it_behaves_like 'matching' - end - - context 'with 2 children more' do - let(:ruby) { '1.blah(1, :something)' } - - it_behaves_like 'matching' - end - end - - context 'on a node with the same type and fewer children' do - let(:pattern) { '(send int :blah int int ...)' } - let(:ruby) { '1.blah(2)' } - - it_behaves_like 'nonmatching' - end - - context 'on a node with fewer children, with a wildcard preceding' do - let(:pattern) { '(hash _ ...)' } - let(:ruby) { '{}' } - - it_behaves_like 'nonmatching' - end - - context 'on a node with a different type' do - let(:ruby) { 'A = 1' } - - it_behaves_like 'nonmatching' - end - - context 'on a node with non-matching children' do - let(:ruby) { '1.foo' } - - it_behaves_like 'nonmatching' - end - end - - describe 'wildcards' do - describe 'unnamed wildcards' do - context 'at the root level' do - let(:pattern) { '_' } - let(:ruby) { 'class << self; def something; 1; end end.freeze' } - - it_behaves_like 'matching' - end - - context 'within a sequence' do - let(:pattern) { '(const _ _)' } - let(:ruby) { 'Const' } - - it_behaves_like 'matching' - end - - context 'within a sequence with other patterns intervening' do - let(:pattern) { '(ivasgn _ (int _))' } - let(:ruby) { '@abc = 22' } - - it_behaves_like 'matching' - end - - context 'in head position of a sequence' do - let(:pattern) { '(_ int ...)' } - let(:ruby) { '1 + a' } - - it_behaves_like 'matching' - end - - context 'negated' do - let(:pattern) { '!_' } - let(:ruby) { '123' } - - it_behaves_like 'nonmatching' - end - end - - describe 'named wildcards' do - # unification is done on named wildcards! - context 'at the root level' do - let(:pattern) { '_node' } - let(:ruby) { 'class << self; def something; 1; end end.freeze' } - - it_behaves_like 'matching' - end - - context 'within a sequence' do - context 'with values which can be unified' do - let(:pattern) { '(send _num :+ _num)' } - let(:ruby) { '5 + 5' } - - it_behaves_like 'matching' - end - - context 'with values which cannot be unified' do - let(:pattern) { '(send _num :+ _num)' } - let(:ruby) { '5 + 4' } - - it_behaves_like 'nonmatching' - end - - context 'unifying the node type with an argument' do - let(:pattern) { '(_type _ _type)' } - let(:ruby) { 'obj.send' } - - it_behaves_like 'matching' - end - end - - context 'within a sequence with other patterns intervening' do - let(:pattern) { '(ivasgn _ivar (int _val))' } - let(:ruby) { '@abc = 22' } - - it_behaves_like 'matching' - end - - context 'in head position of a sequence' do - let(:pattern) { '(_type int ...)' } - let(:ruby) { '1 + a' } - - it_behaves_like 'matching' - end - - context 'within a union' do - context 'confined to the union' do - context 'without unification' do - let(:pattern) { '{(array (int 1) _num) (array _num (int 1))}' } - let(:ruby) { '[2, 1]' } - - it_behaves_like 'matching' - end - - context 'with partial unification' do - let(:pattern) { '{(array _num _num) (array _num (int 1))}' } - - context 'matching the unified branch' do - let(:ruby) { '[5, 5]' } - - it_behaves_like 'matching' - end - - context 'matching the free branch' do - let(:ruby) { '[2, 1]' } - - it_behaves_like 'matching' - end - - context 'that can not be unified' do - let(:ruby) { '[3, 2]' } - - it_behaves_like 'nonmatching' - end - end - end - - context 'with a preceding unifying constraint' do - let(:pattern) do - '(array _num {(array (int 1) _num) - send - (array _num (int 1))})' - end - - context 'matching a branch' do - let(:ruby) { '[2, [2, 1]]' } - - it_behaves_like 'matching' - end - - context 'that can not be unified' do - let(:ruby) { '[3, [2, 1]]' } - - it_behaves_like 'nonmatching' - end - end - - context 'with a succeeding unifying constraint' do - context 'with branches without the wildcard' do - context 'encountered first' do - let(:pattern) do - '(array {send - (array (int 1) _num) - } _num)' - end - - it_behaves_like 'invalid' - end - - context 'encountered after' do - let(:pattern) do - '(array {(array (int 1) _num) - (array _num (int 1)) - send - } _num)' - end - - it_behaves_like 'invalid' - end - end - - context 'with all branches with the wildcard' do - let(:pattern) do - '(array {(array (int 1) _num) - (array _num (int 1)) - } _num)' - end - - context 'matching the first branch' do - let(:ruby) { '[[1, 2], 2]' } - - it_behaves_like 'matching' - end - - context 'matching another branch' do - let(:ruby) { '[[2, 1], 2]' } - - it_behaves_like 'matching' - end - - context 'that can not be unified' do - let(:ruby) { '[[2, 1], 1]' } - - it_behaves_like 'nonmatching' - end - end - end - end - end - end - - describe 'unions' do - context 'at the top level' do - context 'containing symbol literals' do - context 'when the AST has a matching symbol' do - let(:pattern) { '(send _ {:a :b})' } - let(:ruby) { 'obj.b' } - - it_behaves_like 'matching' - end - - context 'when the AST does not have a matching symbol' do - let(:pattern) { '(send _ {:a :b})' } - let(:ruby) { 'obj.c' } - - it_behaves_like 'nonmatching' - end - end - - context 'containing string literals' do - let(:pattern) { '(send (str {"a" "b"}) :upcase)' } - let(:ruby) { '"a".upcase' } - - it_behaves_like 'matching' - end - - context 'containing integer literals' do - let(:pattern) { '(send (int {1 10}) :abs)' } - let(:ruby) { '10.abs' } - - it_behaves_like 'matching' - end - - context 'containing mixed node and literals' do - let(:pattern) { '(send {int nil?} ...)' } - let(:ruby) { 'obj' } - - it_behaves_like 'matching' - end - - context 'containing multiple []' do - let(:pattern) { '{[(int odd?) int] [!nil float]}' } - - context 'on a node which meets all requirements of the first []' do - let(:ruby) { '3' } - - it_behaves_like 'matching' - end - - context 'on a node which meets all requirements of the second []' do - let(:ruby) { '2.4' } - - it_behaves_like 'matching' - end - - context 'on a node which meets some requirements but not all' do - let(:ruby) { '2' } - - it_behaves_like 'nonmatching' - end - end - end - - context 'nested inside a sequence' do - let(:pattern) { '(send {const int} ...)' } - let(:ruby) { 'Const.method' } - - it_behaves_like 'matching' - end - - context 'with a nested sequence' do - let(:pattern) { '{(send int ...) (send const ...)}' } - let(:ruby) { 'Const.method' } - - it_behaves_like 'matching' - end - end - - describe 'captures on a wildcard' do - context 'at the root level' do - let(:pattern) { '$_' } - let(:ruby) { 'begin; raise StandardError; rescue Exception => e; end' } - let(:captured_val) { node } - - it_behaves_like 'single capture' - end - - context 'in head position in a sequence' do - let(:pattern) { '($_ ...)' } - let(:ruby) { 'A.method' } - let(:captured_val) { :send } - - it_behaves_like 'single capture' - end - - context 'in head position in a sequence against nil (bug #5470)' do - let(:pattern) { '($_ ...)' } - let(:ruby) { '' } - - it_behaves_like 'nonmatching' - end - - context 'in head position in a sequence against literal (bug #5470)' do - let(:pattern) { '(int ($_ ...))' } - let(:ruby) { '42' } - - it_behaves_like 'nonmatching' - end - - context 'in non-head position in a sequence' do - let(:pattern) { '(send $_ ...)' } - let(:ruby) { 'A.method' } - let(:captured_val) { s(:const, nil, :A) } - - it_behaves_like 'single capture' - end - - context 'in a nested sequence' do - let(:pattern) { '(send (const nil? $_) ...)' } - let(:ruby) { 'A.method' } - let(:captured_val) { :A } - - it_behaves_like 'single capture' - end - end - - describe 'captures which also perform a match' do - context 'on a sequence' do - let(:pattern) { '(send $(send _ :keys) :each)' } - let(:ruby) { '{}.keys.each' } - let(:captured_val) { s(:send, s(:hash), :keys) } - - it_behaves_like 'single capture' - end - - context 'on a set' do - let(:pattern) { '(send _ ${:inc :dec})' } - let(:ruby) { '1.dec' } - let(:captured_val) { :dec } - - it_behaves_like 'single capture' - end - - context 'on []' do - let(:pattern) { '(send (int $[!odd? !zero?]) :inc)' } - let(:ruby) { '2.inc' } - let(:captured_val) { 2 } - - it_behaves_like 'single capture' - end - - context 'on a node type' do - let(:pattern) { '(send $int :inc)' } - let(:ruby) { '5.inc' } - let(:captured_val) { s(:int, 5) } - - it_behaves_like 'single capture' - end - - context 'on a literal' do - let(:pattern) { '(send int $:inc)' } - let(:ruby) { '5.inc' } - let(:captured_val) { :inc } - - it_behaves_like 'single capture' - end - - context 'when nested' do - let(:pattern) { '(send $(int $_) :inc)' } - let(:ruby) { '5.inc' } - let(:captured_vals) { [s(:int, 5), 5] } - - it_behaves_like 'multiple capture' - end - end - - describe 'captures on ...' do - context 'with no remaining pattern at the end' do - let(:pattern) { '(send $...)' } - let(:ruby) { '5.inc' } - let(:captured_val) { [s(:int, 5), :inc] } - - it_behaves_like 'single capture' - end - - context 'with a remaining node type at the end' do - let(:pattern) { '(send $... int)' } - let(:ruby) { '5 + 4' } - let(:captured_val) { [s(:int, 5), :+] } - - it_behaves_like 'single capture' - end - - context 'with remaining patterns at the end' do - let(:pattern) { '(send $... int int)' } - let(:ruby) { '[].push(1, 2, 3)' } - let(:captured_val) { [s(:array), :push, s(:int, 1)] } - - it_behaves_like 'single capture' - end - - context 'with a remaining sequence at the end' do - let(:pattern) { '(send $... (int 4))' } - let(:ruby) { '5 + 4' } - let(:captured_val) { [s(:int, 5), :+] } - - it_behaves_like 'single capture' - end - - context 'with a remaining set at the end' do - let(:pattern) { '(send $... {int float})' } - let(:ruby) { '5 + 4' } - let(:captured_val) { [s(:int, 5), :+] } - - it_behaves_like 'single capture' - end - - context 'with a remaining [] at the end' do - let(:pattern) { '(send $... [(int even?) (int zero?)])' } - let(:ruby) { '5 + 0' } - let(:captured_val) { [s(:int, 5), :+] } - - it_behaves_like 'single capture' - end - - context 'with a remaining literal at the end' do - let(:pattern) { '(send $... :inc)' } - let(:ruby) { '5.inc' } - let(:captured_val) { [s(:int, 5)] } - - it_behaves_like 'single capture' - end - - context 'with a remaining wildcard at the end' do - let(:pattern) { '(send $... _)' } - let(:ruby) { '5.inc' } - let(:captured_val) { [s(:int, 5)] } - - it_behaves_like 'single capture' - end - - context 'with a remaining capture at the end' do - let(:pattern) { '(send $... $_)' } - let(:ruby) { '5 + 4' } - let(:captured_vals) { [[s(:int, 5), :+], s(:int, 4)] } - - it_behaves_like 'multiple capture' - end - - context 'at the very beginning of a sequence' do - let(:pattern) { '($... (int 1))' } - let(:ruby) { '10 * 1' } - let(:captured_val) { [s(:int, 10), :*] } - - it_behaves_like 'single capture' - end - - context 'after a child' do - let(:pattern) { '(send (int 10) $...)' } - let(:ruby) { '10 * 1' } - let(:captured_val) { [:*, s(:int, 1)] } - - it_behaves_like 'single capture' - end - end - - describe 'captures within union' do - context 'on simple subpatterns' do - let(:pattern) { '{$send $int $float}' } - let(:ruby) { '2.0' } - let(:captured_val) { s(:float, 2.0) } - - it_behaves_like 'single capture' - end - - context 'within nested sequences' do - let(:pattern) { '{(send $_ $_) (const $_ $_)}' } - let(:ruby) { 'Namespace::CONST' } - let(:captured_vals) { [s(:const, nil, :Namespace), :CONST] } - - it_behaves_like 'multiple capture' - end - - context 'with complex nesting' do - let(:pattern) do - '{(send {$int $float} {$:inc $:dec}) ' \ - '[!nil {($_ sym $_) (send ($_ $_) :object_id)}]}' - end - let(:ruby) { '10.object_id' } - let(:captured_vals) { [:int, 10] } - - it_behaves_like 'multiple capture' - end - - context 'with a different number of captures in each branch' do - let(:pattern) { '{(send $...) (int $...) (send $_ $_)}' } - - it_behaves_like 'invalid' - end - end - - describe 'negation' do - context 'on a symbol' do - let(:pattern) { '(send _ !:abc)' } - - context 'with a matching symbol' do - let(:ruby) { 'obj.abc' } - - it_behaves_like 'nonmatching' - end - - context 'with a non-matching symbol' do - let(:ruby) { 'obj.xyz' } - - it_behaves_like 'matching' - end - - context 'with a non-matching symbol, but too many children' do - let(:ruby) { 'obj.xyz(1)' } - - it_behaves_like 'nonmatching' - end - end - - context 'on a string' do - let(:pattern) { '(send (str !"foo") :upcase)' } - - context 'with a matching string' do - let(:ruby) { '"foo".upcase' } - - it_behaves_like 'nonmatching' - end - - context 'with a non-matching symbol' do - let(:ruby) { '"bar".upcase' } - - it_behaves_like 'matching' - end - end - - context 'on a set' do - let(:pattern) { '(ivasgn _ !(int {1 2}))' } - - context 'with a matching value' do - let(:ruby) { '@a = 1' } - - it_behaves_like 'nonmatching' - end - - context 'with a non-matching value' do - let(:ruby) { '@a = 3' } - - it_behaves_like 'matching' - end - end - - context 'on a sequence' do - let(:pattern) { '!(ivasgn :@a ...)' } - - context 'with a matching node' do - let(:ruby) { '@a = 1' } - - it_behaves_like 'nonmatching' - end - - context 'with a node of different type' do - let(:ruby) { '@@a = 1' } - - it_behaves_like 'matching' - end - - context 'with a node with non-matching children' do - let(:ruby) { '@b = 1' } - - it_behaves_like 'matching' - end - end - - context 'on square brackets' do - let(:pattern) { '![!int !float]' } - - context 'with a node which meets all requirements of []' do - let(:ruby) { '"abc"' } - - it_behaves_like 'nonmatching' - end - - context 'with a node which meets only 1 requirement of []' do - let(:ruby) { '1' } - - it_behaves_like 'matching' - end - end - - context 'when nested in complex ways' do - let(:pattern) { '!(send !{int float} !:+ !(send _ :to_i))' } - - context 'with (send str :+ (send str :to_i))' do - let(:ruby) { '"abc" + "1".to_i' } - - it_behaves_like 'matching' - end - - context 'with (send int :- int)' do - let(:ruby) { '1 - 1' } - - it_behaves_like 'matching' - end - - context 'with (send str :<< str)' do - let(:ruby) { '"abc" << "xyz"' } - - it_behaves_like 'nonmatching' - end - end - end - - describe 'ellipsis' do - context 'preceding a capture' do - let(:pattern) { '(send array :push ... $_)' } - let(:ruby) { '[1].push(2, 3, 4)' } - let(:captured_val) { s(:int, 4) } - - it_behaves_like 'single capture' - end - - context 'preceding multiple captures' do - let(:pattern) { '(send array :push ... $_ $_)' } - let(:ruby) { '[1].push(2, 3, 4, 5)' } - let(:captured_vals) { [s(:int, 4), s(:int, 5)] } - - it_behaves_like 'multiple capture' - end - - context 'with a wildcard at the end, but no remaining child to match it' do - let(:pattern) { '(send array :zip array ... _)' } - let(:ruby) { '[1,2].zip([3,4])' } - - it_behaves_like 'nonmatching' - end - - context 'with a nodetype at the end, but no remaining child to match it' do - let(:pattern) { '(send array :zip array ... array)' } - let(:ruby) { '[1,2].zip([3,4])' } - - it_behaves_like 'nonmatching' - end - - context 'with a nested sequence at the end, but no remaining child' do - let(:pattern) { '(send array :zip array ... (array ...))' } - let(:ruby) { '[1,2].zip([3,4])' } - - it_behaves_like 'nonmatching' - end - - context 'with a set at the end, but no remaining child to match it' do - let(:pattern) { '(send array :zip array ... {array})' } - let(:ruby) { '[1,2].zip([3,4])' } - - it_behaves_like 'nonmatching' - end - - context 'with [] at the end, but no remaining child to match it' do - let(:pattern) { '(send array :zip array ... [array !nil])' } - let(:ruby) { '[1,2].zip([3,4])' } - - it_behaves_like 'nonmatching' - end - - context 'at the very beginning of a sequence' do - let(:pattern) { '(... (int 1))' } - let(:ruby) { '10 * 1' } - - it_behaves_like 'matching' - end - end - - describe 'predicates' do - context 'in root position' do - let(:pattern) { 'send_type?' } - let(:ruby) { '1.inc' } - - it_behaves_like 'matching' - - context 'with name containing a numeral' do - before { RuboCop::AST::Node.def_node_matcher :custom_42?, 'send_type?' } - - let(:pattern) { 'custom_42?' } - - it_behaves_like 'matching' - end - end - - context 'at head position of a sequence' do - # called on the type symbol - let(:pattern) { '(!nil? int ...)' } - let(:ruby) { '1.inc' } - - it_behaves_like 'matching' - end - - context 'applied to an integer for which the predicate is true' do - let(:pattern) { '(send (int odd?) :inc)' } - let(:ruby) { '1.inc' } - - it_behaves_like 'matching' - end - - context 'applied to an integer for which the predicate is false' do - let(:pattern) { '(send (int odd?) :inc)' } - let(:ruby) { '2.inc' } - - it_behaves_like 'nonmatching' - end - - context 'when captured' do - let(:pattern) { '(send (int $odd?) :inc)' } - let(:ruby) { '1.inc' } - let(:captured_val) { 1 } - - it_behaves_like 'single capture' - end - - context 'when negated' do - let(:pattern) { '(send int !nil?)' } - let(:ruby) { '1.inc' } - - it_behaves_like 'matching' - end - - context 'when in last-child position, but all children have already ' \ - 'been matched' do - let(:pattern) { '(send int :inc ... !nil?)' } - let(:ruby) { '1.inc' } - - it_behaves_like 'nonmatching' - end - - context 'with one extra argument' do - let(:pattern) { '(send (int equal?(%1)) ...)' } - let(:ruby) { '1 + 2' } - - context 'for which the predicate is true' do - let(:params) { [1] } - - it_behaves_like 'matching' - end - - context 'for which the predicate is false' do - let(:params) { [2] } - - it_behaves_like 'nonmatching' - end - end - - context 'with multiple arguments' do - let(:pattern) { '(str between?(%1, %2))' } - let(:ruby) { '"c"' } - - context 'for which the predicate is true' do - let(:params) { %w[a d] } - - it_behaves_like 'matching' - end - - context 'for which the predicate is false' do - let(:params) { %w[a b] } - - it_behaves_like 'nonmatching' - end - end - end - - describe 'params' do - context 'in root position' do - let(:pattern) { '%1' } - let(:params) { [s(:int, 10)] } - let(:ruby) { '10' } - - it_behaves_like 'matching' - end - - context 'in a nested sequence' do - let(:pattern) { '(send (send _ %2) %1)' } - let(:params) { %i[inc dec] } - let(:ruby) { '5.dec.inc' } - - it_behaves_like 'matching' - end - - context 'when preceded by ...' do - let(:pattern) { '(send ... %1)' } - let(:params) { [s(:int, 10)] } - let(:ruby) { '1 + 10' } - - it_behaves_like 'matching' - end - - context 'when preceded by $...' do - let(:pattern) { '(send $... %1)' } - let(:params) { [s(:int, 10)] } - let(:ruby) { '1 + 10' } - let(:captured_val) { [s(:int, 1), :+] } - - it_behaves_like 'single capture' - end - - context 'when captured' do - let(:pattern) { '(const _ $%1)' } - let(:params) { [:A] } - let(:ruby) { 'Namespace::A' } - let(:captured_val) { :A } - - it_behaves_like 'single capture' - end - - context 'when negated, with a matching value' do - let(:pattern) { '(const _ !%1)' } - let(:params) { [:A] } - let(:ruby) { 'Namespace::A' } - - it_behaves_like 'nonmatching' - end - - context 'when negated, with a nonmatching value' do - let(:pattern) { '(const _ !%1)' } - let(:params) { [:A] } - let(:ruby) { 'Namespace::B' } - - it_behaves_like 'matching' - end - - context 'without explicit number' do - let(:pattern) { '(const %2 %)' } - let(:params) { [:A, s(:const, nil, :Namespace)] } - let(:ruby) { 'Namespace::A' } - - it_behaves_like 'matching' - end - - context 'when inside a union, with a matching value' do - let(:pattern) { '{str (int %)}' } - let(:params) { [10] } - let(:ruby) { '10' } - - it_behaves_like 'matching' - end - - context 'when inside a union, with a nonmatching value' do - let(:pattern) { '{str (int %)}' } - let(:params) { [10] } - let(:ruby) { '1.0' } - - it_behaves_like 'nonmatching' - end - - context 'when inside an intersection' do - let(:pattern) { '(int [!%1 %2 !zero?])' } - let(:params) { [10, 20] } - let(:ruby) { '20' } - - it_behaves_like 'matching' - end - - context 'param number zero' do - # refers to original target node passed to #match - let(:pattern) { '^(send %0 :+ (int 2))' } - let(:ruby) { '1 + 2' } - - context 'in a position which matches original target node' do - let(:node) { root_node.children[0] } - - it_behaves_like 'matching' - end - - context 'in a position which does not match original target node' do - let(:node) { root_node.children[2] } - - it_behaves_like 'nonmatching' - end - end - end - - describe 'caret (ascend)' do - context 'used with a node type' do - let(:ruby) { '1.inc' } - let(:node) { root_node.children[0] } - - context 'which matches' do - let(:pattern) { '^send' } - - it_behaves_like 'matching' - end - - context "which doesn't match" do - let(:pattern) { '^const' } - - it_behaves_like 'nonmatching' - end - end - - context 'within sequence' do - let(:ruby) { '1.inc' } - - context 'not in head' do - let(:ruby) { '1.inc' } - let(:pattern) { '(send ^send :inc)' } - - it_behaves_like 'matching' - - context 'of a sequence' do - let(:pattern) { '(send ^(send _ _) :inc)' } - - it_behaves_like 'matching' - end - end - - context 'in head' do - let(:node) { root_node.children[0] } - let(:pattern) { '(^send 1)' } - - it_behaves_like 'matching' - - context 'of a sequence' do - let(:pattern) { '(^(send _ _) 1)' } - - it_behaves_like 'matching' - end - end - end - - context 'repeated twice' do - # ascends to grandparent node - let(:pattern) { '^^block' } - let(:ruby) { '1.inc { something }' } - let(:node) { root_node.children[0].children[0] } - - it_behaves_like 'matching' - end - - context 'inside an intersection' do - let(:pattern) { '^[!nil send ^(block ...)]' } - let(:ruby) { '1.inc { something }' } - let(:node) { root_node.children[0].children[0] } - - it_behaves_like 'matching' - end - - context 'inside a union' do - let(:pattern) { '{^send ^^send}' } - let(:ruby) { '"str".concat(local += "abc")' } - let(:node) { root_node.children[2].children[2] } - - it_behaves_like 'matching' - end - - # NOTE!! a pitfall of doing this is that unification is done using #== - # This means that 'identical' AST nodes, which are not really identical - # because they have different metadata, will still unify - context 'using unification to match self within parent' do - let(:pattern) { '[_self ^(send _ _ _self)]' } - let(:ruby) { '1 + 2' } - - context 'with self in the right position' do - let(:node) { root_node.children[2] } - - it_behaves_like 'matching' - end - - context 'with self in the wrong position' do - let(:node) { root_node.children[0] } - - it_behaves_like 'nonmatching' - end - end - end - - describe 'funcalls' do - module RuboCop - class NodePattern - def goodmatch(_foo) - true - end - - def badmatch(_foo) - false - end - - def witharg(foo, bar) - foo == bar - end - - def withargs(foo, bar, qux) - foo.between?(bar, qux) - end - end - end - - context 'without extra arguments' do - let(:pattern) { '(lvasgn #goodmatch ...)' } - let(:ruby) { 'a = 1' } - - it_behaves_like 'matching' - end - - context 'with one argument' do - let(:pattern) { '(str #witharg(%1))' } - let(:ruby) { '"foo"' } - let(:params) { %w[foo] } - - it_behaves_like 'matching' - end - - context 'with multiple arguments' do - let(:pattern) { '(str #withargs(%1, %2))' } - let(:ruby) { '"c"' } - let(:params) { %w[a d] } - - it_behaves_like 'matching' - end - end - - describe 'commas' do - context 'with commas randomly strewn around' do - let(:pattern) { ',,(,send,, ,int,:+, int ), ' } - - it_behaves_like 'invalid' - end - end - - describe 'in any order' do - let(:ruby) { '[:hello, "world", 1, 2, 3]' } - - context 'without ellipsis' do - context 'with matching children' do - let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_) $_>)' } - - let(:captured_vals) { ['world', 2, s(:sym, :hello)] } - - it_behaves_like 'multiple capture' - end - - context 'with too many children' do - let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_)>)' } - - it_behaves_like 'nonmatching' - end - - context 'with too few children' do - let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_) _ _>)' } - - it_behaves_like 'nonmatching' - end - end - - context 'with a captured ellipsis' do - context 'matching non sequential children' do - let(:pattern) { '(array <(str "world") (int 2) $...>)' } - - let(:captured_val) { [s(:sym, :hello), s(:int, 1), s(:int, 3)] } - - it_behaves_like 'single capture' - end - - context 'matching all children' do - let(:pattern) { '(array <(str "world") (int 2) _ _ _ $...>)' } - - let(:captured_val) { [] } - - it_behaves_like 'single capture' - end - - context 'nested' do - let(:ruby) { '[:x, 1, [:y, 2, 3], 42]' } - let(:pattern) { '(array <(int $_) (array <(int $_) $...>) $...>)' } - - let(:captured_vals) do - [1, 2, [s(:sym, :y), s(:int, 3)], - [s(:sym, :x), s(:int, 42)]] - end - - it_behaves_like 'multiple capture' - end - end - - context 'with an ellipsis' do - let(:pattern) { '(array <(str "world") (int 2) ...> $_)' } - - let(:captured_val) { s(:int, 3) } - - it_behaves_like 'single capture' - end - - context 'captured' do - context 'without ellipsis' do - let(:pattern) { '(array sym $)' } - let(:captured_val) { node.children.last(4) } - - it_behaves_like 'single capture' - end - end - - context 'doubled' do - context 'separated by fixed argument' do - let(:pattern) { '(array <(str $_) (sym $_)> $_ <(int 3) (int $_)>)' } - - let(:captured_vals) { ['world', :hello, s(:int, 1), 2] } - - it_behaves_like 'multiple capture' - end - - context 'separated by an ellipsis' do - let(:pattern) { '(array <(str $_) (sym $_)> $... <(int 3) (int $_)>)' } - - let(:captured_vals) { ['world', :hello, [s(:int, 1)], 2] } - - it_behaves_like 'multiple capture' - end - end - - describe 'invalid' do - context 'at the beginning of a sequence' do - let(:pattern) { '(<(str $_) (sym $_)> ...)' } - - it_behaves_like 'invalid' - end - - context 'containing ellipsis not at the end' do - let(:pattern) { '(array <(str $_) ... (sym $_)>)' } - - it_behaves_like 'invalid' - end - - context 'with an ellipsis inside and outside' do - let(:pattern) { '(array <(str $_) (sym $_) ...> ...)' } - - it_behaves_like 'invalid' - end - - context 'doubled with ellipsis' do - let(:pattern) { '(array <(str $_) ...> <(str $_) ...>)' } - - it_behaves_like 'invalid' - end - - context 'nested' do - let(:pattern) { '(array <(str $_) > ...)' } - - it_behaves_like 'invalid' - end - end - end - - describe 'repeated' do - let(:ruby) { '[:hello, 1, 2, 3]' } - - shared_examples 'repeated pattern' do - context 'with one match' do - let(:pattern) { "(array sym int $int #{symbol} int)" } - - let(:captured_val) { [s(:int, 2)] } - - it_behaves_like 'single capture' - end - - context 'at beginning of sequence' do - let(:pattern) { "(int #{symbol} int)" } - - it_behaves_like 'invalid' - end - - context 'with an ellipsis in the same sequence' do - let(:pattern) { "(array int #{symbol} ...)" } - - it_behaves_like 'invalid' - end - end - - context 'using *' do - let(:symbol) { :* } - - it_behaves_like 'repeated pattern' - - context 'without capture' do - let(:pattern) { '(array sym int* int)' } - - it_behaves_like 'matching' - end - - context 'with matching children' do - let(:pattern) { '(array sym $int* int)' } - - let(:captured_val) { [s(:int, 1), s(:int, 2)] } - - it_behaves_like 'single capture' - end - - context 'with zero match' do - let(:pattern) { '(array sym int int $sym* int)' } - - let(:captured_val) { [] } - - it_behaves_like 'single capture' - end - - context 'with no match' do - let(:pattern) { '(array sym int $sym* int)' } - - it_behaves_like 'nonmatching' - end - - context 'with multiple subcaptures' do - let(:pattern) { '(array ($_ $_)* int int)' } - - let(:captured_vals) { [%i[sym int], [:hello, 1]] } - - it_behaves_like 'multiple capture' - end - - context 'nested with multiple subcaptures' do - let(:ruby) { '[[:hello, 1, 2, 3], [:world, 3, 4]]' } - let(:pattern) { '(array (array (sym $_) (int $_)*)*)' } - - let(:captured_vals) { [%i[hello world], [[1, 2, 3], [3, 4]]] } - - it_behaves_like 'multiple capture' - end - end - - context 'using +' do - let(:symbol) { :+ } - - it_behaves_like 'repeated pattern' - - context 'with matching children' do - let(:pattern) { '(array sym $int+ int)' } - - let(:captured_val) { [s(:int, 1), s(:int, 2)] } - - it_behaves_like 'single capture' - end - - context 'with zero match' do - let(:pattern) { '(array sym int int $sym+ int)' } - - it_behaves_like 'nonmatching' - end - end - - context 'using ?' do - let(:symbol) { '?' } - - it_behaves_like 'repeated pattern' - - context 'with too many matching children' do - let(:pattern) { '(array sym $int ? int)' } - - let(:captured_val) { [s(:int, 1), s(:int, 2)] } - - it_behaves_like 'nonmatching' - end - - context 'with zero match' do - let(:pattern) { '(array sym int int $(sym _)? int)' } - - let(:captured_val) { [] } - - it_behaves_like 'single capture' - end - end - end - - describe 'descend' do - let(:ruby) { '[1, [[2, 3, [[5]]], 4]]' } - - context 'with an immediate match' do - let(:pattern) { '(array `$int _)' } - - let(:captured_val) { s(:int, 1) } - - it_behaves_like 'single capture' - end - - context 'with a match multiple levels, depth first' do - let(:pattern) { '(array (int 1) `$int)' } - - let(:captured_val) { s(:int, 2) } - - it_behaves_like 'single capture' - end - - context 'nested' do - let(:pattern) { '(array (int 1) `(array <`(array $int) ...>))' } - - let(:captured_val) { s(:int, 5) } - - it_behaves_like 'single capture' - end - - context 'with a literal match' do - let(:pattern) { '(array (int 1) `4)' } - - it_behaves_like 'matching' - end - - context 'without match' do - let(:pattern) { '(array `$str ...)' } - - it_behaves_like 'nonmatching' - end - end - - describe 'bad syntax' do - context 'with empty parentheses' do - let(:pattern) { '()' } - - it_behaves_like 'invalid' - end - - context 'with empty union' do - let(:pattern) { '{}' } - - it_behaves_like 'invalid' - end - - context 'with empty intersection' do - let(:pattern) { '[]' } - - it_behaves_like 'invalid' - end - - context 'with unmatched opening paren' do - let(:pattern) { '(send (const)' } - - it_behaves_like 'invalid' - end - - context 'with unmatched opening paren and `...`' do - let(:pattern) { '(send ...' } - - it_behaves_like 'invalid' - end - - context 'with unmatched closing paren' do - let(:pattern) { '(send (const)))' } - - it_behaves_like 'invalid' - end - - context 'with unmatched opening curly' do - let(:pattern) { '{send const' } - - it_behaves_like 'invalid' - end - - context 'with unmatched closing curly' do - let(:pattern) { '{send const}}' } - - it_behaves_like 'invalid' - end - - context 'with negated closing paren' do - let(:pattern) { '(send (const) !)' } - - it_behaves_like 'invalid' - end - - context 'with negated closing curly' do - let(:pattern) { '{send const !}' } - - it_behaves_like 'invalid' - end - - context 'with negated ellipsis' do - let(:pattern) { '(send !...)' } - - it_behaves_like 'invalid' - end - - context 'with doubled ellipsis' do - let(:pattern) { '(send ... ...)' } - - it_behaves_like 'invalid' - end - end - - describe '.descend' do - let(:ruby) { '[[1, 2], 3]' } - - it 'yields all children depth first' do - e = described_class.descend(node) - expect(e.instance_of?(Enumerator)).to be(true) - array, three = node.children - one, two = array.children - expect(e.to_a).to eq([node, array, one, 1, two, 2, three, 3]) - end - - it 'yields the given argument if it is not a Node' do - expect(described_class.descend(42).to_a).to eq([42]) - end - end -end diff --git a/spec/rubocop/processed_source_spec.rb b/spec/rubocop/processed_source_spec.rb deleted file mode 100644 index 7c72d561624c..000000000000 --- a/spec/rubocop/processed_source_spec.rb +++ /dev/null @@ -1,419 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::ProcessedSource do - subject(:processed_source) { described_class.new(source, ruby_version, path) } - - let(:source) { <<~RUBY } - # an awesome method - def some_method - puts 'foo' - end - some_method - RUBY - let(:path) { 'ast/and_node_spec.rb' } - - describe '.from_file' do - describe 'when the file exists' do - around do |example| - org_pwd = Dir.pwd - Dir.chdir(__dir__) - example.run - Dir.chdir(org_pwd) - end - - let(:processed_source) { described_class.from_file(path, ruby_version) } - - it 'returns an instance of ProcessedSource' do - expect(processed_source.is_a?(described_class)).to be(true) - end - - it "sets the file path to the instance's #path" do - expect(processed_source.path).to eq(path) - end - end - - it 'raises RuboCop::Error when the file does not exist' do - expect do - described_class.from_file('foo', ruby_version) - end.to raise_error(RuboCop::Error) - .with_message(/No such file or directory/) - end - end - - describe '#path' do - it 'is the path passed to .new' do - expect(processed_source.path).to eq(path) - end - end - - describe '#buffer' do - it 'is a source buffer' do - expect(processed_source.buffer.is_a?(Parser::Source::Buffer)).to be(true) - end - end - - describe '#ast' do - it 'is the root node of AST' do - expect(processed_source.ast.is_a?(RuboCop::AST::Node)).to be(true) - end - end - - describe '#comments' do - it 'is an array of comments' do - expect(processed_source.comments.is_a?(Array)).to be(true) - expect( - processed_source.comments.first.is_a?(Parser::Source::Comment) - ).to be(true) - end - end - - describe '#tokens' do - it 'has an array of tokens' do - expect(processed_source.tokens.is_a?(Array)).to be(true) - expect(processed_source.tokens.first.is_a?(RuboCop::Token)).to be(true) - end - end - - shared_context 'invalid encoding source' do - let(:source) do - [ - "# \xf9" - ].join("\n") - end - end - - describe '#parser_error' do - context 'when the source was properly parsed' do - it 'is nil' do - expect(processed_source.parser_error.nil?).to be(true) - end - end - - context 'when the source lacks encoding comment and is really utf-8 ' \ - 'encoded but has been read as US-ASCII' do - let(:source) do - # When files are read into RuboCop, the encoding of source code - # lacking an encoding comment will default to the external encoding, - # which could for example be US-ASCII if the LC_ALL environment - # variable is set to "C". - (+'号码 = 3').force_encoding('US-ASCII') - end - - it 'is nil' do - # ProcessedSource#parse sets UTF-8 as default encoding, so no error. - expect(processed_source.parser_error.nil?).to be(true) - end - end - - context 'when the source could not be parsed due to encoding error' do - include_context 'invalid encoding source' - - it 'returns the error' do - expect(processed_source.parser_error.is_a?(Exception)).to be(true) - expect(processed_source.parser_error.message) - .to include('invalid byte sequence') - end - end - end - - describe '#lines' do - it 'is an array' do - expect(processed_source.lines.is_a?(Array)).to be(true) - end - - it 'has same number of elements as line count' do - # Since the source has a trailing newline, there is a final empty line - expect(processed_source.lines.size).to eq(6) - end - - it 'contains lines as string without linefeed' do - first_line = processed_source.lines.first - expect(first_line).to eq('# an awesome method') - end - end - - describe '#[]' do - context 'when an index is passed' do - it 'returns the line' do - expect(processed_source[3]).to eq('end') - end - end - - context 'when a range is passed' do - it 'returns the array of lines' do - expect(processed_source[3..4]).to eq(%w[end some_method]) - end - end - - context 'when start index and length are passed' do - it 'returns the array of lines' do - expect(processed_source[3, 2]).to eq(%w[end some_method]) - end - end - end - - describe 'valid_syntax?' do - subject { processed_source.valid_syntax? } - - context 'when the source is completely valid' do - let(:source) { 'def valid_code; end' } - - it 'returns true' do - expect(processed_source.diagnostics.empty?).to be(true) - expect(processed_source.valid_syntax?).to be(true) - end - end - - context 'when the source is invalid' do - let(:source) { 'def invalid_code; en' } - - it 'returns false' do - expect(processed_source.valid_syntax?).to be(false) - end - end - - context 'when the source is valid but has some warning diagnostics' do - let(:source) { 'do_something *array' } - - it 'returns true' do - expect(processed_source.diagnostics.empty?).to be(false) - expect(processed_source.diagnostics.first.level).to eq(:warning) - expect(processed_source.valid_syntax?).to be(true) - end - end - - context 'when the source could not be parsed due to encoding error' do - include_context 'invalid encoding source' - - it 'returns false' do - expect(processed_source.valid_syntax?).to be(false) - end - end - - # https://github.com/whitequark/parser/issues/283 - context 'when the source itself is valid encoding but includes strange ' \ - 'encoding literals that are accepted by MRI' do - let(:source) do - 'p "\xff"' - end - - it 'returns true' do - expect(processed_source.diagnostics.empty?).to be(true) - expect(processed_source.valid_syntax?).to be(true) - end - end - - context 'when a line starts with an integer literal' do - let(:source) { '1 + 1' } - - # regression test - it 'tokenizes the source correctly' do - expect(processed_source.tokens[0].text).to eq '1' - end - end - end - - context 'with heavily commented source' do - let(:source) { <<~RUBY } - def foo # comment one - bar # comment two - end # comment three - foo - RUBY - - describe '#each_comment' do - it 'yields all comments' do - comments = [] - - processed_source.each_comment do |item| - expect(item.is_a?(Parser::Source::Comment)).to be true - comments << item - end - - expect(comments.size).to eq 3 - end - end - - describe '#find_comment' do - it 'yields correct comment' do - comment = processed_source.find_comment do |item| - item.text == '# comment three' - end - - expect(comment.text).to eq '# comment three' - end - - it 'yields nil when there is no match' do - comment = processed_source.find_comment do |item| - item.text == '# comment four' - end - - expect(comment).to eq nil - end - end - - describe '#commented?' do - let(:source) { <<~RUBY } - # comment - [ 1, 2 ] - RUBY - - context 'provided source_range on line with comment' do - it 'returns true' do - bracket_range = processed_source.find_token(&:left_bracket?).pos - expect(processed_source.commented?(bracket_range)).to be false - end - end - - context 'provided source_range on line without comment' do - it 'returns false' do - comment_range = processed_source.find_token(&:comment?).pos - expect(processed_source.commented?(comment_range)).to be true - end - end - end - - describe '#comments_before_line' do - let(:source) { <<~RUBY } - # comment one - # comment two - [ 1, 2 ] - # comment three - RUBY - - it 'returns comments on or before given line' do - expect(processed_source.comments_before_line(1).size).to eq 1 - expect(processed_source.comments_before_line(2).size).to eq 2 - expect(processed_source.comments_before_line(3).size).to eq 2 - expect(processed_source.comments_before_line(4).size).to eq 3 - - expect(processed_source.comments_before_line(1) - .first - .is_a?(Parser::Source::Comment)).to be true - end - end - end - - context 'token enumerables' do - let(:source) { <<~RUBY } - foo(1, 2) - RUBY - - describe '#each_token' do - it 'yields all tokens' do - tokens = [] - - processed_source.each_token do |item| - expect(item.is_a?(RuboCop::Token)).to be true - tokens << item - end - - expect(tokens.size).to eq 7 - end - end - - describe '#find_token' do - it 'yields correct token' do - token = processed_source.find_token(&:comma?) - - expect(token.text).to eq ',' - end - - it 'yields nil when there is no match' do - token = processed_source.find_token(&:right_bracket?) - - expect(token).to eq nil - end - end - end - - describe '#file_path' do - it 'returns file path' do - expect(processed_source.file_path).to eq path - end - end - - describe '#blank?' do - context 'with source of no content' do - let(:source) { <<~RUBY } - RUBY - - it 'returns true' do - expect(processed_source.blank?).to eq true - end - end - - context 'with source with content' do - let(:source) { <<~RUBY } - foo - RUBY - - it 'returns false' do - expect(processed_source.blank?).to eq false - end - end - end - - describe '#start_with?' do - context 'with blank source' do - let(:source) { <<~RUBY } - RUBY - - it 'returns false' do - expect(processed_source.start_with?('start')).to eq false - expect(processed_source.start_with?('#')).to eq false - expect(processed_source.start_with?('')).to eq false - end - end - - context 'with present source' do - let(:source) { <<~RUBY } - foo - RUBY - - it 'returns true when passed string that starts source' do - expect(processed_source.start_with?('foo')).to eq true - expect(processed_source.start_with?('f')).to eq true - expect(processed_source.start_with?('')).to eq true - end - - it 'returns false when passed string that does not start source' do - expect(processed_source.start_with?('bar')).to eq false - expect(processed_source.start_with?('qux')).to eq false - expect(processed_source.start_with?('1')).to eq false - end - end - end - - describe '#preceding_line' do - let(:source) { <<~RUBY } - [ line, 1 ] - { line: 2 } - # line 3 - RUBY - - it 'returns source of line before token' do - brace_token = processed_source.find_token(&:left_brace?) - expect(processed_source.preceding_line(brace_token)).to eq '[ line, 1 ]' - - comment_token = processed_source.find_token(&:comment?) - expect(processed_source.preceding_line(comment_token)).to eq '{ line: 2 }' - end - end - - describe '#following_line' do - let(:source) { <<~RUBY } - [ line, 1 ] - { line: 2 } - # line 3 - RUBY - - it 'returns source of line after token' do - bracket_token = processed_source.find_token(&:right_bracket?) - expect(processed_source.following_line(bracket_token)).to eq '{ line: 2 }' - - brace_token = processed_source.find_token(&:left_brace?) - expect(processed_source.following_line(brace_token)).to eq '# line 3' - end - end -end diff --git a/spec/rubocop/token_spec.rb b/spec/rubocop/token_spec.rb deleted file mode 100644 index abb205eb87eb..000000000000 --- a/spec/rubocop/token_spec.rb +++ /dev/null @@ -1,361 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Token do - let(:processed_source) { parse_source(source) } - - let(:source) { <<~RUBY } - # comment - def some_method - [ 1, 2 ]; - foo[0] = 3 - end - RUBY - - let(:first_token) { processed_source.tokens.first } - let(:comment_token) do - processed_source.find_token do |t| - t.text.start_with?('#') && t.line == 1 - end - end - - let(:left_array_bracket_token) do - processed_source.find_token { |t| t.text == '[' && t.line == 3 } - end - let(:comma_token) { processed_source.find_token { |t| t.text == ',' } } - let(:right_array_bracket_token) do - processed_source.find_token { |t| t.text == ']' && t.line == 3 } - end - let(:semicolon_token) { processed_source.find_token { |t| t.text == ';' } } - - let(:left_ref_bracket_token) do - processed_source.find_token { |t| t.text == '[' && t.line == 4 } - end - let(:zero_token) { processed_source.find_token { |t| t.text == '0' } } - let(:right_ref_bracket_token) do - processed_source.find_token { |t| t.text == ']' && t.line == 4 } - end - let(:equals_token) { processed_source.find_token { |t| t.text == '=' } } - - let(:end_token) { processed_source.find_token { |t| t.text == 'end' } } - - describe '.from_parser_token' do - subject(:token) { described_class.from_parser_token(parser_token) } - - let(:parser_token) { [type, [text, range]] } - let(:type) { :kDEF } - let(:text) { 'def' } - let(:range) do - instance_double(Parser::Source::Range, line: 42, column: 30) - end - - it "sets parser token's type to rubocop token's type" do - expect(token.type).to eq(type) - end - - it "sets parser token's text to rubocop token's text" do - expect(token.text).to eq(text) - end - - it "sets parser token's range to rubocop token's pos" do - expect(token.pos).to eq(range) - end - - it 'returns a #to_s useful for debugging' do - expect(token.to_s).to eq('[[42, 30], kDEF, "def"]') - end - end - - describe '#line' do - it 'returns line of token' do - expect(first_token.line).to eq 1 - expect(zero_token.line).to eq 4 - expect(end_token.line).to eq 5 - end - end - - describe '#column' do - it 'returns index of first char in token range on that line' do - expect(first_token.column).to eq 0 - expect(zero_token.column).to eq 6 - expect(end_token.column).to eq 0 - end - end - - describe '#begin_pos' do - it 'returns index of first char in token range of entire source' do - expect(first_token.begin_pos).to eq 0 - expect(zero_token.begin_pos).to eq 44 - expect(end_token.begin_pos).to eq 51 - end - end - - describe '#end_pos' do - it 'returns index of last char in token range of entire source' do - expect(first_token.end_pos).to eq 9 - expect(zero_token.end_pos).to eq 45 - expect(end_token.end_pos).to eq 54 - end - end - - describe '#space_after' do - it 'returns truthy MatchData when there is a space after token' do - expect(left_array_bracket_token.space_after?.is_a?(MatchData)).to be true - expect(right_ref_bracket_token.space_after?.is_a?(MatchData)).to be true - - expect(left_array_bracket_token.space_after?).to be_truthy - expect(right_ref_bracket_token.space_after?).to be_truthy - end - - it 'returns nil when there is not a space after token' do - expect(left_ref_bracket_token.space_after?).to be nil - expect(zero_token.space_after?).to be nil - end - end - - describe '#to_s' do - it 'returns string of token data' do - expect(end_token.to_s).to include end_token.line.to_s - expect(end_token.to_s).to include end_token.column.to_s - expect(end_token.to_s).to include end_token.type.to_s - expect(end_token.to_s).to include end_token.text.to_s - end - end - - describe '#space_before' do - it 'returns truthy MatchData when there is a space before token' do - expect(left_array_bracket_token.space_before?.is_a?(MatchData)).to be true - expect(equals_token.space_before?.is_a?(MatchData)).to be true - - expect(left_array_bracket_token.space_before?).to be_truthy - expect(equals_token.space_before?).to be_truthy - end - - it 'returns nil when there is not a space before token' do - expect(semicolon_token.space_before?).to be nil - expect(zero_token.space_before?).to be nil - end - - it 'returns nil when it is on the first line' do - expect(processed_source.tokens[0].space_before?).to be nil - end - end - - context 'type predicates' do - describe '#comment?' do - it 'returns true for comment tokens' do - expect(comment_token.comment?).to be true - end - - it 'returns false for non comment tokens' do - expect(zero_token.comment?).to be false - expect(semicolon_token.comment?).to be false - end - end - - describe '#semicolon?' do - it 'returns true for semicolon tokens' do - expect(semicolon_token.semicolon?).to be true - end - - it 'returns false for non semicolon tokens' do - expect(comment_token.semicolon?).to be false - expect(comma_token.semicolon?).to be false - end - end - - describe '#left_array_bracket?' do - it 'returns true for left_array_bracket tokens' do - expect(left_array_bracket_token.left_array_bracket?).to be true - end - - it 'returns false for non left_array_bracket tokens' do - expect(left_ref_bracket_token.left_array_bracket?).to be false - expect(right_array_bracket_token.left_array_bracket?).to be false - end - end - - describe '#left_ref_bracket?' do - it 'returns true for left_ref_bracket tokens' do - expect(left_ref_bracket_token.left_ref_bracket?).to be true - end - - it 'returns false for non left_ref_bracket tokens' do - expect(left_array_bracket_token.left_ref_bracket?).to be false - expect(right_ref_bracket_token.left_ref_bracket?).to be false - end - end - - describe '#left_bracket?' do - it 'returns true for all left_bracket tokens' do - expect(left_ref_bracket_token.left_bracket?).to be true - expect(left_array_bracket_token.left_bracket?).to be true - end - - it 'returns false for non left_bracket tokens' do - expect(right_ref_bracket_token.left_bracket?).to be false - expect(right_array_bracket_token.left_bracket?).to be false - end - end - - describe '#right_bracket?' do - it 'returns true for all right_bracket tokens' do - expect(right_ref_bracket_token.right_bracket?).to be true - expect(right_array_bracket_token.right_bracket?).to be true - end - - it 'returns false for non right_bracket tokens' do - expect(left_ref_bracket_token.right_bracket?).to be false - expect(left_array_bracket_token.right_bracket?).to be false - end - end - - describe '#left_brace?' do - it 'returns true for right_bracket tokens' do - expect(right_ref_bracket_token.right_bracket?).to be true - expect(right_array_bracket_token.right_bracket?).to be true - end - - it 'returns false for non right_bracket tokens' do - expect(left_ref_bracket_token.right_bracket?).to be false - expect(left_array_bracket_token.right_bracket?).to be false - end - end - - describe '#comma?' do - it 'returns true for comma tokens' do - expect(comma_token.comma?).to be true - end - - it 'returns false for non comma tokens' do - expect(semicolon_token.comma?).to be false - expect(right_ref_bracket_token.comma?).to be false - end - end - - describe '#rescue_modifier?' do - let(:source) { <<~RUBY } - def foo - bar rescue qux - end - RUBY - - let(:rescue_modifier_token) do - processed_source.find_token { |t| t.text == 'rescue' } - end - - it 'returns true for rescue modifier tokens' do - expect(rescue_modifier_token.rescue_modifier?).to be true - end - - it 'returns false for non rescue modifier tokens' do - expect(first_token.rescue_modifier?).to be false - expect(end_token.rescue_modifier?).to be false - end - end - - describe '#end?' do - it 'returns true for end tokens' do - expect(end_token.end?).to be true - end - - it 'returns false for non end tokens' do - expect(semicolon_token.end?).to be false - expect(comment_token.end?).to be false - end - end - - describe '#equals_sign?' do - it 'returns true for equals sign tokens' do - expect(equals_token.equal_sign?).to be true - end - - it 'returns false for non equals sign tokens' do - expect(semicolon_token.equal_sign?).to be false - expect(comma_token.equal_sign?).to be false - end - end - - context 'with braces & parens' do - let(:source) { <<~RUBY } - { a: 1 } - foo { |f| bar(f) } - RUBY - - let(:left_hash_brace_token) do - processed_source.find_token { |t| t.text == '{' && t.line == 1 } - end - let(:right_hash_brace_token) do - processed_source.find_token { |t| t.text == '}' && t.line == 1 } - end - - let(:left_block_brace_token) do - processed_source.find_token { |t| t.text == '{' && t.line == 2 } - end - let(:left_parens_token) do - processed_source.find_token { |t| t.text == '(' } - end - let(:right_parens_token) do - processed_source.find_token { |t| t.text == ')' } - end - let(:right_block_brace_token) do - processed_source.find_token { |t| t.text == '}' && t.line == 2 } - end - - describe '#left_brace?' do - it 'returns true for left hash brace tokens' do - expect(left_hash_brace_token.left_brace?).to be true - end - - it 'returns false for non left hash brace tokens' do - expect(left_block_brace_token.left_brace?).to be false - expect(right_hash_brace_token.left_brace?).to be false - end - end - - describe '#left_curly_brace?' do - it 'returns true for left block brace tokens' do - expect(left_block_brace_token.left_curly_brace?).to be true - end - - it 'returns false for non left block brace tokens' do - expect(left_hash_brace_token.left_curly_brace?).to be false - expect(right_block_brace_token.left_curly_brace?).to be false - end - end - - describe '#right_curly_brace?' do - it 'returns true for all right brace tokens' do - expect(right_hash_brace_token.right_curly_brace?).to be true - expect(right_block_brace_token.right_curly_brace?).to be true - end - - it 'returns false for non right brace tokens' do - expect(left_hash_brace_token.right_curly_brace?).to be false - expect(left_parens_token.right_curly_brace?).to be false - end - end - - describe '#left_parens?' do - it 'returns true for left parens tokens' do - expect(left_parens_token.left_parens?).to be true - end - - it 'returns false for non left parens tokens' do - expect(left_hash_brace_token.left_parens?).to be false - expect(right_parens_token.left_parens?).to be false - end - end - - describe '#right_parens?' do - it 'returns true for right parens tokens' do - expect(right_parens_token.right_parens?).to be true - end - - it 'returns false for non right parens tokens' do - expect(right_hash_brace_token.right_parens?).to be false - expect(left_parens_token.right_parens?).to be false - end - end - end - end -end