momocop 0.1.14 → 0.2.0

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: 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.