rubocop-dev_doc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/config/default.yml +81 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb +101 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_json_column.rb +40 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_update_column.rb +53 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_vague_column_names.rb +66 -0
- data/lib/rubocop/cop/dev_doc/migration/date_column_naming.rb +71 -0
- data/lib/rubocop/cop/dev_doc/migration/prefer_belongs_to.rb +41 -0
- data/lib/rubocop/cop/dev_doc/migration/require_timestamps.rb +99 -0
- data/lib/rubocop/cop/dev_doc/rails/no_deliver_later_in_transaction.rb +69 -0
- data/lib/rubocop/cop/dev_doc/rails/no_perform_later_in_model.rb +61 -0
- data/lib/rubocop/cop/dev_doc/route/resources_require_only.rb +59 -0
- data/lib/rubocop/cop/dev_doc/style/avoid_head_response.rb +57 -0
- data/lib/rubocop/cop/dev_doc/style/avoid_send.rb +61 -0
- data/lib/rubocop/dev_doc/plugin.rb +26 -0
- data/lib/rubocop/dev_doc/version.rb +5 -0
- data/lib/rubocop/dev_doc.rb +13 -0
- data/lib/rubocop-dev_doc.rb +3 -0
- metadata +96 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 32fcdd022e9dde952826050d447d83e9fa7f9bf1cfaf7caaaddda3309e392a3b
|
|
4
|
+
data.tar.gz: e6775d4b37a1d763966c25eb3b2bf7926026b2ba2fdeae6d11fc05ae9d2ba46e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fb0572fc498d052e2f49d45490c37ddfcfa349d5a4e17b199159537b713cb5206ee828ffa96d5466c5e386e8da7713a22b5d3e51eca9daf228041be278fbe014
|
|
7
|
+
data.tar.gz: a96d7bcdb8c34f3b0ab557a8bee46ef6ce91028174728cf06efa7df2943dafe7b67a47b9dff813260f2c824c6d7b1094bb6bc69c15bcb63fbf1c2f0bfab62333
|
data/config/default.yml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Default configuration for rubocop-dev_doc cops.
|
|
2
|
+
# Cops are enabled by default; disable them in your project's .rubocop.yml if needed.
|
|
3
|
+
|
|
4
|
+
DevDoc/Migration/AvoidJsonColumn:
|
|
5
|
+
Description: 'Use `jsonb` instead of `json` for column types.'
|
|
6
|
+
Enabled: true
|
|
7
|
+
Include:
|
|
8
|
+
- 'db/migrate/*.rb'
|
|
9
|
+
- 'db/migrate/**/*.rb'
|
|
10
|
+
|
|
11
|
+
DevDoc/Migration/RequireTimestamps:
|
|
12
|
+
Description: 'Always include `t.timestamps` in every `create_table` migration.'
|
|
13
|
+
Enabled: true
|
|
14
|
+
Include:
|
|
15
|
+
- 'db/migrate/*.rb'
|
|
16
|
+
- 'db/migrate/**/*.rb'
|
|
17
|
+
|
|
18
|
+
DevDoc/Migration/PreferBelongsTo:
|
|
19
|
+
Description: 'Use `t.belongs_to` instead of `t.references` for foreign keys.'
|
|
20
|
+
Enabled: true
|
|
21
|
+
Include:
|
|
22
|
+
- 'db/migrate/*.rb'
|
|
23
|
+
- 'db/migrate/**/*.rb'
|
|
24
|
+
|
|
25
|
+
DevDoc/Migration/AvoidColumnDefault:
|
|
26
|
+
Description: 'Avoid setting `default:` in migrations; keep business logic in the application layer.'
|
|
27
|
+
Enabled: true
|
|
28
|
+
Include:
|
|
29
|
+
- 'db/migrate/*.rb'
|
|
30
|
+
- 'db/migrate/**/*.rb'
|
|
31
|
+
|
|
32
|
+
DevDoc/Migration/AvoidUpdateColumn:
|
|
33
|
+
Description: 'Avoid `update_column`/`update_all`/`update_columns`; they bypass validations and callbacks.'
|
|
34
|
+
Enabled: true
|
|
35
|
+
Include:
|
|
36
|
+
- 'db/migrate/*.rb'
|
|
37
|
+
- 'db/migrate/**/*.rb'
|
|
38
|
+
|
|
39
|
+
DevDoc/Migration/DateColumnNaming:
|
|
40
|
+
Description: 'Date columns should end with `_on`; datetime columns should end with `_at`.'
|
|
41
|
+
Enabled: true
|
|
42
|
+
Include:
|
|
43
|
+
- 'db/migrate/*.rb'
|
|
44
|
+
- 'db/migrate/**/*.rb'
|
|
45
|
+
|
|
46
|
+
DevDoc/Migration/AvoidVagueColumnNames:
|
|
47
|
+
Description: 'Avoid vague column names like `status` or `group`. Use more specific names.'
|
|
48
|
+
Enabled: true
|
|
49
|
+
VagueNames:
|
|
50
|
+
- status
|
|
51
|
+
- group
|
|
52
|
+
Include:
|
|
53
|
+
- 'db/migrate/*.rb'
|
|
54
|
+
- 'db/migrate/**/*.rb'
|
|
55
|
+
|
|
56
|
+
DevDoc/Route/ResourcesRequireOnly:
|
|
57
|
+
Description: 'Always use `only:` or `except:` when defining `resources` or `resource` routes.'
|
|
58
|
+
Enabled: true
|
|
59
|
+
Include:
|
|
60
|
+
- 'config/routes.rb'
|
|
61
|
+
- 'config/routes/**/*.rb'
|
|
62
|
+
|
|
63
|
+
DevDoc/Rails/NoDeliverLaterInTransaction:
|
|
64
|
+
Description: 'Avoid `deliver_later`/`perform_later` inside a `transaction` block; the job may use stale data.'
|
|
65
|
+
Enabled: true
|
|
66
|
+
|
|
67
|
+
DevDoc/Rails/NoPerformLaterInModel:
|
|
68
|
+
Description: 'Avoid `perform_later` inside model files; use explicit methods called from the controller.'
|
|
69
|
+
Enabled: true
|
|
70
|
+
Include:
|
|
71
|
+
- 'app/models/**/*.rb'
|
|
72
|
+
|
|
73
|
+
DevDoc/Style/AvoidSend:
|
|
74
|
+
Description: 'Avoid `send`/`public_send` with an explicit receiver; prefer direct calls or safer alternatives.'
|
|
75
|
+
Enabled: true
|
|
76
|
+
|
|
77
|
+
DevDoc/Style/AvoidHeadResponse:
|
|
78
|
+
Description: 'Avoid `head()` responses; delegate error handling to Rails exceptions or model validations.'
|
|
79
|
+
Enabled: true
|
|
80
|
+
Include:
|
|
81
|
+
- 'app/controllers/**/*.rb'
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Avoid setting a `default:` value in migrations.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Avoid adding business logic to the database. Keep it centralized in the
|
|
9
|
+
# application layer (controller or model) for easier maintenance and
|
|
10
|
+
# flexibility. A `default:` in a migration embeds a business-logic
|
|
11
|
+
# assumption into the database schema, which is harder to change later
|
|
12
|
+
# than code.
|
|
13
|
+
#
|
|
14
|
+
# Instead of relying on a database default, set the value explicitly in
|
|
15
|
+
# the application — and for existing rows, backfill via a reversible
|
|
16
|
+
# migration that goes through model validations:
|
|
17
|
+
#
|
|
18
|
+
# ❌
|
|
19
|
+
# class AddProfileCompletionRateToUsers < ActiveRecord::Migration[6.1]
|
|
20
|
+
# def change
|
|
21
|
+
# add_column :users, :profile_completion_rate, :float, default: 0.0
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# ✔
|
|
26
|
+
# class AddProfileCompletionRateToUsers < ActiveRecord::Migration[6.1]
|
|
27
|
+
# def change
|
|
28
|
+
# add_column :users, :profile_completion_rate, :float
|
|
29
|
+
#
|
|
30
|
+
# reversible do |dir|
|
|
31
|
+
# dir.up do
|
|
32
|
+
# # Make sure Rails picks up the new column.
|
|
33
|
+
# User.reset_column_information
|
|
34
|
+
#
|
|
35
|
+
# User.where(profile_completion_rate: nil).find_each do |user|
|
|
36
|
+
# user.profile_completion_rate = 0.0
|
|
37
|
+
# # This may fail if existing records are invalid (e.g. nil required fields).
|
|
38
|
+
# # In that case, fix those records first rather than bypassing validation.
|
|
39
|
+
# user.save!
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# ## Exception
|
|
47
|
+
# For performance reasons (large tables with millions of records) or when
|
|
48
|
+
# using `null: false`, you may temporarily set a default and then
|
|
49
|
+
# immediately remove it in the same migration:
|
|
50
|
+
#
|
|
51
|
+
# ✔
|
|
52
|
+
# add_column :users, :profile_completion_rate, :float, default: 0.0
|
|
53
|
+
# change_column_default :users, :profile_completion_rate, from: 0.0, to: nil
|
|
54
|
+
#
|
|
55
|
+
# NOTE: This cop currently flags the first line of the exception pattern.
|
|
56
|
+
# The follow-up `change_column_default ..., to: nil` is not auto-detected.
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# # bad
|
|
60
|
+
# add_column :users, :score, :integer, default: 0
|
|
61
|
+
#
|
|
62
|
+
# # bad
|
|
63
|
+
# t.string :status, default: 'active'
|
|
64
|
+
#
|
|
65
|
+
# # good
|
|
66
|
+
# add_column :users, :score, :integer
|
|
67
|
+
#
|
|
68
|
+
# # good (temporary default immediately removed)
|
|
69
|
+
# add_column :users, :score, :integer, default: 0
|
|
70
|
+
# change_column_default :users, :score, from: 0, to: nil
|
|
71
|
+
class AvoidColumnDefault < Base
|
|
72
|
+
MSG = 'Avoid setting `default:` in migrations. Keep business logic defaults in the application layer.'.freeze
|
|
73
|
+
|
|
74
|
+
COLUMN_METHODS = %i[
|
|
75
|
+
string integer float boolean datetime date text binary decimal
|
|
76
|
+
json jsonb bigint primary_key references belongs_to
|
|
77
|
+
].freeze
|
|
78
|
+
|
|
79
|
+
def_node_matcher :has_default_option?, <<~PATTERN
|
|
80
|
+
(hash <(pair (sym :default) _) ...>)
|
|
81
|
+
PATTERN
|
|
82
|
+
|
|
83
|
+
def on_send(node)
|
|
84
|
+
return unless node.method?(:add_column) || COLUMN_METHODS.include?(node.method_name)
|
|
85
|
+
|
|
86
|
+
check_options(node.arguments.find(&:hash_type?))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def check_options(options)
|
|
92
|
+
return unless options && has_default_option?(options)
|
|
93
|
+
|
|
94
|
+
default_pair = options.pairs.find { |p| p.key.sym_type? && p.key.value == :default }
|
|
95
|
+
add_offense(default_pair)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Prefer `jsonb` over `json` for column types.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# `jsonb` stores data in a binary format that supports indexing,
|
|
9
|
+
# querying with operators (`->`, `->>`, `@>`), and is generally faster to
|
|
10
|
+
# read. Use `json` only if you need to preserve key order or exact
|
|
11
|
+
# formatting of the original JSON string — which is rare in practice.
|
|
12
|
+
#
|
|
13
|
+
# ❌
|
|
14
|
+
# t.json :metadata
|
|
15
|
+
#
|
|
16
|
+
# ✔️
|
|
17
|
+
# t.jsonb :metadata
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# # bad
|
|
21
|
+
# t.json :metadata
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# t.jsonb :metadata
|
|
25
|
+
class AvoidJsonColumn < Base
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
|
|
28
|
+
MSG = 'Use `jsonb` instead of `json`. `jsonb` supports indexing and is faster to read.'.freeze
|
|
29
|
+
RESTRICT_ON_SEND = %i[json].freeze
|
|
30
|
+
|
|
31
|
+
def on_send(node)
|
|
32
|
+
add_offense(node.loc.selector) do |corrector|
|
|
33
|
+
corrector.replace(node.loc.selector, 'jsonb')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Avoid `update_column`, `update_columns`, and `update_all` in migrations.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Avoid bypassing validation unless absolutely necessary. These methods
|
|
9
|
+
# skip validations and callbacks, which hides data integrity issues
|
|
10
|
+
# rather than surfacing them.
|
|
11
|
+
#
|
|
12
|
+
# Even in migrations, check the code to see if there is any blatant
|
|
13
|
+
# reason why existing records may be invalid. If there is, fix those
|
|
14
|
+
# records first rather than bypassing validation.
|
|
15
|
+
#
|
|
16
|
+
# ❌ Bypasses validation — hides data integrity issues
|
|
17
|
+
# Faq.where(purpose: nil).update_all(purpose: :intro)
|
|
18
|
+
#
|
|
19
|
+
# ✔️ Runs validation — surfaces problems early
|
|
20
|
+
# Faq.where(purpose: nil).find_each do |faq|
|
|
21
|
+
# faq.purpose = :intro
|
|
22
|
+
# faq.save!
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# NOTE: The broader principle ("avoid bypassing validation") also covers
|
|
26
|
+
# things this cop does not catch, e.g. `save(validate: false)`,
|
|
27
|
+
# `insert_all`, `upsert_all`, `delete_all`. Apply the same judgement to
|
|
28
|
+
# those patterns.
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# # bad
|
|
32
|
+
# Faq.where(purpose: nil).update_all(purpose: :intro)
|
|
33
|
+
#
|
|
34
|
+
# # bad
|
|
35
|
+
# user.update_column(:status, 'active')
|
|
36
|
+
#
|
|
37
|
+
# # good
|
|
38
|
+
# Faq.where(purpose: nil).find_each do |faq|
|
|
39
|
+
# faq.purpose = :intro
|
|
40
|
+
# faq.save!
|
|
41
|
+
# end
|
|
42
|
+
class AvoidUpdateColumn < Base
|
|
43
|
+
MSG = 'Avoid `%<method>s` in migrations; it bypasses validations. Use `save!` instead.'.freeze
|
|
44
|
+
RESTRICT_ON_SEND = %i[update_column update_columns update_all].freeze
|
|
45
|
+
|
|
46
|
+
def on_send(node)
|
|
47
|
+
add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Avoid vague column names like `status` or `group`.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Use more specific names that describe the domain context. A column
|
|
9
|
+
# named `status` reveals nothing about *what* status it tracks; a column
|
|
10
|
+
# named `processing_status` makes the intent obvious at the call site.
|
|
11
|
+
#
|
|
12
|
+
# ❌
|
|
13
|
+
# t.string :status
|
|
14
|
+
# add_column :orders, :group, :integer
|
|
15
|
+
#
|
|
16
|
+
# ✔️
|
|
17
|
+
# t.string :processing_status
|
|
18
|
+
# add_column :orders, :user_group, :integer
|
|
19
|
+
#
|
|
20
|
+
# ## Note about `type`
|
|
21
|
+
# `type` is reserved by Rails for Single Table Inheritance (STI). Even
|
|
22
|
+
# if STI is not in use, naming a column `type` is misleading and should
|
|
23
|
+
# be avoided. This cop does not flag `type` by default — that is left
|
|
24
|
+
# to the user's discretion via the `VagueNames` config.
|
|
25
|
+
#
|
|
26
|
+
# Configure the list of vague names via `VagueNames` in .rubocop.yml.
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# # bad
|
|
30
|
+
# t.string :status
|
|
31
|
+
# add_column :orders, :group, :integer
|
|
32
|
+
#
|
|
33
|
+
# # good
|
|
34
|
+
# t.string :processing_status
|
|
35
|
+
# add_column :orders, :user_group, :integer
|
|
36
|
+
class AvoidVagueColumnNames < Base
|
|
37
|
+
MSG = 'Avoid vague column name `%<name>s`. Use a more specific name that includes context.'.freeze
|
|
38
|
+
|
|
39
|
+
COLUMN_METHODS = %i[
|
|
40
|
+
string integer float boolean datetime date text binary decimal
|
|
41
|
+
json jsonb bigint primary_key
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
def on_send(node)
|
|
45
|
+
col_name_node = if node.method?(:add_column)
|
|
46
|
+
node.arguments[1]
|
|
47
|
+
elsif COLUMN_METHODS.include?(node.method_name)
|
|
48
|
+
node.first_argument
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return unless col_name_node&.sym_type?
|
|
52
|
+
return unless vague_names.include?(col_name_node.value.to_s)
|
|
53
|
+
|
|
54
|
+
add_offense(col_name_node, message: format(MSG, name: col_name_node.value))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def vague_names
|
|
60
|
+
cop_config.fetch('VagueNames', %w[status group])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Date columns should end with `_on`; datetime columns should end with `_at`.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Use the suffix `_on` for `date` columns and `_at` for `datetime`
|
|
9
|
+
# columns. This convention makes the column type immediately clear from
|
|
10
|
+
# the name, without having to look at the schema. For example:
|
|
11
|
+
#
|
|
12
|
+
# service_subscription.expiring_on # date
|
|
13
|
+
# service_subscription.expired_at # datetime
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad
|
|
17
|
+
# t.date :expiry
|
|
18
|
+
# t.datetime :created
|
|
19
|
+
# add_column :users, :birth, :date
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# t.date :expiring_on
|
|
23
|
+
# t.datetime :created_at
|
|
24
|
+
# add_column :users, :born_on, :date
|
|
25
|
+
class DateColumnNaming < Base
|
|
26
|
+
DATE_MSG = 'Date column `%<name>s` should end with `_on` (e.g. `%<name>s_on`).'.freeze
|
|
27
|
+
DATETIME_MSG = 'Datetime column `%<name>s` should end with `_at` (e.g. `%<name>s_at`).'.freeze
|
|
28
|
+
|
|
29
|
+
def on_send(node)
|
|
30
|
+
if node.method?(:add_column)
|
|
31
|
+
check_add_column(node)
|
|
32
|
+
elsif %i[date datetime timestamptz].include?(node.method_name)
|
|
33
|
+
check_column_definition(node)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def check_add_column(node)
|
|
40
|
+
col_name_node = node.arguments[1]
|
|
41
|
+
type_node = node.arguments[2]
|
|
42
|
+
return unless col_name_node&.sym_type? && type_node&.sym_type?
|
|
43
|
+
|
|
44
|
+
check_name(col_name_node, type_node.value)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def check_column_definition(node)
|
|
48
|
+
col_name_node = node.first_argument
|
|
49
|
+
return unless col_name_node&.sym_type?
|
|
50
|
+
|
|
51
|
+
check_name(col_name_node, node.method_name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_name(col_name_node, col_type)
|
|
55
|
+
name = col_name_node.value.to_s
|
|
56
|
+
case col_type.to_sym
|
|
57
|
+
when :date
|
|
58
|
+
return if name.end_with?('_on')
|
|
59
|
+
|
|
60
|
+
add_offense(col_name_node, message: format(DATE_MSG, name: name))
|
|
61
|
+
when :datetime, :timestamptz
|
|
62
|
+
return if name.end_with?('_at')
|
|
63
|
+
|
|
64
|
+
add_offense(col_name_node, message: format(DATETIME_MSG, name: name))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Prefer `t.belongs_to` over `t.references` for foreign keys.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# `t.belongs_to` and `t.references` are aliases and functionally
|
|
9
|
+
# identical. Prefer `belongs_to` because it matches the model's
|
|
10
|
+
# association declaration (`belongs_to :user`), which makes the
|
|
11
|
+
# migration easier to map to the model and improves readability.
|
|
12
|
+
#
|
|
13
|
+
# ❌
|
|
14
|
+
# t.references :user, foreign_key: true, null: false
|
|
15
|
+
#
|
|
16
|
+
# ✔️
|
|
17
|
+
# t.belongs_to :user, foreign_key: true, null: false
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# # bad
|
|
21
|
+
# t.references :user, foreign_key: true, null: false
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# t.belongs_to :user, foreign_key: true, null: false
|
|
25
|
+
class PreferBelongsTo < Base
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
|
|
28
|
+
MSG = 'Use `t.belongs_to` instead of `t.references` ' \
|
|
29
|
+
'(aliases, but `belongs_to` matches the model association).'.freeze
|
|
30
|
+
RESTRICT_ON_SEND = %i[references].freeze
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
add_offense(node.loc.selector) do |corrector|
|
|
34
|
+
corrector.replace(node.loc.selector, 'belongs_to')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Migration
|
|
5
|
+
# Every `create_table` migration must include `t.timestamps`.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Every table should have `created_at` and `updated_at` columns —
|
|
9
|
+
# there is no reason to omit them. They are essential for debugging,
|
|
10
|
+
# auditing, and ordering records, and adding them later is much more
|
|
11
|
+
# painful than including them up front.
|
|
12
|
+
#
|
|
13
|
+
# ❌
|
|
14
|
+
# create_table :ai_api_failures do |t|
|
|
15
|
+
# t.belongs_to :ai_chat_message, null: false, foreign_key: true
|
|
16
|
+
# t.string :error_type
|
|
17
|
+
# t.datetime :created_at
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# ✔️
|
|
21
|
+
# create_table :ai_api_failures do |t|
|
|
22
|
+
# t.belongs_to :ai_chat_message, null: false, foreign_key: true
|
|
23
|
+
# t.string :error_type
|
|
24
|
+
# t.timestamps
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# NOTE: This cop skips tables declared with `id: false` (typically join
|
|
28
|
+
# tables), where omitting timestamps is a deliberate choice.
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# # bad
|
|
32
|
+
# create_table :users do |t|
|
|
33
|
+
# t.string :name
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # good
|
|
37
|
+
# create_table :users do |t|
|
|
38
|
+
# t.string :name
|
|
39
|
+
# t.timestamps
|
|
40
|
+
# end
|
|
41
|
+
class RequireTimestamps < Base
|
|
42
|
+
extend AutoCorrector
|
|
43
|
+
|
|
44
|
+
MSG = 'Add `t.timestamps` to this `create_table` migration.'.freeze
|
|
45
|
+
RESTRICT_ON_SEND = %i[create_table].freeze
|
|
46
|
+
|
|
47
|
+
def_node_search :timestamps_included?, <<~PATTERN
|
|
48
|
+
(send _ :timestamps ...)
|
|
49
|
+
PATTERN
|
|
50
|
+
|
|
51
|
+
def_node_search :manual_timestamp_column?, <<~PATTERN
|
|
52
|
+
(send _ {:datetime :timestamptz}
|
|
53
|
+
{(sym {:created_at :updated_at}) (str {"created_at" "updated_at"})}
|
|
54
|
+
...)
|
|
55
|
+
PATTERN
|
|
56
|
+
|
|
57
|
+
def_node_matcher :id_false_option?, <<~PATTERN
|
|
58
|
+
(pair (sym :id) (false))
|
|
59
|
+
PATTERN
|
|
60
|
+
|
|
61
|
+
def on_send(node)
|
|
62
|
+
return if skip_table?(node)
|
|
63
|
+
|
|
64
|
+
block = node.parent
|
|
65
|
+
return unless block&.block_type?
|
|
66
|
+
return if timestamps_present?(block.body)
|
|
67
|
+
|
|
68
|
+
add_offense(node.loc.selector) do |corrector|
|
|
69
|
+
autocorrect(corrector, block)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def skip_table?(node)
|
|
76
|
+
node.each_descendant(:pair).any? { |pair| id_false_option?(pair) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def timestamps_present?(body)
|
|
80
|
+
return false if body.nil?
|
|
81
|
+
|
|
82
|
+
timestamps_included?(body) || manual_timestamp_column?(body)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def autocorrect(corrector, block)
|
|
86
|
+
return unless block.multiline?
|
|
87
|
+
|
|
88
|
+
table_var = block.arguments.first&.source || 't'
|
|
89
|
+
end_range = block.loc.end
|
|
90
|
+
indent = ' ' * (end_range.column + 2)
|
|
91
|
+
line_start_pos = end_range.begin_pos - end_range.column
|
|
92
|
+
insert_range = end_range.with(begin_pos: line_start_pos, end_pos: line_start_pos)
|
|
93
|
+
corrector.insert_before(insert_range, "#{indent}#{table_var}.timestamps\n")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Rails
|
|
5
|
+
# Avoid `deliver_later` and `perform_later` inside a `transaction` block.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Do not use `perform_later` or `deliver_later` inside a transaction
|
|
9
|
+
# block because there is a possibility that the job will use stale data,
|
|
10
|
+
# as the transaction has not yet completed (not yet committed changes
|
|
11
|
+
# to the database).
|
|
12
|
+
#
|
|
13
|
+
# ❌
|
|
14
|
+
# organization.transaction do
|
|
15
|
+
# if organization.save
|
|
16
|
+
# # This mailer may receive the organization with stale data
|
|
17
|
+
# # (data before `save()`)
|
|
18
|
+
# OrganizationMailer.with(organization: organization).deliver_later
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# ✔️
|
|
23
|
+
# organization.transaction do
|
|
24
|
+
# save_succeeded = organization.save
|
|
25
|
+
# end
|
|
26
|
+
# if save_succeeded
|
|
27
|
+
# OrganizationMailer.with(organization: organization).deliver_later
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# ## Watch out for indirect calls
|
|
31
|
+
# Some libraries call `perform_later` / `deliver_later` behind the
|
|
32
|
+
# scenes — e.g. `@user.send_verification_email!` from the Devise gem.
|
|
33
|
+
# This cop cannot detect those wrappers; reviewers should still flag
|
|
34
|
+
# them when they appear inside a `transaction` block.
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# # bad
|
|
38
|
+
# organization.transaction do
|
|
39
|
+
# organization.save!
|
|
40
|
+
# OrganizationMailer.with(organization: organization).deliver_later
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# # good
|
|
44
|
+
# organization.transaction do
|
|
45
|
+
# organization.save!
|
|
46
|
+
# end
|
|
47
|
+
# OrganizationMailer.with(organization: organization).deliver_later
|
|
48
|
+
class NoDeliverLaterInTransaction < Base
|
|
49
|
+
MSG = '`%<method>s` inside a `transaction` block may use stale data. Move it outside the transaction.'.freeze
|
|
50
|
+
RESTRICT_ON_SEND = %i[deliver_later perform_later].freeze
|
|
51
|
+
|
|
52
|
+
def on_send(node)
|
|
53
|
+
return unless inside_transaction?(node)
|
|
54
|
+
|
|
55
|
+
add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def inside_transaction?(node)
|
|
61
|
+
node.each_ancestor(:block).any? do |ancestor|
|
|
62
|
+
ancestor.method_name == :transaction
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Rails
|
|
5
|
+
# Avoid `perform_later` calls inside model files.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# Avoid using ActiveJob inside Models, because:
|
|
9
|
+
#
|
|
10
|
+
# - It is prone to conflicts between `transaction` and `perform_later`
|
|
11
|
+
# (the job may run before the transaction commits and read stale data
|
|
12
|
+
# — see `DevDoc/Rails/NoDeliverLaterInTransaction`).
|
|
13
|
+
# - Execution flow control should be done in the Controller, not in the
|
|
14
|
+
# Model.
|
|
15
|
+
# - If it really must be done in the Model, name the method explicitly
|
|
16
|
+
# so the side effect is obvious at the call site.
|
|
17
|
+
#
|
|
18
|
+
# ❌ (in app/models/order.rb)
|
|
19
|
+
# def finalize
|
|
20
|
+
# save!
|
|
21
|
+
# OrderJob.perform_later(self)
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# ✔️ Explicit method name signals the side effect; reviewers
|
|
25
|
+
# immediately see that this should not be called inside a
|
|
26
|
+
# `transaction` block.
|
|
27
|
+
# def save_with_email_sending
|
|
28
|
+
# save!
|
|
29
|
+
# OrderJob.perform_later(self)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# NOTE: This cop flags *any* `perform_later` in `app/models/**/*.rb`,
|
|
33
|
+
# including the legitimate "explicitly named method" case above. The
|
|
34
|
+
# cop is intentionally conservative — disable per-line with a rubocop
|
|
35
|
+
# comment when you have deliberately followed the naming convention.
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# # bad (in app/models/order.rb)
|
|
39
|
+
# def finalize
|
|
40
|
+
# save!
|
|
41
|
+
# OrderJob.perform_later(self)
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# # good — explicit method communicates the side effect
|
|
45
|
+
# def finalize_with_job
|
|
46
|
+
# save!
|
|
47
|
+
# OrderJob.perform_later(self)
|
|
48
|
+
# end
|
|
49
|
+
class NoPerformLaterInModel < Base
|
|
50
|
+
MSG = 'Avoid `perform_later` in model files. Call it from the controller, ' \
|
|
51
|
+
'or use an explicit method name to signal the side effect.'.freeze
|
|
52
|
+
RESTRICT_ON_SEND = %i[perform_later].freeze
|
|
53
|
+
|
|
54
|
+
def on_send(node)
|
|
55
|
+
add_offense(node.loc.selector)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Route
|
|
5
|
+
# Always use `only:` (or `except:`) for `resources` / `resource` in routes.rb.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# When defining routes in routes.rb, it is important to explicitly
|
|
9
|
+
# specify the desired actions using the `only` option. This helps
|
|
10
|
+
# prevent accidentally exposing actions that should not be accessible
|
|
11
|
+
# — leaving the default opens the full RESTful set, which often
|
|
12
|
+
# exposes routes the application has no controller action for, or
|
|
13
|
+
# routes that probably should be locked down.
|
|
14
|
+
#
|
|
15
|
+
# ✔️
|
|
16
|
+
# resources :job_applications, only: [:index, :new, :create]
|
|
17
|
+
#
|
|
18
|
+
# In this example, only three actions are exposed for
|
|
19
|
+
# `job_applications`: index, new, and create. This is safer because
|
|
20
|
+
# only the needed actions are declared and accessible.
|
|
21
|
+
#
|
|
22
|
+
# `except:` is also acceptable, but `only:` is preferred because it
|
|
23
|
+
# is more explicit about what is being exposed.
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# # bad
|
|
27
|
+
# resources :users
|
|
28
|
+
# resource :profile
|
|
29
|
+
#
|
|
30
|
+
# # good
|
|
31
|
+
# resources :users, only: %i[index show]
|
|
32
|
+
# resource :profile, only: %i[show edit update]
|
|
33
|
+
# resources :users, except: [:destroy]
|
|
34
|
+
class ResourcesRequireOnly < Base
|
|
35
|
+
MSG = 'Specify `only:` or `except:` for `%<method>s :%<name>s` to avoid exposing unintended actions.'.freeze
|
|
36
|
+
RESTRICT_ON_SEND = %i[resources resource].freeze
|
|
37
|
+
|
|
38
|
+
def on_send(node)
|
|
39
|
+
return if only_or_except?(node)
|
|
40
|
+
|
|
41
|
+
name = node.first_argument&.value || '?'
|
|
42
|
+
add_offense(node.loc.selector, message: format(MSG, method: node.method_name, name: name))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def only_or_except?(node)
|
|
48
|
+
options = node.arguments.find(&:hash_type?)
|
|
49
|
+
return false unless options
|
|
50
|
+
|
|
51
|
+
options.pairs.any? do |pair|
|
|
52
|
+
pair.key.sym_type? && %i[only except].include?(pair.key.value)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Style
|
|
5
|
+
# Avoid `head()` responses in controllers.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# `head()` returns an empty body with no useful information for the
|
|
9
|
+
# client. Its presence is usually a sign that error handling should be
|
|
10
|
+
# delegated to Rails exceptions (e.g. `ActiveRecord::RecordNotFound`)
|
|
11
|
+
# or model validations instead.
|
|
12
|
+
#
|
|
13
|
+
# If you find yourself reaching for `head()`, consider whether a
|
|
14
|
+
# well-known Rails exception or a model validation can handle the
|
|
15
|
+
# case more cleanly:
|
|
16
|
+
#
|
|
17
|
+
# ❌ Manually returns 404 with no body
|
|
18
|
+
# def show
|
|
19
|
+
# @user = User.find_by(id: params[:id])
|
|
20
|
+
# head(:not_found) unless @user
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# ✔️ Let Rails raise RecordNotFound — it renders the standard 404
|
|
24
|
+
# def show
|
|
25
|
+
# @user = User.find(params[:id])
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# NOTE: The cop flags every bare `head(...)` call. Some legitimate uses
|
|
29
|
+
# (e.g. `head :no_content` for a successful DELETE, or simple webhook
|
|
30
|
+
# acknowledgements) still get flagged — disable per-line in those cases.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# # bad
|
|
34
|
+
# def glib_load_resource
|
|
35
|
+
# @user = User.find_by(id: params[:id])
|
|
36
|
+
# head(:not_found) unless @user
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# # good
|
|
40
|
+
# def glib_load_resource
|
|
41
|
+
# @user = User.find(params[:id])
|
|
42
|
+
# end
|
|
43
|
+
class AvoidHeadResponse < Base
|
|
44
|
+
MSG = 'Avoid `head()`. Delegate error handling to Rails exceptions ' \
|
|
45
|
+
'(e.g. use `find` instead of `find_by` + `head(:not_found)`) or model validations.'.freeze
|
|
46
|
+
RESTRICT_ON_SEND = %i[head].freeze
|
|
47
|
+
|
|
48
|
+
def on_send(node)
|
|
49
|
+
return unless node.receiver.nil?
|
|
50
|
+
|
|
51
|
+
add_offense(node.loc.selector)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Style
|
|
5
|
+
# Avoid `send` and `public_send` with an explicit receiver.
|
|
6
|
+
#
|
|
7
|
+
# ## Rationale
|
|
8
|
+
# `send()` can call *any* method, including destructive ones like
|
|
9
|
+
# `destroy`. When the method name is dynamic, this is a real risk — a
|
|
10
|
+
# crafted parameter can invoke methods the developer never intended to
|
|
11
|
+
# expose. If `send()` is unavoidable, add safeguards to restrict which
|
|
12
|
+
# methods can be called.
|
|
13
|
+
#
|
|
14
|
+
# ## Safer alternatives
|
|
15
|
+
#
|
|
16
|
+
# **a) For model attributes — use bracket notation instead.**
|
|
17
|
+
# `@model[column_name]` only accesses database columns, so it cannot
|
|
18
|
+
# accidentally invoke methods like `destroy`.
|
|
19
|
+
#
|
|
20
|
+
# ❌ Dangerous — method_name could be :destroy or any other method
|
|
21
|
+
# @user.send(method_name)
|
|
22
|
+
#
|
|
23
|
+
# ✔️ Safe — only accesses database columns
|
|
24
|
+
# @user[method_name]
|
|
25
|
+
#
|
|
26
|
+
# **b) For non-model objects — use a prefix to restrict callable methods.**
|
|
27
|
+
# By interpolating the dynamic part into a fixed prefix, only methods
|
|
28
|
+
# with that prefix (e.g. `export_csv`, `export_pdf`) can be invoked,
|
|
29
|
+
# preventing accidental calls to unintended methods.
|
|
30
|
+
#
|
|
31
|
+
# ❌ Unrestricted — any method can be called
|
|
32
|
+
# obj.send(method_name)
|
|
33
|
+
#
|
|
34
|
+
# ✔️ Restricted — only methods with the prefix can be called
|
|
35
|
+
# obj.send("export_#{method_name}")
|
|
36
|
+
#
|
|
37
|
+
# **c) For known methods — call directly instead of via `send`.**
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# # bad
|
|
41
|
+
# @user.send(method_name)
|
|
42
|
+
# obj.public_send(action)
|
|
43
|
+
#
|
|
44
|
+
# # good
|
|
45
|
+
# @user[attribute_name]
|
|
46
|
+
# obj.send("export_#{method_name}")
|
|
47
|
+
class AvoidSend < Base
|
|
48
|
+
MSG = 'Avoid `%<method>s` with an explicit receiver. ' \
|
|
49
|
+
'Use bracket notation for model attributes, or restrict callable methods with a prefix.'.freeze
|
|
50
|
+
RESTRICT_ON_SEND = %i[send public_send].freeze
|
|
51
|
+
|
|
52
|
+
def on_send(node)
|
|
53
|
+
return if node.receiver.nil?
|
|
54
|
+
|
|
55
|
+
add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module DevDoc
|
|
3
|
+
class Plugin < LintRoller::Plugin
|
|
4
|
+
def about
|
|
5
|
+
LintRoller::About.new(
|
|
6
|
+
name: "rubocop-dev_doc",
|
|
7
|
+
version: VERSION,
|
|
8
|
+
homepage: "https://github.com/hgani/dev-doc",
|
|
9
|
+
description: "RuboCop cops enforcing dev-doc best practices"
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def supported?(context)
|
|
14
|
+
context.engine == :rubocop
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def rules(_context)
|
|
18
|
+
LintRoller::Rules.new(
|
|
19
|
+
type: :path,
|
|
20
|
+
config_format: :rubocop,
|
|
21
|
+
value: RuboCop::DevDoc::CONFIG_DEFAULT
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative "dev_doc/version"
|
|
2
|
+
require_relative "dev_doc/plugin"
|
|
3
|
+
|
|
4
|
+
module RuboCop
|
|
5
|
+
module DevDoc
|
|
6
|
+
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
|
7
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
RuboCop::ConfigLoader.ignore_parent_exclusion = true
|
|
12
|
+
|
|
13
|
+
Dir[File.join(__dir__, "cop", "dev_doc", "**", "*.rb")].each { |f| require f }
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-dev_doc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- dev-doc contributors
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rubocop
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.72'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.72'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop-rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: lint_roller
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
executables: []
|
|
55
|
+
extensions: []
|
|
56
|
+
extra_rdoc_files: []
|
|
57
|
+
files:
|
|
58
|
+
- config/default.yml
|
|
59
|
+
- lib/rubocop-dev_doc.rb
|
|
60
|
+
- lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb
|
|
61
|
+
- lib/rubocop/cop/dev_doc/migration/avoid_json_column.rb
|
|
62
|
+
- lib/rubocop/cop/dev_doc/migration/avoid_update_column.rb
|
|
63
|
+
- lib/rubocop/cop/dev_doc/migration/avoid_vague_column_names.rb
|
|
64
|
+
- lib/rubocop/cop/dev_doc/migration/date_column_naming.rb
|
|
65
|
+
- lib/rubocop/cop/dev_doc/migration/prefer_belongs_to.rb
|
|
66
|
+
- lib/rubocop/cop/dev_doc/migration/require_timestamps.rb
|
|
67
|
+
- lib/rubocop/cop/dev_doc/rails/no_deliver_later_in_transaction.rb
|
|
68
|
+
- lib/rubocop/cop/dev_doc/rails/no_perform_later_in_model.rb
|
|
69
|
+
- lib/rubocop/cop/dev_doc/route/resources_require_only.rb
|
|
70
|
+
- lib/rubocop/cop/dev_doc/style/avoid_head_response.rb
|
|
71
|
+
- lib/rubocop/cop/dev_doc/style/avoid_send.rb
|
|
72
|
+
- lib/rubocop/dev_doc.rb
|
|
73
|
+
- lib/rubocop/dev_doc/plugin.rb
|
|
74
|
+
- lib/rubocop/dev_doc/version.rb
|
|
75
|
+
licenses: []
|
|
76
|
+
metadata:
|
|
77
|
+
default_lint_roller_plugin: RuboCop::DevDoc::Plugin
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 4.0.6
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: RuboCop cops enforcing dev-doc best practices
|
|
96
|
+
test_files: []
|