diff --git a/changelog/new_env_cop.md b/changelog/new_env_cop.md new file mode 100644 index 0000000000..c97bc19915 --- /dev/null +++ b/changelog/new_env_cop.md @@ -0,0 +1 @@ +* [#1376](https://github.com/rubocop/rubocop-rails/issues/1376): Add new `Rails/Env` cop. ([@cdudas17][]) diff --git a/config/default.yml b/config/default.yml index 0e7d183460..326c3cada1 100644 --- a/config/default.yml +++ b/config/default.yml @@ -441,6 +441,11 @@ Rails/EnumUniqueness: Include: - app/models/**/*.rb +Rails/Env: + Description: 'Use Feature Flags or config instead of `Rails.env`.' + Enabled: false + VersionAdded: '<>' + Rails/EnvLocal: Description: 'Use `Rails.env.local?` instead of `Rails.env.development? || Rails.env.test?`.' Enabled: pending diff --git a/lib/rubocop/cop/rails/env.rb b/lib/rubocop/cop/rails/env.rb new file mode 100644 index 0000000000..18ccdb8060 --- /dev/null +++ b/lib/rubocop/cop/rails/env.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # Checks for usage of `Rails.env` which can be replaced with Feature Flags + # + # @example + # + # # bad + # Rails.env.production? || Rails.env.local? + # + # # good + # if FeatureFlag.enabled?(:new_feature) + # # new feature code + # end + # + class Env < Base + MSG = 'Use Feature Flags or config instead of `Rails.env`.' + RESTRICT_ON_SEND = %i[env].freeze + # This allow list is derived from (Rails.env.methods - Object.methods).select { |m| m.to_s.end_with?('?') } + # and then removing the environment specific methods like development?, test?, production?, local? + ALLOWED_LIST = Set.new( + %i[ + unicode_normalized? + exclude? + empty? + starts_with? + acts_like_string? + ends_with? + contains_mb4_chars? + casecmp? + match? + blank_as? + start_with? + end_with? + is_utf8? + valid_encoding? + ascii_only? + colorized? + between? + ] + ).freeze + + def on_send(node) + return unless node.receiver&.const_name == 'Rails' + + parent = node.parent + return unless parent&.predicate_method? + + return if ALLOWED_LIST.include?(parent.method_name) + + add_offense(parent) + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index b7eddc4fe4..c966c86f13 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -48,6 +48,7 @@ require_relative 'rails/enum_hash' require_relative 'rails/enum_syntax' require_relative 'rails/enum_uniqueness' +require_relative 'rails/env' require_relative 'rails/env_local' require_relative 'rails/environment_comparison' require_relative 'rails/environment_variable_access' diff --git a/spec/rubocop/cop/rails/env_spec.rb b/spec/rubocop/cop/rails/env_spec.rb new file mode 100644 index 0000000000..69628ea9cb --- /dev/null +++ b/spec/rubocop/cop/rails/env_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::Env, :config do + it 'registers an offense for `Rails.env.development? || Rails.env.test?`' do + expect_offense(<<~RUBY) + Rails.env.development? || Rails.env.test? + ^^^^^^^^^^^^^^^ Use Feature Flags or config instead of `Rails.env`. + ^^^^^^^^^^^^^^^^^^^^^^ Use Feature Flags or config instead of `Rails.env`. + RUBY + end + + it 'registers an offense for `Rails.env.production?`' do + expect_offense(<<~RUBY) + Rails.env.production? + ^^^^^^^^^^^^^^^^^^^^^ Use Feature Flags or config instead of `Rails.env`. + RUBY + end + + it 'does not register an offense for `Rails.env`' do + expect_no_offenses(<<~RUBY) + Rails.env + RUBY + end + + it 'does not register an offense for valid Rails.env methods' do + expect_no_offenses(<<~RUBY) + Rails.env.capitalize + Rails.env.empty? + RUBY + end + + it 'does not register an offense for unrelated config' do + expect_no_offenses(<<~RUBY) + Rails.environment + RUBY + end +end