From 91c40973dc620430b173ea2df968587d2a708dff Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 27 Aug 2018 17:30:20 +0200 Subject: Added RuboCop cops to enforce code reuse rules These Cops enforces the code reuse rules as defined in merge request https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21254. --- rubocop/code_reuse_helpers.rb | 156 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 rubocop/code_reuse_helpers.rb (limited to 'rubocop/code_reuse_helpers.rb') diff --git a/rubocop/code_reuse_helpers.rb b/rubocop/code_reuse_helpers.rb new file mode 100644 index 00000000000..0929a55d901 --- /dev/null +++ b/rubocop/code_reuse_helpers.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +module RuboCop + module CodeReuseHelpers + # Returns true for a `(send const ...)` node. + def send_to_constant?(node) + node.type == :send && node.children&.first&.type == :const + end + + # Returns `true` if the name of the receiving constant ends with a given + # `String`. + def send_receiver_name_ends_with?(node, suffix) + return false unless send_to_constant?(node) + + receiver_name = name_of_receiver(node) + + receiver_name != suffix && + receiver_name.end_with?(suffix) + end + + # Returns the file path (as a `String`) for an AST node. + def file_path_for_node(node) + node.location.expression.source_buffer.name + end + + # Returns the name of a constant node. + # + # Given the AST node `(const nil :Foo)`, this method will return `:Foo`. + def name_of_constant(node) + node.children[1] + end + + # Returns true if the given node resides in app/finders or ee/app/finders. + def in_finder?(node) + in_directory?(node, 'finders') + end + + # Returns true if the given node resides in app/models or ee/app/models. + def in_model?(node) + in_directory?(node, 'models') + end + + # Returns true if the given node resides in app/services or ee/app/services. + def in_service_class?(node) + in_directory?(node, 'services') + end + + # Returns true if the given node resides in app/presenters or + # ee/app/presenters. + def in_presenter?(node) + in_directory?(node, 'presenters') + end + + # Returns true if the given node resides in app/serializers or + # ee/app/serializers. + def in_serializer?(node) + in_directory?(node, 'serializers') + end + + # Returns true if the given node resides in app/workers or ee/app/workers. + def in_worker?(node) + in_directory?(node, 'workers') + end + + # Returns true if the given node resides in app/controllers or + # ee/app/controllers. + def in_controller?(node) + in_directory?(node, 'controllers') + end + + # Returns true if the given node resides in lib/api or ee/lib/api. + def in_api?(node) + file_path_for_node(node).start_with?( + File.join(ce_lib_directory, 'api'), + File.join(ee_lib_directory, 'api') + ) + end + + # Returns `true` if the given AST node resides in the given directory, + # relative to app and/or ee/app. + def in_directory?(node, directory) + file_path_for_node(node).start_with?( + File.join(ce_app_directory, directory), + File.join(ee_app_directory, directory) + ) + end + + # Returns the receiver name of a send node. + # + # For the AST node `(send (const nil :Foo) ...)` this would return + # `'Foo'`. + def name_of_receiver(node) + name_of_constant(node.children.first).to_s + end + + # Yields every defined class method in the given AST node. + def each_class_method(node) + return to_enum(__method__, node) unless block_given? + + # class << self + # def foo + # end + # end + node.each_descendant(:sclass) do |sclass| + sclass.each_descendant(:def) do |def_node| + yield def_node + end + end + + # def self.foo + # end + node.each_descendant(:defs) do |defs_node| + yield defs_node + end + end + + # Yields every send node found in the given AST node. + def each_send_node(node, &block) + node.each_descendant(:send, &block) + end + + # Registers a RuboCop offense for a `(send)` node with a receiver that ends + # with a given suffix. + # + # node - The AST node to check. + # suffix - The suffix of the receiver name, such as "Finder". + # message - The message to use for the offense. + def disallow_send_to(node, suffix, message) + each_send_node(node) do |send_node| + next unless send_receiver_name_ends_with?(send_node, suffix) + + add_offense(send_node, location: :expression, message: message) + end + end + + def ce_app_directory + File.join(rails_root, 'app') + end + + def ee_app_directory + File.join(rails_root, 'ee', 'app') + end + + def ce_lib_directory + File.join(rails_root, 'lib') + end + + def ee_lib_directory + File.join(rails_root, 'ee', 'lib') + end + + def rails_root + File.expand_path('..', __dir__) + end + end +end -- cgit v1.2.3