Add cop to make sure we don't use ivar in a module

This commit is contained in:
Lin Jen-Shin 2017-07-12 02:29:33 +08:00
parent 4cadf22e20
commit 9ae92b8caa
81 changed files with 475 additions and 34 deletions

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module BoardsResponses
def authorize_read_list
authorize_action_for!(board.parent, :read_list)

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module CreatesCommit
extend ActiveSupport::Concern

View File

@ -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

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module IssuableActions
extend ActiveSupport::Concern

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module IssuableCollections
extend ActiveSupport::Concern
include SortingHelper

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module IssuesAction
extend ActiveSupport::Concern
include IssuableCollections

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module MergeRequestsAction
extend ActiveSupport::Concern
include IssuableCollections

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module MilestoneActions
extend ActiveSupport::Concern

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module NotesActions
include RendersNotes
extend ActiveSupport::Concern

View File

@ -13,6 +13,7 @@ module OauthApplications
end
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def load_scopes
@scopes = Doorkeeper.configuration.scopes
end

View File

@ -1,4 +1,5 @@
module RendersCommits
# rubocop:disable Cop/ModuleWithInstanceVariables
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -4,6 +4,7 @@ module SnippetsActions
def edit
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def raw
disposition = params[:inline] == 'false' ? 'attachment' : 'inline'

View File

@ -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)

View File

@ -11,6 +11,7 @@ module ToggleSubscriptionAction
private
# rubocop:disable Cop/ModuleWithInstanceVariables
def subscribable_project
@project || raise(NotImplementedError)
end

View File

@ -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

View File

@ -5,6 +5,7 @@
#
# Used by Issue, Note, MergeRequest, and Commit.
#
# # rubocop:disable Cop/ModuleWithInstanceVariables
module Mentionable
extend ActiveSupport::Concern

View File

@ -94,6 +94,7 @@ module Milestoneish
end
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def memoize_per_user(user, method_name)
@memoized ||= {}
@memoized[method_name] ||= {}

View File

@ -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

View File

@ -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)

View File

@ -55,6 +55,7 @@ module ProtectedRef
private
# rubocop:disable Cop/ModuleWithInstanceVariables
def ref_matcher
@ref_matcher ||= ProtectedRefMatcher.new(self)
end

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module RelativePositioning
extend ActiveSupport::Concern

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module ResolvableDiscussion
extend ActiveSupport::Concern

View File

@ -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

View File

@ -35,7 +35,7 @@ module Spammable
end
def spam?
@spam
spam
end
def check_for_spam

View File

@ -49,6 +49,7 @@ module Storage
private
# rubocop:disable Cop/ModuleWithInstanceVariables
def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths
end

View File

@ -17,6 +17,7 @@ module StripAttribute
strip_attrs.concat(attrs)
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def strip_attrs
@strip_attrs ||= []
end

View File

@ -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

View File

@ -4,7 +4,7 @@
#
# Used by Issue and MergeRequest.
#
# rubocop:disable Cop/ModuleWithInstanceVariables
module TimeTrackable
extend ActiveSupport::Concern

View File

@ -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

View File

@ -6,6 +6,7 @@
# Dependencies:
# - params with :request
#
# rubocop:disable Cop/ModuleWithInstanceVariables
module SpamCheckService
def filter_spam_check_params
@request = params.delete(:request)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -16,6 +16,7 @@ module RspecProfilingExt
end
module Run
# rubocop:disable Cop/ModuleWithInstanceVariables
def example_finished(*args)
super
rescue => err

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -20,6 +20,7 @@ module AfterCommitQueue
end
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def _after_commit_queue
@after_commit_queue ||= []
end

View File

@ -2,6 +2,7 @@
require 'rack/oauth2'
# rubocop:disable Cop/ModuleWithInstanceVariables
module API
module APIGuard
extend ActiveSupport::Concern

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module API
module Helpers
include Gitlab::Utils

View File

@ -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])

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -13,6 +13,7 @@ module Gitlab
# script: ...
# artifacts: ...
#
# rubocop:disable Cop/ModuleWithInstanceVariables
module Configurable
extend ActiveSupport::Concern

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Ci
class Config

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,6 +7,7 @@ module Gitlab
private
# rubocop:disable Cop/ModuleWithInstanceVariables
def base_query
@base_query ||= stage_query
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module CycleAnalytics
module ProductionHelper

View File

@ -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

View File

@ -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?

View File

@ -12,6 +12,7 @@ module Gitlab
raise NotImplementedError
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def message
@message ||= process_message
end

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Emoji
extend self

View File

@ -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

View File

@ -52,6 +52,7 @@ module Gitlab
end
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def identification_cache
@identification_cache ||= {
email: {},

View File

@ -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?

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Metrics
module InfluxDb

View File

@ -1,5 +1,6 @@
require 'prometheus/client'
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Metrics
module Prometheus

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module PathRegex
extend self

View File

@ -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

View File

@ -56,6 +56,7 @@ module Gitlab
query
end
# rubocop:disable Cop/ModuleWithInstanceVariables
def available_metrics
@available_metrics ||= client_label_values || []
end

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Regex
extend self

View File

@ -1,3 +1,4 @@
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module SlashCommands
module Presenters

View File

@ -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

View File

@ -1,5 +1,6 @@
require 'rainbow/ext/string'
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
TaskFailedError = Class.new(StandardError)
TaskAbortedByUserError = Class.new(StandardError)

View File

@ -3,6 +3,7 @@ module QA
module Namespace
extend self
# rubocop:disable Cop/ModuleWithInstanceVariables
def time
@time ||= Time.now
end

View File

@ -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

View File

@ -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'

View File

@ -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