Add cop to make sure we don't use ivar in a module
This commit is contained in:
		
							parent
							
								
									4cadf22e20
								
							
						
					
					
						commit
						9ae92b8caa
					
				|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module BoardsResponses | ||||
|   def authorize_read_list | ||||
|     authorize_action_for!(board.parent, :read_list) | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module CreatesCommit | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| module CycleAnalyticsParams | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def options(params) | ||||
|     @options ||= { from: start_date(params), current_user: current_user } | ||||
|   end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module IssuableActions | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module IssuableCollections | ||||
|   extend ActiveSupport::Concern | ||||
|   include SortingHelper | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module IssuesAction | ||||
|   extend ActiveSupport::Concern | ||||
|   include IssuableCollections | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ module LfsRequest | |||
|     has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def storage_project | ||||
|     @storage_project ||= begin | ||||
|       result = project | ||||
|  | @ -103,6 +104,7 @@ module LfsRequest | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def objects | ||||
|     @objects ||= (params[:objects] || []).to_a | ||||
|   end | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ module MembershipActions | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def source_type | ||||
|     @source_type ||= membershipable.class.to_s.humanize(capitalize: false) | ||||
|   end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module MergeRequestsAction | ||||
|   extend ActiveSupport::Concern | ||||
|   include IssuableCollections | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module MilestoneActions | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module NotesActions | ||||
|   include RendersNotes | ||||
|   extend ActiveSupport::Concern | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ module OauthApplications | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def load_scopes | ||||
|     @scopes = Doorkeeper.configuration.scopes | ||||
|   end | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| module RendersCommits | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def prepare_commits_for_rendering(commits) | ||||
|     Banzai::CommitRenderer.render(commits, @project, current_user) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| module RendersNotes | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def prepare_notes_for_rendering(notes, noteable = nil) | ||||
|     preload_noteable_for_regular_notes(notes) | ||||
|     preload_max_access_for_authors(notes, @project) | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ module RequiresWhitelistedMonitoringClient | |||
|     ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) } | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def ip_whitelist | ||||
|     @ip_whitelist ||= Settings.monitoring.ip_whitelist.map(&IPAddr.method(:new)) | ||||
|   end | ||||
|  |  | |||
|  | @ -65,6 +65,7 @@ module ServiceParams | |||
|   # Parameters to ignore if no value is specified | ||||
|   FILTER_BLANK_PARAMS = [:password].freeze | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def service_params | ||||
|     dynamic_params = @service.event_channel_names + @service.event_names | ||||
|     service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params) | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ module SnippetsActions | |||
|   def edit | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def raw | ||||
|     disposition = params[:inline] == 'false' ? 'attachment' : 'inline' | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ module SpammableActions | |||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def ensure_spam_config_loaded! | ||||
|     return @spam_config_loaded if defined?(@spam_config_loaded) | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ module ToggleSubscriptionAction | |||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def subscribable_project | ||||
|     @project || raise(NotImplementedError) | ||||
|   end | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ module IgnorableColumn | |||
|       super.reject { |column| ignored_columns.include?(column.name) } | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def ignored_columns | ||||
|       @ignored_columns ||= Set.new | ||||
|     end | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| # | ||||
| # Used by Issue, Note, MergeRequest, and Commit. | ||||
| # | ||||
| # # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Mentionable | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ module Milestoneish | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def memoize_per_user(user, method_name) | ||||
|     @memoized ||= {} | ||||
|     @memoized[method_name] ||= {} | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ module Noteable | |||
|   # | ||||
|   #   noteable.class           # => MergeRequest | ||||
|   #   noteable.human_class_name # => "merge request" | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def human_class_name | ||||
|     @human_class_name ||= base_class_name.titleize.downcase | ||||
|   end | ||||
|  | @ -34,6 +35,7 @@ module Noteable | |||
| 
 | ||||
|   delegate :find_discussion, to: :discussion_notes | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def discussions | ||||
|     @discussions ||= discussion_notes | ||||
|       .inc_relations_for_view | ||||
|  | @ -46,6 +48,7 @@ module Noteable | |||
|     notes.inc_relations_for_view.grouped_diff_discussions(*args) | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def resolvable_discussions | ||||
|     @resolvable_discussions ||= | ||||
|       if defined?(@discussions) | ||||
|  | @ -67,6 +70,7 @@ module Noteable | |||
|     discussions_resolvable? && !discussions_resolved? | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def discussions_to_be_resolved | ||||
|     @discussions_to_be_resolved ||= resolvable_discussions.select(&:to_be_resolved?) | ||||
|   end | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ module Participable | |||
|   # This method processes attributes of objects in breadth-first order. | ||||
|   # | ||||
|   # Returns an Array of User instances. | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def participants(current_user = nil) | ||||
|     @participants ||= Hash.new do |hash, user| | ||||
|       hash[user] = raw_participants(user) | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ module ProtectedRef | |||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def ref_matcher | ||||
|     @ref_matcher ||= ProtectedRefMatcher.new(self) | ||||
|   end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module RelativePositioning | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module ResolvableDiscussion | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| # Store object full path in separate table for easy lookup and uniq validation | ||||
| # Object must have name and path db fields and respond to parent and parent_changed? methods. | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Routable | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ module Spammable | |||
|   end | ||||
| 
 | ||||
|   def spam? | ||||
|     @spam | ||||
|     spam | ||||
|   end | ||||
| 
 | ||||
|   def check_for_spam | ||||
|  |  | |||
|  | @ -49,6 +49,7 @@ module Storage | |||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def old_repository_storage_paths | ||||
|       @old_repository_storage_paths ||= repository_storage_paths | ||||
|     end | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ module StripAttribute | |||
|       strip_attrs.concat(attrs) | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def strip_attrs | ||||
|       @strip_attrs ||= [] | ||||
|     end | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ require 'task_list/filter' | |||
| # bugs". | ||||
| # | ||||
| # Used by MergeRequest and Issue | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Taskable | ||||
|   COMPLETED    = 'completed'.freeze | ||||
|   INCOMPLETE   = 'incomplete'.freeze | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| # | ||||
| # Used by Issue and MergeRequest. | ||||
| # | ||||
| 
 | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module TimeTrackable | ||||
|   extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,11 +2,13 @@ module Issues | |||
|   module ResolveDiscussions | ||||
|     attr_reader :merge_request_to_resolve_discussions_of_iid, :discussion_to_resolve_id | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def filter_resolve_discussion_params | ||||
|       @merge_request_to_resolve_discussions_of_iid ||= params.delete(:merge_request_to_resolve_discussions_of) | ||||
|       @discussion_to_resolve_id ||= params.delete(:discussion_to_resolve) | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def merge_request_to_resolve_discussions_of | ||||
|       return @merge_request_to_resolve_discussions_of if defined?(@merge_request_to_resolve_discussions_of) | ||||
| 
 | ||||
|  | @ -15,6 +17,7 @@ module Issues | |||
|                                                      .find_by(iid: merge_request_to_resolve_discussions_of_iid) | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def discussions_to_resolve | ||||
|       return [] unless merge_request_to_resolve_discussions_of | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| # Dependencies: | ||||
| # - params with :request | ||||
| # | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module SpamCheckService | ||||
|   def filter_spam_check_params | ||||
|     @request            = params.delete(:request) | ||||
|  |  | |||
|  | @ -662,6 +662,7 @@ module SystemNoteService | |||
|     Rack::Utils.escape_html(text) | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def url_helpers | ||||
|     @url_helpers ||= Gitlab::Routing.url_helpers | ||||
|   end | ||||
|  |  | |||
|  | @ -8,12 +8,14 @@ module NewIssuable | |||
|     user && issuable | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def set_user(user_id) | ||||
|     @user = User.find_by(id: user_id) | ||||
| 
 | ||||
|     log_error(User, user_id) unless @user | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def set_issuable(issuable_id) | ||||
|     @issuable = issuable_class.find_by(id: issuable_id) | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ module LocalCacheRegistryCleanupWithEnsure | |||
|   LocalStore = | ||||
|     ActiveSupport::Cache::Strategy::LocalCache::LocalStore | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def call(env) | ||||
|     LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) | ||||
|     response = @app.call(env) | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ module RspecProfilingExt | |||
|   end | ||||
| 
 | ||||
|   module Run | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def example_finished(*args) | ||||
|       super | ||||
|     rescue => err | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| # anyway, and there is no great efficiency gain from just fetching the listed | ||||
| # attributes with our implementation, so we ignore the additional arguments. | ||||
| # | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Rugged | ||||
|   class Repository | ||||
|     module UseGitlabGitAttributes | ||||
|  |  | |||
|  | @ -0,0 +1,183 @@ | |||
| ## Usually modules with instance variables considered harmful | ||||
| 
 | ||||
| ### Background | ||||
| 
 | ||||
| Rails somehow encourages people using modules and instance variables | ||||
| everywhere. For example, using instance variables in the controllers, | ||||
| helpers, and views. They're also encouraging the use of | ||||
| `ActiveSupport::Concern`, which further strengthens the idea of | ||||
| saving everything in a giant, single object, and people could access | ||||
| everything in that one giant object. | ||||
| 
 | ||||
| ### The problems | ||||
| 
 | ||||
| Of course this is convenient to develop, because we just have everything | ||||
| within reach. However this has a number of downsides when that chosen object | ||||
| is growing, it would later become out of control for the same reason. | ||||
| 
 | ||||
| There are just too many things in the same context, and we don't know if | ||||
| those things are tightly coupled or not, depending on each others or not. | ||||
| It's very hard to tell when the complexity grows to a point, and it makes | ||||
| tracking the code also extremely hard. For example, a class could be using | ||||
| 3 different instance variables, and all of them could be initialized and | ||||
| manipulated from 3 different modules. It's hard to track when those variables | ||||
| start giving us troubles. We don't know which module would suddenly change | ||||
| one of the variables. Everything could touch anything. | ||||
| 
 | ||||
| ### Similar concerns | ||||
| 
 | ||||
| People are saying multiple inheritance is bad. Mixing multiple modules with | ||||
| multiple instance variables scattering everywhere suffer from the same issue. | ||||
| The same applies to `ActiveSupport::Concern`. See: | ||||
| [Consider replacing concerns with dedicated classes & composition]( | ||||
| https://gitlab.com/gitlab-org/gitlab-ce/issues/23786) | ||||
| 
 | ||||
| There's also a similar idea: | ||||
| [Use decorators and interface segregation to solve overgrowing models problem]( | ||||
| https://gitlab.com/gitlab-org/gitlab-ce/issues/13484) | ||||
| 
 | ||||
| Note that `included` doesn't solve the whole issue. They define the | ||||
| dependencies, but they still allow each modules to talk implicitly via the | ||||
| instance variables in the final giant object, and that's where the problem is. | ||||
| 
 | ||||
| ### Solutions | ||||
| 
 | ||||
| We should split the giant object into multiple objects, and they communicate | ||||
| each other with the API, i.e. public methods. In short, composition over | ||||
| inheritance. This way, each smaller objects would have their own respective | ||||
| limited states, i.e. instance variables. If one instance variable goes wrong, | ||||
| we would be very clear that it's from that single small object, because | ||||
| no one else could be touching it. | ||||
| 
 | ||||
| With clearly defined API, this would make things less coupled and much easier | ||||
| to debug and track, and much more extensible for other objects to use, because | ||||
| they communicate in a clear way, rather than implicit dependencies. | ||||
| 
 | ||||
| ### Exceptions | ||||
| 
 | ||||
| However, it's not all that bad when using instance variables in a module, | ||||
| as long as it's contained in the same module, that is no other modules or | ||||
| objects are touching them. If that's the case, then it would be an acceptable | ||||
| use. Unfortunately it's a bit hard to code this principle in the cop, so | ||||
| for now we rely on people turning off the cops, if they think that the use | ||||
| conform this rule. | ||||
| 
 | ||||
| Here's an acceptable case: | ||||
| 
 | ||||
| ``` ruby | ||||
| # This is ok, as long as `@attributes` is never used anywhere else. | ||||
| # Consider adding some prefix or suffix to avoid name conflicts though. | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Rugged | ||||
|   class Repository | ||||
|     module UseGitlabGitAttributes | ||||
|       def fetch_attributes(name, *) | ||||
|         @attributes ||= Gitlab::Git::Attributes.new(path) | ||||
|         @attributes.attributes(name) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     prepend UseGitlabGitAttributes | ||||
|   end | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| Here's a bad example which we should rewrite: | ||||
| 
 | ||||
| ``` ruby | ||||
| module SpamCheckService | ||||
|   def filter_spam_check_params | ||||
|     @request            = params.delete(:request) | ||||
|     @api                = params.delete(:api) | ||||
|     @recaptcha_verified = params.delete(:recaptcha_verified) | ||||
|     @spam_log_id        = params.delete(:spam_log_id) | ||||
|   end | ||||
| 
 | ||||
|   def spam_check(spammable, user) | ||||
|     spam_service = SpamService.new(spammable, @request) | ||||
| 
 | ||||
|     spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do | ||||
|       user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| There are several implicit dependencies here. First, `params` should be | ||||
| defined before using. Second, `filter_spam_check_params` should be called | ||||
| before `spam_check`. These are all implicit and the includer could be using | ||||
| those instance variables without awareness. | ||||
| 
 | ||||
| This should be rewritten like: | ||||
| 
 | ||||
| ``` ruby | ||||
| class SpamCheckService | ||||
|   def initialize(request:, api:, recaptcha_verified:, spam_log_id:) | ||||
|     @request            = request | ||||
|     @api                = api | ||||
|     @recaptcha_verified = recaptcha_verified | ||||
|     @spam_log_id        = spam_log_id | ||||
|   end | ||||
| 
 | ||||
|   def spam_check(spammable, user) | ||||
|     spam_service = SpamService.new(spammable, @request) | ||||
| 
 | ||||
|     spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do | ||||
|       user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| And use it like: | ||||
| 
 | ||||
| ``` ruby | ||||
| class UpdateSnippetService < BaseService | ||||
|   def execute | ||||
|     # ... | ||||
|     spam = SpamCheckService.new(params.slice!(:request, :api, :recaptcha_verified, :spam_log_id)) | ||||
| 
 | ||||
|     spam.check(snippet, current_user) | ||||
|     # ... | ||||
|   end | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| This way, all those instance variables are isolated in `SpamCheckService` | ||||
| rather than who ever include the module, and those modules which were also | ||||
| included, making it much easier to track down the issues if there's any, | ||||
| and it also reduce the chance of having name conflicts. | ||||
| 
 | ||||
| ### Things we might need to ignore right now | ||||
| 
 | ||||
| Since the way how Rails helpers and mailers work, we might not be able to | ||||
| avoid the use of instance variables there. For those cases, we could ignore | ||||
| them at the moment. At least we're not going to share those modules with | ||||
| other random objects, so they're still somehow isolated. | ||||
| 
 | ||||
| ### Instance variables in the views | ||||
| 
 | ||||
| They're terrible, because they're also shared between different controllers, | ||||
| and it's very hard to track where those instance variables were set when we | ||||
| saw somewhere is using it, neither do we know where those were used when we | ||||
| saw somewhere is setting up them. We hit into a number of 500 errors when we | ||||
| tried to remove some instance variables in the controller in the past. | ||||
| 
 | ||||
| Somewhere, some partials might be using it, and we don't know. | ||||
| 
 | ||||
| We're trying to use something like this instead: | ||||
| 
 | ||||
| ``` haml | ||||
| = render 'projects/commits/commit', commit: commit, ref: ref, project: project | ||||
| ``` | ||||
| 
 | ||||
| And in the partial: | ||||
| 
 | ||||
| ``` haml | ||||
| - ref = local_assigns.fetch(:ref) | ||||
| - commit = local_assigns.fetch(:commit) | ||||
| - project = local_assigns.fetch(:project) | ||||
| ``` | ||||
| 
 | ||||
| This way it's very clear where those values were coming from. In the future, | ||||
| we should also forbid the use of instance variables in partials. | ||||
|  | @ -42,11 +42,11 @@ module StdoutReporterWithScenarioLocation | |||
|   # Override the standard reporter to show filename and line number next to each | ||||
|   # scenario for easy, focused re-runs | ||||
|   def before_scenario_run(scenario, step_definitions = nil) | ||||
|     @max_step_name_length = scenario.steps.map(&:name).map(&:length).max if scenario.steps.any? | ||||
|     max_step_name_length = scenario.steps.map(&:name).map(&:length).max if scenario.steps.any? | ||||
|     name = scenario.name | ||||
| 
 | ||||
|     # This number has no significance, it's just to line things up | ||||
|     max_length = @max_step_name_length + 19 | ||||
|     max_length = max_step_name_length + 19 | ||||
|     out.puts "\n  #{'Scenario:'.green} #{name.light_green.ljust(max_length)}" \ | ||||
|       " # #{scenario.feature.filename}:#{scenario.line}" | ||||
|   end | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ module AfterCommitQueue | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|   def _after_commit_queue | ||||
|     @after_commit_queue ||= [] | ||||
|   end | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| require 'rack/oauth2' | ||||
| 
 | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module API | ||||
|   module APIGuard | ||||
|     extend ActiveSupport::Concern | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module API | ||||
|   module Helpers | ||||
|     include Gitlab::Utils | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module API | ||||
|   module Helpers | ||||
|     module InternalHelpers | ||||
|  | @ -57,6 +58,7 @@ module API | |||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def set_project | ||||
|         if params[:gl_repository] | ||||
|           @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository]) | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ module API | |||
|         forbidden! unless current_runner | ||||
|       end | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def current_runner | ||||
|         @runner ||= ::Ci::Runner.find_by_token(params[:token].to_s) | ||||
|       end | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| # Module providing methods for dealing with separating a tree-ish string and a | ||||
| # file path string when combined in a request parameter | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module ExtractsPath | ||||
|   # Raised when given an invalid file path | ||||
|   InvalidPathError = Class.new(StandardError) | ||||
|  |  | |||
|  | @ -45,6 +45,7 @@ module Gitlab | |||
|         klass.prepend(extension) | ||||
|       end | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def request_cache_key(&block) | ||||
|         if block_given? | ||||
|           @request_cache_key = block | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ module Gitlab | |||
|   module Ci | ||||
|     module Charts | ||||
|       module DailyInterval | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         def grouped_count(query) | ||||
|           query | ||||
|             .group("DATE(#{::Ci::Pipeline.table_name}.created_at)") | ||||
|  | @ -9,6 +10,7 @@ module Gitlab | |||
|             .transform_keys { |date| date.strftime(@format) } | ||||
|         end | ||||
| 
 | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         def interval_step | ||||
|           @interval_step ||= 1.day | ||||
|         end | ||||
|  | @ -28,6 +30,7 @@ module Gitlab | |||
|           end | ||||
|         end | ||||
| 
 | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         def interval_step | ||||
|           @interval_step ||= 1.month | ||||
|         end | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ module Gitlab | |||
|         #   script: ... | ||||
|         #   artifacts: ... | ||||
|         # | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         module Configurable | ||||
|           extend ActiveSupport::Concern | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module Ci | ||||
|     class Config | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ module Gitlab | |||
|         "ci_" | ||||
|       end | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def model_name | ||||
|         @model_name ||= ActiveModel::Name.new(self, nil, self.name.split("::").last) | ||||
|       end | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ module Gitlab | |||
|       ::ApplicationSetting.create_from_defaults || in_memory_application_settings | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def in_memory_application_settings | ||||
|       @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) | ||||
|     rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError | ||||
|  |  | |||
|  | @ -59,6 +59,12 @@ module Gitlab | |||
|         nil | ||||
|       end | ||||
| 
 | ||||
|       def load_allowed_ids | ||||
|         allowed_ids_finder_class | ||||
|           .new(@options[:current_user], project_id: @project.id) | ||||
|           .execute.where(id: event_result_ids).pluck(:id) | ||||
|       end | ||||
| 
 | ||||
|       def event_result_ids | ||||
|         event_result.map { |event| event['id'] } | ||||
|       end | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ module Gitlab | |||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def base_query | ||||
|         @base_query ||= stage_query | ||||
|       end | ||||
|  |  | |||
|  | @ -1,8 +1,6 @@ | |||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     class CodeEventFetcher < BaseEventFetcher | ||||
|       include MergeRequestAllowed | ||||
| 
 | ||||
|       def initialize(*args) | ||||
|         @projections = [mr_table[:title], | ||||
|                         mr_table[:iid], | ||||
|  | @ -20,6 +18,14 @@ module Gitlab | |||
|       def serialize(event) | ||||
|         AnalyticsMergeRequestSerializer.new(project: @project).represent(event) | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids | ||||
|         load_allowed_ids | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids_finder_class | ||||
|         MergeRequestsFinder | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     module IssueAllowed | ||||
|       def allowed_ids | ||||
|         @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -1,8 +1,6 @@ | |||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     class IssueEventFetcher < BaseEventFetcher | ||||
|       include IssueAllowed | ||||
| 
 | ||||
|       def initialize(*args) | ||||
|         @projections = [issue_table[:title], | ||||
|                         issue_table[:iid], | ||||
|  | @ -18,6 +16,14 @@ module Gitlab | |||
|       def serialize(event) | ||||
|         AnalyticsIssueSerializer.new(project: @project).represent(event) | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids | ||||
|         load_allowed_ids | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids_finder_class | ||||
|         IssuesFinder | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     module MergeRequestAllowed | ||||
|       def allowed_ids | ||||
|         @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     module ProductionHelper | ||||
|  |  | |||
|  | @ -1,8 +1,6 @@ | |||
| module Gitlab | ||||
|   module CycleAnalytics | ||||
|     class ReviewEventFetcher < BaseEventFetcher | ||||
|       include MergeRequestAllowed | ||||
| 
 | ||||
|       def initialize(*args) | ||||
|         @projections = [mr_table[:title], | ||||
|                         mr_table[:iid], | ||||
|  | @ -14,9 +12,19 @@ module Gitlab | |||
|         super(*args) | ||||
|       end | ||||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       def serialize(event) | ||||
|         AnalyticsMergeRequestSerializer.new(project: @project).represent(event) | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids | ||||
|         load_allowed_ids | ||||
|       end | ||||
| 
 | ||||
|       def allowed_ids_finder_class | ||||
|         MergeRequestsFinder | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ module Gitlab | |||
|     module RenameReservedPathsMigration | ||||
|       module V1 | ||||
|         module MigrationClasses | ||||
|           # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|           module Routable | ||||
|             def full_path | ||||
|               if route && route.path.present? | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ module Gitlab | |||
|           raise NotImplementedError | ||||
|         end | ||||
| 
 | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         def message | ||||
|           @message ||= process_message | ||||
|         end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module Emoji | ||||
|     extend self | ||||
|  |  | |||
|  | @ -13,15 +13,15 @@ module Gitlab | |||
|         vars = { "PWD" => path } | ||||
|         options = { chdir: path } | ||||
| 
 | ||||
|         @cmd_output = "" | ||||
|         @cmd_status = 0 | ||||
|         cmd_output = "" | ||||
|         cmd_status = 0 | ||||
|         Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| | ||||
|           @cmd_output << stdout.read | ||||
|           @cmd_output << stderr.read | ||||
|           @cmd_status = wait_thr.value.exitstatus | ||||
|           cmd_output << stdout.read | ||||
|           cmd_output << stderr.read | ||||
|           cmd_status = wait_thr.value.exitstatus | ||||
|         end | ||||
| 
 | ||||
|         [@cmd_output, @cmd_status] | ||||
|         [cmd_output, cmd_status] | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ module Gitlab | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def identification_cache | ||||
|       @identification_cache ||= { | ||||
|         email: {}, | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ module Gitlab | |||
|         execute(%W(tar -#{options} #{archive} -C #{dir})) | ||||
|       end | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def execute(cmd) | ||||
|         output, status = Gitlab::Popen.popen(cmd) | ||||
|         @shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero? | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module Metrics | ||||
|     module InfluxDb | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| require 'prometheus/client' | ||||
| 
 | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module Metrics | ||||
|     module Prometheus | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module PathRegex | ||||
|     extend self | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ module Gitlab | |||
|         load_yaml_file&.map(&:deep_symbolize_keys).freeze | ||||
|       end | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def load_yaml_file | ||||
|         @loaded_yaml_file ||= YAML.load_file(Rails.root.join('config/prometheus/additional_metrics.yml')) | ||||
|       end | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ module Gitlab | |||
|           query | ||||
|         end | ||||
| 
 | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|         def available_metrics | ||||
|           @available_metrics ||= client_label_values || [] | ||||
|         end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module Regex | ||||
|     extend self | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   module SlashCommands | ||||
|     module Presenters | ||||
|  |  | |||
|  | @ -72,6 +72,7 @@ module Gitlab | |||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|     def default_id | ||||
|       @default_id ||= begin | ||||
|         id = Gitlab.config.gitlab.default_theme.to_i | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| require 'rainbow/ext/string' | ||||
| 
 | ||||
| # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
| module Gitlab | ||||
|   TaskFailedError = Class.new(StandardError) | ||||
|   TaskAbortedByUserError = Class.new(StandardError) | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ module QA | |||
|     module Namespace | ||||
|       extend self | ||||
| 
 | ||||
|       # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       def time | ||||
|         @time ||= Time.now | ||||
|       end | ||||
|  |  | |||
|  | @ -0,0 +1,55 @@ | |||
| module RuboCop | ||||
|   module Cop | ||||
|     class ModuleWithInstanceVariables < RuboCop::Cop::Cop | ||||
|       MSG = <<~EOL.freeze | ||||
|         Do not use instance variables in a module. Please read this | ||||
|         for the rationale behind it: | ||||
| 
 | ||||
|         doc/development/module_with_instance_variables.md | ||||
| 
 | ||||
|         If you think the use for this is fine, please just add: | ||||
|         # rubocop:disable Cop/ModuleWithInstanceVariables | ||||
|       EOL | ||||
| 
 | ||||
|       def on_module(node) | ||||
|         return if | ||||
|           rails_helper?(node) || rails_mailer?(node) || spec_helper?(node) | ||||
| 
 | ||||
|         check_method_definition(node) | ||||
| 
 | ||||
|         # Not sure why some module would have an extra begin wrapping around | ||||
|         node.each_child_node(:begin) do |begin_node| | ||||
|           check_method_definition(begin_node) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       # We ignore Rails helpers right now because it's hard to workaround it | ||||
|       def rails_helper?(node) | ||||
|         node.source_range.source_buffer.name =~ | ||||
|           %r{app/helpers/\w+_helper.rb\z} | ||||
|       end | ||||
| 
 | ||||
|       # We ignore Rails mailers right now because it's hard to workaround it | ||||
|       def rails_mailer?(node) | ||||
|         node.source_range.source_buffer.name =~ | ||||
|           %r{app/mailers/emails/} | ||||
|       end | ||||
| 
 | ||||
|       # We ignore spec helpers because it usually doesn't matter | ||||
|       def spec_helper?(node) | ||||
|         node.source_range.source_buffer.name =~ | ||||
|           %r{spec/support/|features/steps/} | ||||
|       end | ||||
| 
 | ||||
|       def check_method_definition(node) | ||||
|         node.each_child_node(:def) do |definition| | ||||
|           definition.each_descendant(:ivar, :ivasgn) do |offense| | ||||
|             add_offense(offense, :expression) | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -6,6 +6,7 @@ require_relative 'cop/polymorphic_associations' | |||
| require_relative 'cop/project_path_helper' | ||||
| require_relative 'cop/active_record_dependent' | ||||
| require_relative 'cop/in_batches' | ||||
| require_relative 'cop/module_with_instance_variables' | ||||
| require_relative 'cop/migration/add_column' | ||||
| require_relative 'cop/migration/add_column_with_default_to_large_table' | ||||
| require_relative 'cop/migration/add_concurrent_foreign_key' | ||||
|  |  | |||
|  | @ -0,0 +1,117 @@ | |||
| require 'spec_helper' | ||||
| require 'rubocop' | ||||
| require 'rubocop/rspec/support' | ||||
| require_relative '../../../rubocop/cop/module_with_instance_variables' | ||||
| 
 | ||||
| describe RuboCop::Cop::ModuleWithInstanceVariables do | ||||
|   include CopHelper | ||||
| 
 | ||||
|   subject(:cop) { described_class.new } | ||||
| 
 | ||||
|   shared_examples('registering offense') do | ||||
|     it 'registers an offense when instance variable is used in a module' do | ||||
|       inspect_source(cop, source) | ||||
| 
 | ||||
|       aggregate_failures do | ||||
|         expect(cop.offenses.size).to eq(offending_lines.size) | ||||
|         expect(cop.offenses.map(&:line)).to eq(offending_lines) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when source is a regular module' do | ||||
|     let(:source) do | ||||
|       <<~RUBY | ||||
|         module M | ||||
|           def f | ||||
|             @f ||= true | ||||
|           end | ||||
|         end | ||||
|       RUBY | ||||
|     end | ||||
| 
 | ||||
|     let(:offending_lines) { [3] } | ||||
| 
 | ||||
|     it_behaves_like 'registering offense' | ||||
|   end | ||||
| 
 | ||||
|   context 'when source is a nested module' do | ||||
|     let(:source) do | ||||
|       <<~RUBY | ||||
|         module N | ||||
|           module M | ||||
|             def f | ||||
|               @f = true | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|       RUBY | ||||
|     end | ||||
| 
 | ||||
|     let(:offending_lines) { [4] } | ||||
| 
 | ||||
|     it_behaves_like 'registering offense' | ||||
|   end | ||||
| 
 | ||||
|   context 'when source is a nested module with multiple offenses' do | ||||
|     let(:source) do | ||||
|       <<~RUBY | ||||
|         module N | ||||
|           module M | ||||
|             def f | ||||
|               @f ||= true | ||||
|             end | ||||
| 
 | ||||
|             def g | ||||
|               true | ||||
|             end | ||||
| 
 | ||||
|             def h | ||||
|               @h = true | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|       RUBY | ||||
|     end | ||||
| 
 | ||||
|     let(:offending_lines) { [4, 12] } | ||||
| 
 | ||||
|     it_behaves_like 'registering offense' | ||||
|   end | ||||
| 
 | ||||
|   context 'when source is offending but it is a rails helper' do | ||||
|     before do | ||||
|       allow(cop).to receive(:rails_helper?).and_return(true) | ||||
|     end | ||||
| 
 | ||||
|     it 'does not register offenses' do | ||||
|       inspect_source(cop, <<~RUBY) | ||||
|         module M | ||||
|           def f | ||||
|             @f ||= true | ||||
|           end | ||||
|         end | ||||
|       RUBY | ||||
| 
 | ||||
|       expect(cop.offenses).to be_empty | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when source is offending but it is a rails mailer' do | ||||
|     before do | ||||
|       allow(cop).to receive(:rails_mailer?).and_return(true) | ||||
|     end | ||||
| 
 | ||||
|     it 'does not register offenses' do | ||||
|       inspect_source(cop, <<~RUBY) | ||||
|         module M | ||||
|           def f | ||||
|             @f = true | ||||
|           end | ||||
|         end | ||||
|       RUBY | ||||
| 
 | ||||
|       expect(cop.offenses).to be_empty | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Loading…
	
		Reference in New Issue