Commit 9ae92b8c authored by Lin Jen-Shin's avatar Lin Jen-Shin

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

parent 4cadf22e
# rubocop:disable Cop/ModuleWithInstanceVariables
module BoardsResponses
def authorize_read_list
authorize_action_for!(board.parent, :read_list)
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module CreatesCommit
extend ActiveSupport::Concern
......
module CycleAnalyticsParams
extend ActiveSupport::Concern
# rubocop:disable Cop/ModuleWithInstanceVariables
def options(params)
@options ||= { from: start_date(params), current_user: current_user }
end
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module IssuableActions
extend ActiveSupport::Concern
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module IssuableCollections
extend ActiveSupport::Concern
include SortingHelper
......
# 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
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module MergeRequestsAction
extend ActiveSupport::Concern
include IssuableCollections
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module MilestoneActions
extend ActiveSupport::Concern
......
# 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
......
module RendersCommits
# rubocop:disable Cop/ModuleWithInstanceVariables
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user)
......
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
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module RelativePositioning
extend ActiveSupport::Concern
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module ResolvableDiscussion
extend ActiveSupport::Concern
......
# 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
......
## 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
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module API
module Helpers
include Gitlab::Utils
......
# 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
......
# 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
......
# 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
......
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
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
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
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
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module CycleAnalytics
module ProductionHelper
......
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
......
# 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?
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Metrics
module InfluxDb
......
require 'prometheus/client'
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Metrics
module Prometheus
......
# 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
......
# rubocop:disable Cop/ModuleWithInstanceVariables
module Gitlab
module Regex
extend self
......
# 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
......
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
......
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'
......
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment