diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb index e9adc1e0d..a8fabb72b 100644 --- a/lib/rubocop/ast/node.rb +++ b/lib/rubocop/ast/node.rb @@ -139,12 +139,15 @@ def #{recursive_kind} # def recursive_litera # Define a +_type?+ predicate method for the given node type. private_class_method def self.def_node_type_predicate(name, type = name) + @node_types_with_documented_predicate_method << type + class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{name}_type? # def block_type? @type == :#{type} # @type == :block end # end RUBY end + @node_types_with_documented_predicate_method = [] # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize def initialize(type, children = EMPTY_CHILDREN, properties = EMPTY_PROPERTIES) @@ -317,6 +320,14 @@ def send_type? # separately to make this check as fast as possible. false end + @node_types_with_documented_predicate_method << :send + + # Ensure forward compatibility with new node types, by defining methods for unknown node types too. + # Note these won't get auto-generated documentation, which is why we prefer defining them above. + (Parser::Meta::NODE_TYPES - @node_types_with_documented_predicate_method).each do |node_type| + method_name = :"#{node_type.to_s.gsub(/\W/, '')}_type?" + define_method(method_name) { false } + end # @!endgroup diff --git a/spec/rubocop/ast/node_spec.rb b/spec/rubocop/ast/node_spec.rb index d161e7f6d..5d70dda9e 100644 --- a/spec/rubocop/ast/node_spec.rb +++ b/spec/rubocop/ast/node_spec.rb @@ -1110,13 +1110,34 @@ class << expr end end - describe '*_type? methods on Node' do - Parser::Meta::NODE_TYPES.each do |node_type| - method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?" + Parser::Meta::NODE_TYPES.each do |node_type| + node_name = node_type.to_s.gsub(/\W/, '') + method_name = :"#{node_name}_type?" - it "is not of #{method_name}" do + describe "##{method_name}" do + it 'is false' do expect(described_class.allocate.public_send(method_name)).to be(false) end + + it 'is documented' do + is_documented = described_class + .instance_variable_get(:@node_types_with_documented_predicate_method) + .include?(node_type) + + failure_message = <<~MSG + #{described_class.name}##{method_name} is not documented as it was generated automatically as a fallback. + + To fix this, define it using the following macro instead: + + class #{described_class.name} < #{described_class.superclass.name} + # ... + def_node_type_predicate :#{node_name}#{unless node_type.to_s == node_name + ", :#{node_type}" + end} + MSG + + expect(is_documented).to be(true), failure_message + end end end end