momocop 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 583e60d7747c3d18b2e39a5fe6d2f4cb4a02d9484ee21d49c3b6e06381c7db1a
4
- data.tar.gz: 72139ae77375a441a56925c906be69d34993c867a3a23337dde3f7e47e257b31
3
+ metadata.gz: 2217b77f50980c7b628b6be000f20a76093b41f13933b5e0e4c04c860215eb68
4
+ data.tar.gz: 3fd67a7eb04625b7021db01f453477f804b8e48609c9f41e859579d46239d89e
5
5
  SHA512:
6
- metadata.gz: e0fb80f1fa23509e2ace6883efd371d3aeffac8f8472f484b8e797ba2ce1347518e2b124da72116fffe86ad82610a1d7e9af557da0cc04666bc214e4228d7248
7
- data.tar.gz: 125d23305c4a085e35a0f3785f5b5f3f6cb00c4cfbeb44063e838ea5138b755e3d8b183a813c2776ddd56bf566cf682081bde359fc4e5c713d28459494b124e1
6
+ metadata.gz: 5d5dfa8b78691d8cf4bc8938d3bed507969eec0dbc3940032a22eb3ca0fa705abbecc82f1f4586be8e8d9e963afd5144572399c2854abed205f85c866b75fbe4
7
+ data.tar.gz: 7feb8e1104912f6d595becbd4b4fa20ec3fd38910892d52da3f64b12f5fc6ac37f4ef352f6a57b29b094d5c2fce434895eb9c54d8bc0e5516c5ce24b9ce9a082
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.7'
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,18 +23,20 @@ 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
@@ -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 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,7 +43,7 @@ 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
@@ -188,14 +190,6 @@ module RuboCop
188
190
  return property_names
189
191
  end
190
192
 
191
- private def factory_bot_define_block_with_class_option?(node)
192
- factory_method_call?(node) && inside_factory_bot_define?(node)
193
- end
194
-
195
- private def factory_method_call?(node)
196
- node.method_name == :factory
197
- end
198
-
199
193
  private def inside_factory_bot_define?(node)
200
194
  ancestors = node.each_ancestor(:block).to_a
201
195
  ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Momocop
6
+ # Ensures that FactoryBot factories has ordered property definitions.
7
+ # 1. Associations should be defined before other properties.
8
+ # 2. Associations and properties should be defined in alphabetical order.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # factory :user, class: 'User' do
13
+ # address { '123 Main St' }
14
+ # association :profile
15
+ # end
16
+ #
17
+ # # good
18
+ # factory :user, class: 'User' do
19
+ # association :profile
20
+ # address { '123 Main St' }
21
+ # end
22
+ #
23
+ # # bad
24
+ # factory :user, class: 'User' do
25
+ # association :profile
26
+ # association :account
27
+ # zipcode { '111-1111' }
28
+ # address { '123 Main St' }
29
+ # end
30
+ #
31
+ # # good
32
+ # factory :user, class: 'User' do
33
+ # association :account
34
+ # association :profile
35
+ # address { '123 Main St' }
36
+ # zipcode { '111-1111' }
37
+ # end
38
+ class FactoryBotPropertyOrder < RuboCop::Cop::Base
39
+ extend AutoCorrector
40
+ include RangeHelp
41
+
42
+ include ::Sevencop::CopConcerns::Ordered
43
+
44
+ MSG = 'Sort properties and associations alphabetically.'
45
+
46
+ RESTRICT_ON_SEND = %i[factory].freeze
47
+
48
+ def on_send(node)
49
+ return unless inside_factory_bot_define?(node)
50
+
51
+ block_node = node.block_node
52
+ entire_definitions = defined_properties(block_node)
53
+
54
+ sections =
55
+ entire_definitions
56
+ .slice_when { |a, b| b.loc.last_line - a.loc.last_line > 1 }
57
+ .select { |definitions| definitions.size >= 2 }
58
+
59
+ sections.each do |definitions|
60
+ add_offense(definitions.last) do |corrector|
61
+ (a, b) =
62
+ definitions
63
+ .lazy
64
+ .each_cons(2)
65
+ .find { |a, b| (order(a) <=> order(b)) == 1 }
66
+
67
+ break unless a && b
68
+
69
+ swap(
70
+ range_with_comments_and_lines(a),
71
+ range_with_comments_and_lines(b),
72
+ corrector:
73
+ )
74
+ end
75
+ end
76
+ end
77
+
78
+ private def order(node)
79
+ group = definition_type(node) == :association ? 0 : 1
80
+ index = definition_name(node)
81
+ [group, index]
82
+ end
83
+
84
+ private def defined_properties(block_node)
85
+ body_node = block_node&.children&.last
86
+ body_node&.children&.select { |node| definition_node?(node) }
87
+ end
88
+
89
+ private def definition_node?(node)
90
+ node.send_type? || (node.block_type? && node.children.first.send_type?)
91
+ end
92
+
93
+ private def definition_type(node)
94
+ send_node = node.send_type? ? node : node.children.first
95
+ if %i[association sequence].include? send_node.method_name
96
+ send_node.method_name
97
+ else
98
+ :property
99
+ end
100
+ end
101
+
102
+ private def definition_name(node)
103
+ send_node = node.send_type? ? node : node.children.first
104
+ if %i[association sequence].include? send_node.method_name
105
+ send_node.arguments.first.value
106
+ else
107
+ send_node.method_name.to_sym
108
+ end
109
+ end
110
+
111
+ private def inside_factory_bot_define?(node)
112
+ ancestors = node.each_ancestor(:block).to_a
113
+ ancestors.any? { |ancestor| ancestor.method_name == :define && ancestor.receiver&.const_name == 'FactoryBot' }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: momocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
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-18 00:00:00.000000000 Z
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/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