momocop 0.1.6 → 0.1.7
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/.rubocop.yml +3 -0
- data/README.ja.md +53 -0
- data/README.ja.md.erb +62 -0
- data/README.md +8 -5
- data/README.md.erb +62 -0
- data/Rakefile +21 -0
- data/lib/momocop/version.rb +1 -1
- data/lib/momocop.rb +3 -0
- data/lib/rubocop/cop/momocop/factory_bot_class_existence.rb +61 -0
- data/lib/rubocop/cop/momocop/{factory_bot_rails_factory_associations_coverage.rb → factory_bot_missing_associations.rb} +4 -10
- data/lib/rubocop/cop/momocop/{factory_bot_rails_class_option_specified.rb → factory_bot_missing_class_option.rb} +10 -7
- data/lib/rubocop/cop/momocop/{factory_bot_rails_factory_properties_coverage.rb → factory_bot_missing_properties.rb} +4 -10
- data/lib/rubocop/cop/momocop/factory_bot_property_order.rb +118 -0
- metadata +24 -6
- data/lib/rubocop/cop/momocop/factory_bot_rails_class_name.rb +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2217b77f50980c7b628b6be000f20a76093b41f13933b5e0e4c04c860215eb68
|
4
|
+
data.tar.gz: 3fd67a7eb04625b7021db01f453477f804b8e48609c9f41e859579d46239d89e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d5dfa8b78691d8cf4bc8938d3bed507969eec0dbc3940032a22eb3ca0fa705abbecc82f1f4586be8e8d9e963afd5144572399c2854abed205f85c866b75fbe4
|
7
|
+
data.tar.gz: 7feb8e1104912f6d595becbd4b4fa20ec3fd38910892d52da3f64b12f5fc6ac37f4ef352f6a57b29b094d5c2fce434895eb9c54d8bc0e5516c5ce24b9ce9a082
|
data/.rubocop.yml
CHANGED
data/README.ja.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
[English](./README.md) | 日本語
|
2
|
+
|
3
|
+
# momocop
|
4
|
+
|
5
|
+
[](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [](https://badge.fury.io/rb/momocop)
|
6
|
+
|
7
|
+
Momocopは、規約ベースのOpinionatedな[RuboCop](https://github.com/rubocop/rubocop)カスタムCopを提供します。
|
8
|
+
|
9
|
+
## インストール
|
10
|
+
|
11
|
+
`momocop`をGemfileに追加してください。
|
12
|
+
|
13
|
+
```rb
|
14
|
+
# Gemfile
|
15
|
+
gem 'momocop', require: false
|
16
|
+
```
|
17
|
+
|
18
|
+
## 使用方法
|
19
|
+
|
20
|
+
`.rubocop.yml`を編集して、`momocop`をrequireしてください。
|
21
|
+
|
22
|
+
すべてのCopはデフォルトで無効になっているため、利用したいCopを有効にしてください。
|
23
|
+
|
24
|
+
```yaml
|
25
|
+
# .rubocop.yaml
|
26
|
+
require:
|
27
|
+
- momocop
|
28
|
+
|
29
|
+
Momocop/FactoryBotMissingAssociations:
|
30
|
+
Enabled: true
|
31
|
+
```
|
32
|
+
|
33
|
+
## Cop一覧
|
34
|
+
|
35
|
+
|Cop|Rails|FactoryBot|
|
36
|
+
|---|:-:|:-:|
|
37
|
+
|[`Momocop/FactoryBotClassExistence`](lib/rubocop/cop/momocop/factory_bot_class_existence.rb)|:white_check_mark:|:white_check_mark:|
|
38
|
+
|[`Momocop/FactoryBotMissingAssociations`](lib/rubocop/cop/momocop/factory_bot_missing_associations.rb)|:white_check_mark:|:white_check_mark:|
|
39
|
+
|[`Momocop/FactoryBotMissingClassOption`](lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb)|:white_check_mark:|:white_check_mark:|
|
40
|
+
|[`Momocop/FactoryBotMissingProperties`](lib/rubocop/cop/momocop/factory_bot_missing_properties.rb)|:white_check_mark:|:white_check_mark:|
|
41
|
+
|[`Momocop/FactoryBotPropertyOrder`](lib/rubocop/cop/momocop/factory_bot_property_order.rb)|:white_check_mark:|:white_check_mark:|
|
42
|
+
|
43
|
+
## 貢献
|
44
|
+
|
45
|
+
GitHubの https://github.com/supermomonga/momocop でのバグ報告やプルリクエストは歓迎します。このプロジェクトは安全で、協力的な空間であることを目指しており、貢献者は[行動規範](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md)に従うことが求められます。
|
46
|
+
|
47
|
+
## ライセンス
|
48
|
+
|
49
|
+
このgemは[MITライセンス](https://opensource.org/licenses/MIT)の条件の下でオープンソースとして利用可能です。
|
50
|
+
|
51
|
+
## 行動規範
|
52
|
+
|
53
|
+
Momocopプロジェクトのコードベース、イシュートラッカー、チャットルーム、およびメーリングリストにおいて交流する全員は、[行動規範](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md)に従うことが期待されます。
|
data/README.ja.md.erb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
[English](./README.md) | 日本語
|
2
|
+
|
3
|
+
# momocop
|
4
|
+
|
5
|
+
[](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [](https://badge.fury.io/rb/momocop)
|
6
|
+
|
7
|
+
Momocopは、規約ベースのOpinionatedな[RuboCop](https://github.com/rubocop/rubocop)カスタムCopを提供します。
|
8
|
+
|
9
|
+
## インストール
|
10
|
+
|
11
|
+
`momocop`をGemfileに追加してください。
|
12
|
+
|
13
|
+
```rb
|
14
|
+
# Gemfile
|
15
|
+
gem 'momocop', require: false
|
16
|
+
```
|
17
|
+
|
18
|
+
## 使用方法
|
19
|
+
|
20
|
+
`.rubocop.yml`を編集して、`momocop`をrequireしてください。
|
21
|
+
|
22
|
+
すべてのCopはデフォルトで無効になっているため、利用したいCopを有効にしてください。
|
23
|
+
|
24
|
+
```yaml
|
25
|
+
# .rubocop.yaml
|
26
|
+
require:
|
27
|
+
- momocop
|
28
|
+
|
29
|
+
Momocop/FactoryBotMissingAssociations:
|
30
|
+
Enabled: true
|
31
|
+
```
|
32
|
+
|
33
|
+
## Cop一覧
|
34
|
+
|
35
|
+
|Cop|Rails|FactoryBot|
|
36
|
+
|---|:-:|:-:|
|
37
|
+
<%
|
38
|
+
files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
|
39
|
+
files.each do |cop_file_path|
|
40
|
+
base_name = File.basename(cop_file_path, '.rb')
|
41
|
+
cop_class_name = "Momocop/#{base_name.camelize}"
|
42
|
+
is_factory_cop = cop_file_path.include?('factory_bot_')
|
43
|
+
is_rails_cop = cop_file_path.match?(/(factory_bot_|rails_)/)
|
44
|
+
rails_mark = is_rails_cop ? ':white_check_mark:' : ''
|
45
|
+
factory_bot_mark = is_factory_cop ? ':white_check_mark:' : ''
|
46
|
+
|
47
|
+
table_row = "|[`#{cop_class_name}`](#{cop_file_path})|#{rails_mark}|#{factory_bot_mark}|"
|
48
|
+
-%>
|
49
|
+
<%= table_row %>
|
50
|
+
<% end -%>
|
51
|
+
|
52
|
+
## 貢献
|
53
|
+
|
54
|
+
GitHubの https://github.com/supermomonga/momocop でのバグ報告やプルリクエストは歓迎します。このプロジェクトは安全で、協力的な空間であることを目指しており、貢献者は[行動規範](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md)に従うことが求められます。
|
55
|
+
|
56
|
+
## ライセンス
|
57
|
+
|
58
|
+
このgemは[MITライセンス](https://opensource.org/licenses/MIT)の条件の下でオープンソースとして利用可能です。
|
59
|
+
|
60
|
+
## 行動規範
|
61
|
+
|
62
|
+
Momocopプロジェクトのコードベース、イシュートラッカー、チャットルーム、およびメーリングリストにおいて交流する全員は、[行動規範](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md)に従うことが期待されます。
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
English | [日本語](./README.ja.md)
|
2
|
+
|
1
3
|
# momocop
|
2
4
|
|
3
5
|
[](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [](https://badge.fury.io/rb/momocop)
|
@@ -24,7 +26,7 @@ All cops are disabled by default.
|
|
24
26
|
require:
|
25
27
|
- momocop
|
26
28
|
|
27
|
-
Momocop/
|
29
|
+
Momocop/FactoryBotMissingAssociations:
|
28
30
|
Enabled: true
|
29
31
|
```
|
30
32
|
|
@@ -32,10 +34,11 @@ Momocop/FactoryBotRailsFactoryAssociationsCoverage:
|
|
32
34
|
|
33
35
|
|Cop|Rails|FactoryBot|
|
34
36
|
|---|:-:|:-:|
|
35
|
-
|[`Momocop/
|
36
|
-
|[`Momocop/
|
37
|
-
|[`Momocop/
|
38
|
-
|[`Momocop/
|
37
|
+
|[`Momocop/FactoryBotClassExistence`](lib/rubocop/cop/momocop/factory_bot_class_existence.rb)|:white_check_mark:|:white_check_mark:|
|
38
|
+
|[`Momocop/FactoryBotMissingAssociations`](lib/rubocop/cop/momocop/factory_bot_missing_associations.rb)|:white_check_mark:|:white_check_mark:|
|
39
|
+
|[`Momocop/FactoryBotMissingClassOption`](lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb)|:white_check_mark:|:white_check_mark:|
|
40
|
+
|[`Momocop/FactoryBotMissingProperties`](lib/rubocop/cop/momocop/factory_bot_missing_properties.rb)|:white_check_mark:|:white_check_mark:|
|
41
|
+
|[`Momocop/FactoryBotPropertyOrder`](lib/rubocop/cop/momocop/factory_bot_property_order.rb)|:white_check_mark:|:white_check_mark:|
|
39
42
|
|
40
43
|
## Contributing
|
41
44
|
|
data/README.md.erb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
English | [日本語](./README.ja.md)
|
2
|
+
|
3
|
+
# momocop
|
4
|
+
|
5
|
+
[](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [](https://badge.fury.io/rb/momocop)
|
6
|
+
|
7
|
+
Momocop is highly opinionated custom cops for [RuboCop](https://github.com/rubocop/rubocop).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add `momocop` to your Gemfile.
|
12
|
+
|
13
|
+
```rb
|
14
|
+
# Gemfile
|
15
|
+
gem 'momocop', require: false
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Edit `.rubocop.yml` to require `momocop` and enable the cops you want.
|
21
|
+
|
22
|
+
All cops are disabled by default.
|
23
|
+
|
24
|
+
```yaml
|
25
|
+
# .rubocop.yaml
|
26
|
+
require:
|
27
|
+
- momocop
|
28
|
+
|
29
|
+
Momocop/FactoryBotMissingAssociations:
|
30
|
+
Enabled: true
|
31
|
+
```
|
32
|
+
|
33
|
+
## Cops
|
34
|
+
|
35
|
+
|Cop|Rails|FactoryBot|
|
36
|
+
|---|:-:|:-:|
|
37
|
+
<%
|
38
|
+
files = Dir.glob('lib/rubocop/cop/momocop/*.rb').sort_by { |f| File.basename(f)}
|
39
|
+
files.each do |cop_file_path|
|
40
|
+
base_name = File.basename(cop_file_path, '.rb')
|
41
|
+
cop_class_name = "Momocop/#{base_name.camelize}"
|
42
|
+
is_factory_cop = cop_file_path.include?('factory_bot_')
|
43
|
+
is_rails_cop = cop_file_path.match?(/(factory_bot_|rails_)/)
|
44
|
+
rails_mark = is_rails_cop ? ':white_check_mark:' : ''
|
45
|
+
factory_bot_mark = is_factory_cop ? ':white_check_mark:' : ''
|
46
|
+
|
47
|
+
table_row = "|[`#{cop_class_name}`](#{cop_file_path})|#{rails_mark}|#{factory_bot_mark}|"
|
48
|
+
-%>
|
49
|
+
<%= table_row %>
|
50
|
+
<% end -%>
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/supermomonga/momocop. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md).
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
59
|
+
|
60
|
+
## Code of Conduct
|
61
|
+
|
62
|
+
Everyone interacting in the Momocop project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/supermomonga/momocop/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -10,3 +10,24 @@ require 'rubocop/rake_task'
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
require 'active_support/core_ext/string/inflections'
|
15
|
+
require 'erb'
|
16
|
+
|
17
|
+
desc 'Update README.md from README.md.erb'
|
18
|
+
task 'readme:update' do
|
19
|
+
files = Dir.glob(File.join(__dir__, 'README.*.erb'))
|
20
|
+
files.each do |src_path|
|
21
|
+
dst_path = src_path.sub(/\.erb\z/, '')
|
22
|
+
# ERBテンプレートの読み込み
|
23
|
+
template = File.read(src_path)
|
24
|
+
|
25
|
+
# ERBテンプレートのレンダリング
|
26
|
+
renderer = ERB.new(template, trim_mode: '-')
|
27
|
+
output = renderer.result
|
28
|
+
|
29
|
+
# README.mdへの書き込み
|
30
|
+
File.write(dst_path, output)
|
31
|
+
puts "#{dst_path} has been successfully updated."
|
32
|
+
end
|
33
|
+
end
|
data/lib/momocop/version.rb
CHANGED
data/lib/momocop.rb
CHANGED
@@ -8,6 +8,9 @@ require 'rubocop/cop/mixin/active_record_helper'
|
|
8
8
|
require 'rubocop/rails/schema_loader'
|
9
9
|
require 'rubocop/rails/schema_loader/schema'
|
10
10
|
|
11
|
+
# sevencop
|
12
|
+
require 'sevencop/cop_concerns'
|
13
|
+
|
11
14
|
# Momocop
|
12
15
|
require_relative 'momocop/association_extractor'
|
13
16
|
require_relative 'momocop/config_injector'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Momocop
|
6
|
+
# Ensures that FactoryBot factories has a valid class option.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad (if 'app/models/user.rb' does not exist)
|
10
|
+
# factory :user, class: 'User' do
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # good (if 'app/models/admin.rb' exists)
|
14
|
+
# factory :user, class: 'Admin' do
|
15
|
+
# end
|
16
|
+
class FactoryBotClassExistence < RuboCop::Cop::Base
|
17
|
+
extend AutoCorrector
|
18
|
+
|
19
|
+
MSG = 'Specified class does not exist. Please make sure that the class exists.'
|
20
|
+
|
21
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
22
|
+
|
23
|
+
def_node_matcher :factory_class_option_symbol?, <<~PATTERN
|
24
|
+
(send nil? :factory _ (hash <(pair (sym :class) $(sym _)) ...>))
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def_node_matcher :factory_class_option_string?, <<~PATTERN
|
28
|
+
(send nil? :factory _ (hash <(pair (sym :class) $(str _)) ...>))
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
return unless inside_factory_bot_define?(node)
|
33
|
+
|
34
|
+
class_node = factory_class_option_symbol?(node) || factory_class_option_string?(node)
|
35
|
+
|
36
|
+
return unless class_node
|
37
|
+
|
38
|
+
class_name = class_node.value.to_s
|
39
|
+
puts class_name
|
40
|
+
|
41
|
+
return if class_exists?(class_name)
|
42
|
+
|
43
|
+
add_offense(class_node)
|
44
|
+
end
|
45
|
+
|
46
|
+
private def model_file_path(class_name)
|
47
|
+
"app/models/#{class_name.underscore}.rb"
|
48
|
+
end
|
49
|
+
|
50
|
+
private def class_exists?(class_name)
|
51
|
+
File.exist?(model_file_path(class_name))
|
52
|
+
end
|
53
|
+
|
54
|
+
private def inside_factory_bot_define?(node)
|
55
|
+
ancestors = node.each_ancestor(:block).to_a
|
56
|
+
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -23,18 +23,20 @@ module RuboCop
|
|
23
23
|
# association(:account)
|
24
24
|
# end
|
25
25
|
# end
|
26
|
-
class
|
26
|
+
class FactoryBotMissingAssociations < RuboCop::Cop::Base
|
27
27
|
include RuboCop::Cop::ActiveRecordHelper
|
28
28
|
extend AutoCorrector
|
29
29
|
|
30
30
|
MSG = 'Ensure all associations of the model class are defined in the factory.'
|
31
31
|
|
32
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
33
|
+
|
32
34
|
def_node_search :association_definitions, <<~PATTERN
|
33
35
|
(send nil? :association ...)
|
34
36
|
PATTERN
|
35
37
|
|
36
38
|
def on_send(node)
|
37
|
-
return unless
|
39
|
+
return unless inside_factory_bot_define?(node)
|
38
40
|
|
39
41
|
class_name = get_class_name(node)
|
40
42
|
return unless class_name
|
@@ -104,14 +106,6 @@ module RuboCop
|
|
104
106
|
"association(:#{property})"
|
105
107
|
end
|
106
108
|
|
107
|
-
private def factory_bot_define_block_with_class_option?(node)
|
108
|
-
factory_method_call?(node) && inside_factory_bot_define?(node)
|
109
|
-
end
|
110
|
-
|
111
|
-
private def factory_method_call?(node)
|
112
|
-
node.method_name == :factory
|
113
|
-
end
|
114
|
-
|
115
109
|
private def inside_factory_bot_define?(node)
|
116
110
|
ancestors = node.each_ancestor(:block).to_a
|
117
111
|
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
@@ -13,22 +13,20 @@ module RuboCop
|
|
13
13
|
# # good
|
14
14
|
# factory :user, class: 'User' do
|
15
15
|
# end
|
16
|
-
class
|
16
|
+
class FactoryBotMissingClassOption < RuboCop::Cop::Base
|
17
17
|
extend AutoCorrector
|
18
18
|
|
19
19
|
MSG = 'Specify a class option explicitly in FactoryBot factory.'
|
20
20
|
|
21
|
-
|
22
|
-
(send nil? :factory ...)
|
23
|
-
PATTERN
|
21
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
24
22
|
|
25
23
|
def on_send(node)
|
26
|
-
return unless
|
24
|
+
return unless inside_factory_bot_define?(node)
|
27
25
|
|
28
26
|
# `factory`メソッドの呼び出しで、ハッシュ引数に`:class`キーが含まれているかを調べる
|
29
|
-
class_option_specified = node.arguments.any?
|
27
|
+
class_option_specified = node.arguments.any? { |arg|
|
30
28
|
arg.hash_type? && arg.pairs.any? { |pair| pair.key.sym_type? && pair.key.children.first == :class }
|
31
|
-
|
29
|
+
}
|
32
30
|
return if class_option_specified
|
33
31
|
|
34
32
|
add_offense(node.loc.selector) do |corrector|
|
@@ -39,6 +37,11 @@ module RuboCop
|
|
39
37
|
corrector.insert_after(node.first_argument.loc.expression, ", class: '#{class_name}'")
|
40
38
|
end
|
41
39
|
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
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -26,12 +26,14 @@ module RuboCop
|
|
26
26
|
# name { 'John Doe' }
|
27
27
|
# end
|
28
28
|
# end
|
29
|
-
class
|
29
|
+
class FactoryBotMissingProperties < RuboCop::Cop::Base
|
30
30
|
include RuboCop::Cop::ActiveRecordHelper
|
31
31
|
extend AutoCorrector
|
32
32
|
|
33
33
|
MSG = 'Ensure all properties of the model class are defined in the factory.'
|
34
34
|
|
35
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
36
|
+
|
35
37
|
def_node_search :sequence_definitions, <<~PATTERN
|
36
38
|
(send nil? :sequence ...)
|
37
39
|
PATTERN
|
@@ -41,7 +43,7 @@ module RuboCop
|
|
41
43
|
PATTERN
|
42
44
|
|
43
45
|
def on_send(node)
|
44
|
-
return unless
|
46
|
+
return unless inside_factory_bot_define?(node)
|
45
47
|
|
46
48
|
class_name = get_class_name(node)
|
47
49
|
return unless class_name
|
@@ -188,14 +190,6 @@ module RuboCop
|
|
188
190
|
return property_names
|
189
191
|
end
|
190
192
|
|
191
|
-
private def factory_bot_define_block_with_class_option?(node)
|
192
|
-
factory_method_call?(node) && inside_factory_bot_define?(node)
|
193
|
-
end
|
194
|
-
|
195
|
-
private def factory_method_call?(node)
|
196
|
-
node.method_name == :factory
|
197
|
-
end
|
198
|
-
|
199
193
|
private def inside_factory_bot_define?(node)
|
200
194
|
ancestors = node.each_ancestor(:block).to_a
|
201
195
|
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Momocop
|
6
|
+
# Ensures that FactoryBot factories has ordered property definitions.
|
7
|
+
# 1. Associations should be defined before other properties.
|
8
|
+
# 2. Associations and properties should be defined in alphabetical order.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# factory :user, class: 'User' do
|
13
|
+
# address { '123 Main St' }
|
14
|
+
# association :profile
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# factory :user, class: 'User' do
|
19
|
+
# association :profile
|
20
|
+
# address { '123 Main St' }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # bad
|
24
|
+
# factory :user, class: 'User' do
|
25
|
+
# association :profile
|
26
|
+
# association :account
|
27
|
+
# zipcode { '111-1111' }
|
28
|
+
# address { '123 Main St' }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# factory :user, class: 'User' do
|
33
|
+
# association :account
|
34
|
+
# association :profile
|
35
|
+
# address { '123 Main St' }
|
36
|
+
# zipcode { '111-1111' }
|
37
|
+
# end
|
38
|
+
class FactoryBotPropertyOrder < RuboCop::Cop::Base
|
39
|
+
extend AutoCorrector
|
40
|
+
include RangeHelp
|
41
|
+
|
42
|
+
include ::Sevencop::CopConcerns::Ordered
|
43
|
+
|
44
|
+
MSG = 'Sort properties and associations alphabetically.'
|
45
|
+
|
46
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
47
|
+
|
48
|
+
def on_send(node)
|
49
|
+
return unless inside_factory_bot_define?(node)
|
50
|
+
|
51
|
+
block_node = node.block_node
|
52
|
+
entire_definitions = defined_properties(block_node)
|
53
|
+
|
54
|
+
sections =
|
55
|
+
entire_definitions
|
56
|
+
.slice_when { |a, b| b.loc.last_line - a.loc.last_line > 1 }
|
57
|
+
.select { |definitions| definitions.size >= 2 }
|
58
|
+
|
59
|
+
sections.each do |definitions|
|
60
|
+
add_offense(definitions.last) do |corrector|
|
61
|
+
(a, b) =
|
62
|
+
definitions
|
63
|
+
.lazy
|
64
|
+
.each_cons(2)
|
65
|
+
.find { |a, b| (order(a) <=> order(b)) == 1 }
|
66
|
+
|
67
|
+
break unless a && b
|
68
|
+
|
69
|
+
swap(
|
70
|
+
range_with_comments_and_lines(a),
|
71
|
+
range_with_comments_and_lines(b),
|
72
|
+
corrector:
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private def order(node)
|
79
|
+
group = definition_type(node) == :association ? 0 : 1
|
80
|
+
index = definition_name(node)
|
81
|
+
[group, index]
|
82
|
+
end
|
83
|
+
|
84
|
+
private def defined_properties(block_node)
|
85
|
+
body_node = block_node&.children&.last
|
86
|
+
body_node&.children&.select { |node| definition_node?(node) }
|
87
|
+
end
|
88
|
+
|
89
|
+
private def definition_node?(node)
|
90
|
+
node.send_type? || (node.block_type? && node.children.first.send_type?)
|
91
|
+
end
|
92
|
+
|
93
|
+
private def definition_type(node)
|
94
|
+
send_node = node.send_type? ? node : node.children.first
|
95
|
+
if %i[association sequence].include? send_node.method_name
|
96
|
+
send_node.method_name
|
97
|
+
else
|
98
|
+
:property
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private def definition_name(node)
|
103
|
+
send_node = node.send_type? ? node : node.children.first
|
104
|
+
if %i[association sequence].include? send_node.method_name
|
105
|
+
send_node.arguments.first.value
|
106
|
+
else
|
107
|
+
send_node.method_name.to_sym
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private def inside_factory_bot_define?(node)
|
112
|
+
ancestors = node.each_ancestor(:block).to_a
|
113
|
+
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
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.
|
4
|
+
version: 0.1.7
|
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-
|
11
|
+
date: 2024-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sevencop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Convention focused opinionated custom cops for RuboCop.
|
56
70
|
email:
|
57
71
|
- hi@supermomonga.com
|
@@ -66,7 +80,10 @@ files:
|
|
66
80
|
- CODEOWNERS
|
67
81
|
- CODE_OF_CONDUCT.md
|
68
82
|
- LICENSE.txt
|
83
|
+
- README.ja.md
|
84
|
+
- README.ja.md.erb
|
69
85
|
- README.md
|
86
|
+
- README.md.erb
|
70
87
|
- Rakefile
|
71
88
|
- config/default.yml
|
72
89
|
- lib/momocop.rb
|
@@ -74,10 +91,11 @@ files:
|
|
74
91
|
- lib/momocop/config_injector.rb
|
75
92
|
- lib/momocop/enum_extractor.rb
|
76
93
|
- lib/momocop/version.rb
|
77
|
-
- lib/rubocop/cop/momocop/
|
78
|
-
- lib/rubocop/cop/momocop/
|
79
|
-
- lib/rubocop/cop/momocop/
|
80
|
-
- lib/rubocop/cop/momocop/
|
94
|
+
- lib/rubocop/cop/momocop/factory_bot_class_existence.rb
|
95
|
+
- lib/rubocop/cop/momocop/factory_bot_missing_associations.rb
|
96
|
+
- lib/rubocop/cop/momocop/factory_bot_missing_class_option.rb
|
97
|
+
- lib/rubocop/cop/momocop/factory_bot_missing_properties.rb
|
98
|
+
- lib/rubocop/cop/momocop/factory_bot_property_order.rb
|
81
99
|
- sig/momocop.rbs
|
82
100
|
homepage: https://github.com/supermomonga/momocop
|
83
101
|
licenses:
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RuboCop
|
4
|
-
module Cop
|
5
|
-
module Momocop
|
6
|
-
# Ensures that FactoryBot factories use `ClassName.name` for the class option instead of symbol or string literals.
|
7
|
-
#
|
8
|
-
# @example
|
9
|
-
# # bad
|
10
|
-
# factory :user, class: :User do
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# factory :admin, class: 'Admin' do
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# factory :moderator, class: Moderator.to_s do
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# # good
|
20
|
-
# factory :user, class: User.name do
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# factory :admin, class: Admin.name do
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# factory :moderator, class: Moderator.name do
|
27
|
-
# end
|
28
|
-
class FactoryBotRailsClassName < RuboCop::Cop::Base
|
29
|
-
extend AutoCorrector
|
30
|
-
|
31
|
-
MSG = 'Use `ClassName.name` for the class option instead of a symbol, string literal, or `ClassName.to_s`.'
|
32
|
-
|
33
|
-
def_node_matcher :factory_class_option_symbol?, <<~PATTERN
|
34
|
-
(send nil? :factory _ (hash <(pair (sym :class) $(sym _)) ...>))
|
35
|
-
PATTERN
|
36
|
-
|
37
|
-
def_node_matcher :factory_class_option_string?, <<~PATTERN
|
38
|
-
(send nil? :factory _ (hash <(pair (sym :class) $(str _)) ...>))
|
39
|
-
PATTERN
|
40
|
-
|
41
|
-
def_node_matcher :factory_class_option_to_s?, <<~PATTERN
|
42
|
-
(send nil? :factory _ (hash <(pair (sym :class) $(send _ :to_s)) ...>))
|
43
|
-
PATTERN
|
44
|
-
|
45
|
-
def on_send(node)
|
46
|
-
factory_class_option_symbol?(node) do |class_option|
|
47
|
-
add_offense(class_option) do |corrector|
|
48
|
-
corrector.replace(class_option, "#{class_option.value.capitalize}.name")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
factory_class_option_string?(node) do |class_option|
|
53
|
-
class_name = class_option.value.delete_prefix("'").delete_suffix("'").capitalize
|
54
|
-
add_offense(class_option) do |corrector|
|
55
|
-
corrector.replace(class_option, "#{class_name}.name")
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
factory_class_option_to_s?(node) do |class_option|
|
60
|
-
class_name = class_option.receiver.const_name
|
61
|
-
add_offense(class_option) do |corrector|
|
62
|
-
corrector.replace(class_option, "#{class_name}.name")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|