From 9e967ed28605e0296e71452210154f8bc5fc98ab Mon Sep 17 00:00:00 2001 From: Peter Leitzen <pleitzen@gitlab.com> Date: Mon, 15 May 2023 07:26:28 +0200 Subject: [PATCH] Add and enable cop Graphql/ResourceNotAvailable Current offenses are excludes via rake rubocop:todo:generate[Graphql/ResourceNotAvailableError] Put this cop in grace period. --- .rubocop.yml | 5 ++ .../graphql/resource_not_available_error.yml | 42 +++++++++++++++++ .../graphql/resource_not_available_error.rb | 47 +++++++++++++++++++ rubocop/node_pattern_helper.rb | 14 ++++++ .../resource_not_available_error_spec.rb | 37 +++++++++++++++ spec/rubocop/node_pattern_helper_spec.rb | 20 ++++++++ 6 files changed, 165 insertions(+) create mode 100644 .rubocop_todo/graphql/resource_not_available_error.yml create mode 100644 rubocop/cop/graphql/resource_not_available_error.rb create mode 100644 rubocop/node_pattern_helper.rb create mode 100644 spec/rubocop/cop/graphql/resource_not_available_error_spec.rb create mode 100644 spec/rubocop/node_pattern_helper_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 81ad4cd31f354..9ceb846627056 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1007,3 +1007,8 @@ SidekiqLoadBalancing/WorkerDataConsistency: # This cop is disabled for Ruby 3.0+ anyway. Lint/NonDeterministicRequireOrder: Enabled: false + +Graphql/ResourceNotAvailableError: + Exclude: + # Definition of `raise_resource_not_available_error!` + - 'lib/gitlab/graphql/authorize/authorize_resource.rb' diff --git a/.rubocop_todo/graphql/resource_not_available_error.yml b/.rubocop_todo/graphql/resource_not_available_error.yml new file mode 100644 index 0000000000000..68e68c51277c7 --- /dev/null +++ b/.rubocop_todo/graphql/resource_not_available_error.yml @@ -0,0 +1,42 @@ +--- +# Cop supports --autocorrect. +Graphql/ResourceNotAvailableError: + Details: grace period + Exclude: + - 'app/graphql/mutations/achievements/create.rb' + - 'app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb' + - 'app/graphql/mutations/ci/ci_cd_settings_update.rb' + - 'app/graphql/mutations/ci/job_artifact/bulk_destroy.rb' + - 'app/graphql/mutations/ci/runner/create.rb' + - 'app/graphql/mutations/custom_emoji/create.rb' + - 'app/graphql/mutations/custom_emoji/destroy.rb' + - 'app/graphql/mutations/design_management/move.rb' + - 'app/graphql/mutations/issues/bulk_update.rb' + - 'app/graphql/mutations/issues/set_crm_contacts.rb' + - 'app/graphql/mutations/issues/set_escalation_status.rb' + - 'app/graphql/mutations/notes/create/base.rb' + - 'app/graphql/mutations/notes/create/note.rb' + - 'app/graphql/mutations/notes/reposition_image_diff_note.rb' + - 'app/graphql/mutations/notes/update/image_diff_note.rb' + - 'app/graphql/mutations/saved_replies/create.rb' + - 'app/graphql/mutations/saved_replies/destroy.rb' + - 'app/graphql/mutations/saved_replies/update.rb' + - 'app/graphql/mutations/todos/mark_all_done.rb' + - 'app/graphql/mutations/work_items/export.rb' + - 'app/graphql/resolvers/ci/runner_setup_resolver.rb' + - 'app/graphql/resolvers/concerns/search_arguments.rb' + - 'app/graphql/resolvers/container_repository_tags_resolver.rb' + - 'app/graphql/resolvers/design_management/versions_resolver.rb' + - 'app/graphql/resolvers/kas/agent_configurations_resolver.rb' + - 'app/graphql/resolvers/kas/agent_connections_resolver.rb' + - 'app/graphql/resolvers/projects/snippets_resolver.rb' + - 'app/graphql/types/container_repository_details_type.rb' + - 'app/graphql/types/container_repository_type.rb' + - 'ee/app/graphql/ee/types/query_type.rb' + - 'ee/app/graphql/mutations/ai/action.rb' + - 'ee/app/graphql/mutations/audit_events/instance_external_audit_event_destinations/base.rb' + - 'ee/app/graphql/mutations/issues/set_escalation_policy.rb' + - 'ee/app/graphql/mutations/projects/set_locked.rb' + - 'ee/app/graphql/resolvers/incident_management/oncall_shifts_resolver.rb' + - 'ee/app/graphql/resolvers/product_analytics/visualization_resolver.rb' + - 'ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb' diff --git a/rubocop/cop/graphql/resource_not_available_error.rb b/rubocop/cop/graphql/resource_not_available_error.rb new file mode 100644 index 0000000000000..d759e14500874 --- /dev/null +++ b/rubocop/cop/graphql/resource_not_available_error.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative '../../node_pattern_helper' + +module RuboCop + module Cop + module Graphql + # Encourages the use of `raise_resource_not_available_error!` method + # instead of `raise Gitlab::Graphql::Errors::ResourceNotAvailable`. + # + # @example + # + # # bad + # raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'message' + # + # # good + # raise_resource_not_available_error! 'message' + class ResourceNotAvailableError < Base + extend NodePatternHelper + extend AutoCorrector + + MSG = 'Prefer using `raise_resource_not_available_error!` instead.' + + EXCEPTION = 'Gitlab::Graphql::Errors::ResourceNotAvailable' + + RESTRICT_ON_SEND = %i[raise].freeze + + def_node_matcher :error, const_pattern(EXCEPTION) + + def_node_matcher :raise_error, <<~PATTERN + (send nil? :raise #error $...) + PATTERN + + def on_send(node) + raise_error(node) do |args| + add_offense(node) do |corrector| + replacement = +'raise_resource_not_available_error!' + replacement << " #{args.map(&:source).join(', ')}" if args.any? + + corrector.replace(node, replacement) + end + end + end + end + end + end +end diff --git a/rubocop/node_pattern_helper.rb b/rubocop/node_pattern_helper.rb new file mode 100644 index 0000000000000..ecd4560763c5b --- /dev/null +++ b/rubocop/node_pattern_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module RuboCop + module NodePatternHelper + # Returns a nested `(const ...)` node pattern for a full qualified +name+. + # + # @examples + # const_pattern 'Foo::Bar' # => (const (const {nil? cbase} :Foo) :Bar) + # const_pattern 'Foo::Bar', parent: ':Baz' # => (const (const :Baz :Foo) :Bar) + def const_pattern(name, parent: '{nil? cbase}') + name.split('::').inject(parent) { |memo, name_part| "(const #{memo} :#{name_part})" } + end + end +end diff --git a/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb b/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb new file mode 100644 index 0000000000000..6003b9f39546b --- /dev/null +++ b/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rubocop_spec_helper' + +require_relative '../../../../rubocop/cop/graphql/resource_not_available_error' + +RSpec.describe RuboCop::Cop::Graphql::ResourceNotAvailableError, feature_category: :shared do + shared_examples 'flagging and auto-correction' do |exception| + it "flags and auto-corrects `raise #{exception}`" do + expect_offense(<<~'RUBY', exception: exception) + raise %{exception} + ^^^^^^^{exception} Prefer using `raise_resource_not_available_error!` instead. + + raise %{exception}, 'message ' \ + ^^^^^^^{exception}^^^^^^^^^^^^^^ Prefer using `raise_resource_not_available_error!` instead. + 'with new lines' + RUBY + + expect_correction(<<~'RUBY') + raise_resource_not_available_error! + + raise_resource_not_available_error! 'message ' \ + 'with new lines' + RUBY + end + end + + it_behaves_like 'flagging and auto-correction', 'Gitlab::Graphql::Errors::ResourceNotAvailable' + it_behaves_like 'flagging and auto-correction', '::Gitlab::Graphql::Errors::ResourceNotAvailable' + + it 'does not flag unrelated exceptions' do + expect_no_offenses(<<~RUBY) + raise Gitlab::Graphql::Errors::ResourceVeryAvailable + raise ::Gitlab::Graphql::Errors::ResourceVeryAvailable + RUBY + end +end diff --git a/spec/rubocop/node_pattern_helper_spec.rb b/spec/rubocop/node_pattern_helper_spec.rb new file mode 100644 index 0000000000000..a141e81b61868 --- /dev/null +++ b/spec/rubocop/node_pattern_helper_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rubocop_spec_helper' + +require_relative '../../rubocop/node_pattern_helper' + +RSpec.describe RuboCop::NodePatternHelper, feature_category: :tooling do + include described_class + + describe '#const_pattern' do + it 'returns nested const node patterns' do + expect(const_pattern('Foo')).to eq('(const {nil? cbase} :Foo)') + expect(const_pattern('Foo::Bar')).to eq('(const (const {nil? cbase} :Foo) :Bar)') + end + + it 'returns nested const node patterns with custom parent' do + expect(const_pattern('Foo::Bar', parent: 'nil?')).to eq('(const (const nil? :Foo) :Bar)') + end + end +end -- GitLab