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