momocop 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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} +7 -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
|
+
[![Spec](https://github.com/supermomonga/momocop/actions/workflows/spec.yml/badge.svg)](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [![Gem Version](https://badge.fury.io/rb/momocop.svg)](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
|
+
[![Spec](https://github.com/supermomonga/momocop/actions/workflows/spec.yml/badge.svg)](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [![Gem Version](https://badge.fury.io/rb/momocop.svg)](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
|
[![Spec](https://github.com/supermomonga/momocop/actions/workflows/spec.yml/badge.svg)](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [![Gem Version](https://badge.fury.io/rb/momocop.svg)](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
|
+
[![Spec](https://github.com/supermomonga/momocop/actions/workflows/spec.yml/badge.svg)](https://github.com/supermomonga/momocop/actions/workflows/spec.yml) [![Gem Version](https://badge.fury.io/rb/momocop.svg)](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
|
@@ -126,12 +128,15 @@ module RuboCop
|
|
126
128
|
associations = extractor.extract(source)
|
127
129
|
foreign_key_names =
|
128
130
|
associations
|
131
|
+
.select { _1[:type] == :belongs_to }
|
129
132
|
.map { |association|
|
130
133
|
options = association[:options]
|
131
134
|
if options[:foreign_key]
|
132
135
|
options[:foreign_key]
|
133
136
|
elsif options[:class_name]
|
134
137
|
foreign_key_name(options[:class_name])
|
138
|
+
else
|
139
|
+
"#{association[:name]}_id"
|
135
140
|
end
|
136
141
|
}
|
137
142
|
.compact
|
@@ -185,14 +190,6 @@ module RuboCop
|
|
185
190
|
return property_names
|
186
191
|
end
|
187
192
|
|
188
|
-
private def factory_bot_define_block_with_class_option?(node)
|
189
|
-
factory_method_call?(node) && inside_factory_bot_define?(node)
|
190
|
-
end
|
191
|
-
|
192
|
-
private def factory_method_call?(node)
|
193
|
-
node.method_name == :factory
|
194
|
-
end
|
195
|
-
|
196
193
|
private def inside_factory_bot_define?(node)
|
197
194
|
ancestors = node.each_ancestor(:block).to_a
|
198
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
|