diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb index e9adc1e0d..1b6fa44cd 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. + # 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..02958d52d 100644 --- a/spec/rubocop/ast/node_spec.rb +++ b/spec/rubocop/ast/node_spec.rb @@ -1110,13 +1110,43 @@ 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 + expect(node_type_predicate_is_documented?(node_type)).to( + be(true), + missing_documentation_failure_message(method_name, node_name, node_type) + ) + end + + private + + def node_type_predicate_is_documented?(node_type) + described_class + .instance_variable_get(:@node_types_with_documented_predicate_method) + .include?(node_type) + end + + def missing_documentation_failure_message(method_name, node_name, node_type) + name_matches_type = node_type.to_s == node_name + + <<~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}#{", :#{node_type}" unless name_matches_type} + MSG + end end end end