From f0e9127eaab8d93bc4785b00a1c30bb41fbab243 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 29 Jun 2020 11:30:39 +0300 Subject: [PATCH] Add new `Rails/MailerName` cop --- CHANGELOG.md | 1 + config/default.yml | 8 +++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rails.adoc | 50 ++++++++++++++ lib/rubocop/cop/rails/mailer_name.rb | 80 ++++++++++++++++++++++ lib/rubocop/cop/rails_cops.rb | 1 + spec/rubocop/cop/rails/mailer_name_spec.rb | 48 +++++++++++++ 7 files changed, 189 insertions(+) create mode 100644 lib/rubocop/cop/rails/mailer_name.rb create mode 100644 spec/rubocop/cop/rails/mailer_name_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f66cee4e8..40fa45f84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* [#281](https://github.com/rubocop-hq/rubocop-rails/pull/281): Add new `Rails/MailerName` cop. ([@fatkodima][]) * [#246](https://github.com/rubocop-hq/rubocop-rails/issues/246): Add new `Rails/PluckInWhere` cop. ([@fatkodima][]) * [#17](https://github.com/rubocop-hq/rubocop-rails/issues/17): Add new `Rails/NegateInclude` cop. ([@fatkodima][]) * [#278](https://github.com/rubocop-hq/rubocop-rails/pull/278): Add new `Rails/Pluck` cop. ([@eugeneius][]) diff --git a/config/default.yml b/config/default.yml index 7007c357d2..fb963a8d4e 100644 --- a/config/default.yml +++ b/config/default.yml @@ -325,6 +325,14 @@ Rails/LinkToBlank: Enabled: true VersionAdded: '0.62' +Rails/MailerName: + Description: 'Mailer should end with `Mailer` suffix.' + StyleGuide: 'https://rails.rubystyle.guide/#mailer-name' + Enabled: 'pending' + VersionAdded: '2.7' + Include: + - app/mailers/**/*.rb + Rails/NegateInclude: Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.' Enabled: 'pending' diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 083f90f3c5..173dde6849 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -38,6 +38,7 @@ * xref:cops_rails.adoc#railsinverseof[Rails/InverseOf] * xref:cops_rails.adoc#railslexicallyscopedactionfilter[Rails/LexicallyScopedActionFilter] * xref:cops_rails.adoc#railslinktoblank[Rails/LinkToBlank] +* xref:cops_rails.adoc#railsmailername[Rails/MailerName] * xref:cops_rails.adoc#railsnegateinclude[Rails/NegateInclude] * xref:cops_rails.adoc#railsnotnullcolumn[Rails/NotNullColumn] * xref:cops_rails.adoc#railsoutput[Rails/Output] diff --git a/docs/modules/ROOT/pages/cops_rails.adoc b/docs/modules/ROOT/pages/cops_rails.adoc index 1aa78fc77c..2df6be8c23 100644 --- a/docs/modules/ROOT/pages/cops_rails.adoc +++ b/docs/modules/ROOT/pages/cops_rails.adoc @@ -1894,6 +1894,56 @@ link_to 'Click here', url, target: '_blank', rel: 'noreferrer' * https://html.spec.whatwg.org/multipage/links.html#link-type-noopener * https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer +== Rails/MailerName + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| Yes +| Yes +| 2.7 +| - +|=== + +This cop enforces that mailer names end with `Mailer` suffix. + +Without the `Mailer` suffix it isn't immediately apparent what's a mailer +and which views are related to the mailer. + +=== Examples + +[source,ruby] +---- +# bad +class User < ActionMailer::Base +end + +class User < ApplicationMailer +end + +# good +class UserMailer < ActionMailer::Base +end + +class UserMailer < ApplicationMailer +end +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Include +| `app/mailers/**/*.rb` +| Array +|=== + +=== References + +* https://rails.rubystyle.guide/#mailer-name + == Rails/NegateInclude |=== diff --git a/lib/rubocop/cop/rails/mailer_name.rb b/lib/rubocop/cop/rails/mailer_name.rb new file mode 100644 index 0000000000..9019660500 --- /dev/null +++ b/lib/rubocop/cop/rails/mailer_name.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # This cop enforces that mailer names end with `Mailer` suffix. + # + # Without the `Mailer` suffix it isn't immediately apparent what's a mailer + # and which views are related to the mailer. + # + # @example + # # bad + # class User < ActionMailer::Base + # end + # + # class User < ApplicationMailer + # end + # + # # good + # class UserMailer < ActionMailer::Base + # end + # + # class UserMailer < ApplicationMailer + # end + # + class MailerName < Cop + MSG = 'Mailer should end with `Mailer` suffix.' + + def_node_matcher :mailer_base_class?, <<~PATTERN + { + (const (const nil? :ActionMailer) :Base) + (const nil? :ApplicationMailer) + } + PATTERN + + def_node_matcher :class_definition?, <<~PATTERN + (class $(const _ !#mailer_suffix?) #mailer_base_class? ...) + PATTERN + + def_node_matcher :class_new_definition?, <<~PATTERN + (send (const nil? :Class) :new #mailer_base_class?) + PATTERN + + def on_class(node) + class_definition?(node) do |name_node| + add_offense(name_node) + end + end + + def on_send(node) + return unless class_new_definition?(node) + + casgn_parent = node.each_ancestor(:casgn).first + return unless casgn_parent + + name = casgn_parent.children[1] + add_offense(casgn_parent, location: :name) unless mailer_suffix?(name) + end + + def autocorrect(node) + lambda do |corrector| + if node.casgn_type? + name = node.children[1] + corrector.replace(node.loc.name, "#{name}Mailer") + else + name = node.children.last + corrector.replace(node.source_range, "#{name}Mailer") + end + end + end + + private + + def mailer_suffix?(mailer_name) + mailer_name.to_s.end_with?('Mailer') + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index 206803b1d5..bae2566d8a 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -40,6 +40,7 @@ require_relative 'rails/inverse_of' require_relative 'rails/lexically_scoped_action_filter' require_relative 'rails/link_to_blank' +require_relative 'rails/mailer_name' require_relative 'rails/negate_include' require_relative 'rails/not_null_column' require_relative 'rails/output' diff --git a/spec/rubocop/cop/rails/mailer_name_spec.rb b/spec/rubocop/cop/rails/mailer_name_spec.rb new file mode 100644 index 0000000000..b3e27d2d56 --- /dev/null +++ b/spec/rubocop/cop/rails/mailer_name_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::MailerName do + subject(:cop) { described_class.new } + + ['ActionMailer::Base', 'ApplicationMailer'].each do |base| + context 'when regular class definition' do + it 'registers an offense and corrects when name without suffix' do + expect_offense(<<~RUBY) + class User < #{base} + ^^^^ Mailer should end with `Mailer` suffix. + end + RUBY + + expect_correction(<<~RUBY) + class UserMailer < #{base} + end + RUBY + end + + it 'does not register an offense when name with suffix' do + expect_no_offenses(<<~RUBY) + class UserMailer < #{base} + end + RUBY + end + end + + context 'when `Class.new` definition' do + it 'registers an offense and corrects when name without suffix' do + expect_offense(<<~RUBY) + User = Class.new(#{base}) + ^^^^ Mailer should end with `Mailer` suffix. + RUBY + + expect_correction(<<~RUBY) + UserMailer = Class.new(#{base}) + RUBY + end + + it 'does not register an offense when name with suffix' do + expect_no_offenses(<<~RUBY) + UserMailer = Class.new(#{base}) + RUBY + end + end + end +end