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