rubocop-rails 2.4.2 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|