momocop 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 125920382da157b5f151d8d7de60a05df4943fbbdf521db3b082001891f7af25
4
- data.tar.gz: 8860b388475b79b0eec94ee28e9635a2c288b662bdcdbe33a5172b8959d969c9
3
+ metadata.gz: f20cc9ff605a765cbb69d7540eab272368da8ec2db1508bf15f68c055256f306
4
+ data.tar.gz: ae33c4d6d07c9db8707f49694b3609f55297280f3910b2fb98613d52a44b2c7b
5
5
  SHA512:
6
- metadata.gz: f538d288ad3858eb9c5c6bbd0c6576b6e8bd0811282da99215b295426e83b4d51f5b111fa256d1c6246eb1577209a114d8745c28c0568e8fc93b5f774774b93b
7
- data.tar.gz: e49a4e8e1ab9ad2ce341566257730d7eab510002319a8c4c48db179f2be5dbdfc6ee7ddecec9552debef73360729546e6eb954eee14c2489f8c8037f950ac891
6
+ metadata.gz: f1f054e0cff3c118d11191948622c99e5ccdffde3c09bacc7ea0e9fdffdc6dd2fb61158bc4d30ac7627ac87473a8188de34082780fee715fd7609abe7cadac71
7
+ data.tar.gz: 8838377046d17cf9f238747d0a0c6de7d2d78fbb0f01c672c391c3252dd47cacd962518b23e3f72ff15aeb3acd17d20e71bf2e3eb98557a8c7fccee982e7a279
data/README.ja.md CHANGED
@@ -46,6 +46,8 @@ Momocop/FactoryBotSingleFactoryPerDefine:
46
46
  Enabled: true
47
47
  Momocop/FactoryBotSingularFactoryName:
48
48
  Enabled: true
49
+ Momocop/Layout/LeadingCommentSpace:
50
+ Enabled: true
49
51
  ```
50
52
 
51
53
  ## Cop一覧
@@ -62,6 +64,7 @@ Momocop/FactoryBotSingularFactoryName:
62
64
  |[`Momocop/FactoryBotSingleDefinePerFile`](lib/rubocop/cop/momocop/factory_bot_single_define_per_file.rb)|:white_check_mark:|:white_check_mark:|
63
65
  |[`Momocop/FactoryBotSingleFactoryPerDefine`](lib/rubocop/cop/momocop/factory_bot_single_factory_per_define.rb)|:white_check_mark:|:white_check_mark:|
64
66
  |[`Momocop/FactoryBotSingularFactoryName`](lib/rubocop/cop/momocop/factory_bot_singular_factory_name.rb)|:white_check_mark:|:white_check_mark:|
67
+ |[`Momocop/Layout/LeadingCommentSpace`](lib/rubocop/cop/momocop/layout/leading_comment_space.rb)|||
65
68
 
66
69
  ## 貢献
67
70
 
data/README.ja.md.erb CHANGED
@@ -27,10 +27,11 @@ require:
27
27
  - momocop
28
28
 
29
29
  <%
30
- files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
30
+ files = Dir.glob('lib/rubocop/cop/momocop/**/*.rb').sort_by { |f| File.basename(f)}
31
31
  files.each do |cop_file_path|
32
32
  base_name = File.basename(cop_file_path, '.rb')
33
- cop_class_name = "Momocop/#{base_name.camelize}"
33
+ namespace = File.dirname(cop_file_path).sub('lib/rubocop/cop/', '').split('/').map(&:camelize).join('/')
34
+ cop_class_name = "#{namespace}/#{base_name.camelize}"
34
35
 
35
36
  -%>
36
37
  <%= cop_class_name %>:
@@ -43,10 +44,11 @@ require:
43
44
  |Cop|Rails|FactoryBot|
44
45
  |---|:-:|:-:|
45
46
  <%
46
- files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
47
+ files = Dir.glob('lib/rubocop/cop/momocop/**/*.rb').sort_by { |f| File.basename(f)}
47
48
  files.each do |cop_file_path|
48
49
  base_name = File.basename(cop_file_path, '.rb')
49
- cop_class_name = "Momocop/#{base_name.camelize}"
50
+ namespace = File.dirname(cop_file_path).sub('lib/rubocop/cop/', '').split('/').map(&:camelize).join('/')
51
+ cop_class_name = "#{namespace}/#{base_name.camelize}"
50
52
  is_factory_cop = cop_file_path.include?('factory_bot_')
51
53
  is_rails_cop = cop_file_path.match?(/(factory_bot_|rails_)/)
52
54
  rails_mark = is_rails_cop ? ':white_check_mark:' : ''
data/README.md CHANGED
@@ -46,6 +46,8 @@ Momocop/FactoryBotSingleFactoryPerDefine:
46
46
  Enabled: true
47
47
  Momocop/FactoryBotSingularFactoryName:
48
48
  Enabled: true
49
+ Momocop/Layout/LeadingCommentSpace:
50
+ Enabled: true
49
51
  ```
50
52
 
51
53
  ## Cops
@@ -62,6 +64,7 @@ Momocop/FactoryBotSingularFactoryName:
62
64
  |[`Momocop/FactoryBotSingleDefinePerFile`](lib/rubocop/cop/momocop/factory_bot_single_define_per_file.rb)|:white_check_mark:|:white_check_mark:|
63
65
  |[`Momocop/FactoryBotSingleFactoryPerDefine`](lib/rubocop/cop/momocop/factory_bot_single_factory_per_define.rb)|:white_check_mark:|:white_check_mark:|
64
66
  |[`Momocop/FactoryBotSingularFactoryName`](lib/rubocop/cop/momocop/factory_bot_singular_factory_name.rb)|:white_check_mark:|:white_check_mark:|
67
+ |[`Momocop/Layout/LeadingCommentSpace`](lib/rubocop/cop/momocop/layout/leading_comment_space.rb)|||
65
68
 
66
69
  ## Contributing
67
70
 
data/README.md.erb CHANGED
@@ -27,10 +27,11 @@ require:
27
27
  - momocop
28
28
 
29
29
  <%
30
- files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
30
+ files = Dir.glob('lib/rubocop/cop/momocop/**/*.rb').sort_by { |f| File.basename(f)}
31
31
  files.each do |cop_file_path|
32
32
  base_name = File.basename(cop_file_path, '.rb')
33
- cop_class_name = "Momocop/#{base_name.camelize}"
33
+ namespace = File.dirname(cop_file_path).sub('lib/rubocop/cop/', '').split('/').map(&:camelize).join('/')
34
+ cop_class_name = "#{namespace}/#{base_name.camelize}"
34
35
 
35
36
  -%>
36
37
  <%= cop_class_name %>:
@@ -43,10 +44,11 @@ require:
43
44
  |Cop|Rails|FactoryBot|
44
45
  |---|:-:|:-:|
45
46
  <%
46
- files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
47
+ files = Dir.glob('lib/rubocop/cop/momocop/**/*.rb').sort_by { |f| File.basename(f)}
47
48
  files.each do |cop_file_path|
48
49
  base_name = File.basename(cop_file_path, '.rb')
49
- cop_class_name = "Momocop/#{base_name.camelize}"
50
+ namespace = File.dirname(cop_file_path).sub('lib/rubocop/cop/', '').split('/').map(&:camelize).join('/')
51
+ cop_class_name = "#{namespace}/#{base_name.camelize}"
50
52
  is_factory_cop = cop_file_path.include?('factory_bot_')
51
53
  is_rails_cop = cop_file_path.match?(/(factory_bot_|rails_)/)
52
54
  rails_mark = is_rails_cop ? ':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.14'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/momocop.rb CHANGED
@@ -17,8 +17,12 @@ 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
- Dir[File.join(__dir__, 'rubocop/cop/momocop', '*.rb')].each do |file|
26
+ Dir[File.join(__dir__, 'rubocop/cop/momocop', '**', '*.rb')].each do |file|
23
27
  require file
24
28
  end
@@ -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
 
@@ -37,7 +40,7 @@ module RuboCop
37
40
  convert_options(options) in {
38
41
  factory_option:, rest_options:
39
42
  }
40
- factory = factory_option&.source || ":#{association_name}"
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 = options&.pairs&.find { |pair| pair.key.value == :factory }&.value
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 ::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
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Momocop
6
+ module Layout
7
+ # Checks whether comments have a leading space after the
8
+ # `#` denoting the start of the comment. The leading space is not
9
+ # required for some RDoc special syntax, like `#++`, `#--`,
10
+ # `#:nodoc`, `=begin`- and `=end` comments, "shebang" directives,
11
+ # or rackup options, or rbs-inline typing.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # #Some comment
17
+ #
18
+ # # good
19
+ # # Some comment
20
+ #
21
+ # @example AllowDoxygenCommentStyle: false (default)
22
+ #
23
+ # # bad
24
+ #
25
+ # #**
26
+ # # Some comment
27
+ # # Another line of comment
28
+ # #*
29
+ #
30
+ # @example AllowDoxygenCommentStyle: true
31
+ #
32
+ # # good
33
+ #
34
+ # #**
35
+ # # Some comment
36
+ # # Another line of comment
37
+ # #*
38
+ #
39
+ # @example AllowGemfileRubyComment: false (default)
40
+ #
41
+ # # bad
42
+ #
43
+ # #ruby=2.7.0
44
+ # #ruby-gemset=myproject
45
+ #
46
+ # @example AllowGemfileRubyComment: true
47
+ #
48
+ # # good
49
+ #
50
+ # #ruby=2.7.0
51
+ # #ruby-gemset=myproject
52
+ #
53
+ class LeadingCommentSpace < RuboCop::Cop::Layout::LeadingCommentSpace
54
+ def on_new_investigation
55
+ processed_source.comments.each do |comment|
56
+ next unless /\A(?!#\+\+|#--)(#+[^#\s=])/.match?(comment.text)
57
+ next if comment.loc.line == 1 && allowed_on_first_line?(comment)
58
+ next if doxygen_comment_style?(comment)
59
+ next if gemfile_ruby_comment?(comment)
60
+ next if rbs_inline_comment_style?(comment)
61
+
62
+ add_offense(comment) do |corrector|
63
+ expr = comment.source_range
64
+
65
+ corrector.insert_after(hash_mark(expr), ' ')
66
+ end
67
+ end
68
+ end
69
+
70
+ def rbs_inline_comment_style?(comment)
71
+ comment.text.start_with?('#:')
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ 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.14
4
+ version: 0.2.0
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-08-07 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
@@ -101,6 +103,7 @@ files:
101
103
  - lib/rubocop/cop/momocop/factory_bot_single_define_per_file.rb
102
104
  - lib/rubocop/cop/momocop/factory_bot_single_factory_per_define.rb
103
105
  - lib/rubocop/cop/momocop/factory_bot_singular_factory_name.rb
106
+ - lib/rubocop/cop/momocop/layout/leading_comment_space.rb
104
107
  - sig/momocop.rbs
105
108
  homepage: https://github.com/supermomonga/momocop
106
109
  licenses:
@@ -124,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
127
  - !ruby/object:Gem::Version
125
128
  version: '0'
126
129
  requirements: []
127
- rubygems_version: 3.3.26
130
+ rubygems_version: 3.3.27
128
131
  signing_key:
129
132
  specification_version: 4
130
133
  summary: Convention focused opinionated custom cops for RuboCop.