From 41bbeb1d87d5948ef0fae82084e90c4acfd6b477 Mon Sep 17 00:00:00 2001
From: Peter Leitzen <pleitzen@gitlab.com>
Date: Tue, 30 May 2023 18:14:50 +0000
Subject: [PATCH] Cop/IgnoredColumns: Flag use of `self.ignored_columns =
 [...]`

Previously, this cop only flagged `self.ignored_columns += [...]`.

Because of this change the cop rule is put back in "grace period" to
maintain `master` stability.
---
 .rubocop_todo/cop/ignored_columns.yml         |  8 +++
 ...e_end_string_concatenation_indentation.yml |  1 -
 .rubocop_todo/naming/inclusive_language.yml   |  1 -
 .../rspec/missing_feature_category.yml        |  1 -
 rubocop/cop/ignored_columns.rb                | 52 +++++++++++++------
 spec/rubocop/cop/ignored_columns_spec.rb      | 27 ++++++----
 6 files changed, 62 insertions(+), 28 deletions(-)
 create mode 100644 .rubocop_todo/cop/ignored_columns.yml

diff --git a/.rubocop_todo/cop/ignored_columns.yml b/.rubocop_todo/cop/ignored_columns.yml
new file mode 100644
index 0000000000000..eaba218d38578
--- /dev/null
+++ b/.rubocop_todo/cop/ignored_columns.yml
@@ -0,0 +1,8 @@
+---
+Cop/IgnoredColumns:
+  Details: grace period
+  Exclude:
+    - 'app/models/loose_foreign_keys/deleted_record.rb'
+    - 'ee/lib/ee/gitlab/background_migration/create_vulnerability_links.rb'
+    - 'ee/lib/ee/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb'
+    - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
diff --git a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
index d1b6eddb465d6..b7fdd3979bccb 100644
--- a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
+++ b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
@@ -215,7 +215,6 @@ Layout/LineEndStringConcatenationIndentation:
     - 'rubocop/cop/graphql/descriptions.rb'
     - 'rubocop/cop/graphql/resolver_type.rb'
     - 'rubocop/cop/group_public_or_visible_to_user.rb'
-    - 'rubocop/cop/ignored_columns.rb'
     - 'rubocop/cop/inject_enterprise_edition_module.rb'
     - 'rubocop/cop/migration/add_concurrent_index.rb'
     - 'rubocop/cop/migration/add_limit_to_text_columns.rb'
diff --git a/.rubocop_todo/naming/inclusive_language.yml b/.rubocop_todo/naming/inclusive_language.yml
index 465e338bbe73d..8950e2e77f4ff 100644
--- a/.rubocop_todo/naming/inclusive_language.yml
+++ b/.rubocop_todo/naming/inclusive_language.yml
@@ -53,7 +53,6 @@ Naming/InclusiveLanguage:
     - 'rubocop/cop/destroy_all.rb'
     - 'rubocop/cop/graphql/id_type.rb'
     - 'rubocop/cop/group_public_or_visible_to_user.rb'
-    - 'rubocop/cop/ignored_columns.rb'
     - 'rubocop/cop/inject_enterprise_edition_module.rb'
     - 'rubocop/cop/migration/add_columns_to_wide_tables.rb'
     - 'spec/controllers/concerns/issuable_collections_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 992295c1bcb69..f73bafcaf3c8e 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -5312,7 +5312,6 @@ RSpec/MissingFeatureCategory:
     - 'spec/rubocop/cop/graphql/old_types_spec.rb'
     - 'spec/rubocop/cop/graphql/resolver_type_spec.rb'
     - 'spec/rubocop/cop/group_public_or_visible_to_user_spec.rb'
-    - 'spec/rubocop/cop/ignored_columns_spec.rb'
     - 'spec/rubocop/cop/include_sidekiq_worker_spec.rb'
     - 'spec/rubocop/cop/inject_enterprise_edition_module_spec.rb'
     - 'spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb'
diff --git a/rubocop/cop/ignored_columns.rb b/rubocop/cop/ignored_columns.rb
index 542d78c59e959..8b17447c46b7a 100644
--- a/rubocop/cop/ignored_columns.rb
+++ b/rubocop/cop/ignored_columns.rb
@@ -2,40 +2,60 @@
 
 module RuboCop
   module Cop
-    # Cop that blacklists the usage of `ActiveRecord::Base.ignored_columns=` directly
+    # Cop that flags the usage of `ActiveRecord::Base.ignored_columns=` directly
+    #
+    # @example
+    #   # bad
+    #   class User < ApplicationRecord
+    #     self.ignored_columns = [:name]
+    #     self.ignored_columns += [:full_name]
+    #   end
+    #
+    #   # good
+    #   class User < ApplicationRecord
+    #     include IgnorableColumns
+    #
+    #     ignore_column :name, remove_after: '2023-05-22', remove_with: '16.0'
+    #     ignore_column :full_name, remove_after: '2023-05-22', remove_with: '16.0'
+    #   end
     class IgnoredColumns < RuboCop::Cop::Base
-      USE_CONCERN_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.'
-      WRONG_MODEL_MSG = 'If the model exists in CE and EE, the column has to be ignored ' \
-        'in the CE model. If the model only exists in EE, then it has to be added there.'
+      USE_CONCERN_ADD_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.'
+      USE_CONCERN_SET_MSG = 'Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.'
+      WRONG_MODEL_MSG = <<~MSG
+        If the model exists in CE and EE, the column has to be ignored
+        in the CE model. If the model only exists in EE, then it has to be added there.
+      MSG
 
-      def_node_matcher :ignored_columns?, <<~PATTERN
+      RESTRICT_ON_SEND = %i[ignored_columns ignored_columns= ignore_column ignore_columns].freeze
+
+      def_node_matcher :ignored_columns_add?, <<~PATTERN
         (send (self) :ignored_columns)
       PATTERN
 
-      def_node_matcher :ignore_columns?, <<~PATTERN
-        (send nil? :ignore_columns ...)
+      def_node_matcher :ignored_columns_set?, <<~PATTERN
+        (send (self) :ignored_columns= ...)
       PATTERN
 
-      def_node_matcher :ignore_column?, <<~PATTERN
-        (send nil? :ignore_column ...)
+      def_node_matcher :using_ignore_columns?, <<~PATTERN
+        (send nil? {:ignore_columns :ignore_column}...)
       PATTERN
 
       def on_send(node)
-        if ignored_columns?(node)
-          add_offense(node, message: USE_CONCERN_MSG)
+        if ignored_columns_add?(node)
+          add_offense(node.loc.selector, message: USE_CONCERN_ADD_MSG)
+        end
+
+        if ignored_columns_set?(node)
+          add_offense(node.loc.selector, message: USE_CONCERN_SET_MSG)
         end
 
-        if using_ignore?(node) && used_in_wrong_model?
+        if using_ignore_columns?(node) && used_in_wrong_model?
           add_offense(node, message: WRONG_MODEL_MSG)
         end
       end
 
       private
 
-      def using_ignore?(node)
-        ignore_columns?(node) || ignore_column?(node)
-      end
-
       def used_in_wrong_model?
         file_path = processed_source.file_path
 
diff --git a/spec/rubocop/cop/ignored_columns_spec.rb b/spec/rubocop/cop/ignored_columns_spec.rb
index c6c44399624b8..8d2c6b92c7098 100644
--- a/spec/rubocop/cop/ignored_columns_spec.rb
+++ b/spec/rubocop/cop/ignored_columns_spec.rb
@@ -3,12 +3,21 @@
 require 'rubocop_spec_helper'
 require_relative '../../../rubocop/cop/ignored_columns'
 
-RSpec.describe RuboCop::Cop::IgnoredColumns do
-  it 'flags direct use of ignored_columns instead of the IgnoredColumns concern' do
+RSpec.describe RuboCop::Cop::IgnoredColumns, feature_category: :database do
+  it 'flags use of `self.ignored_columns +=` instead of the IgnoredColumns concern' do
     expect_offense(<<~RUBY)
       class Foo < ApplicationRecord
         self.ignored_columns += %i[id]
-        ^^^^^^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.
+             ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.
+      end
+    RUBY
+  end
+
+  it 'flags use of `self.ignored_columns =` instead of the IgnoredColumns concern' do
+    expect_offense(<<~RUBY)
+      class Foo < ApplicationRecord
+        self.ignored_columns = %i[id]
+             ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.
       end
     RUBY
   end
@@ -16,7 +25,7 @@ class Foo < ApplicationRecord
   context 'when only CE model exist' do
     let(:file_path) { full_path('app/models/bar.rb') }
 
-    it 'does not flag ignore_columns usage in CE model' do
+    it 'does not flag `ignore_columns` usage in CE model' do
       expect_no_offenses(<<~RUBY, file_path)
         class Bar < ApplicationRecord
           ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@@ -24,7 +33,7 @@ class Bar < ApplicationRecord
       RUBY
     end
 
-    it 'flags ignore_column usage in EE model' do
+    it 'does not flag `ignore_column` usage in CE model' do
       expect_no_offenses(<<~RUBY, file_path)
         class Baz < ApplicationRecord
           ignore_column :bar, remove_with: '14.3', remove_after: '2021-09-22'
@@ -40,7 +49,7 @@ class Baz < ApplicationRecord
       allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(false)
     end
 
-    it 'flags ignore_columns usage in EE model' do
+    it 'does not flag `ignore_columns` usage in EE model' do
       expect_no_offenses(<<~RUBY, file_path)
         class Bar < ApplicationRecord
           ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@@ -48,7 +57,7 @@ class Bar < ApplicationRecord
       RUBY
     end
 
-    it 'flags ignore_column usage in EE model' do
+    it 'does not flag `ignore_column` usage in EE model' do
       expect_no_offenses(<<~RUBY, file_path)
         class Bar < ApplicationRecord
           ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22'
@@ -64,7 +73,7 @@ class Bar < ApplicationRecord
       allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(true)
     end
 
-    it 'flags ignore_columns usage in EE model' do
+    it 'flags `ignore_columns` usage in EE model' do
       expect_offense(<<~RUBY, file_path)
         class Bar < ApplicationRecord
           ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@@ -73,7 +82,7 @@ class Bar < ApplicationRecord
       RUBY
     end
 
-    it 'flags ignore_column usage in EE model' do
+    it 'flags `ignore_column` usage in EE model' do
       expect_offense(<<~RUBY, file_path)
         class Bar < ApplicationRecord
           ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22'
-- 
GitLab