momocop 0.1.6 → 0.1.8
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} +16 -12
- 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} +12 -10
- data/lib/rubocop/cop/momocop/factory_bot_property_order.rb +126 -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: 5441b62d81c8c1015b71ffb8f1457afe3be66007280372c5d239102ab08e9382
|
4
|
+
data.tar.gz: 3e030584e817127a5d77d83c7ff25d87438ce11d494556d19c196067503db69a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62c93ee8fc1733bf0a7e27e3e1a4636f6e8d70016d97646bf32e7a158ceb2950657993b15a6be622e7a579f9a7341b3ca6ff8f391ef0db53cef43bccb4e24a13
|
7
|
+
data.tar.gz: 4f0cde02768b237df824f17e231ac8444a7d18603644873f45974f3dcdb931e6e4e7e946780e4f6eda3ebb88e9cda278b27665e6586da7043377fafa7bbd765b
|
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,24 +23,34 @@ 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
|
41
43
|
|
42
44
|
block_node = node.block_node
|
43
45
|
|
46
|
+
# Add block if it's missing
|
47
|
+
unless block_node
|
48
|
+
add_offense(node, message: MSG) do |corrector|
|
49
|
+
indentation = ' ' * node.loc.column
|
50
|
+
corrector.insert_after(node.source_range.end, " do\n#{indentation}end")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
# Check missing associations
|
45
55
|
defined_associations = get_defined_association_names(block_node)
|
46
56
|
model_associations = get_model_association_names(class_name)
|
@@ -50,8 +60,6 @@ module RuboCop
|
|
50
60
|
return unless missing_associations.any?
|
51
61
|
|
52
62
|
add_offense(node, message: MSG) do |corrector|
|
53
|
-
next unless block_node
|
54
|
-
|
55
63
|
# Add newline before closing block if it's a one-liner
|
56
64
|
if one_line_block?(block_node)
|
57
65
|
indentation = ' ' * node.loc.column
|
@@ -64,6 +72,8 @@ module RuboCop
|
|
64
72
|
|
65
73
|
# TODO: calculate indentation size
|
66
74
|
indentation = ' ' * (node.loc.column + 2)
|
75
|
+
|
76
|
+
# use send node if blockless
|
67
77
|
corrector.insert_after(block_node.loc.begin, "\n#{indentation}#{definition}")
|
68
78
|
end
|
69
79
|
end
|
@@ -74,6 +84,8 @@ module RuboCop
|
|
74
84
|
end
|
75
85
|
|
76
86
|
private def one_line_block?(block_node)
|
87
|
+
return false if block_node.nil?
|
88
|
+
|
77
89
|
block_node.loc.begin.line == block_node.loc.end.line
|
78
90
|
end
|
79
91
|
|
@@ -104,14 +116,6 @@ module RuboCop
|
|
104
116
|
"association(:#{property})"
|
105
117
|
end
|
106
118
|
|
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
119
|
private def inside_factory_bot_define?(node)
|
116
120
|
ancestors = node.each_ancestor(:block).to_a
|
117
121
|
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,13 +43,21 @@ 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
|
48
50
|
|
49
51
|
block_node = node.block_node
|
50
52
|
|
53
|
+
# Add block if it's missing
|
54
|
+
unless block_node
|
55
|
+
add_offense(node, message: MSG) do |corrector|
|
56
|
+
indentation = ' ' * node.loc.column
|
57
|
+
corrector.insert_after(node.source_range.end, " do\n#{indentation}end")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
51
61
|
# Check missing associations
|
52
62
|
|
53
63
|
# Exclude defined sequences
|
@@ -188,14 +198,6 @@ module RuboCop
|
|
188
198
|
return property_names
|
189
199
|
end
|
190
200
|
|
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
201
|
private def inside_factory_bot_define?(node)
|
200
202
|
ancestors = node.each_ancestor(:block).to_a
|
201
203
|
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
@@ -0,0 +1,126 @@
|
|
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
|
+
return unless block_node
|
53
|
+
|
54
|
+
entire_definitions = defined_properties(block_node)
|
55
|
+
|
56
|
+
sections =
|
57
|
+
entire_definitions
|
58
|
+
.slice_when { |a, b| b.loc.last_line - a.loc.last_line > 1 }
|
59
|
+
.select { |definitions| definitions.size >= 2 }
|
60
|
+
|
61
|
+
sections.each do |definitions|
|
62
|
+
add_offense(definitions.last) do |corrector|
|
63
|
+
(a, b) =
|
64
|
+
definitions
|
65
|
+
.lazy
|
66
|
+
.each_cons(2)
|
67
|
+
.find { |a, b| (order(a) <=> order(b)) == 1 }
|
68
|
+
|
69
|
+
break unless a && b
|
70
|
+
|
71
|
+
swap(
|
72
|
+
range_with_comments_and_lines(a),
|
73
|
+
range_with_comments_and_lines(b),
|
74
|
+
corrector:
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private def order(node)
|
81
|
+
group = definition_type(node) == :association ? 0 : 1
|
82
|
+
index = definition_name(node)
|
83
|
+
[group, index]
|
84
|
+
end
|
85
|
+
|
86
|
+
private def defined_properties(block_node)
|
87
|
+
body_node = block_node&.children&.last
|
88
|
+
body_node&.children&.select { |node| definition_node?(node) } || []
|
89
|
+
end
|
90
|
+
|
91
|
+
private def definition_node?(node)
|
92
|
+
if node.send_type?
|
93
|
+
return node.method_name != :trait
|
94
|
+
elsif node.block_type? && node.children.first.send_type?
|
95
|
+
return node.children.first.method_name != :trait
|
96
|
+
end
|
97
|
+
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
private def definition_type(node)
|
102
|
+
send_node = node.send_type? ? node : node.children.first
|
103
|
+
if %i[association sequence].include? send_node.method_name
|
104
|
+
send_node.method_name
|
105
|
+
else
|
106
|
+
:property
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private def definition_name(node)
|
111
|
+
send_node = node.send_type? ? node : node.children.first
|
112
|
+
if %i[association sequence].include? send_node.method_name
|
113
|
+
send_node.arguments.first.value
|
114
|
+
else
|
115
|
+
send_node.method_name.to_sym
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private def inside_factory_bot_define?(node)
|
120
|
+
ancestors = node.each_ancestor(:block).to_a
|
121
|
+
ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
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.8
|
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-20 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
|