diff --git a/CHANGELOG.md b/CHANGELOG.md index f8de2cffe1..f41e383284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ ### New features +* [#291](https://github.com/rubocop-hq/rubocop-rails/pull/291): Add new `Rails/SquishedSQLHeredocs` cop. ([@mobilutz][]) * [#283](https://github.com/rubocop-hq/rubocop-rails/pull/283): Add new `Rails/FindById` cop. ([@fatkodima][]) * [#285](https://github.com/rubocop-hq/rubocop-rails/pull/285): Add new `Rails/ActiveRecordCallbacksOrder` cop. ([@fatkodima][]) * [#276](https://github.com/rubocop-hq/rubocop-rails/pull/276): Add new `Rails/RenderPlainText` cop. ([@fatkodima][]) @@ -269,3 +270,4 @@ [@philcoggins]: https://github.com/philcoggins [@kunitoo]: https://github.com/kunitoo [@jaredmoody]: https://github.com/jaredmoody +[@mobilutz]: https://github.com/mobilutz diff --git a/config/default.yml b/config/default.yml index 85f49ae318..08ca8cd1de 100644 --- a/config/default.yml +++ b/config/default.yml @@ -648,6 +648,12 @@ Rails/SkipsModelValidations: - upsert_all AllowedMethods: [] +Rails/SquishedSQLHeredocs: + Description: 'Checks SQL heredocs to use `.squish`.' + StyleGuide: 'https://rails.rubystyle.guide/#squished-heredocs' + Enabled: 'pending' + VersionAdded: '2.8' + Rails/TimeZone: Description: 'Checks the correct usage of time zone aware methods.' StyleGuide: 'https://rails.rubystyle.guide#time' diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 587ef63f19..7b11177ea9 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -89,6 +89,7 @@ based on the https://rails.rubystyle.guide/[Rails Style Guide]. * xref:cops_rails.adoc#railsscopeargs[Rails/ScopeArgs] * xref:cops_rails.adoc#railsshorti18n[Rails/ShortI18n] * xref:cops_rails.adoc#railsskipsmodelvalidations[Rails/SkipsModelValidations] +* xref:cops_rails.adoc#railssquishedsqlheredocs[Rails/SquishedSQLHeredocs] * xref:cops_rails.adoc#railstimezone[Rails/TimeZone] * xref:cops_rails.adoc#railsuniqbeforepluck[Rails/UniqBeforePluck] * xref:cops_rails.adoc#railsuniquevalidationwithoutindex[Rails/UniqueValidationWithoutIndex] diff --git a/docs/modules/ROOT/pages/cops_rails.adoc b/docs/modules/ROOT/pages/cops_rails.adoc index 5451633c39..01f72092fe 100644 --- a/docs/modules/ROOT/pages/cops_rails.adoc +++ b/docs/modules/ROOT/pages/cops_rails.adoc @@ -3932,6 +3932,59 @@ user.touch * https://guides.rubyonrails.org/active_record_validations.html#skipping-validations +== Rails/SquishedSQLHeredocs + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| Yes +| Yes +| 2.8 +| - +|=== + +Checks SQL heredocs to use `.squish`. + +=== Examples + +[source,ruby] +---- +# bad +<<-SQL + SELECT * FROM posts; +SQL + +<<-SQL + SELECT * FROM posts + WHERE id = 1 +SQL + +execute(<<~SQL, "Post Load") + SELECT * FROM posts + WHERE post_id = 1 +SQL + +# good +<<-SQL.squish + SELECT * FROM posts; +SQL + +<<~SQL.squish + SELECT * FROM table + WHERE id = 1 +SQL + +execute(<<~SQL.squish, "Post Load") + SELECT * FROM posts + WHERE post_id = 1 +SQL +---- + +=== References + +* https://rails.rubystyle.guide/#squished-heredocs + == Rails/TimeZone |=== diff --git a/lib/rubocop/cop/rails/squished_sql_heredocs.rb b/lib/rubocop/cop/rails/squished_sql_heredocs.rb new file mode 100644 index 0000000000..497744076c --- /dev/null +++ b/lib/rubocop/cop/rails/squished_sql_heredocs.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # + # Checks SQL heredocs to use `.squish`. + # + # @example + # # bad + # <<-SQL + # SELECT * FROM posts; + # SQL + # + # <<-SQL + # SELECT * FROM posts + # WHERE id = 1 + # SQL + # + # execute(<<~SQL, "Post Load") + # SELECT * FROM posts + # WHERE post_id = 1 + # SQL + # + # # good + # <<-SQL.squish + # SELECT * FROM posts; + # SQL + # + # <<~SQL.squish + # SELECT * FROM table + # WHERE id = 1 + # SQL + # + # execute(<<~SQL.squish, "Post Load") + # SELECT * FROM posts + # WHERE post_id = 1 + # SQL + # + class SquishedSQLHeredocs < Cop + include Heredoc + + SQL = 'SQL' + SQUISH = '.squish' + MSG = 'Use `%s` instead of `%s`.' + + def on_heredoc(node) + return unless offense_detected?(node) + + add_offense(node) + end + + def autocorrect(node) + lambda do |corrector| + corrector.insert_after(node, SQUISH) + end + end + + private + + def offense_detected?(node) + sql_heredoc?(node) && !using_squish?(node) + end + + def sql_heredoc?(node) + delimiter_string(node) == SQL + end + + def using_squish?(node) + node.parent&.send_type? && node.parent&.method?(:squish) + end + + def message(node) + format( + MSG, + expect: "#{node.source}#{SQUISH}", + current: node.source + ) + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index 6b5679ed80..8531298c27 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -77,6 +77,7 @@ require_relative 'rails/scope_args' require_relative 'rails/short_i18n' require_relative 'rails/skips_model_validations' +require_relative 'rails/squished_sql_heredocs' require_relative 'rails/time_zone' require_relative 'rails/uniq_before_pluck' require_relative 'rails/unique_validation_without_index' diff --git a/spec/rubocop/cop/rails/squished_sql_heredocs_spec.rb b/spec/rubocop/cop/rails/squished_sql_heredocs_spec.rb new file mode 100644 index 0000000000..61cbee926d --- /dev/null +++ b/spec/rubocop/cop/rails/squished_sql_heredocs_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::SquishedSQLHeredocs, :config do + subject(:cop) { described_class.new(config) } + + context 'with multi line heredoc' do + it 'registers an offense and corrects it' do + expect_offense(<<~RUBY) + <<~SQL + ^^^^^^ Use `<<~SQL.squish` instead of `<<~SQL`. + SELECT * FROM posts + WHERE id = 1 + SQL + RUBY + + expect_correction(<<~RUBY) + <<~SQL.squish + SELECT * FROM posts + WHERE id = 1 + SQL + RUBY + end + + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + <<-SQL.squish + SELECT * FROM posts + WHERE id = 1 + SQL + RUBY + end + end + + context 'with single line heredoc' do + it 'registers an offense and corrects it' do + expect_offense(<<~RUBY) + <<-SQL + ^^^^^^ Use `<<-SQL.squish` instead of `<<-SQL`. + SELECT * FROM posts; + SQL + RUBY + + expect_correction(<<~RUBY) + <<-SQL.squish + SELECT * FROM posts; + SQL + RUBY + end + + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + <<-SQL.squish + SELECT * FROM posts; + SQL + RUBY + end + end + + context 'with heredocs as method parameters' do + it 'registers an offense and corrects it' do + expect_offense(<<~RUBY) + execute(<<~SQL, "Post Load") + ^^^^^^ Use `<<~SQL.squish` instead of `<<~SQL`. + SELECT * FROM posts + WHERE post_id = 1 + SQL + RUBY + + expect_correction(<<~RUBY) + execute(<<~SQL.squish, "Post Load") + SELECT * FROM posts + WHERE post_id = 1 + SQL + RUBY + end + + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + execute(<<~SQL.squish, "Post Load") + SELECT * FROM posts + WHERE post_id = 1 + SQL + RUBY + end + end +end