diff --git a/changelog/change_update_namingfilename_to_recognize.md b/changelog/change_update_namingfilename_to_recognize.md new file mode 100644 index 00000000000..3425206c7f6 --- /dev/null +++ b/changelog/change_update_namingfilename_to_recognize.md @@ -0,0 +1 @@ +* [#10221](https://github.com/rubocop/rubocop/issues/10221): Update `Naming::FileName` to recognize `Struct`s as classes that satisfy the `ExpectMatchingDefinition` requirement. ([@dvandersluis][]) diff --git a/config/default.yml b/config/default.yml index 3b2d09403ac..afc44267d95 100644 --- a/config/default.yml +++ b/config/default.yml @@ -2506,6 +2506,7 @@ Naming/FileName: StyleGuide: '#snake-case-files' Enabled: true VersionAdded: '0.50' + VersionChanged: '<>' # Camel case file names listed in `AllCops:Include` and all file names listed # in `AllCops:Exclude` are excluded by default. Add extra excludes here. Exclude: [] diff --git a/lib/rubocop/cop/naming/file_name.rb b/lib/rubocop/cop/naming/file_name.rb index ef9ef0bb1a6..a1740f4878e 100644 --- a/lib/rubocop/cop/naming/file_name.rb +++ b/lib/rubocop/cop/naming/file_name.rb @@ -33,6 +33,14 @@ class FileName < Base SNAKE_CASE = /^[\d[[:lower:]]_.?!]+$/.freeze + # @!method struct_definition(node) + def_node_matcher :struct_definition, <<~PATTERN + { + (casgn $_ $_ (send (const {nil? cbase} :Struct) :new ...)) + (casgn $_ $_ (block (send (const {nil? cbase} :Struct) :new ...) ...)) + } + PATTERN + def on_new_investigation file_path = processed_source.file_path return if config.file_to_exclude?(file_path) || config.allowed_camel_case_file?(file_path) @@ -126,7 +134,7 @@ def find_class_or_module(node, namespace) name = namespace.pop on_node(%i[class module casgn], node) do |child| - next unless (const = child.defined_module) + next unless (const = find_definition(child)) const_namespace, const_name = *const next if name != const_name && !match_acronym?(name, const_name) @@ -138,6 +146,15 @@ def find_class_or_module(node, namespace) nil end + def find_definition(node) + node.defined_module || defined_struct(node) + end + + def defined_struct(node) + namespace, name = *struct_definition(node) + s(:const, namespace, name) if name + end + def match_namespace(node, namespace, expected) match_partial = partial_matcher!(expected) diff --git a/spec/rubocop/cop/naming/file_name_spec.rb b/spec/rubocop/cop/naming/file_name_spec.rb index 37e2ec0c2ff..3ea874d5792 100644 --- a/spec/rubocop/cop/naming/file_name_spec.rb +++ b/spec/rubocop/cop/naming/file_name_spec.rb @@ -251,6 +251,23 @@ class B end RUBY end + + context 'on a file which defines a Struct without a block' do + include_examples 'matching module or class', <<~RUBY + module A + B = Struct.new(:foo, :bar) + end + RUBY + end + + context 'on a file which defines a Struct with a block' do + include_examples 'matching module or class', <<~RUBY + module A + B = Struct.new(:foo, :bar) do + end + end + RUBY + end end context 'when CheckDefinitionPathHierarchy is false' do @@ -285,6 +302,23 @@ class PictureCollection end end + context 'on a file with a matching struct' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY, '/lib/image_collection.rb') + ImageCollection = Struct.new + RUBY + end + end + + context 'on a file with a non-matching struct' do + it 'registers an offense' do + expect_offense(<<~RUBY, '/lib/image_collection.rb') + PictureCollection = Struct.new + ^ `image_collection.rb` should define a class or module called `ImageCollection`. + RUBY + end + end + context 'on an empty file' do it 'registers an offense' do expect_offense(<<~RUBY, '/lib/rubocop/foo.rb') @@ -329,6 +363,30 @@ class NonMatching RUBY end end + + context 'with a non-matching module containing a matching struct' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY, 'lib/foo.rb') + begin + module NonMatching + Foo = Struct.new + end + end + RUBY + end + end + + context 'with a matching module containing a non-matching struct' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY, 'lib/foo.rb') + begin + module Foo + NonMatching = Struct.new + end + end + RUBY + end + end end context 'when Regex is set' do