momocop 0.1.13 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.ja.md +3 -0
- data/README.md +3 -0
- 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 +27 -15
- data/lib/rubocop/cop/momocop/factory_bot_missing_associations.rb +14 -29
- 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
|
data/README.ja.md
CHANGED
@@ -30,6 +30,8 @@ Momocop/FactoryBotClassExistence:
|
|
30
30
|
Enabled: true
|
31
31
|
Momocop/FactoryBotConsistentFileName:
|
32
32
|
Enabled: true
|
33
|
+
Momocop/FactoryBotInlineAssociation:
|
34
|
+
Enabled: true
|
33
35
|
Momocop/FactoryBotMissingAssociations:
|
34
36
|
Enabled: true
|
35
37
|
Momocop/FactoryBotMissingClassOption:
|
@@ -52,6 +54,7 @@ Momocop/FactoryBotSingularFactoryName:
|
|
52
54
|
|---|:-:|:-:|
|
53
55
|
|[`Momocop/FactoryBotClassExistence`](lib/rubocop/cop/momocop/factory_bot_class_existence.rb)|:white_check_mark:|:white_check_mark:|
|
54
56
|
|[`Momocop/FactoryBotConsistentFileName`](lib/rubocop/cop/momocop/factory_bot_consistent_file_name.rb)|:white_check_mark:|:white_check_mark:|
|
57
|
+
|[`Momocop/FactoryBotInlineAssociation`](lib/rubocop/cop/momocop/factory_bot_inline_association.rb)|:white_check_mark:|:white_check_mark:|
|
55
58
|
|[`Momocop/FactoryBotMissingAssociations`](lib/rubocop/cop/momocop/factory_bot_missing_associations.rb)|:white_check_mark:|:white_check_mark:|
|
56
59
|
|[`Momocop/FactoryBotMissingClassOption`](lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb)|:white_check_mark:|:white_check_mark:|
|
57
60
|
|[`Momocop/FactoryBotMissingProperties`](lib/rubocop/cop/momocop/factory_bot_missing_properties.rb)|:white_check_mark:|:white_check_mark:|
|
data/README.md
CHANGED
@@ -30,6 +30,8 @@ Momocop/FactoryBotClassExistence:
|
|
30
30
|
Enabled: true
|
31
31
|
Momocop/FactoryBotConsistentFileName:
|
32
32
|
Enabled: true
|
33
|
+
Momocop/FactoryBotInlineAssociation:
|
34
|
+
Enabled: true
|
33
35
|
Momocop/FactoryBotMissingAssociations:
|
34
36
|
Enabled: true
|
35
37
|
Momocop/FactoryBotMissingClassOption:
|
@@ -52,6 +54,7 @@ Momocop/FactoryBotSingularFactoryName:
|
|
52
54
|
|---|:-:|:-:|
|
53
55
|
|[`Momocop/FactoryBotClassExistence`](lib/rubocop/cop/momocop/factory_bot_class_existence.rb)|:white_check_mark:|:white_check_mark:|
|
54
56
|
|[`Momocop/FactoryBotConsistentFileName`](lib/rubocop/cop/momocop/factory_bot_consistent_file_name.rb)|:white_check_mark:|:white_check_mark:|
|
57
|
+
|[`Momocop/FactoryBotInlineAssociation`](lib/rubocop/cop/momocop/factory_bot_inline_association.rb)|:white_check_mark:|:white_check_mark:|
|
55
58
|
|[`Momocop/FactoryBotMissingAssociations`](lib/rubocop/cop/momocop/factory_bot_missing_associations.rb)|:white_check_mark:|:white_check_mark:|
|
56
59
|
|[`Momocop/FactoryBotMissingClassOption`](lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb)|:white_check_mark:|:white_check_mark:|
|
57
60
|
|[`Momocop/FactoryBotMissingProperties`](lib/rubocop/cop/momocop/factory_bot_missing_properties.rb)|:white_check_mark:|:white_check_mark:|
|
@@ -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
|
|
@@ -32,28 +35,37 @@ module RuboCop
|
|
32
35
|
|
33
36
|
add_offense(node.loc.selector, message: MSG) do |corrector|
|
34
37
|
association_name = node.arguments.first.value
|
35
|
-
options = node.arguments.
|
38
|
+
options = node.arguments.at(1)
|
36
39
|
|
40
|
+
convert_options(options) in {
|
41
|
+
factory_option:, rest_options:
|
42
|
+
}
|
43
|
+
factory = factory_option || ":#{association_name}"
|
37
44
|
replacement =
|
38
|
-
if
|
39
|
-
"#{association_name} { association
|
45
|
+
if rest_options.empty?
|
46
|
+
"#{association_name} { association #{factory} }"
|
40
47
|
else
|
41
|
-
|
48
|
+
rest_options_source = rest_options.map(&:source).join(', ')
|
49
|
+
"#{association_name} { association #{factory}, #{rest_options_source} }"
|
42
50
|
end
|
43
51
|
corrector.replace(node, replacement)
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
# `association :foo, factory: :bar, baz: 1 ...` => `foo { association :bar, baz: 1 ...}`
|
56
|
+
private def convert_options(options)
|
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 }
|
63
|
+
rest_options = options&.pairs&.reject { |pair| pair.key.value == :factory } || []
|
50
64
|
|
51
|
-
return
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
ancestors = node.each_ancestor(:block).to_a
|
56
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
65
|
+
return {
|
66
|
+
factory_option:,
|
67
|
+
rest_options:
|
68
|
+
}
|
57
69
|
end
|
58
70
|
end
|
59
71
|
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,50 +81,33 @@ 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
|
113
93
|
|
114
94
|
associations = association_definitions(body_node)
|
115
|
-
association_names = associations&.map(
|
95
|
+
association_names = associations&.map { |node| get_association_name(node) }
|
116
96
|
return association_names
|
117
97
|
end
|
118
98
|
|
119
|
-
private def
|
120
|
-
|
99
|
+
private def get_association_name(node)
|
100
|
+
# Explicit definition
|
101
|
+
return node.arguments.first.value.to_s if inside_factory_bot_factory?(node)
|
102
|
+
|
103
|
+
# Inline definition
|
104
|
+
context = node.each_ancestor(:block).first
|
105
|
+
send_node = context.block_type? ? context.send_node : context
|
106
|
+
return send_node.method_name.to_s
|
121
107
|
end
|
122
108
|
|
123
|
-
private def
|
124
|
-
|
125
|
-
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
109
|
+
private def generate_association_definition(property)
|
110
|
+
"#{property} { association :#{property} }"
|
126
111
|
end
|
127
112
|
|
128
113
|
private def get_class_name(node)
|
@@ -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
|