Commit 23f63f4e authored by Rémy Coutable's avatar Rémy Coutable

Introduce a new Gitlab/FeatureAvailableUsage cop

This new cop will ensure consistency in usage of the
`#feature_available?` method.
Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 2115836f
......@@ -106,6 +106,7 @@ linters:
- Cop/LineBreakAfterGuardClauses
- Cop/LineBreakAroundConditionalBlock
- Cop/ProjectPathHelper
- Gitlab/FeatureAvailableUsage
- GitlabSecurity/PublicSend
- Layout/EmptyLineAfterGuardClause
- Layout/LeadingCommentSpace
......
This diff is collapsed.
......@@ -3,6 +3,7 @@
class ProjectFeature < ApplicationRecord
include Featurable
# When updating this array, make sure to update rubocop/cop/gitlab/feature_available_usage.rb as well.
FEATURES = %i[
issues
forking
......
......@@ -4,6 +4,7 @@ module EE
module ProjectFeature
extend ActiveSupport::Concern
# When updating this array, make sure to update rubocop/cop/gitlab/feature_available_usage.rb as well.
EE_FEATURES = %i(requirements).freeze
NOTES_PERMISSION_TRACKED_FIELDS = %w(
issues_access_level
......
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# Cop that checks for correct calling of #feature_available?
class FeatureAvailableUsage < RuboCop::Cop::Cop
OBSERVED_METHOD = :feature_available?
LICENSED_FEATURE_LITERAL_ARG_MSG = '`feature_available?` should not be called for features that can be licensed (`%s` given), use `licensed_feature_available?(feature)` instead.'
LICENSED_FEATURE_DYNAMIC_ARG_MSG = "`feature_available?` should not be called for features that can be licensed (`%s` isn't a literal so we cannot say if it's legit or not), using `licensed_feature_available?(feature)` may be more appropriate."
NOT_ENOUGH_ARGS_MSG = '`feature_available?` should be called with two arguments: `feature` and `user`.'
FEATURES = %i[
issues
forking
merge_requests
wiki
snippets
builds
repository
pages
metrics_dashboard
analytics
operations
security_and_compliance
container_registry
].freeze
EE_FEATURES = %i[requirements].freeze
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
SPECIAL_CLASS = %w[License].freeze
def on_send(node)
return unless method_name(node) == OBSERVED_METHOD
return if caller_is_special_case?(node)
return if feature_name(node).nil?
return if ALL_FEATURES.include?(feature_name(node)) && args_count(node) == 2
if !ALL_FEATURES.include?(feature_name(node))
add_offense(node, location: :expression, message: licensed_feature_message(node))
elsif args_count(node) < 2
add_offense(node, location: :expression, message: NOT_ENOUGH_ARGS_MSG)
end
end
def licensed_feature_message(node)
message_template = dynamic_feature?(node) ? LICENSED_FEATURE_DYNAMIC_ARG_MSG : LICENSED_FEATURE_LITERAL_ARG_MSG
message_template % feature_arg_name(node)
end
private
def method_name(node)
node.children[1]
end
def class_caller(node)
node.children[0]&.const_name.to_s
end
def caller_is_special_case?(node)
SPECIAL_CLASS.include?(class_caller(node))
end
def feature_arg(node)
node.children[2]
end
def dynamic_feature?(node)
feature = feature_arg(node)
return false unless feature
!feature.literal?
end
def feature_name(node)
feature = feature_arg(node)
return unless feature
return feature.children.compact.join('.') if dynamic_feature?(node)
return feature.value if feature.respond_to?(:value)
feature.type
end
def feature_arg_name(node)
feature = feature_arg(node)
return unless feature
return feature.children.compact.join('.') if dynamic_feature?(node)
return feature.children[0].inspect if feature.literal?
feature.type
end
def args_count(node)
node.children[2..].size
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/feature_available_usage'
RSpec.describe RuboCop::Cop::Gitlab::FeatureAvailableUsage do
subject(:cop) { described_class.new }
context 'no arguments given' do
it 'does not flag the use of Gitlab::Sourcegraph.feature_available? with no arguments' do
expect_no_offenses('Gitlab::Sourcegraph.feature_available?')
expect_no_offenses('subject { described_class.feature_available? }')
end
end
context 'one argument given' do
it 'does not flag the use of License.feature_available?' do
expect_no_offenses('License.feature_available?(:push_rules)')
end
it 'flags the use with a dynamic feature as nil' do
expect_offense(<<~SOURCE)
feature_available?(nil)
^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`nil` given), use `licensed_feature_available?(feature)` instead.
SOURCE
expect_offense(<<~SOURCE)
project.feature_available?(nil)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`nil` given), use `licensed_feature_available?(feature)` instead.
SOURCE
end
it 'flags the use with an OSS project feature' do
expect_offense(<<~SOURCE)
project.feature_available?(:issues)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should be called with two arguments: `feature` and `user`.
SOURCE
expect_offense(<<~SOURCE)
feature_available?(:issues)
^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should be called with two arguments: `feature` and `user`.
SOURCE
end
it 'flags the use with a feature that is not a project feature' do
expect_offense(<<~SOURCE)
feature_available?(:foo)
^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`:foo` given), use `licensed_feature_available?(feature)` instead.
SOURCE
expect_offense(<<~SOURCE)
project.feature_available?(:foo)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`:foo` given), use `licensed_feature_available?(feature)` instead.
SOURCE
expect_offense(<<~SOURCE)
feature_available?(foo)
^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`foo` isn't a literal so we cannot say if it's legit or not), using `licensed_feature_available?(feature)` may be more appropriate.
SOURCE
expect_offense(<<~SOURCE)
foo = :feature
feature_available?(foo)
^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`foo` isn't a literal so we cannot say if it's legit or not), using `licensed_feature_available?(feature)` may be more appropriate.
SOURCE
end
end
context 'two arguments given' do
it 'does not flag the use with an OSS project feature' do
expect_no_offenses('feature_available?(:issues, user)')
expect_no_offenses('project.feature_available?(:issues, user)')
end
it 'does not flag the use with an EE project feature' do
expect_no_offenses('feature_available?(:requirements, user)')
expect_no_offenses('project.feature_available?(:requirements, user)')
end
it 'flags the use with a dynamic feature as a method call with two args' do
expect_offense(<<~SOURCE)
feature_available?(:foo, current_user)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`:foo` given), use `licensed_feature_available?(feature)` instead.
SOURCE
expect_offense(<<~SOURCE)
project.feature_available?(:foo, current_user)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`:foo` given), use `licensed_feature_available?(feature)` instead.
SOURCE
expect_offense(<<~SOURCE)
feature_available?(foo, current_user)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`foo` isn't a literal so we cannot say if it's legit or not), using `licensed_feature_available?(feature)` may be more appropriate.
SOURCE
expect_offense(<<~SOURCE)
project.feature_available?(foo, current_user)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `feature_available?` should not be called for features that can be licensed (`foo` isn't a literal so we cannot say if it's legit or not), using `licensed_feature_available?(feature)` may be more appropriate.
SOURCE
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