momocop 0.1.6 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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