diff options
Diffstat (limited to 'rubocop/cop/gitlab/namespaced_class.rb')
-rw-r--r-- | rubocop/cop/gitlab/namespaced_class.rb | 59 |
1 files changed, 51 insertions, 8 deletions
diff --git a/rubocop/cop/gitlab/namespaced_class.rb b/rubocop/cop/gitlab/namespaced_class.rb index 1f1fd280922..914cc8720b9 100644 --- a/rubocop/cop/gitlab/namespaced_class.rb +++ b/rubocop/cop/gitlab/namespaced_class.rb @@ -5,33 +5,76 @@ module RuboCop module Gitlab # Cop that enforces use of namespaced classes in order to better identify # high level domains within the codebase. - + # # @example # # bad # class MyClass # end # + # module Gitlab + # class MyClass + # end + # end + # + # class Gitlab::MyClass + # end + # # # good # module MyDomain # class MyClass # end # end - + # + # module Gitlab + # module MyDomain + # class MyClass + # end + # end + # end + # + # class Gitlab::MyDomain::MyClass + # end class NamespacedClass < RuboCop::Cop::Cop MSG = 'Classes must be declared inside a module indicating a product domain namespace. For more info: https://gitlab.com/gitlab-org/gitlab/-/issues/212156' - def_node_matcher :compact_namespaced_class?, <<~PATTERN - (class (const (const ...) ...) ...) - PATTERN + # These namespaces are considered top-level semantically. + # Note: Nested namespace like Foo::Bar are also supported. + PSEUDO_TOPLEVEL = %w[Gitlab] + .map { _1.split('::') }.freeze def on_module(node) - @namespaced = true + add_potential_domain_namespace(node) end def on_class(node) - return if @namespaced + # Add potential namespaces from compact definitions like `class Foo::Bar`. + # Remove class name because it's not a domain namespace. + add_potential_domain_namespace(node) { _1.pop } + + add_offense(node) if domain_namespaces.none? + end + + private + + def domain_namespaces + @domain_namespaces ||= [] + end + + def add_potential_domain_namespace(node) + return if domain_namespaces.any? + + identifiers = identifiers_for(node) + yield(identifiers) if block_given? + + PSEUDO_TOPLEVEL.each do |namespaces| + identifiers.shift(namespaces.size) if namespaces == identifiers.first(namespaces.size) + end + + domain_namespaces.concat(identifiers) + end - add_offense(node) unless compact_namespaced_class?(node) + def identifiers_for(node) + node.identifier.source.sub(/^::/, '').split('::') end end end |