rubocop-dev_doc 0.1.0 → 0.2.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 +4 -4
- data/config/default.yml +125 -9
- data/lib/rubocop/cop/dev_doc/migration/amount_column_in_cents.rb +92 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_bypassing_validation.rb +86 -0
- data/lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb +68 -13
- data/lib/rubocop/cop/dev_doc/migration/avoid_json_column.rb +18 -3
- data/lib/rubocop/cop/dev_doc/migration/no_create_join_table.rb +53 -0
- data/lib/rubocop/cop/dev_doc/migration/require_primary_key.rb +55 -0
- data/lib/rubocop/cop/dev_doc/migration/require_timestamps.rb +4 -13
- data/lib/rubocop/cop/dev_doc/rails/application_record_transaction.rb +56 -0
- data/lib/rubocop/cop/dev_doc/rails/avoid_rails_callbacks.rb +135 -0
- data/lib/rubocop/cop/dev_doc/rails/enum_must_be_symbolized.rb +83 -0
- data/lib/rubocop/cop/dev_doc/rails/no_deliver_later_in_transaction.rb +22 -5
- data/lib/rubocop/cop/dev_doc/route/resources_require_only.rb +29 -15
- data/lib/rubocop/cop/dev_doc/style/avoid_head_response.rb +56 -22
- data/lib/rubocop/cop/dev_doc/style/avoid_options_hash.rb +102 -0
- data/lib/rubocop/cop/dev_doc/style/avoid_send.rb +14 -9
- data/lib/rubocop/cop/dev_doc/style/string_symbol_comparison.rb +91 -0
- data/lib/rubocop-dev_doc.rb +1 -0
- metadata +19 -11
- data/lib/rubocop/cop/dev_doc/migration/avoid_update_column.rb +0 -53
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module DevDoc
|
|
4
|
+
module Style
|
|
5
|
+
# Flag direct `==` / `!=` comparisons between a known-string source
|
|
6
|
+
# and a symbol literal. Such comparisons are silently always false
|
|
7
|
+
# (`'draft' == :draft` is `false` in Ruby), so they signal that the
|
|
8
|
+
# caller forgot to convert at the boundary.
|
|
9
|
+
#
|
|
10
|
+
# ## Rationale
|
|
11
|
+
# Strings and symbols are not equal in Ruby, and string-typed values
|
|
12
|
+
# arrive from boundaries the developer doesn't control: HTTP params,
|
|
13
|
+
# request headers, ENV. Comparing one of these directly against a
|
|
14
|
+
# symbol literal is a guaranteed bug. Convert at the boundary instead
|
|
15
|
+
# — see `best_practices/backend/en/01a_defensive_programming.md` item 7.
|
|
16
|
+
#
|
|
17
|
+
# ❌
|
|
18
|
+
# if params[:status] == :draft # always false
|
|
19
|
+
#
|
|
20
|
+
# ✔ Convert at the boundary, then compare
|
|
21
|
+
# @filter_status = params[:status]&.to_sym
|
|
22
|
+
# if @filter_status == :draft
|
|
23
|
+
#
|
|
24
|
+
# ## Sources detected
|
|
25
|
+
# - `params[…]`
|
|
26
|
+
# - `request.headers[…]`
|
|
27
|
+
# - `ENV[…]`
|
|
28
|
+
#
|
|
29
|
+
# ## Limitations
|
|
30
|
+
# The cop only catches the *direct* comparison form. Once the value
|
|
31
|
+
# flows into a local or instance variable, type information is lost
|
|
32
|
+
# and Rubocop cannot trace it back to the original string source.
|
|
33
|
+
# Those cases are caught by review.
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# # bad
|
|
37
|
+
# params[:status] == :draft
|
|
38
|
+
# :production == ENV['MODE']
|
|
39
|
+
# request.headers['X-Mode'] != :debug
|
|
40
|
+
#
|
|
41
|
+
# # good
|
|
42
|
+
# params[:status]&.to_sym == :draft
|
|
43
|
+
# ENV['MODE'] == 'production'
|
|
44
|
+
class StringSymbolComparison < Base
|
|
45
|
+
MSG = 'Comparing string-typed `%<source>s` to a symbol literal is always false. ' \
|
|
46
|
+
'Convert at the boundary with `&.to_sym` or compare against a string instead ' \
|
|
47
|
+
'(see backend/01a_defensive_programming.md item 7).'.freeze
|
|
48
|
+
|
|
49
|
+
RESTRICT_ON_SEND = %i[== !=].freeze
|
|
50
|
+
|
|
51
|
+
def_node_matcher :params_access?, <<~PATTERN
|
|
52
|
+
(send (send nil? :params) :[] _)
|
|
53
|
+
PATTERN
|
|
54
|
+
|
|
55
|
+
def_node_matcher :request_headers_access?, <<~PATTERN
|
|
56
|
+
(send (send _ :headers) :[] _)
|
|
57
|
+
PATTERN
|
|
58
|
+
|
|
59
|
+
def_node_matcher :env_access?, <<~PATTERN
|
|
60
|
+
(send (const nil? :ENV) :[] _)
|
|
61
|
+
PATTERN
|
|
62
|
+
|
|
63
|
+
def on_send(node)
|
|
64
|
+
lhs = node.receiver
|
|
65
|
+
rhs = node.first_argument
|
|
66
|
+
return unless lhs && rhs
|
|
67
|
+
|
|
68
|
+
source_node = pick_string_source(lhs, rhs)
|
|
69
|
+
return unless source_node
|
|
70
|
+
|
|
71
|
+
add_offense(node, message: format(MSG, source: source_node.source))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def pick_string_source(left, right)
|
|
77
|
+
if string_source?(left) && right.sym_type?
|
|
78
|
+
left
|
|
79
|
+
elsif string_source?(right) && left.sym_type?
|
|
80
|
+
right
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def string_source?(node)
|
|
85
|
+
params_access?(node) || request_headers_access?(node) || env_access?(node)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/rubocop-dev_doc.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-dev_doc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dev-doc contributors
|
|
@@ -10,65 +10,73 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: lint_roller
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rubocop
|
|
27
|
+
name: rubocop
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '1.72'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '1.72'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: rubocop-rails
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
46
|
+
version: '2.0'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
53
|
+
version: '2.0'
|
|
54
54
|
executables: []
|
|
55
55
|
extensions: []
|
|
56
56
|
extra_rdoc_files: []
|
|
57
57
|
files:
|
|
58
58
|
- config/default.yml
|
|
59
59
|
- lib/rubocop-dev_doc.rb
|
|
60
|
+
- lib/rubocop/cop/dev_doc/migration/amount_column_in_cents.rb
|
|
61
|
+
- lib/rubocop/cop/dev_doc/migration/avoid_bypassing_validation.rb
|
|
60
62
|
- lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb
|
|
61
63
|
- lib/rubocop/cop/dev_doc/migration/avoid_json_column.rb
|
|
62
|
-
- lib/rubocop/cop/dev_doc/migration/avoid_update_column.rb
|
|
63
64
|
- lib/rubocop/cop/dev_doc/migration/avoid_vague_column_names.rb
|
|
64
65
|
- lib/rubocop/cop/dev_doc/migration/date_column_naming.rb
|
|
66
|
+
- lib/rubocop/cop/dev_doc/migration/no_create_join_table.rb
|
|
65
67
|
- lib/rubocop/cop/dev_doc/migration/prefer_belongs_to.rb
|
|
68
|
+
- lib/rubocop/cop/dev_doc/migration/require_primary_key.rb
|
|
66
69
|
- lib/rubocop/cop/dev_doc/migration/require_timestamps.rb
|
|
70
|
+
- lib/rubocop/cop/dev_doc/rails/application_record_transaction.rb
|
|
71
|
+
- lib/rubocop/cop/dev_doc/rails/avoid_rails_callbacks.rb
|
|
72
|
+
- lib/rubocop/cop/dev_doc/rails/enum_must_be_symbolized.rb
|
|
67
73
|
- lib/rubocop/cop/dev_doc/rails/no_deliver_later_in_transaction.rb
|
|
68
74
|
- lib/rubocop/cop/dev_doc/rails/no_perform_later_in_model.rb
|
|
69
75
|
- lib/rubocop/cop/dev_doc/route/resources_require_only.rb
|
|
70
76
|
- lib/rubocop/cop/dev_doc/style/avoid_head_response.rb
|
|
77
|
+
- lib/rubocop/cop/dev_doc/style/avoid_options_hash.rb
|
|
71
78
|
- lib/rubocop/cop/dev_doc/style/avoid_send.rb
|
|
79
|
+
- lib/rubocop/cop/dev_doc/style/string_symbol_comparison.rb
|
|
72
80
|
- lib/rubocop/dev_doc.rb
|
|
73
81
|
- lib/rubocop/dev_doc/plugin.rb
|
|
74
82
|
- lib/rubocop/dev_doc/version.rb
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|