diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa6b11ae8..8a53dc3210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* [#276](https://github.com/rubocop-hq/rubocop-rails/pull/276): Add new `Rails/RenderText` cop. ([@fatkodima][]) * [#271](https://github.com/rubocop-hq/rubocop-rails/pull/271): Add new `Rails/RenderInline` cop. ([@fatkodima][]) * [#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][]) diff --git a/config/default.yml b/config/default.yml index 88ab9e12d6..01a1bd7788 100644 --- a/config/default.yml +++ b/config/default.yml @@ -471,6 +471,12 @@ Rails/RenderInline: Enabled: 'pending' VersionAdded: '2.7' +Rails/RenderText: + Description: 'Prefer `render plain:` over `render text:`.' + StyleGuide: 'https://rails.rubystyle.guide/#plain-text-rendering' + Enabled: 'pending' + VersionAdded: '2.7' + Rails/RequestReferer: Description: 'Use consistent syntax for request.referer.' Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 69915512d3..d29e4e35ca 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -58,6 +58,7 @@ * xref:cops_rails.adoc#railsrefutemethods[Rails/RefuteMethods] * xref:cops_rails.adoc#railsrelativedateconstant[Rails/RelativeDateConstant] * xref:cops_rails.adoc#railsrenderinline[Rails/RenderInline] +* xref:cops_rails.adoc#railsrendertext[Rails/RenderText] * xref:cops_rails.adoc#railsrequestreferer[Rails/RequestReferer] * xref:cops_rails.adoc#railsreversiblemigration[Rails/ReversibleMigration] * xref:cops_rails.adoc#railssafenavigation[Rails/SafeNavigation] diff --git a/docs/modules/ROOT/pages/cops_rails.adoc b/docs/modules/ROOT/pages/cops_rails.adoc index 96095f8eae..31607c1249 100644 --- a/docs/modules/ROOT/pages/cops_rails.adoc +++ b/docs/modules/ROOT/pages/cops_rails.adoc @@ -2820,6 +2820,39 @@ end * https://rails.rubystyle.guide/#inline-rendering +== Rails/RenderText + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| Yes +| Yes +| 2.7 +| - +|=== + +This cop identifies places where `render text:` can be +replaced with `render plain:`. + +=== Examples + +[source,ruby] +---- +# bad - sets MIME type to `text/html` +render text: 'Ruby!' + +# bad - requires explicit MIME type declaration +render text: 'Ruby!', content_type: 'text/plain' + +# good - short and precise +render plain: 'Ruby!' +---- + +=== References + +* https://rails.rubystyle.guide/#plain-text-rendering + == Rails/RequestReferer |=== diff --git a/lib/rubocop/cop/rails/render_text.rb b/lib/rubocop/cop/rails/render_text.rb new file mode 100644 index 0000000000..95b4a341f2 --- /dev/null +++ b/lib/rubocop/cop/rails/render_text.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # This cop identifies places where `render text:` can be + # replaced with `render plain:`. + # + # @example + # # bad - sets MIME type to `text/html` + # render text: 'Ruby!' + # + # # bad - requires explicit MIME type declaration + # render text: 'Ruby!', content_type: 'text/plain' + # + # # good - short and precise + # render plain: 'Ruby!' + # + class RenderText < Cop + MSG = 'Prefer `render plain:` over `render text:`.' + + def_node_matcher :render_text_call?, <<~PATTERN + (send nil? :render $(hash <$(pair (sym :text) $_) ...>)) + PATTERN + + def on_send(node) + add_offense(node) if render_text_call?(node) + end + + def autocorrect(node) + render_text_call?(node) do |options_node, option_node, option_value| + content_type_node = find_content_type(options_node) + + if content_type_node && content_type_node.value.value == 'text/plain' + rest_options = options_node.pairs - [option_node, content_type_node] + + lambda do |corrector| + corrector.replace( + node, + replacement(rest_options, option_value) + ) + end + end + end + end + + private + + def find_content_type(node) + node.pairs.find { |p| p.key.value.to_sym == :content_type } + end + + def replacement(rest_options, option_value) + if rest_options.any? + "render plain: #{option_value.source}, #{rest_options.map(&:source).join(', ')}" + else + "render plain: #{option_value.source}" + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index a999a8142a..74fd3dc1e1 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -60,6 +60,7 @@ require_relative 'rails/refute_methods' require_relative 'rails/relative_date_constant' require_relative 'rails/render_inline' +require_relative 'rails/render_text' require_relative 'rails/request_referer' require_relative 'rails/reversible_migration' require_relative 'rails/safe_navigation' diff --git a/spec/rubocop/cop/rails/render_text_spec.rb b/spec/rubocop/cop/rails/render_text_spec.rb new file mode 100644 index 0000000000..614774ca0f --- /dev/null +++ b/spec/rubocop/cop/rails/render_text_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::RenderText do + subject(:cop) { described_class.new } + + it 'registers an offense when using `render text:`' do + expect_offense(<<~RUBY) + render text: 'Ruby!' + ^^^^^^^^^^^^^^^^^^^^ Prefer `render plain:` over `render text:`. + RUBY + + expect_no_corrections + end + + it 'registers an offense and corrects when using `render text:` with `content_type: "text/plain"`' do + expect_offense(<<~RUBY) + render text: 'Ruby!', content_type: 'text/plain' + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `render plain:` over `render text:`. + RUBY + + expect_correction(<<~RUBY) + render plain: 'Ruby!' + RUBY + end + + it 'does not register an offense when using `render plain:`' do + expect_no_offenses(<<~RUBY) + render plain: 'Ruby!' + RUBY + end +end