summaryrefslogtreecommitdiff
path: root/rubocop
diff options
context:
space:
mode:
Diffstat (limited to 'rubocop')
-rw-r--r--rubocop/cop/gitlab/policy_rule_boolean.rb56
-rw-r--r--rubocop/cop/graphql/descriptions.rb60
-rw-r--r--rubocop/cop/graphql/id_type.rb2
-rw-r--r--rubocop/cop/lint/last_keyword_argument.rb71
-rw-r--r--rubocop/cop/rspec/httparty_basic_auth.rb49
-rw-r--r--rubocop/rubocop-migrations.yml3
-rw-r--r--rubocop/rubocop-usage-data.yml2
7 files changed, 228 insertions, 15 deletions
diff --git a/rubocop/cop/gitlab/policy_rule_boolean.rb b/rubocop/cop/gitlab/policy_rule_boolean.rb
new file mode 100644
index 00000000000..ca69eebab6e
--- /dev/null
+++ b/rubocop/cop/gitlab/policy_rule_boolean.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module Gitlab
+ # This cop checks for usage of boolean operators in rule blocks, which
+ # does not work because conditions are objects, not booleans.
+ #
+ # @example
+ #
+ # # bad, `conducts_electricity` returns a Rule object, not a boolean!
+ # rule { conducts_electricity && batteries }.enable :light_bulb
+ #
+ # # good
+ # rule { conducts_electricity & batteries }.enable :light_bulb
+ #
+ # @example
+ #
+ # # bad, `conducts_electricity` returns a Rule object, so the ternary is always going to be true
+ # rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
+ #
+ # # good
+ # rule { conducts_electricity & can?(:magnetize) }.enable :motor
+ # rule { ~conducts_electricity & batteries }.enable :motor
+ class PolicyRuleBoolean < RuboCop::Cop::Cop
+ def_node_search :has_and_operator?, <<~PATTERN
+ (and ...)
+ PATTERN
+
+ def_node_search :has_or_operator?, <<~PATTERN
+ (or ...)
+ PATTERN
+
+ def_node_search :has_if?, <<~PATTERN
+ (if ...)
+ PATTERN
+
+ def on_block(node)
+ return unless node.method_name == :rule
+
+ if has_and_operator?(node)
+ add_offense(node, message: '&& is not allowed within a rule block. Did you mean to use `&`?')
+ end
+
+ if has_or_operator?(node)
+ add_offense(node, message: '|| is not allowed within a rule block. Did you mean to use `|`?')
+ end
+
+ if has_if?(node)
+ add_offense(node, message: 'if and ternary operators are not allowed within a rule block.')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/graphql/descriptions.rb b/rubocop/cop/graphql/descriptions.rb
index b4e00dfe336..1585e5c9788 100644
--- a/rubocop/cop/graphql/descriptions.rb
+++ b/rubocop/cop/graphql/descriptions.rb
@@ -13,42 +13,74 @@
# argument :some_argument, GraphQL::STRING_TYPE
# end
#
+# class UngoodClass
+# field :some_argument,
+# GraphQL::STRING_TYPE,
+# description: "A description that does not end in a period"
+# end
+#
# # good
# class GreatClass
# argument :some_field,
# GraphQL::STRING_TYPE,
-# description: "Well described - a superb description"
+# description: "Well described - a superb description."
#
# field :some_field,
# GraphQL::STRING_TYPE,
-# description: "A thorough and compelling description"
+# description: "A thorough and compelling description."
# end
module RuboCop
module Cop
module Graphql
class Descriptions < RuboCop::Cop::Cop
- MSG = 'Please add a `description` property.'
+ MSG_NO_DESCRIPTION = 'Please add a `description` property.'
+ MSG_NO_PERIOD = '`description` strings must end with a `.`.'
# ability_field and permission_field set a default description.
- def_node_matcher :fields, <<~PATTERN
- (send nil? :field $...)
- PATTERN
-
- def_node_matcher :arguments, <<~PATTERN
- (send nil? :argument $...)
+ def_node_matcher :field_or_argument?, <<~PATTERN
+ (send nil? {:field :argument} ...)
PATTERN
- def_node_matcher :has_description?, <<~PATTERN
- (hash <(pair (sym :description) _) ...>)
+ def_node_matcher :description, <<~PATTERN
+ (... (hash <(pair (sym :description) $_) ...>))
PATTERN
def on_send(node)
- matches = fields(node) || arguments(node)
+ return unless field_or_argument?(node)
+
+ description = description(node)
+
+ return add_offense(node, location: :expression, message: MSG_NO_DESCRIPTION) unless description
+
+ add_offense(node, location: :expression, message: MSG_NO_PERIOD) if no_period?(description)
+ end
+
+ # Autocorrect missing periods at end of description.
+ def autocorrect(node)
+ lambda do |corrector|
+ description = description(node)
+ next unless description
+
+ corrector.insert_after(before_end_quote(description), '.')
+ end
+ end
+
+ private
+
+ def no_period?(description)
+ # Test that the description node is a `:str` (as opposed to
+ # a `#copy_field_description` call) before checking.
+ description.type == :str && !description.value.strip.end_with?('.')
+ end
- return if matches.nil?
+ # Returns a Parser::Source::Range that ends just before the final String delimiter.
+ def before_end_quote(string)
+ return string.source_range.adjust(end_pos: -1) unless string.heredoc?
- add_offense(node, location: :expression) unless has_description?(matches.last)
+ heredoc_source = string.location.heredoc_body.source
+ adjust = heredoc_source.index(/\s+\Z/) - heredoc_source.length
+ string.location.heredoc_body.adjust(end_pos: adjust)
end
end
end
diff --git a/rubocop/cop/graphql/id_type.rb b/rubocop/cop/graphql/id_type.rb
index 96f90ac136a..0d2fb6ad852 100644
--- a/rubocop/cop/graphql/id_type.rb
+++ b/rubocop/cop/graphql/id_type.rb
@@ -6,7 +6,7 @@ module RuboCop
class IDType < RuboCop::Cop::Cop
MSG = 'Do not use GraphQL::ID_TYPE, use a specific GlobalIDType instead'
- WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path].freeze
+ WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path namespace_path].freeze
def_node_search :graphql_id_type?, <<~PATTERN
(send nil? :argument (_ #does_not_match?) (const (const nil? :GraphQL) :ID_TYPE) ...)
diff --git a/rubocop/cop/lint/last_keyword_argument.rb b/rubocop/cop/lint/last_keyword_argument.rb
new file mode 100644
index 00000000000..430ea66e9a1
--- /dev/null
+++ b/rubocop/cop/lint/last_keyword_argument.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module Lint
+ # This cop only works if there are files from deprecation_toolkit. You can
+ # generate these files by:
+ #
+ # 1. Running specs with RECORD_DEPRECATIONS=1
+ # 1. Downloading the complete set of deprecations/ files from a CI
+ # pipeline (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47720)
+ class LastKeywordArgument < Cop
+ MSG = 'Using the last argument as keyword parameters is deprecated'.freeze
+
+ DEPRECATIONS_GLOB = File.expand_path('../../../deprecations/**/*.yml', __dir__)
+ KEYWORD_DEPRECATION_STR = 'maybe ** should be added to the call'
+
+ def on_send(node)
+ arg = node.arguments.last
+ return unless arg
+
+ return unless known_match?(processed_source.file_path, node.first_line, node.method_name.to_s)
+
+ return if arg.children.first.respond_to?(:kwsplat_type?) && arg.children.first&.kwsplat_type?
+
+ # parser thinks `a: :b, c: :d` is hash type, it's actually kwargs
+ return if arg.hash_type? && !arg.source.match(/\A{/)
+
+ add_offense(arg)
+ end
+
+ def autocorrect(arg)
+ lambda do |corrector|
+ if arg.hash_type?
+ kwarg = arg.source.sub(/\A{\s*/, '').sub(/\s*}\z/, '')
+ corrector.replace(arg, kwarg)
+ elsif arg.splat_type?
+ corrector.insert_before(arg, '*')
+ else
+ corrector.insert_before(arg, '**')
+ end
+ end
+ end
+
+ private
+
+ def known_match?(file_path, line_number, method_name)
+ file_path_from_root = file_path.sub(File.expand_path('../../..', __dir__), '')
+
+ method_name = 'initialize' if method_name == 'new'
+
+ self.class.keyword_warnings.any? do |warning|
+ warning.include?("#{file_path_from_root}:#{line_number}") && warning.include?("called method `#{method_name}'")
+ end
+ end
+
+ def self.keyword_warnings
+ @keyword_warnings ||= keywords_list
+ end
+
+ def self.keywords_list
+ hash = Dir.glob(DEPRECATIONS_GLOB).each_with_object({}) do |file, hash|
+ hash.merge!(YAML.safe_load(File.read(file)))
+ end
+
+ hash.values.flatten.select { |str| str.include?(KEYWORD_DEPRECATION_STR) }.uniq
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/rspec/httparty_basic_auth.rb b/rubocop/cop/rspec/httparty_basic_auth.rb
new file mode 100644
index 00000000000..529a5808662
--- /dev/null
+++ b/rubocop/cop/rspec/httparty_basic_auth.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module RSpec
+ # This cop checks for invalid credentials passed to HTTParty
+ #
+ # @example
+ #
+ # # bad
+ # HTTParty.get(url, basic_auth: { user: 'foo' })
+ #
+ # # good
+ # HTTParty.get(url, basic_auth: { username: 'foo' })
+ class HTTPartyBasicAuth < RuboCop::Cop::Cop
+ MESSAGE = "`basic_auth: { user: ... }` does not work - replace `user:` with `username:`".freeze
+
+ RESTRICT_ON_SEND = %i(get put post delete).freeze
+
+ def_node_matcher :httparty_basic_auth?, <<~PATTERN
+ (send
+ (const _ :HTTParty)
+ {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}}
+ <(hash
+ <(pair
+ (sym :basic_auth)
+ (hash
+ <(pair $(sym :user) _) ...>
+ )
+ ) ...>
+ ) ...>
+ )
+ PATTERN
+
+ def on_send(node)
+ return unless m = httparty_basic_auth?(node)
+
+ add_offense(m, location: :expression, message: MESSAGE)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(node.loc.expression, 'username')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop-migrations.yml b/rubocop/rubocop-migrations.yml
index e1772deee9b..41bd2a4ce7d 100644
--- a/rubocop/rubocop-migrations.yml
+++ b/rubocop/rubocop-migrations.yml
@@ -25,6 +25,7 @@ Migration/UpdateLargeTable:
- :project_authorizations
- :projects
- :project_ci_cd_settings
+ - :project_settings
- :project_features
- :push_event_payloads
- :resource_label_events
@@ -35,6 +36,8 @@ Migration/UpdateLargeTable:
- :taggings
- :todos
- :users
+ - :user_preferences
+ - :user_details
- :web_hook_logs
DeniedMethods:
- :change_column_type_concurrently
diff --git a/rubocop/rubocop-usage-data.yml b/rubocop/rubocop-usage-data.yml
index 51ad3ed0bef..0e40a5971ee 100644
--- a/rubocop/rubocop-usage-data.yml
+++ b/rubocop/rubocop-usage-data.yml
@@ -20,6 +20,7 @@ UsageData/LargeTable:
- :Gitlab::Runtime
- :Gitaly::Server
- :Gitlab::UsageData
+ - :Gitlab::UsageDataCounters
- :License
- :Rails
- :Time
@@ -51,3 +52,4 @@ UsageData/DistinctCountByLargeForeignKey:
- 'project_id'
- 'user_id'
- 'resource_owner_id'
+ - 'requirement_id'