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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 220069b3119b5c48271dcd6881e4a1530074eabbc697d12dca9ea70cf5dbbbe8
4
- data.tar.gz: 02fcf5cad166113116cb7876aa46f1ffa0de65391488901727286711645a4917
3
+ metadata.gz: 711882a976895992e975ef619137dfe75d05cecdd82b249d3d5607aa87bff05d
4
+ data.tar.gz: 86b276769f8e043d927b556f245bea1d4c80cea5cae8a2a04b05cbd89f9e14f7
5
5
  SHA512:
6
- metadata.gz: 2ae69bdfff4429dc93e5e0a78fa4d87afb29bdf5d3bec1bffc3aec97b569066d47e2f0ba215593d0599cbc781591ea38b44dcf128cffbda503ba4b03ced79224
7
- data.tar.gz: 22b9e10997ec1a2f8f32d33e503285a7f0ca6809d3c7081713b481c8ba245753d8ed944e57d2032f9f3a243894dbd16018f4d8cff7dcb24b55bf3144280d4813
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Momocop
4
- VERSION = '0.1.13'
4
+ VERSION = '0.1.15'
5
5
  end
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 :user }
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.drop(1).map(&:source).join(', ')
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 options.empty?
39
- "#{association_name} { association :#{association_name} }"
45
+ if rest_options.empty?
46
+ "#{association_name} { association #{factory} }"
40
47
  else
41
- "#{association_name} { association :#{association_name}, #{options} }"
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
- private def inside_factory_bot_factory?(node)
48
- context = node.each_ancestor(:block).first
49
- send_node = context.block_type? ? context.send_node : context
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 send_node.method_name == :factory
52
- end
53
-
54
- private def inside_factory_bot_define?(node)
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(&:first_argument)&.map(&:value)&.map(&:to_s)
95
+ association_names = associations&.map { |node| get_association_name(node) }
116
96
  return association_names
117
97
  end
118
98
 
119
- private def generate_association_definition(property)
120
- "#{property} { association :#{property} }"
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 inside_factory_bot_define?(node)
124
- ancestors = node.each_ancestor(:block).to_a
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 ::Sevencop::CopConcerns::Ordered
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 factory_bot_define_block?(node)
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.13
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-26 00:00:00.000000000 Z
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