rubocop-rails 2.4.2 → 2.5.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/README.md +4 -0
- data/config/default.yml +23 -0
- data/lib/rubocop-rails.rb +3 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +62 -0
- data/lib/rubocop/cop/mixin/index_method.rb +154 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
- data/lib/rubocop/cop/rails/index_by.rb +56 -0
- data/lib/rubocop/cop/rails/index_with.rb +59 -0
- data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
- data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +133 -0
- data/lib/rubocop/cop/rails_cops.rb +5 -0
- data/lib/rubocop/rails/schema_loader.rb +61 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +158 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd5bcec81363b7952d67bc476f82e25fbe0e0a8e4eb62e490712ddb5cfb58807
|
4
|
+
data.tar.gz: 0eeec3b789aed712f41d0f842620ec0fd871dc884fe791819256b5777a0edb9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fb1e35ca0fab64ed16959e29d2144b5cb9aafaf5064d04c67f1bee3cba9eaa4cc2c7c87b71dc240d10c1d11a8361ccacb3cb7002b9e14ef0a3908bfe271f79d
|
7
|
+
data.tar.gz: 108de4501d9f50c50d7d3e23c93574a1a2eee389d5abef80b7e9982d3d7dba524dc263cf76986ae3caa1ab202fc4c7b20ce8106ac82dab5c43e7a578f4fe04ef
|
data/README.md
CHANGED
data/config/default.yml
CHANGED
@@ -268,6 +268,16 @@ Rails/IgnoredSkipActionFilterOption:
|
|
268
268
|
Include:
|
269
269
|
- app/controllers/**/*.rb
|
270
270
|
|
271
|
+
Rails/IndexBy:
|
272
|
+
Description: 'Prefer `index_by` over `each_with_object` or `map`.'
|
273
|
+
Enabled: true
|
274
|
+
VersionAdded: '2.5'
|
275
|
+
|
276
|
+
Rails/IndexWith:
|
277
|
+
Description: 'Prefer `index_with` over `each_with_object` or `map`.'
|
278
|
+
Enabled: true
|
279
|
+
VersionAdded: '2.5'
|
280
|
+
|
271
281
|
Rails/InverseOf:
|
272
282
|
Description: 'Checks for associations where the inverse cannot be determined automatically.'
|
273
283
|
Enabled: true
|
@@ -346,6 +356,8 @@ Rails/RakeEnvironment:
|
|
346
356
|
Include:
|
347
357
|
- '**/Rakefile'
|
348
358
|
- '**/*.rake'
|
359
|
+
Exclude:
|
360
|
+
- 'lib/capistrano/tasks/**/*.rake'
|
349
361
|
|
350
362
|
Rails/ReadWriteAttribute:
|
351
363
|
Description: >-
|
@@ -381,6 +393,10 @@ Rails/RefuteMethods:
|
|
381
393
|
Description: 'Use `assert_not` methods instead of `refute` methods.'
|
382
394
|
Enabled: true
|
383
395
|
VersionAdded: '0.56'
|
396
|
+
EnforcedStyle: assert_not
|
397
|
+
SupportedStyles:
|
398
|
+
- assert_not
|
399
|
+
- refute
|
384
400
|
Include:
|
385
401
|
- '**/test/**/*'
|
386
402
|
|
@@ -496,6 +512,13 @@ Rails/UniqBeforePluck:
|
|
496
512
|
- aggressive
|
497
513
|
AutoCorrect: false
|
498
514
|
|
515
|
+
Rails/UniqueValidationWithoutIndex:
|
516
|
+
Description: 'Uniqueness validation should be with a unique index.'
|
517
|
+
Enabled: true
|
518
|
+
VersionAdded: '2.5'
|
519
|
+
Include:
|
520
|
+
- app/models/**/*.rb
|
521
|
+
|
499
522
|
Rails/UnknownEnv:
|
500
523
|
Description: 'Use correct environment name.'
|
501
524
|
Enabled: true
|
data/lib/rubocop-rails.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'rubocop'
|
4
4
|
require 'rack/utils'
|
5
|
+
require 'active_support/inflector'
|
5
6
|
|
6
7
|
require_relative 'rubocop/rails'
|
7
8
|
require_relative 'rubocop/rails/version'
|
8
9
|
require_relative 'rubocop/rails/inject'
|
10
|
+
require_relative 'rubocop/rails/schema_loader'
|
11
|
+
require_relative 'rubocop/rails/schema_loader/schema'
|
9
12
|
|
10
13
|
RuboCop::Rails::Inject.defaults!
|
11
14
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# A mixin to extend cops for Active Record features
|
6
|
+
module ActiveRecordHelper
|
7
|
+
extend NodePattern::Macros
|
8
|
+
|
9
|
+
def_node_search :find_set_table_name, <<~PATTERN
|
10
|
+
(send self :table_name= {str sym})
|
11
|
+
PATTERN
|
12
|
+
|
13
|
+
def_node_search :find_belongs_to, <<~PATTERN
|
14
|
+
(send nil? :belongs_to {str sym} ...)
|
15
|
+
PATTERN
|
16
|
+
|
17
|
+
def table_name(class_node)
|
18
|
+
table_name = find_set_table_name(class_node).to_a.last&.first_argument
|
19
|
+
return table_name.value.to_s if table_name
|
20
|
+
|
21
|
+
namespaces = class_node.each_ancestor(:class, :module)
|
22
|
+
[class_node, *namespaces]
|
23
|
+
.reverse
|
24
|
+
.map { |klass| klass.identifier.children[1] }.join('_')
|
25
|
+
.tableize
|
26
|
+
end
|
27
|
+
|
28
|
+
# Resolve relation into column name.
|
29
|
+
# It just returns column_name if the column exists.
|
30
|
+
# Or it tries to resolve column_name as a relation.
|
31
|
+
# It returns `nil` if it can't resolve.
|
32
|
+
#
|
33
|
+
# @param name [String]
|
34
|
+
# @param class_node [RuboCop::AST::Node]
|
35
|
+
# @param table [RuboCop::Rails::SchemaLoader::Table]
|
36
|
+
# @return [String, nil]
|
37
|
+
def resolve_relation_into_column(name:, class_node:, table:)
|
38
|
+
return name if table.with_column?(name: name)
|
39
|
+
|
40
|
+
find_belongs_to(class_node) do |belongs_to|
|
41
|
+
next unless belongs_to.first_argument.value.to_s == name
|
42
|
+
|
43
|
+
fk = foreign_key_of(belongs_to) || "#{name}_id"
|
44
|
+
return fk if table.with_column?(name: fk)
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def foreign_key_of(belongs_to)
|
50
|
+
options = belongs_to.last_argument
|
51
|
+
return unless options.hash_type?
|
52
|
+
|
53
|
+
options.each_pair.find do |pair|
|
54
|
+
next unless pair.key.sym_type? && pair.key.value == :foreign_key
|
55
|
+
next unless pair.value.sym_type? || pair.value.str_type?
|
56
|
+
|
57
|
+
break pair.value.value.to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for Rails/IndexBy and Rails/IndexWith
|
6
|
+
module IndexMethod # rubocop:disable Metrics/ModuleLength
|
7
|
+
def on_block(node)
|
8
|
+
on_bad_each_with_object(node) do |*match|
|
9
|
+
handle_possible_offense(node, match, 'each_with_object')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_send(node)
|
14
|
+
on_bad_map_to_h(node) do |*match|
|
15
|
+
handle_possible_offense(node, match, 'map { ... }.to_h')
|
16
|
+
end
|
17
|
+
|
18
|
+
on_bad_hash_brackets_map(node) do |*match|
|
19
|
+
handle_possible_offense(node, match, 'Hash[map { ... }]')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_csend(node)
|
24
|
+
on_bad_map_to_h(node) do |*match|
|
25
|
+
handle_possible_offense(node, match, 'map { ... }.to_h')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocorrect(node)
|
30
|
+
lambda do |corrector|
|
31
|
+
correction = prepare_correction(node)
|
32
|
+
execute_correction(corrector, node, correction)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @abstract Implemented with `def_node_matcher`
|
39
|
+
def on_bad_each_with_object(_node)
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
# @abstract Implemented with `def_node_matcher`
|
44
|
+
def on_bad_map_to_h(_node)
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
# @abstract Implemented with `def_node_matcher`
|
49
|
+
def on_bad_hash_brackets_map(_node)
|
50
|
+
raise NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_possible_offense(node, match, match_desc)
|
54
|
+
captures = extract_captures(match)
|
55
|
+
|
56
|
+
return if captures.noop_transformation?
|
57
|
+
|
58
|
+
add_offense(
|
59
|
+
node,
|
60
|
+
message: "Prefer `#{new_method_name}` over `#{match_desc}`."
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_captures(match)
|
65
|
+
argname, body_expr = *match
|
66
|
+
Captures.new(argname, body_expr)
|
67
|
+
end
|
68
|
+
|
69
|
+
def new_method_name
|
70
|
+
raise NotImplementedError
|
71
|
+
end
|
72
|
+
|
73
|
+
def prepare_correction(node)
|
74
|
+
if (match = on_bad_each_with_object(node))
|
75
|
+
Autocorrection.from_each_with_object(node, match)
|
76
|
+
elsif (match = on_bad_map_to_h(node))
|
77
|
+
Autocorrection.from_map_to_h(node, match)
|
78
|
+
elsif (match = on_bad_hash_brackets_map(node))
|
79
|
+
Autocorrection.from_hash_brackets_map(node, match)
|
80
|
+
else
|
81
|
+
raise 'unreachable'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def execute_correction(corrector, node, correction)
|
86
|
+
correction.strip_prefix_and_suffix(node, corrector)
|
87
|
+
correction.set_new_method_name(new_method_name, corrector)
|
88
|
+
|
89
|
+
captures = extract_captures(correction.match)
|
90
|
+
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
91
|
+
correction.set_new_body_expression(
|
92
|
+
captures.transforming_body_expr,
|
93
|
+
corrector
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal helper class to hold match data
|
98
|
+
Captures = Struct.new(
|
99
|
+
:transformed_argname,
|
100
|
+
:transforming_body_expr
|
101
|
+
) do
|
102
|
+
def noop_transformation?
|
103
|
+
transforming_body_expr.lvar_type? &&
|
104
|
+
transforming_body_expr.children == [transformed_argname]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal helper class to hold autocorrect data
|
109
|
+
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
|
110
|
+
def self.from_each_with_object(node, match)
|
111
|
+
new(match, node, 0, 0)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.from_map_to_h(node, match)
|
115
|
+
strip_trailing_chars = node.parent&.block_type? ? 0 : '.to_h'.length
|
116
|
+
new(match, node.children.first, 0, strip_trailing_chars)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.from_hash_brackets_map(node, match)
|
120
|
+
new(match, node.children.last, 'Hash['.length, ']'.length)
|
121
|
+
end
|
122
|
+
|
123
|
+
def strip_prefix_and_suffix(node, corrector)
|
124
|
+
expression = node.loc.expression
|
125
|
+
corrector.remove_leading(expression, leading)
|
126
|
+
corrector.remove_trailing(expression, trailing)
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_new_method_name(new_method_name, corrector)
|
130
|
+
range = block_node.send_node.loc.selector
|
131
|
+
if (send_end = block_node.send_node.loc.end)
|
132
|
+
# If there are arguments (only true in the `each_with_object` case)
|
133
|
+
range = range.begin.join(send_end)
|
134
|
+
end
|
135
|
+
corrector.replace(range, new_method_name)
|
136
|
+
end
|
137
|
+
|
138
|
+
def set_new_arg_name(transformed_argname, corrector)
|
139
|
+
corrector.replace(
|
140
|
+
block_node.arguments.loc.expression,
|
141
|
+
"|#{transformed_argname}|"
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def set_new_body_expression(transforming_body_expr, corrector)
|
146
|
+
corrector.replace(
|
147
|
+
block_node.body.loc.expression,
|
148
|
+
transforming_body_expr.loc.expression.source
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -16,52 +16,98 @@ module RuboCop
|
|
16
16
|
# # good
|
17
17
|
# Rails.env.production?
|
18
18
|
class EnvironmentComparison < Cop
|
19
|
-
MSG =
|
19
|
+
MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
|
20
20
|
|
21
21
|
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
|
22
22
|
'evaluate to `false`.'
|
23
23
|
|
24
|
-
def_node_matcher :
|
24
|
+
def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
|
25
25
|
(send
|
26
26
|
(send (const {nil? cbase} :Rails) :env)
|
27
|
-
:==
|
27
|
+
{:== :!=}
|
28
28
|
$str
|
29
29
|
)
|
30
30
|
PATTERN
|
31
31
|
|
32
|
-
def_node_matcher :
|
32
|
+
def_node_matcher :comparing_str_env_with_rails_env_on_rhs?, <<~PATTERN
|
33
|
+
(send
|
34
|
+
$str
|
35
|
+
{:== :!=}
|
36
|
+
(send (const {nil? cbase} :Rails) :env)
|
37
|
+
)
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def_node_matcher :comparing_sym_env_with_rails_env_on_lhs?, <<~PATTERN
|
33
41
|
(send
|
34
42
|
(send (const {nil? cbase} :Rails) :env)
|
35
|
-
:==
|
43
|
+
{:== :!=}
|
36
44
|
$sym
|
37
45
|
)
|
38
46
|
PATTERN
|
39
47
|
|
48
|
+
def_node_matcher :comparing_sym_env_with_rails_env_on_rhs?, <<~PATTERN
|
49
|
+
(send
|
50
|
+
$sym
|
51
|
+
{:== :!=}
|
52
|
+
(send (const {nil? cbase} :Rails) :env)
|
53
|
+
)
|
54
|
+
PATTERN
|
55
|
+
|
56
|
+
def_node_matcher :content, <<~PATTERN
|
57
|
+
({str sym} $_)
|
58
|
+
PATTERN
|
59
|
+
|
40
60
|
def on_send(node)
|
41
|
-
|
61
|
+
if (env_node = comparing_str_env_with_rails_env_on_lhs?(node) ||
|
62
|
+
comparing_str_env_with_rails_env_on_rhs?(node))
|
42
63
|
env, = *env_node
|
43
|
-
|
64
|
+
bang = node.method?(:!=) ? '!' : ''
|
65
|
+
|
66
|
+
add_offense(node, message: format(
|
67
|
+
MSG, bang: bang, env: env, source: node.source
|
68
|
+
))
|
44
69
|
end
|
45
|
-
|
70
|
+
|
71
|
+
if comparing_sym_env_with_rails_env_on_lhs?(node) ||
|
72
|
+
comparing_sym_env_with_rails_env_on_rhs?(node)
|
46
73
|
add_offense(node, message: SYM_MSG)
|
47
74
|
end
|
48
75
|
end
|
49
76
|
|
50
77
|
def autocorrect(node)
|
51
78
|
lambda do |corrector|
|
52
|
-
|
79
|
+
replacement = build_predicate_method(node)
|
80
|
+
|
81
|
+
corrector.replace(node.source_range, replacement)
|
53
82
|
end
|
54
83
|
end
|
55
84
|
|
56
85
|
private
|
57
86
|
|
58
|
-
def
|
59
|
-
|
87
|
+
def build_predicate_method(node)
|
88
|
+
if rails_env_on_lhs?(node)
|
89
|
+
build_predicate_method_for_rails_env_on_lhs(node)
|
90
|
+
else
|
91
|
+
build_predicate_method_for_rails_env_on_rhs(node)
|
92
|
+
end
|
60
93
|
end
|
61
94
|
|
62
|
-
|
63
|
-
(
|
64
|
-
|
95
|
+
def rails_env_on_lhs?(node)
|
96
|
+
comparing_str_env_with_rails_env_on_lhs?(node) ||
|
97
|
+
comparing_sym_env_with_rails_env_on_lhs?(node)
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_predicate_method_for_rails_env_on_lhs(node)
|
101
|
+
bang = node.method?(:!=) ? '!' : ''
|
102
|
+
|
103
|
+
"#{bang}#{node.receiver.source}.#{content(node.first_argument)}?"
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_predicate_method_for_rails_env_on_rhs(node)
|
107
|
+
bang = node.method?(:!=) ? '!' : ''
|
108
|
+
|
109
|
+
"#{bang}#{node.first_argument.source}.#{content(node.receiver)}?"
|
110
|
+
end
|
65
111
|
end
|
66
112
|
end
|
67
113
|
end
|
@@ -22,7 +22,7 @@ module RuboCop
|
|
22
22
|
MSG = 'Use keyword arguments instead of ' \
|
23
23
|
'positional arguments for http call: `%<verb>s`.'
|
24
24
|
KEYWORD_ARGS = %i[
|
25
|
-
method params session body flash xhr as headers env
|
25
|
+
method params session body flash xhr as headers env to
|
26
26
|
].freeze
|
27
27
|
HTTP_METHODS = %i[get post put patch delete head].freeze
|
28
28
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for uses of `each_with_object({}) { ... }`,
|
7
|
+
# `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
|
8
|
+
# an enumerable into a hash where the values are the original elements.
|
9
|
+
# Rails provides the `index_by` method for this purpose.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# [1, 2, 3].each_with_object({}) { |el, h| h[foo(el)] = el }
|
14
|
+
# [1, 2, 3].map { |el| [foo(el), el] }.to_h
|
15
|
+
# Hash[[1, 2, 3].collect { |el| [foo(el), el] }]
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# [1, 2, 3].index_by { |el| foo(el) }
|
19
|
+
class IndexBy < Cop
|
20
|
+
include IndexMethod
|
21
|
+
|
22
|
+
def_node_matcher :on_bad_each_with_object, <<~PATTERN
|
23
|
+
(block
|
24
|
+
({send csend} _ :each_with_object (hash))
|
25
|
+
(args (arg $_el) (arg _memo))
|
26
|
+
({send csend} (lvar _memo) :[]= $_ (lvar _el)))
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
30
|
+
({send csend}
|
31
|
+
(block
|
32
|
+
({send csend} _ {:map :collect})
|
33
|
+
(args (arg $_el))
|
34
|
+
(array $_ (lvar _el)))
|
35
|
+
:to_h)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
|
39
|
+
(send
|
40
|
+
(const _ :Hash)
|
41
|
+
:[]
|
42
|
+
(block
|
43
|
+
({send csend} _ {:map :collect})
|
44
|
+
(args (arg $_el))
|
45
|
+
(array $_ (lvar _el))))
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def new_method_name
|
51
|
+
'index_by'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for uses of `each_with_object({}) { ... }`,
|
7
|
+
# `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
|
8
|
+
# an enumerable into a hash where the keys are the original elements.
|
9
|
+
# Rails provides the `index_with` method for this purpose.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# [1, 2, 3].each_with_object({}) { |el, h| h[el] = foo(el) }
|
14
|
+
# [1, 2, 3].map { |el| [el, foo(el)] }.to_h
|
15
|
+
# Hash[[1, 2, 3].collect { |el| [el, foo(el)] }]
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# [1, 2, 3].index_with { |el| foo(el) }
|
19
|
+
class IndexWith < Cop
|
20
|
+
extend TargetRailsVersion
|
21
|
+
include IndexMethod
|
22
|
+
|
23
|
+
minimum_target_rails_version 6.0
|
24
|
+
|
25
|
+
def_node_matcher :on_bad_each_with_object, <<~PATTERN
|
26
|
+
(block
|
27
|
+
({send csend} _ :each_with_object (hash))
|
28
|
+
(args (arg $_el) (arg _memo))
|
29
|
+
({send csend} (lvar _memo) :[]= (lvar _el) $_))
|
30
|
+
PATTERN
|
31
|
+
|
32
|
+
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
33
|
+
({send csend}
|
34
|
+
(block
|
35
|
+
({send csend} _ {:map :collect})
|
36
|
+
(args (arg $_el))
|
37
|
+
(array (lvar _el) $_))
|
38
|
+
:to_h)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
|
42
|
+
(send
|
43
|
+
(const _ :Hash)
|
44
|
+
:[]
|
45
|
+
(block
|
46
|
+
({send csend} _ {:map :collect})
|
47
|
+
(args (arg $_el))
|
48
|
+
(array (lvar _el) $_)))
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def new_method_name
|
54
|
+
'index_with'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -6,7 +6,7 @@ module RuboCop
|
|
6
6
|
#
|
7
7
|
# Use `assert_not` methods instead of `refute` methods.
|
8
8
|
#
|
9
|
-
# @example
|
9
|
+
# @example EnforcedStyle: assert_not (default)
|
10
10
|
# # bad
|
11
11
|
# refute false
|
12
12
|
# refute_empty [1, 2, 3]
|
@@ -17,29 +17,43 @@ module RuboCop
|
|
17
17
|
# assert_not_empty [1, 2, 3]
|
18
18
|
# assert_not_equal true, false
|
19
19
|
#
|
20
|
+
# @example EnforcedStyle: refute
|
21
|
+
# # bad
|
22
|
+
# assert_not false
|
23
|
+
# assert_not_empty [1, 2, 3]
|
24
|
+
# assert_not_equal true, false
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# refute false
|
28
|
+
# refute_empty [1, 2, 3]
|
29
|
+
# refute_equal true, false
|
30
|
+
#
|
20
31
|
class RefuteMethods < Cop
|
21
|
-
|
32
|
+
include ConfigurableEnforcedStyle
|
33
|
+
|
34
|
+
MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
|
22
35
|
|
23
36
|
CORRECTIONS = {
|
24
|
-
refute:
|
25
|
-
refute_empty:
|
26
|
-
refute_equal:
|
27
|
-
refute_in_delta:
|
28
|
-
refute_in_epsilon:
|
29
|
-
refute_includes:
|
30
|
-
refute_instance_of:
|
31
|
-
refute_kind_of:
|
32
|
-
refute_nil:
|
33
|
-
refute_operator:
|
34
|
-
refute_predicate:
|
35
|
-
refute_respond_to:
|
36
|
-
refute_same:
|
37
|
-
refute_match:
|
37
|
+
refute: :assert_not,
|
38
|
+
refute_empty: :assert_not_empty,
|
39
|
+
refute_equal: :assert_not_equal,
|
40
|
+
refute_in_delta: :assert_not_in_delta,
|
41
|
+
refute_in_epsilon: :assert_not_in_epsilon,
|
42
|
+
refute_includes: :assert_not_includes,
|
43
|
+
refute_instance_of: :assert_not_instance_of,
|
44
|
+
refute_kind_of: :assert_not_kind_of,
|
45
|
+
refute_nil: :assert_not_nil,
|
46
|
+
refute_operator: :assert_not_operator,
|
47
|
+
refute_predicate: :assert_not_predicate,
|
48
|
+
refute_respond_to: :assert_not_respond_to,
|
49
|
+
refute_same: :assert_not_same,
|
50
|
+
refute_match: :assert_no_match
|
38
51
|
}.freeze
|
39
52
|
|
40
|
-
|
53
|
+
REFUTE_METHODS = CORRECTIONS.keys.freeze
|
54
|
+
ASSERT_NOT_METHODS = CORRECTIONS.values.freeze
|
41
55
|
|
42
|
-
def_node_matcher :offensive?, '(send nil? #
|
56
|
+
def_node_matcher :offensive?, '(send nil? #bad_method? ...)'
|
43
57
|
|
44
58
|
def on_send(node)
|
45
59
|
return unless offensive?(node)
|
@@ -49,27 +63,39 @@ module RuboCop
|
|
49
63
|
end
|
50
64
|
|
51
65
|
def autocorrect(node)
|
66
|
+
bad_method = node.method_name
|
67
|
+
good_method = convert_good_method(bad_method)
|
68
|
+
|
52
69
|
lambda do |corrector|
|
53
|
-
corrector.replace(
|
54
|
-
node.loc.selector,
|
55
|
-
CORRECTIONS[node.method_name]
|
56
|
-
)
|
70
|
+
corrector.replace(node.loc.selector, good_method.to_s)
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
60
74
|
private
|
61
75
|
|
62
|
-
def
|
63
|
-
|
76
|
+
def bad_method?(method_name)
|
77
|
+
if style == :assert_not
|
78
|
+
REFUTE_METHODS.include?(method_name)
|
79
|
+
else
|
80
|
+
ASSERT_NOT_METHODS.include?(method_name)
|
81
|
+
end
|
64
82
|
end
|
65
83
|
|
66
84
|
def offense_message(method_name)
|
67
85
|
format(
|
68
86
|
MSG,
|
69
|
-
|
70
|
-
|
87
|
+
bad_method: method_name,
|
88
|
+
good_method: convert_good_method(method_name)
|
71
89
|
)
|
72
90
|
end
|
91
|
+
|
92
|
+
def convert_good_method(bad_method)
|
93
|
+
if style == :assert_not
|
94
|
+
CORRECTIONS.fetch(bad_method)
|
95
|
+
else
|
96
|
+
CORRECTIONS.invert.fetch(bad_method)
|
97
|
+
end
|
98
|
+
end
|
73
99
|
end
|
74
100
|
end
|
75
101
|
end
|
@@ -90,6 +90,11 @@ module RuboCop
|
|
90
90
|
# remove_foreign_key :accounts, :branches
|
91
91
|
# end
|
92
92
|
#
|
93
|
+
# # good
|
94
|
+
# def change
|
95
|
+
# remove_foreign_key :accounts, to_table: :branches
|
96
|
+
# end
|
97
|
+
#
|
93
98
|
# @example
|
94
99
|
# # change_table
|
95
100
|
#
|
@@ -210,7 +215,7 @@ module RuboCop
|
|
210
215
|
|
211
216
|
def check_remove_foreign_key_node(node)
|
212
217
|
remove_foreign_key_call(node) do |arg|
|
213
|
-
if arg.hash_type?
|
218
|
+
if arg.hash_type? && !all_hash_key?(arg, :to_table)
|
214
219
|
add_offense(
|
215
220
|
node,
|
216
221
|
message: format(MSG,
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# When you define a uniqueness validation in Active Record model,
|
7
|
+
# you also should add a unique index for the column. There are two reasons
|
8
|
+
# First, duplicated records may occur even if Active Record's validation
|
9
|
+
# is defined.
|
10
|
+
# Second, it will cause slow queries. The validation executes a `SELECT`
|
11
|
+
# statement with the target column when inserting/updating a record.
|
12
|
+
# If the column does not have an index and the table is large,
|
13
|
+
# the query will be heavy.
|
14
|
+
#
|
15
|
+
# Note that the cop does nothing if db/schema.rb does not exist.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad - if the schema does not have a unique index
|
19
|
+
# validates :account, uniqueness: true
|
20
|
+
#
|
21
|
+
# # good - if the schema has a unique index
|
22
|
+
# validates :account, uniqueness: true
|
23
|
+
#
|
24
|
+
# # good - even if the schema does not have a unique index
|
25
|
+
# validates :account, length: { minimum: MIN_LENGTH }
|
26
|
+
#
|
27
|
+
class UniqueValidationWithoutIndex < Cop
|
28
|
+
include ActiveRecordHelper
|
29
|
+
|
30
|
+
MSG = 'Uniqueness validation should be with a unique index.'
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return unless node.method?(:validates)
|
34
|
+
return unless uniqueness_part(node)
|
35
|
+
return unless schema
|
36
|
+
return if with_index?(node)
|
37
|
+
|
38
|
+
add_offense(node)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def with_index?(node)
|
44
|
+
klass = class_node(node)
|
45
|
+
return true unless klass # Skip analysis
|
46
|
+
|
47
|
+
table = schema.table_by(name: table_name(klass))
|
48
|
+
return true unless table # Skip analysis if it can't find the table
|
49
|
+
|
50
|
+
names = column_names(node)
|
51
|
+
return true unless names
|
52
|
+
|
53
|
+
table.indices.any? do |index|
|
54
|
+
index.unique && index.columns.to_set == names
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def column_names(node)
|
59
|
+
arg = node.first_argument
|
60
|
+
return unless arg.str_type? || arg.sym_type?
|
61
|
+
|
62
|
+
ret = [arg.value]
|
63
|
+
names_from_scope = column_names_from_scope(node)
|
64
|
+
ret.concat(names_from_scope) if names_from_scope
|
65
|
+
|
66
|
+
ret.map! do |name|
|
67
|
+
klass = class_node(node)
|
68
|
+
resolve_relation_into_column(
|
69
|
+
name: name.to_s,
|
70
|
+
class_node: klass,
|
71
|
+
table: schema.table_by(name: table_name(klass))
|
72
|
+
)
|
73
|
+
end
|
74
|
+
ret.include?(nil) ? nil : ret.to_set
|
75
|
+
end
|
76
|
+
|
77
|
+
def column_names_from_scope(node)
|
78
|
+
uniq = uniqueness_part(node)
|
79
|
+
return unless uniq.hash_type?
|
80
|
+
|
81
|
+
scope = find_scope(uniq)
|
82
|
+
return unless scope
|
83
|
+
|
84
|
+
case scope.type
|
85
|
+
when :sym, :str
|
86
|
+
[scope.value]
|
87
|
+
when :array
|
88
|
+
array_node_to_array(scope)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_scope(pairs)
|
93
|
+
pairs.each_pair.find do |pair|
|
94
|
+
key = pair.key
|
95
|
+
next unless key.sym_type? && key.value == :scope
|
96
|
+
|
97
|
+
break pair.value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def class_node(node)
|
102
|
+
node.each_ancestor.find(&:class_type?)
|
103
|
+
end
|
104
|
+
|
105
|
+
def uniqueness_part(node)
|
106
|
+
pairs = node.arguments.last
|
107
|
+
return unless pairs.hash_type?
|
108
|
+
|
109
|
+
pairs.each_pair.find do |pair|
|
110
|
+
next unless pair.key.sym_type? && pair.key.value == :uniqueness
|
111
|
+
|
112
|
+
break pair.value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def array_node_to_array(node)
|
117
|
+
node.values.map do |elm|
|
118
|
+
case elm.type
|
119
|
+
when :str, :sym
|
120
|
+
elm.value
|
121
|
+
else
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def schema
|
128
|
+
RuboCop::Rails::SchemaLoader.load(target_ruby_version)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'mixin/active_record_helper'
|
4
|
+
require_relative 'mixin/index_method'
|
3
5
|
require_relative 'mixin/target_rails_version'
|
4
6
|
|
5
7
|
require_relative 'rails/action_filter'
|
@@ -32,6 +34,8 @@ require_relative 'rails/helper_instance_variable'
|
|
32
34
|
require_relative 'rails/http_positional_arguments'
|
33
35
|
require_relative 'rails/http_status'
|
34
36
|
require_relative 'rails/ignored_skip_action_filter_option'
|
37
|
+
require_relative 'rails/index_by'
|
38
|
+
require_relative 'rails/index_with'
|
35
39
|
require_relative 'rails/inverse_of'
|
36
40
|
require_relative 'rails/lexically_scoped_action_filter'
|
37
41
|
require_relative 'rails/link_to_blank'
|
@@ -57,5 +61,6 @@ require_relative 'rails/scope_args'
|
|
57
61
|
require_relative 'rails/skips_model_validations'
|
58
62
|
require_relative 'rails/time_zone'
|
59
63
|
require_relative 'rails/uniq_before_pluck'
|
64
|
+
require_relative 'rails/unique_validation_without_index'
|
60
65
|
require_relative 'rails/unknown_env'
|
61
66
|
require_relative 'rails/validation'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Rails
|
5
|
+
# It loads db/schema.rb and return Schema object.
|
6
|
+
# Cops refers database schema information with this module.
|
7
|
+
module SchemaLoader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# It parses `db/schema.rb` and return it.
|
11
|
+
# It returns `nil` if it can't find `db/schema.rb`.
|
12
|
+
# So a cop that uses the loader should handle `nil` properly.
|
13
|
+
#
|
14
|
+
# @return [Schema, nil]
|
15
|
+
def load(target_ruby_version)
|
16
|
+
return @schema if defined?(@schema)
|
17
|
+
|
18
|
+
@schema = load!(target_ruby_version)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset!
|
22
|
+
return unless instance_variable_defined?(:@schema)
|
23
|
+
|
24
|
+
remove_instance_variable(:@schema)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load!(target_ruby_version)
|
30
|
+
path = db_schema_path
|
31
|
+
return unless path
|
32
|
+
|
33
|
+
ast = parse(path, target_ruby_version)
|
34
|
+
Schema.new(ast)
|
35
|
+
end
|
36
|
+
|
37
|
+
def db_schema_path
|
38
|
+
path = Pathname.pwd
|
39
|
+
until path.root?
|
40
|
+
schema_path = path.join('db/schema.rb')
|
41
|
+
return schema_path if schema_path.exist?
|
42
|
+
|
43
|
+
path = path.join('../').cleanpath
|
44
|
+
end
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(path, target_ruby_version)
|
50
|
+
klass_name = :"Ruby#{target_ruby_version.to_s.sub('.', '')}"
|
51
|
+
klass = ::Parser.const_get(klass_name)
|
52
|
+
parser = klass.new(RuboCop::AST::Builder.new)
|
53
|
+
|
54
|
+
buffer = Parser::Source::Buffer.new(path, 1)
|
55
|
+
buffer.source = path.read
|
56
|
+
|
57
|
+
parser.parse(buffer)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Rails
|
5
|
+
module SchemaLoader
|
6
|
+
# Represent db/schema.rb
|
7
|
+
class Schema
|
8
|
+
attr_reader :tables
|
9
|
+
|
10
|
+
def initialize(ast)
|
11
|
+
@tables = []
|
12
|
+
build!(ast)
|
13
|
+
end
|
14
|
+
|
15
|
+
def table_by(name:)
|
16
|
+
tables.find do |table|
|
17
|
+
table.name == name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build!(ast)
|
24
|
+
raise "Unexpected type: #{ast.type}" unless ast.block_type?
|
25
|
+
|
26
|
+
each_table(ast) do |table_def|
|
27
|
+
@tables << Table.new(table_def)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def each_table(ast)
|
32
|
+
case ast.body.type
|
33
|
+
when :begin
|
34
|
+
ast.body.children.each do |node|
|
35
|
+
next unless node.block_type? && node.method?(:create_table)
|
36
|
+
|
37
|
+
yield(node)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
yield ast.body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Reprecent a table
|
46
|
+
class Table
|
47
|
+
attr_reader :name, :columns, :indices
|
48
|
+
|
49
|
+
def initialize(node)
|
50
|
+
@name = node.send_node.first_argument.value
|
51
|
+
@columns = build_columns(node)
|
52
|
+
@indices = build_indices(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_column?(name:)
|
56
|
+
@columns.any? { |c| c.name == name }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def build_columns(node)
|
62
|
+
each_content(node).map do |child|
|
63
|
+
next unless child.send_type?
|
64
|
+
next if child.method?(:index)
|
65
|
+
|
66
|
+
Column.new(child)
|
67
|
+
end.compact
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_indices(node)
|
71
|
+
each_content(node).map do |child|
|
72
|
+
next unless child.send_type?
|
73
|
+
next unless child.method?(:index)
|
74
|
+
|
75
|
+
Index.new(child)
|
76
|
+
end.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
def each_content(node)
|
80
|
+
return enum_for(__method__, node) unless block_given?
|
81
|
+
|
82
|
+
case node.body.type
|
83
|
+
when :begin
|
84
|
+
node.body.children.each do |child|
|
85
|
+
yield(child)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
yield(node.body)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reprecent a column
|
94
|
+
class Column
|
95
|
+
attr_reader :name, :type, :not_null
|
96
|
+
|
97
|
+
def initialize(node)
|
98
|
+
@name = node.first_argument.value
|
99
|
+
@type = node.method_name
|
100
|
+
@not_null = nil
|
101
|
+
|
102
|
+
analyze_keywords!(node)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def analyze_keywords!(node)
|
108
|
+
pairs = node.arguments.last
|
109
|
+
return unless pairs.hash_type?
|
110
|
+
|
111
|
+
pairs.each_pair do |k, v|
|
112
|
+
if k.value == :null
|
113
|
+
@not_null = v.true_type? ? false : true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Reprecent an index
|
120
|
+
class Index
|
121
|
+
attr_reader :name, :columns, :expression, :unique
|
122
|
+
|
123
|
+
def initialize(node)
|
124
|
+
node.first_argument
|
125
|
+
@columns, @expression = build_columns_or_expr(node)
|
126
|
+
@unique = nil
|
127
|
+
|
128
|
+
analyze_keywords!(node)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def build_columns_or_expr(node)
|
134
|
+
arg = node.first_argument
|
135
|
+
if arg.array_type?
|
136
|
+
[arg.values.map(&:value), nil]
|
137
|
+
else
|
138
|
+
[[], arg.value]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def analyze_keywords!(node)
|
143
|
+
pairs = node.arguments.last
|
144
|
+
return unless pairs.hash_type?
|
145
|
+
|
146
|
+
pairs.each_pair do |k, v|
|
147
|
+
case k.value
|
148
|
+
when :name
|
149
|
+
@name = v.value
|
150
|
+
when :unique
|
151
|
+
@unique = true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bozhidar Batsov
|
@@ -10,8 +10,22 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-
|
13
|
+
date: 2020-03-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
15
29
|
- !ruby/object:Gem::Dependency
|
16
30
|
name: rack
|
17
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,6 +69,8 @@ files:
|
|
55
69
|
- bin/setup
|
56
70
|
- config/default.yml
|
57
71
|
- lib/rubocop-rails.rb
|
72
|
+
- lib/rubocop/cop/mixin/active_record_helper.rb
|
73
|
+
- lib/rubocop/cop/mixin/index_method.rb
|
58
74
|
- lib/rubocop/cop/mixin/target_rails_version.rb
|
59
75
|
- lib/rubocop/cop/rails/action_filter.rb
|
60
76
|
- lib/rubocop/cop/rails/active_record_aliases.rb
|
@@ -86,6 +102,8 @@ files:
|
|
86
102
|
- lib/rubocop/cop/rails/http_positional_arguments.rb
|
87
103
|
- lib/rubocop/cop/rails/http_status.rb
|
88
104
|
- lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb
|
105
|
+
- lib/rubocop/cop/rails/index_by.rb
|
106
|
+
- lib/rubocop/cop/rails/index_with.rb
|
89
107
|
- lib/rubocop/cop/rails/inverse_of.rb
|
90
108
|
- lib/rubocop/cop/rails/lexically_scoped_action_filter.rb
|
91
109
|
- lib/rubocop/cop/rails/link_to_blank.rb
|
@@ -111,20 +129,23 @@ files:
|
|
111
129
|
- lib/rubocop/cop/rails/skips_model_validations.rb
|
112
130
|
- lib/rubocop/cop/rails/time_zone.rb
|
113
131
|
- lib/rubocop/cop/rails/uniq_before_pluck.rb
|
132
|
+
- lib/rubocop/cop/rails/unique_validation_without_index.rb
|
114
133
|
- lib/rubocop/cop/rails/unknown_env.rb
|
115
134
|
- lib/rubocop/cop/rails/validation.rb
|
116
135
|
- lib/rubocop/cop/rails_cops.rb
|
117
136
|
- lib/rubocop/rails.rb
|
118
137
|
- lib/rubocop/rails/inject.rb
|
138
|
+
- lib/rubocop/rails/schema_loader.rb
|
139
|
+
- lib/rubocop/rails/schema_loader/schema.rb
|
119
140
|
- lib/rubocop/rails/version.rb
|
120
141
|
homepage: https://github.com/rubocop-hq/rubocop-rails
|
121
142
|
licenses:
|
122
143
|
- MIT
|
123
144
|
metadata:
|
124
|
-
homepage_uri: https://docs.rubocop.org/projects/rails
|
145
|
+
homepage_uri: https://docs.rubocop.org/projects/rails/
|
125
146
|
changelog_uri: https://github.com/rubocop-hq/rubocop-rails/blob/master/CHANGELOG.md
|
126
147
|
source_code_uri: https://github.com/rubocop-hq/rubocop-rails/
|
127
|
-
documentation_uri: https://docs.rubocop.org/projects/rails
|
148
|
+
documentation_uri: https://docs.rubocop.org/projects/rails/
|
128
149
|
bug_tracker_uri: https://github.com/rubocop-hq/rubocop-rails/issues
|
129
150
|
post_install_message:
|
130
151
|
rdoc_options: []
|