momocop 0.1.14 → 0.1.15
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/lib/momocop/helpers/factory_bot_helper.rb +88 -0
- data/lib/momocop/helpers/rails_helper.rb +83 -0
- data/lib/momocop/version.rb +1 -1
- data/lib/momocop.rb +4 -0
- data/lib/rubocop/cop/momocop/factory_bot_class_existence.rb +2 -9
- data/lib/rubocop/cop/momocop/factory_bot_consistent_file_name.rb +1 -5
- data/lib/rubocop/cop/momocop/factory_bot_inline_association.rb +12 -16
- data/lib/rubocop/cop/momocop/factory_bot_missing_associations.rb +3 -35
- data/lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb +1 -5
- data/lib/rubocop/cop/momocop/factory_bot_missing_properties.rb +3 -67
- data/lib/rubocop/cop/momocop/factory_bot_property_order.rb +2 -63
- data/lib/rubocop/cop/momocop/factory_bot_single_define_per_file.rb +2 -4
- data/lib/rubocop/cop/momocop/factory_bot_single_factory_per_define.rb +3 -6
- data/lib/rubocop/cop/momocop/factory_bot_singular_factory_name.rb +1 -6
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 711882a976895992e975ef619137dfe75d05cecdd82b249d3d5607aa87bff05d
|
|
4
|
+
data.tar.gz: 86b276769f8e043d927b556f245bea1d4c80cea5cae8a2a04b05cbd89f9e14f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4276e49e54c3833cc3a7b701c06fd482ead664d22b3e4add6d7326ba88b78f7dd331f911aac1e9dd1c2e139945f945d766a23d6bfeb484514059ddb5bae0f3b7
|
|
7
|
+
data.tar.gz: d6155319eb7e442b886dc8768e88442e86527eefa1af48105d1bcc955a87ed460016122f90e502190978032a88d5724807f4dd4d28400bdc0569b3b5acde21dc
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Momocop
|
|
4
|
+
module Helpers
|
|
5
|
+
module FactoryBotHelper
|
|
6
|
+
# @type method inside_factory_bot_factory?(RuboCop::AST::Node): bool
|
|
7
|
+
private def inside_factory_bot_factory?(node)
|
|
8
|
+
context = node.each_ancestor(:block).first
|
|
9
|
+
send_node = context.block_type? ? context.send_node : context
|
|
10
|
+
|
|
11
|
+
return send_node.method_name == :factory
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @type method inside_factory_bot_define?(RuboCop::AST::Node): bool
|
|
15
|
+
private def inside_factory_bot_define?(node)
|
|
16
|
+
ancestors = node.each_ancestor(:block).to_a
|
|
17
|
+
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @type method factory_bot_define?(RuboCop::AST::Node): bool
|
|
21
|
+
private def factory_bot_define?(node)
|
|
22
|
+
node.send_type? && node.method_name == :define && node.receiver&.const_name == 'FactoryBot'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
RUBOCOP_HELPER_METHODS = %i[trait transient before after].freeze
|
|
26
|
+
|
|
27
|
+
# @type method definition_node?(RuboCop::AST::Node): bool
|
|
28
|
+
private def definition_node?(node)
|
|
29
|
+
if node.send_type?
|
|
30
|
+
return !RUBOCOP_HELPER_METHODS.include?(node.method_name)
|
|
31
|
+
elsif node.block_type? && node.children.first.send_type?
|
|
32
|
+
return !RUBOCOP_HELPER_METHODS.include?(node.children.first.method_name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @type method definition_type(RuboCop::AST::Node): (:association | :property | :sequence)
|
|
39
|
+
private def definition_type(node)
|
|
40
|
+
send_node = node.send_type? ? node : node.children.first
|
|
41
|
+
has_association_body =
|
|
42
|
+
send_node
|
|
43
|
+
.block_node
|
|
44
|
+
&.children
|
|
45
|
+
&.last
|
|
46
|
+
# sinble-statement block or multi-statement block
|
|
47
|
+
&.then { _1.send_type? ? [_1] : _1.children }
|
|
48
|
+
&.any? { _1.is_a?(Parser::AST::Node) && _1.send_type? && _1.method_name == :association }
|
|
49
|
+
if %i[association sequence].include? send_node.method_name
|
|
50
|
+
send_node.method_name
|
|
51
|
+
elsif has_association_body
|
|
52
|
+
:association
|
|
53
|
+
else
|
|
54
|
+
:property
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @type method definition_name(RuboCop::AST::Node): Symbol
|
|
59
|
+
private def definition_name(node)
|
|
60
|
+
send_node = node.send_type? ? node : node.children.first
|
|
61
|
+
if %i[association sequence].include? send_node.method_name
|
|
62
|
+
send_node.arguments.first.value
|
|
63
|
+
else
|
|
64
|
+
send_node.method_name.to_sym
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @type method defined_properties(RuboCop::AST::Node): Array[RuboCop::AST::Node]
|
|
69
|
+
private def defined_properties(node)
|
|
70
|
+
block_node = node.block_type? ? node : node.block_node
|
|
71
|
+
body_node = block_node&.children&.last
|
|
72
|
+
|
|
73
|
+
# empty block
|
|
74
|
+
return [] unless body_node
|
|
75
|
+
|
|
76
|
+
# begin
|
|
77
|
+
if body_node.begin_type?
|
|
78
|
+
body_node&.children&.select { |node| definition_node?(node) } || []
|
|
79
|
+
# block
|
|
80
|
+
elsif body_node.send_type? && definition_node?(body_node)
|
|
81
|
+
[body_node]
|
|
82
|
+
else
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Momocop
|
|
4
|
+
module Helpers
|
|
5
|
+
module RailsHelper
|
|
6
|
+
include RuboCop::Cop::ActiveRecordHelper
|
|
7
|
+
|
|
8
|
+
private def model_file_path(class_name)
|
|
9
|
+
"app/models/#{class_name.underscore}.rb"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private def model_file_source(class_name)
|
|
13
|
+
path = model_file_path(class_name)
|
|
14
|
+
return File.read(path) if File.exist?(path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private def get_model_association_names(class_name)
|
|
18
|
+
source = model_file_source(class_name)
|
|
19
|
+
return [] unless source
|
|
20
|
+
|
|
21
|
+
extractor = ::Momocop::AssociationExtractor.new
|
|
22
|
+
associations = extractor.extract(source)
|
|
23
|
+
belongs_to_associations =
|
|
24
|
+
associations
|
|
25
|
+
.select { _1[:type] == :belongs_to }
|
|
26
|
+
.map { _1[:name].to_s }
|
|
27
|
+
return belongs_to_associations
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private def get_model_enum_property_names(class_name)
|
|
31
|
+
source = model_file_source(class_name)
|
|
32
|
+
return [] unless source
|
|
33
|
+
|
|
34
|
+
extractor = ::Momocop::EnumExtractor.new
|
|
35
|
+
enums = extractor.extract(source)
|
|
36
|
+
return enums.map(&:to_sym)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def get_model_foreign_key_column_names(class_name)
|
|
40
|
+
source = model_file_source(class_name)
|
|
41
|
+
return [] unless source
|
|
42
|
+
|
|
43
|
+
extractor = ::Momocop::AssociationExtractor.new
|
|
44
|
+
associations = extractor.extract(source)
|
|
45
|
+
foreign_key_names =
|
|
46
|
+
associations
|
|
47
|
+
.select { _1[:type] == :belongs_to }
|
|
48
|
+
.map { |association|
|
|
49
|
+
options = association[:options]
|
|
50
|
+
options[:foreign_key] || "#{association[:name]}_id"
|
|
51
|
+
}
|
|
52
|
+
.compact
|
|
53
|
+
return foreign_key_names.map(&:to_sym)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
RESTRICTED_COLUMNS = %w[created_at updated_at].freeze
|
|
57
|
+
|
|
58
|
+
private def get_model_property_names(class_name)
|
|
59
|
+
table = schema.table_by(name: table_name(class_name))
|
|
60
|
+
return [] unless table
|
|
61
|
+
|
|
62
|
+
column_names = table.columns.reject { _1.type == :references }.map(&:name) - RESTRICTED_COLUMNS
|
|
63
|
+
return column_names.map(&:to_sym)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# e.g.)
|
|
67
|
+
# 'User' -> ['users']
|
|
68
|
+
# 'Admin::User' -> ['admin_users', 'users']
|
|
69
|
+
private def table_name(class_name)
|
|
70
|
+
# TODO: parse model class file and try to get table_name_prefix and table_name_suffix
|
|
71
|
+
class_name.tableize.gsub('/', '_')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# e.g.)
|
|
75
|
+
# 'User' -> ['users']
|
|
76
|
+
# 'Admin::User' -> ['admin_users', 'users']
|
|
77
|
+
private def foreign_key_name(class_name)
|
|
78
|
+
# TODO: parse model class file and try to get table_name_prefix and table_name_suffix
|
|
79
|
+
"#{class_name.underscore.gsub('/', '_')}_id"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/momocop/version.rb
CHANGED
data/lib/momocop.rb
CHANGED
|
@@ -17,6 +17,10 @@ require_relative 'momocop/config_injector'
|
|
|
17
17
|
require_relative 'momocop/enum_extractor'
|
|
18
18
|
require_relative 'momocop/version'
|
|
19
19
|
|
|
20
|
+
Dir[File.join(__dir__, 'momocop/helpers', '*.rb')].each do |file|
|
|
21
|
+
require file
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
Momocop::ConfigInjector.inject_default_config!
|
|
21
25
|
|
|
22
26
|
Dir[File.join(__dir__, 'rubocop/cop/momocop', '*.rb')].each do |file|
|
|
@@ -15,6 +15,8 @@ module RuboCop
|
|
|
15
15
|
# end
|
|
16
16
|
class FactoryBotClassExistence < RuboCop::Cop::Base
|
|
17
17
|
extend AutoCorrector
|
|
18
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
19
|
+
include ::Momocop::Helpers::RailsHelper
|
|
18
20
|
|
|
19
21
|
MSG = 'Specified class does not exist. Please make sure that the class exists.'
|
|
20
22
|
|
|
@@ -42,18 +44,9 @@ module RuboCop
|
|
|
42
44
|
add_offense(class_node)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
private def model_file_path(class_name)
|
|
46
|
-
"app/models/#{class_name.underscore}.rb"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
47
|
private def class_exists?(class_name)
|
|
50
48
|
File.exist?(model_file_path(class_name))
|
|
51
49
|
end
|
|
52
|
-
|
|
53
|
-
private def inside_factory_bot_define?(node)
|
|
54
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
55
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
56
|
-
end
|
|
57
50
|
end
|
|
58
51
|
end
|
|
59
52
|
end
|
|
@@ -21,6 +21,7 @@ module RuboCop
|
|
|
21
21
|
# end
|
|
22
22
|
class FactoryBotConsistentFileName < RuboCop::Cop::Base
|
|
23
23
|
include RangeHelp
|
|
24
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
24
25
|
|
|
25
26
|
MSG = 'Factory name should match the file name.'
|
|
26
27
|
|
|
@@ -42,11 +43,6 @@ module RuboCop
|
|
|
42
43
|
|
|
43
44
|
add_offense(node.source_range, message: MSG)
|
|
44
45
|
end
|
|
45
|
-
|
|
46
|
-
private def inside_factory_bot_define?(node)
|
|
47
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
48
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
49
|
-
end
|
|
50
46
|
end
|
|
51
47
|
end
|
|
52
48
|
end
|
|
@@ -8,15 +8,18 @@ module RuboCop
|
|
|
8
8
|
# @example
|
|
9
9
|
# # bad
|
|
10
10
|
# factory :blog_post do
|
|
11
|
-
# association(:user)
|
|
11
|
+
# association(:user, factory: :foo)
|
|
12
|
+
# association(:admin, factory: [:foo, :trait])
|
|
12
13
|
# end
|
|
13
14
|
#
|
|
14
15
|
# # good
|
|
15
16
|
# factory :blog_post do
|
|
16
|
-
# user { association :
|
|
17
|
+
# user { association :foo }
|
|
18
|
+
# admin { association :foo, :trait }
|
|
17
19
|
# end
|
|
18
20
|
class FactoryBotInlineAssociation < RuboCop::Cop::Base
|
|
19
21
|
extend AutoCorrector
|
|
22
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
20
23
|
|
|
21
24
|
MSG = 'Use inline association definition instead of separate `association` method call.'
|
|
22
25
|
|
|
@@ -37,7 +40,7 @@ module RuboCop
|
|
|
37
40
|
convert_options(options) in {
|
|
38
41
|
factory_option:, rest_options:
|
|
39
42
|
}
|
|
40
|
-
factory = factory_option
|
|
43
|
+
factory = factory_option || ":#{association_name}"
|
|
41
44
|
replacement =
|
|
42
45
|
if rest_options.empty?
|
|
43
46
|
"#{association_name} { association #{factory} }"
|
|
@@ -51,7 +54,12 @@ module RuboCop
|
|
|
51
54
|
|
|
52
55
|
# `association :foo, factory: :bar, baz: 1 ...` => `foo { association :bar, baz: 1 ...}`
|
|
53
56
|
private def convert_options(options)
|
|
54
|
-
factory_option =
|
|
57
|
+
factory_option =
|
|
58
|
+
options
|
|
59
|
+
&.pairs
|
|
60
|
+
&.find { |pair| pair.key.value == :factory }
|
|
61
|
+
&.value
|
|
62
|
+
&.then { _1.array_type? ? _1.values.map(&:source).join(', ') : _1.source }
|
|
55
63
|
rest_options = options&.pairs&.reject { |pair| pair.key.value == :factory } || []
|
|
56
64
|
|
|
57
65
|
return {
|
|
@@ -59,18 +67,6 @@ module RuboCop
|
|
|
59
67
|
rest_options:
|
|
60
68
|
}
|
|
61
69
|
end
|
|
62
|
-
|
|
63
|
-
private def inside_factory_bot_factory?(node)
|
|
64
|
-
context = node.each_ancestor(:block).first
|
|
65
|
-
send_node = context.block_type? ? context.send_node : context
|
|
66
|
-
|
|
67
|
-
return send_node.method_name == :factory
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private def inside_factory_bot_define?(node)
|
|
71
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
72
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
73
|
-
end
|
|
74
70
|
end
|
|
75
71
|
end
|
|
76
72
|
end
|
|
@@ -24,8 +24,10 @@ module RuboCop
|
|
|
24
24
|
# end
|
|
25
25
|
# end
|
|
26
26
|
class FactoryBotMissingAssociations < RuboCop::Cop::Base
|
|
27
|
-
include RuboCop::Cop::ActiveRecordHelper
|
|
28
27
|
extend AutoCorrector
|
|
28
|
+
include RuboCop::Cop::ActiveRecordHelper
|
|
29
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
30
|
+
include ::Momocop::Helpers::RailsHelper
|
|
29
31
|
|
|
30
32
|
MSG = 'Ensure all associations of the model class are defined in the factory.'
|
|
31
33
|
|
|
@@ -79,34 +81,12 @@ module RuboCop
|
|
|
79
81
|
end
|
|
80
82
|
end
|
|
81
83
|
|
|
82
|
-
private def model_file_path(class_name)
|
|
83
|
-
"app/models/#{class_name.underscore}.rb"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
84
|
private def one_line_block?(block_node)
|
|
87
85
|
return false if block_node.nil?
|
|
88
86
|
|
|
89
87
|
block_node.loc.begin.line == block_node.loc.end.line
|
|
90
88
|
end
|
|
91
89
|
|
|
92
|
-
private def model_file_source(class_name)
|
|
93
|
-
path = model_file_path(class_name)
|
|
94
|
-
return File.read(path) if File.exist?(path)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
private def get_model_association_names(class_name)
|
|
98
|
-
source = model_file_source(class_name)
|
|
99
|
-
return [] unless source
|
|
100
|
-
|
|
101
|
-
extractor = ::Momocop::AssociationExtractor.new
|
|
102
|
-
associations = extractor.extract(source)
|
|
103
|
-
belongs_to_associations =
|
|
104
|
-
associations
|
|
105
|
-
.select { _1[:type] == :belongs_to }
|
|
106
|
-
.map { _1[:name].to_s }
|
|
107
|
-
return belongs_to_associations
|
|
108
|
-
end
|
|
109
|
-
|
|
110
90
|
private def get_defined_association_names(block_node)
|
|
111
91
|
body_node = block_node&.children&.last
|
|
112
92
|
return [] unless body_node
|
|
@@ -126,22 +106,10 @@ module RuboCop
|
|
|
126
106
|
return send_node.method_name.to_s
|
|
127
107
|
end
|
|
128
108
|
|
|
129
|
-
private def inside_factory_bot_factory?(node)
|
|
130
|
-
context = node.each_ancestor(:block).first
|
|
131
|
-
send_node = context.block_type? ? context.send_node : context
|
|
132
|
-
|
|
133
|
-
return send_node.method_name == :factory
|
|
134
|
-
end
|
|
135
|
-
|
|
136
109
|
private def generate_association_definition(property)
|
|
137
110
|
"#{property} { association :#{property} }"
|
|
138
111
|
end
|
|
139
112
|
|
|
140
|
-
private def inside_factory_bot_define?(node)
|
|
141
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
142
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
143
|
-
end
|
|
144
|
-
|
|
145
113
|
private def get_class_name(node)
|
|
146
114
|
options_hash = node.arguments[1]
|
|
147
115
|
return nil unless options_hash&.type == :hash
|
|
@@ -15,6 +15,7 @@ module RuboCop
|
|
|
15
15
|
# end
|
|
16
16
|
class FactoryBotMissingClassOption < RuboCop::Cop::Base
|
|
17
17
|
extend AutoCorrector
|
|
18
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
18
19
|
|
|
19
20
|
MSG = 'Specify a class option explicitly in FactoryBot factory.'
|
|
20
21
|
|
|
@@ -37,11 +38,6 @@ module RuboCop
|
|
|
37
38
|
corrector.insert_after(node.first_argument.loc.expression, ", class: '#{class_name}'")
|
|
38
39
|
end
|
|
39
40
|
end
|
|
40
|
-
|
|
41
|
-
private def inside_factory_bot_define?(node)
|
|
42
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
43
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
44
|
-
end
|
|
45
41
|
end
|
|
46
42
|
end
|
|
47
43
|
end
|
|
@@ -27,8 +27,10 @@ module RuboCop
|
|
|
27
27
|
# end
|
|
28
28
|
# end
|
|
29
29
|
class FactoryBotMissingProperties < RuboCop::Cop::Base
|
|
30
|
-
include RuboCop::Cop::ActiveRecordHelper
|
|
31
30
|
extend AutoCorrector
|
|
31
|
+
include RuboCop::Cop::ActiveRecordHelper
|
|
32
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
33
|
+
include ::Momocop::Helpers::RailsHelper
|
|
32
34
|
|
|
33
35
|
MSG = 'Ensure all properties of the model class are defined in the factory.'
|
|
34
36
|
|
|
@@ -106,71 +108,10 @@ module RuboCop
|
|
|
106
108
|
end
|
|
107
109
|
end
|
|
108
110
|
|
|
109
|
-
private def model_file_path(class_name)
|
|
110
|
-
"app/models/#{class_name.underscore}.rb"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
111
|
private def one_line_block?(block_node)
|
|
114
112
|
block_node.loc.begin.line == block_node.loc.end.line
|
|
115
113
|
end
|
|
116
114
|
|
|
117
|
-
private def model_file_source(class_name)
|
|
118
|
-
path = model_file_path(class_name)
|
|
119
|
-
return File.read(path) if File.exist?(path)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
private def get_model_enum_property_names(class_name)
|
|
123
|
-
source = model_file_source(class_name)
|
|
124
|
-
return [] unless source
|
|
125
|
-
|
|
126
|
-
extractor = ::Momocop::EnumExtractor.new
|
|
127
|
-
enums = extractor.extract(source)
|
|
128
|
-
return enums.map(&:to_sym)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
private def get_model_foreign_key_column_names(class_name)
|
|
132
|
-
source = model_file_source(class_name)
|
|
133
|
-
return [] unless source
|
|
134
|
-
|
|
135
|
-
extractor = ::Momocop::AssociationExtractor.new
|
|
136
|
-
associations = extractor.extract(source)
|
|
137
|
-
foreign_key_names =
|
|
138
|
-
associations
|
|
139
|
-
.select { _1[:type] == :belongs_to }
|
|
140
|
-
.map { |association|
|
|
141
|
-
options = association[:options]
|
|
142
|
-
options[:foreign_key] || "#{association[:name]}_id"
|
|
143
|
-
}
|
|
144
|
-
.compact
|
|
145
|
-
return foreign_key_names.map(&:to_sym)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
RESTRICTED_COLUMNS = %w[created_at updated_at].freeze
|
|
149
|
-
|
|
150
|
-
private def get_model_property_names(class_name)
|
|
151
|
-
table = schema.table_by(name: table_name(class_name))
|
|
152
|
-
return [] unless table
|
|
153
|
-
|
|
154
|
-
column_names = table.columns.reject { _1.type == :references }.map(&:name) - RESTRICTED_COLUMNS
|
|
155
|
-
return column_names.map(&:to_sym)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# e.g.)
|
|
159
|
-
# 'User' -> ['users']
|
|
160
|
-
# 'Admin::User' -> ['admin_users', 'users']
|
|
161
|
-
private def table_name(class_name)
|
|
162
|
-
# TODO: parse model class file and try to get table_name_prefix and table_name_suffix
|
|
163
|
-
class_name.tableize.gsub('/', '_')
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# e.g.)
|
|
167
|
-
# 'User' -> ['users']
|
|
168
|
-
# 'Admin::User' -> ['admin_users', 'users']
|
|
169
|
-
private def foreign_key_name(class_name)
|
|
170
|
-
# TODO: parse model class file and try to get table_name_prefix and table_name_suffix
|
|
171
|
-
"#{class_name.underscore.gsub('/', '_')}_id"
|
|
172
|
-
end
|
|
173
|
-
|
|
174
115
|
private def get_defined_sequence_names(block_node)
|
|
175
116
|
body_node = block_node&.children&.last
|
|
176
117
|
return [] unless body_node
|
|
@@ -192,11 +133,6 @@ module RuboCop
|
|
|
192
133
|
return property_names
|
|
193
134
|
end
|
|
194
135
|
|
|
195
|
-
private def inside_factory_bot_define?(node)
|
|
196
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
197
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
198
|
-
end
|
|
199
|
-
|
|
200
136
|
private def get_class_name(node)
|
|
201
137
|
options_hash = node.arguments[1]
|
|
202
138
|
return nil unless options_hash&.type == :hash
|
|
@@ -38,8 +38,8 @@ module RuboCop
|
|
|
38
38
|
class FactoryBotPropertyOrder < RuboCop::Cop::Base
|
|
39
39
|
extend AutoCorrector
|
|
40
40
|
include RangeHelp
|
|
41
|
-
|
|
42
|
-
include ::
|
|
41
|
+
include Sevencop::CopConcerns::Ordered
|
|
42
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
43
43
|
|
|
44
44
|
MSG = 'Sort properties and associations alphabetically.'
|
|
45
45
|
|
|
@@ -84,67 +84,6 @@ module RuboCop
|
|
|
84
84
|
index = definition_name(node)
|
|
85
85
|
[group, index]
|
|
86
86
|
end
|
|
87
|
-
|
|
88
|
-
private def defined_properties(block_node)
|
|
89
|
-
body_node = block_node&.children&.last
|
|
90
|
-
|
|
91
|
-
# empty block
|
|
92
|
-
return [] unless body_node
|
|
93
|
-
|
|
94
|
-
# begin
|
|
95
|
-
if body_node.begin_type?
|
|
96
|
-
body_node&.children&.select { |node| definition_node?(node) } || []
|
|
97
|
-
# block
|
|
98
|
-
elsif body_node.send_type? && definition_node?(body_node)
|
|
99
|
-
[body_node]
|
|
100
|
-
else
|
|
101
|
-
[]
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
RUBOCOP_HELPER_METHODS = %i[trait transient before after].freeze
|
|
106
|
-
|
|
107
|
-
private def definition_node?(node)
|
|
108
|
-
if node.send_type?
|
|
109
|
-
return !RUBOCOP_HELPER_METHODS.include?(node.method_name)
|
|
110
|
-
elsif node.block_type? && node.children.first.send_type?
|
|
111
|
-
return !RUBOCOP_HELPER_METHODS.include?(node.children.first.method_name)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
return false
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
private def definition_type(node)
|
|
118
|
-
send_node = node.send_type? ? node : node.children.first
|
|
119
|
-
has_association_body =
|
|
120
|
-
send_node
|
|
121
|
-
.block_node
|
|
122
|
-
&.children
|
|
123
|
-
&.last
|
|
124
|
-
&.children
|
|
125
|
-
&.any? { _1.is_a?(Parser::AST::Node) && _1.send_type? && _1.method_name == :association }
|
|
126
|
-
if %i[association sequence].include? send_node.method_name
|
|
127
|
-
send_node.method_name
|
|
128
|
-
elsif has_association_body
|
|
129
|
-
:association
|
|
130
|
-
else
|
|
131
|
-
:property
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
private def definition_name(node)
|
|
136
|
-
send_node = node.send_type? ? node : node.children.first
|
|
137
|
-
if %i[association sequence].include? send_node.method_name
|
|
138
|
-
send_node.arguments.first.value
|
|
139
|
-
else
|
|
140
|
-
send_node.method_name.to_sym
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
private def inside_factory_bot_define?(node)
|
|
145
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
146
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
147
|
-
end
|
|
148
87
|
end
|
|
149
88
|
end
|
|
150
89
|
end
|
|
@@ -27,6 +27,8 @@ module RuboCop
|
|
|
27
27
|
# end
|
|
28
28
|
# end
|
|
29
29
|
class FactoryBotSingleDefinePerFile < RuboCop::Cop::Base
|
|
30
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
31
|
+
|
|
30
32
|
MSG = 'Only one `FactoryBot.define` block is allowed per file.'
|
|
31
33
|
|
|
32
34
|
def on_new_investigation
|
|
@@ -44,10 +46,6 @@ module RuboCop
|
|
|
44
46
|
add_offense(factory_bot_define_block, message: MSG)
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
|
-
|
|
48
|
-
private def factory_bot_define?(node)
|
|
49
|
-
node.send_type? && node.method_name == :define && node.receiver&.const_name == 'FactoryBot'
|
|
50
|
-
end
|
|
51
49
|
end
|
|
52
50
|
end
|
|
53
51
|
end
|
|
@@ -43,13 +43,15 @@ module RuboCop
|
|
|
43
43
|
# end
|
|
44
44
|
# end
|
|
45
45
|
class FactoryBotSingleFactoryPerDefine < RuboCop::Cop::Base
|
|
46
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
47
|
+
|
|
46
48
|
MSG = 'Only one top-level factory is allowed per FactoryBot.define.'
|
|
47
49
|
|
|
48
50
|
RESTRICT_ON_SEND = %i[define].freeze
|
|
49
51
|
|
|
50
52
|
# Define the investigation method to be called during cop processing.
|
|
51
53
|
def on_send(node)
|
|
52
|
-
return unless
|
|
54
|
+
return unless factory_bot_define?(node)
|
|
53
55
|
|
|
54
56
|
factories = top_level_factories(node)
|
|
55
57
|
|
|
@@ -61,11 +63,6 @@ module RuboCop
|
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
64
|
-
# Checks if the node is a FactoryBot definition block.
|
|
65
|
-
private def factory_bot_define_block?(node)
|
|
66
|
-
node.receiver&.const_name == 'FactoryBot'
|
|
67
|
-
end
|
|
68
|
-
|
|
69
66
|
# Returns all top-level factories within a FactoryBot.define block.
|
|
70
67
|
private def top_level_factories(node)
|
|
71
68
|
factory_nodes =
|
|
@@ -21,6 +21,7 @@ module RuboCop
|
|
|
21
21
|
# end
|
|
22
22
|
class FactoryBotSingularFactoryName < RuboCop::Cop::Base
|
|
23
23
|
extend AutoCorrector
|
|
24
|
+
include ::Momocop::Helpers::FactoryBotHelper
|
|
24
25
|
|
|
25
26
|
MSG = 'Factory name should be singular, not plural.'
|
|
26
27
|
|
|
@@ -44,12 +45,6 @@ module RuboCop
|
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
|
-
|
|
48
|
-
# Checks if the node is inside a FactoryBot definition block.
|
|
49
|
-
private def inside_factory_bot_define?(node)
|
|
50
|
-
ancestors = node.each_ancestor(:block).to_a
|
|
51
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
|
52
|
-
end
|
|
53
48
|
end
|
|
54
49
|
end
|
|
55
50
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: momocop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- supermomonga
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-02
|
|
11
|
+
date: 2024-03-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -90,6 +90,8 @@ files:
|
|
|
90
90
|
- lib/momocop/association_extractor.rb
|
|
91
91
|
- lib/momocop/config_injector.rb
|
|
92
92
|
- lib/momocop/enum_extractor.rb
|
|
93
|
+
- lib/momocop/helpers/factory_bot_helper.rb
|
|
94
|
+
- lib/momocop/helpers/rails_helper.rb
|
|
93
95
|
- lib/momocop/version.rb
|
|
94
96
|
- lib/rubocop/cop/momocop/factory_bot_class_existence.rb
|
|
95
97
|
- lib/rubocop/cop/momocop/factory_bot_consistent_file_name.rb
|