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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 583e60d7747c3d18b2e39a5fe6d2f4cb4a02d9484ee21d49c3b6e06381c7db1a
4
- data.tar.gz: 72139ae77375a441a56925c906be69d34993c867a3a23337dde3f7e47e257b31
3
+ metadata.gz: 5441b62d81c8c1015b71ffb8f1457afe3be66007280372c5d239102ab08e9382
4
+ data.tar.gz: 3e030584e817127a5d77d83c7ff25d87438ce11d494556d19c196067503db69a
5
5
  SHA512:
6
- metadata.gz: e0fb80f1fa23509e2ace6883efd371d3aeffac8f8472f484b8e797ba2ce1347518e2b124da72116fffe86ad82610a1d7e9af557da0cc04666bc214e4228d7248
7
- data.tar.gz: 125d23305c4a085e35a0f3785f5b5f3f6cb00c4cfbeb44063e838ea5138b755e3d8b183a813c2776ddd56bf566cf682081bde359fc4e5c713d28459494b124e1
6
+ metadata.gz: 62c93ee8fc1733bf0a7e27e3e1a4636f6e8d70016d97646bf32e7a158ceb2950657993b15a6be622e7a579f9a7341b3ca6ff8f391ef0db53cef43bccb4e24a13
7
+ data.tar.gz: 4f0cde02768b237df824f17e231ac8444a7d18603644873f45974f3dcdb931e6e4e7e946780e4f6eda3ebb88e9cda278b27665e6586da7043377fafa7bbd765b
data/.rubocop.yml CHANGED
@@ -56,6 +56,9 @@ Style/FrozenStringLiteralComment:
56
56
  Style/IfUnlessModifier:
57
57
  Enabled: false
58
58
 
59
+ Style/ParallelAssignment:
60
+ Enabled: false
61
+
59
62
  Style/RequireOrder:
60
63
  Enabled: true
61
64
  SafeAutoCorrect: true
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/FactoryBotRailsFactoryAssociationsCoverage:
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/FactoryBotRailsClassName`](lib/rubocop/cop/momocop/factory_bot_rails_class_name.rb)|:white_check_mark:|:white_check_mark:|
36
- |[`Momocop/FactoryBotRailsClassOptionSpecified`](lib/rubocop/cop/momocop/factory_bot_rails_class_option_specified.rb)|:white_check_mark:|:white_check_mark:|
37
- |[`Momocop/FactoryBotRailsFactoryAssociationsCoverage`](lib/rubocop/cop/momocop/factory_bot_rails_factory_associations_coverage.rb)|:white_check_mark:|:white_check_mark:|
38
- |[`Momocop/FactoryBotRailsFactoryPropertiesCoverage`](lib/rubocop/cop/momocop/factory_bot_rails_factory_properties_coverage.rb)|:white_check_mark:|:white_check_mark:|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Momocop
4
- VERSION = '0.1.6'
4
+ VERSION = '0.1.8'
5
5
  end
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 FactoryBotRailsFactoryAssociationsCoverage < RuboCop::Cop::Base
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 factory_bot_define_block_with_class_option?(node)
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 FactoryBotRailsClassOptionSpecified < RuboCop::Cop::Base
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
- def_node_matcher :factory_call?, <<~PATTERN
22
- (send nil? :factory ...)
23
- PATTERN
21
+ RESTRICT_ON_SEND = %i[factory].freeze
24
22
 
25
23
  def on_send(node)
26
- return unless factory_call?(node)
24
+ return unless inside_factory_bot_define?(node)
27
25
 
28
26
  # `factory`メソッドの呼び出しで、ハッシュ引数に`:class`キーが含まれているかを調べる
29
- class_option_specified = node.arguments.any? do |arg|
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
- end
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 FactoryBotRailsFactoryPropertiesCoverage < RuboCop::Cop::Base
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 factory_bot_define_block_with_class_option?(node)
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.6
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-18 00:00:00.000000000 Z
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/factory_bot_rails_class_name.rb
78
- - lib/rubocop/cop/momocop/factory_bot_rails_class_option_specified.rb
79
- - lib/rubocop/cop/momocop/factory_bot_rails_factory_associations_coverage.rb
80
- - lib/rubocop/cop/momocop/factory_bot_rails_factory_properties_coverage.rb
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