Skip to content

Commit 66b7418

Browse files
committed
Add Rails/SchemaComment cop - This is a squashed commit
Add `Rails/AddColumnComment` cop Update config/default.yml Co-authored-by: Andy Waite <[email protected]> Update lib/rubocop/cop/rails/schema_comment.rb Co-authored-by: Andy Waite <[email protected]> Update Rails/SchemaComment doc Add more column types to Rails/SchemaComment Improve SchemaComment specs Remove uncessary safe project config for SchemaComment Augment rails schema definitions and use Set Change constant into def_node_matcher @SchemaComment
1 parent 3d5e697 commit 66b7418

File tree

10 files changed

+303
-7
lines changed

10 files changed

+303
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,4 @@
483483
[@johnsyweb]: https://github.com/johnsyweb
484484
[@theunraveler]: https://github.com/theunraveler
485485
[@pirj]: https://github.com/pirj
486+
[@vitormd]: https://github.com/vitormd

changelog/new_schema_comment_cop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#568](https://github.com/rubocop/rubocop-rails/issues/568): Add `Rails/SchemaComment` cop. ([@vitormd][])

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,13 @@ Rails/SaveBang:
733733
AllowedReceivers: []
734734
SafeAutoCorrect: false
735735

736+
Rails/SchemaComment:
737+
Description: >-
738+
This cop enforces the use of the `comment` option when adding a new table or column
739+
to the database during a migration.
740+
Enabled: false
741+
VersionAdded: '2.13'
742+
736743
Rails/ScopeArgs:
737744
Description: 'Checks the arguments of ActiveRecord scopes.'
738745
Enabled: true

docs/modules/ROOT/pages/cops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ based on the https://rails.rubystyle.guide/[Rails Style Guide].
9797
* xref:cops_rails.adoc#railssafenavigation[Rails/SafeNavigation]
9898
* xref:cops_rails.adoc#railssafenavigationwithblank[Rails/SafeNavigationWithBlank]
9999
* xref:cops_rails.adoc#railssavebang[Rails/SaveBang]
100+
* xref:cops_rails.adoc#railsschemacomment[Rails/SchemaComment]
100101
* xref:cops_rails.adoc#railsscopeargs[Rails/ScopeArgs]
101102
* xref:cops_rails.adoc#railsshorti18n[Rails/ShortI18n]
102103
* xref:cops_rails.adoc#railsskipsmodelvalidations[Rails/SkipsModelValidations]

docs/modules/ROOT/pages/cops_rails.adoc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4503,6 +4503,40 @@ Service::Mailer::update
45034503

45044504
* https://rails.rubystyle.guide#save-bang
45054505

4506+
== Rails/SchemaComment
4507+
4508+
|===
4509+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
4510+
4511+
| Disabled
4512+
| Yes
4513+
| No
4514+
| 2.13
4515+
| -
4516+
|===
4517+
4518+
This cop enforces the use of the `comment` option when adding a new table or column
4519+
to the database during a migration.
4520+
4521+
=== Examples
4522+
4523+
[source,ruby]
4524+
----
4525+
# bad (no comment for a new column or table)
4526+
add_column :table, :column, :integer
4527+
4528+
create_table :table do |t|
4529+
t.type :column
4530+
end
4531+
4532+
# good
4533+
add_column :table, :column, :integer, comment: 'Number of offenses'
4534+
4535+
create_table :table, comment: 'Table of offenses data' do |t|
4536+
t.type :column, comment: 'Number of offenses'
4537+
end
4538+
----
4539+
45064540
== Rails/ScopeArgs
45074541

45084542
|===
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
# A mixin to extend cops for Active Record features
6+
module ActiveRecordMigrationsHelper
7+
extend NodePattern::Macros
8+
9+
RAILS_ABSTRACT_SCHEMA_DEFINITIONS = %i[
10+
bigint binary boolean date datetime decimal float integer json string
11+
text time timestamp virtual
12+
].freeze
13+
RAILS_ABSTRACT_SCHEMA_DEFINITIONS_HELPERS = %i[
14+
column references belongs_to primary_key numeric
15+
].freeze
16+
POSTGRES_SCHEMA_DEFINITIONS = %i[
17+
bigserial bit bit_varying cidr citext daterange hstore inet interval
18+
int4range int8range jsonb ltree macaddr money numrange oid point line
19+
lseg box path polygon circle serial tsrange tstzrange tsvector uuid xml
20+
].freeze
21+
MYSQL_SCHEMA_DEFINITIONS = %i[
22+
blob tinyblob mediumblob longblob tinytext mediumtext longtext
23+
unsigned_integer unsigned_bigint unsigned_float unsigned_decimal
24+
].freeze
25+
26+
def_node_matcher :create_table_with_block?, <<~PATTERN
27+
(block
28+
(send nil? :create_table ...)
29+
(args (arg _var))
30+
_)
31+
PATTERN
32+
end
33+
end
34+
end

lib/rubocop/cop/rails/create_table_with_timestamps.rb

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,11 @@ module Rails
4141
# t.datetime :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
4242
# end
4343
class CreateTableWithTimestamps < Base
44+
include ActiveRecordMigrationsHelper
45+
4446
MSG = 'Add timestamps when creating a new table.'
4547
RESTRICT_ON_SEND = %i[create_table].freeze
4648

47-
def_node_matcher :create_table_with_block?, <<~PATTERN
48-
(block
49-
(send nil? :create_table ...)
50-
(args (arg _var))
51-
_)
52-
PATTERN
53-
5449
def_node_matcher :create_table_with_timestamps_proc?, <<~PATTERN
5550
(send nil? :create_table (sym _) ... (block-pass (sym :timestamps)))
5651
PATTERN
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# This cop enforces the use of the `comment` option when adding a new table or column
7+
# to the database during a migration.
8+
#
9+
# @example
10+
# # bad (no comment for a new column or table)
11+
# add_column :table, :column, :integer
12+
#
13+
# create_table :table do |t|
14+
# t.type :column
15+
# end
16+
#
17+
# # good
18+
# add_column :table, :column, :integer, comment: 'Number of offenses'
19+
#
20+
# create_table :table, comment: 'Table of offenses data' do |t|
21+
# t.type :column, comment: 'Number of offenses'
22+
# end
23+
#
24+
class SchemaComment < Base
25+
include ActiveRecordMigrationsHelper
26+
27+
COLUMN_MSG = 'New database column without `comment`.'
28+
TABLE_MSG = 'New database table without `comment`.'
29+
RESTRICT_ON_SEND = %i[add_column create_table].freeze
30+
CREATE_TABLE_COLUMN_METHODS = Set[
31+
*(
32+
RAILS_ABSTRACT_SCHEMA_DEFINITIONS |
33+
RAILS_ABSTRACT_SCHEMA_DEFINITIONS_HELPERS |
34+
POSTGRES_SCHEMA_DEFINITIONS |
35+
MYSQL_SCHEMA_DEFINITIONS
36+
)
37+
].freeze
38+
39+
# @!method comment_present?(node)
40+
def_node_matcher :comment_present?, <<~PATTERN
41+
(hash <(pair {(sym :comment) (str "comment")} (_ [present?])) ...>)
42+
PATTERN
43+
44+
# @!method add_column?(node)
45+
def_node_matcher :add_column?, <<~PATTERN
46+
(send nil? :add_column _table _column _type _?)
47+
PATTERN
48+
49+
# @!method add_column_with_comment?(node)
50+
def_node_matcher :add_column_with_comment?, <<~PATTERN
51+
(send nil? :add_column _table _column _type #comment_present?)
52+
PATTERN
53+
54+
# @!method create_table?(node)
55+
def_node_matcher :create_table?, <<~PATTERN
56+
(send nil? :create_table _table _?)
57+
PATTERN
58+
59+
# @!method create_table?(node)
60+
def_node_matcher :create_table_with_comment?, <<~PATTERN
61+
(send nil? :create_table _table #comment_present? ...)
62+
PATTERN
63+
64+
# @!method t_column?(node)
65+
def_node_matcher :t_column?, <<~PATTERN
66+
(send _var CREATE_TABLE_COLUMN_METHODS ...)
67+
PATTERN
68+
69+
# @!method t_column_with_comment?(node)
70+
def_node_matcher :t_column_with_comment?, <<~PATTERN
71+
(send _var CREATE_TABLE_COLUMN_METHODS _column _type? #comment_present?)
72+
PATTERN
73+
74+
def on_send(node)
75+
if add_column_without_comment?(node)
76+
add_offense(node, message: COLUMN_MSG)
77+
elsif create_table?(node)
78+
if create_table_without_comment?(node)
79+
add_offense(node, message: TABLE_MSG)
80+
elsif create_table_column_call_without_comment?(node)
81+
add_offense(node.parent.body, message: COLUMN_MSG)
82+
end
83+
end
84+
end
85+
86+
private
87+
88+
def add_column_without_comment?(node)
89+
add_column?(node) && !add_column_with_comment?(node)
90+
end
91+
92+
def create_table_without_comment?(node)
93+
create_table?(node) && !create_table_with_comment?(node)
94+
end
95+
96+
def create_table_column_call_without_comment?(node)
97+
create_table_with_block?(node.parent) &&
98+
t_column?(node.parent.body) &&
99+
!t_column_with_comment?(node.parent.body)
100+
end
101+
end
102+
end
103+
end
104+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require_relative 'mixin/active_record_helper'
4+
require_relative 'mixin/active_record_migrations_helper'
45
require_relative 'mixin/enforce_superclass'
56
require_relative 'mixin/index_method'
67
require_relative 'mixin/target_rails_version'
@@ -10,6 +11,7 @@
1011
require_relative 'rails/active_record_callbacks_order'
1112
require_relative 'rails/active_record_override'
1213
require_relative 'rails/active_support_aliases'
14+
require_relative 'rails/schema_comment'
1315
require_relative 'rails/add_column_index'
1416
require_relative 'rails/after_commit_override'
1517
require_relative 'rails/application_controller'
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::SchemaComment, :config do
4+
context 'when send add_column' do
5+
it 'registers an offense when `add_column` has no `comment` option' do
6+
expect_offense(<<~RUBY)
7+
add_column :table, :column, :integer
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database column without `comment`.
9+
RUBY
10+
end
11+
12+
it 'registers an offense when `add_column` has no `comment` option, but other options' do
13+
expect_offense(<<~RUBY)
14+
add_column :table, :column, :integer, default: 0
15+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database column without `comment`.
16+
RUBY
17+
end
18+
19+
it 'registers an offense when `add_column` has a nil `comment` option' do
20+
expect_offense(<<~RUBY)
21+
add_column :table, :column, :integer, comment: nil
22+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database column without `comment`.
23+
RUBY
24+
end
25+
26+
it 'registers an offense when `add_column` has an empty `comment` option' do
27+
expect_offense(<<~RUBY)
28+
add_column :table, :column, :integer, comment: ''
29+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database column without `comment`.
30+
RUBY
31+
end
32+
33+
it 'does not register an offense when `add_column` has `comment` option' do
34+
expect_no_offenses(<<~RUBY)
35+
add_column :table, :column, :integer, comment: 'An integer field'
36+
RUBY
37+
end
38+
39+
it 'does not register an offense when `add_column` has `comment` option'\
40+
'among other options' do
41+
expect_no_offenses(<<~RUBY)
42+
add_column :table, :column, :integer, null: false, comment: 'An integer field', default: 0
43+
RUBY
44+
end
45+
end
46+
47+
context 'when send create_table' do
48+
it 'registers an offense when `create_table` has no `comment` option' do
49+
expect_offense(<<~RUBY)
50+
create_table :users do |t|
51+
^^^^^^^^^^^^^^^^^^^ New database table without `comment`.
52+
end
53+
RUBY
54+
end
55+
56+
it 'registers an offense when `create_table` has a nil `comment` option' do
57+
expect_offense(<<~RUBY)
58+
create_table :users, comment: nil do |t|
59+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database table without `comment`.
60+
end
61+
RUBY
62+
end
63+
64+
it 'registers an offense when `create_table` has a empty `comment` option' do
65+
expect_offense(<<~RUBY)
66+
create_table :users, comment: '' do |t|
67+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ New database table without `comment`.
68+
69+
end
70+
RUBY
71+
end
72+
73+
it 'registers an offense when `t.column` has no `comment` option' do
74+
expect_offense(<<~RUBY)
75+
create_table :users, comment: 'Table' do |t|
76+
t.column :column, :integer
77+
^^^^^^^^^^^^^^^^^^^^^^^^^^ New database column without `comment`.
78+
end
79+
RUBY
80+
end
81+
82+
it 'registers an offense when `t.integer` has no `comment` option' do
83+
expect_offense(<<~RUBY)
84+
create_table :users, comment: 'Table' do |t|
85+
t.integer :column
86+
^^^^^^^^^^^^^^^^^ New database column without `comment`.
87+
end
88+
RUBY
89+
end
90+
91+
it 'does not register an offense when `t.column` has `comment` option' do
92+
expect_no_offenses(<<~RUBY)
93+
create_table :users, comment: 'Table' do |t|
94+
t.column :column, :integer, comment: 'I am a column'
95+
end
96+
RUBY
97+
end
98+
99+
it 'does not register an offense when `t.column` has `comment` option' \
100+
'among other options' do
101+
expect_no_offenses(<<~RUBY)
102+
create_table :users, comment: 'Table' do |t|
103+
t.column :column, :integer, default: nil, comment: 'I am a column', null: true
104+
end
105+
RUBY
106+
end
107+
108+
it 'does not register an offense when `t.integer` has `comment` option'\
109+
'among other options' do
110+
expect_no_offenses(<<~RUBY)
111+
create_table :users, comment: 'Table' do |t|
112+
t.integer :column, default: nil, comment: 'I am a column', null: true
113+
end
114+
RUBY
115+
end
116+
end
117+
end

0 commit comments

Comments
 (0)