rubocop-solidus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f5300b213565a1a439d50b1e8a10f1fdb85559dd82c22590d583afaee88cd80
4
+ data.tar.gz: f449296bd8ce1235e5760eb0284ca29b33634ed6fc095230f4837bb60a72a28f
5
+ SHA512:
6
+ metadata.gz: 3c2704f57083a3bfdb599b5e8fd684271703a541a1e636307955a798c8ddcb3db79fa61698840ecc76b90917e6063f24c0582fb45fda1403b605abb4f99c28be
7
+ data.tar.gz: 40fe450071e0b1bfcf88b989090d1fdd4cd6e2837eadab97cdc88bc618124453050c1fce6a9118978cf32683d73fcc6655bed0ddc4878d141004f8a8f80c4bc1
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 2.4
4
+
5
+ Naming/FileName:
6
+ Exclude:
7
+ - lib/rubocop-solidus.rb
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - 'Rakefile'
12
+ - '**/*.rake'
13
+ - 'spec/**/*.rb'
14
+ - '*.gemspec'
15
+
16
+ Metrics/ClassLength:
17
+ Exclude:
18
+ - 'tasks/**/*.rb'
19
+
20
+ # Prevents Ruby 3.1 incompatibility error. You can enable this cop when Ruby 2.4 support is dropped.
21
+ # See https://github.com/rubocop/rubocop/issues/10258
22
+ Layout/BlockAlignment:
23
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## master (unreleased)
2
+
3
+ ## 0.1.0 (2023-07-10)
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in rubocop-solidus.gemspec
6
+ gemspec
7
+
8
+ gem 'bump'
9
+ gem 'pry'
10
+ gem 'rake'
11
+ gem 'rspec'
12
+ gem 'rubocop'
data/Gemfile.lock ADDED
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rubocop-solidus (0.1.0)
5
+ rubocop
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ bump (0.10.0)
12
+ coderay (1.1.3)
13
+ diff-lcs (1.5.0)
14
+ method_source (1.0.0)
15
+ parallel (1.20.1)
16
+ parser (3.2.2.3)
17
+ ast (~> 2.4.1)
18
+ racc
19
+ pry (0.14.2)
20
+ coderay (~> 1.1)
21
+ method_source (~> 1.0)
22
+ racc (1.5.2)
23
+ rainbow (3.1.1)
24
+ rake (13.0.6)
25
+ regexp_parser (2.8.1)
26
+ rexml (3.2.5)
27
+ rspec (3.12.0)
28
+ rspec-core (~> 3.12.0)
29
+ rspec-expectations (~> 3.12.0)
30
+ rspec-mocks (~> 3.12.0)
31
+ rspec-core (3.12.2)
32
+ rspec-support (~> 3.12.0)
33
+ rspec-expectations (3.12.3)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.12.0)
36
+ rspec-mocks (3.12.6)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.12.0)
39
+ rspec-support (3.12.1)
40
+ rubocop (1.12.1)
41
+ parallel (~> 1.10)
42
+ parser (>= 3.0.0.0)
43
+ rainbow (>= 2.2.2, < 4.0)
44
+ regexp_parser (>= 1.8, < 3.0)
45
+ rexml
46
+ rubocop-ast (>= 1.2.0, < 2.0)
47
+ ruby-progressbar (~> 1.7)
48
+ unicode-display_width (>= 1.4.0, < 3.0)
49
+ rubocop-ast (1.4.1)
50
+ parser (>= 2.7.1.5)
51
+ ruby-progressbar (1.13.0)
52
+ unicode-display_width (2.4.2)
53
+
54
+ PLATFORMS
55
+ -darwin-22
56
+ arm64-darwin-21
57
+ arm64-darwin-22
58
+ x86_64-darwin-21
59
+ x86_64-linux
60
+
61
+ DEPENDENCIES
62
+ bump
63
+ pry
64
+ rake
65
+ rspec
66
+ rubocop
67
+ rubocop-solidus!
68
+
69
+ BUNDLED WITH
70
+ 2.3.26
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 piyushswain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Rubocop::Solidus
2
+
3
+ Automatic Solidus code style checking tool.
4
+ A RuboCop extension focused on enforcing Solidus best practices and coding conventions.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rubocop-solidus', github: 'nebulab/rubocop-solidus', require: false
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install rubocop-solidus
21
+
22
+ ## Usage
23
+
24
+ To Start using this extension in your application add the following lines to your `.rubocop.yml` file
25
+
26
+ ```yaml
27
+ require:
28
+ - rubocop-solidus
29
+ ```
30
+
31
+ After this simply use the `rubocop` command to start linting.
32
+
33
+ ## Creating new cops
34
+
35
+ To create a new cop, run the following command:
36
+
37
+ ```bash
38
+ $ bundle exec rake new_cop[Solidus/NameOfTheCop]
39
+ ```
40
+
41
+ and then follow the instructions on the screen.
42
+
43
+ ## Release a new version
44
+
45
+ To release a new version, run the following command:
46
+
47
+ ```bash
48
+ $ bundle exec rake cut_release:[major|minor|patch]
49
+ ```
50
+
51
+ and then follow the instructions on the screen.
52
+
53
+ The type of the release is determined by:
54
+ - `major` if there are breaking changes
55
+ - `minor` if there are new cops or new features
56
+ - `patch` if there are only bug fixes
57
+
58
+ To deploy the new version to RubyGems, run the following command:
59
+
60
+ ```bash
61
+ $ bundle exec rake release
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nebulab/rubocop-solidus.
67
+
68
+ ## License
69
+
70
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+ require 'rspec/core/rake_task'
6
+
7
+ Dir['tasks/**/*.rake'].each { |t| load t }
8
+
9
+ task default: %i[spec rubocop]
10
+
11
+ RuboCop::RakeTask.new
12
+
13
+ RSpec::Core::RakeTask.new(:spec) do |spec|
14
+ spec.pattern = FileList['spec/**/*_spec.rb']
15
+ end
16
+
17
+ desc 'Generate a new cop with a template'
18
+ task :new_cop, [:cop] do |_task, args|
19
+ require 'rubocop'
20
+
21
+ cop_name = args.fetch(:cop) do
22
+ warn 'usage: bundle exec rake new_cop[Department/Name]'
23
+ exit!
24
+ end
25
+
26
+ generator = RuboCop::Cop::Generator.new(cop_name)
27
+
28
+ generator.write_source
29
+ generator.write_spec
30
+ generator.inject_require(root_file_path: 'lib/rubocop/cop/solidus_cops.rb')
31
+ generator.inject_config(config_file_path: 'config/default.yml')
32
+
33
+ puts generator.todo
34
+ end
@@ -0,0 +1,69 @@
1
+ AllCops:
2
+ # What version of Solidus is the inspected code using? If a value is specified
3
+ # for TargetSolidusVersion then it is used. Acceptable values are specified
4
+ # as a float (i.e. 3.1); the patch version of Solidus should not be included.
5
+ # If TargetSolidusVersion is not set, RuboCop will parse the Gemfile.lock or
6
+ # gems.locked file to find the version of Solidus that has been bound to the
7
+ # application. If neither of those files exist, RuboCop will use Solidus 3.0
8
+ # as the default.
9
+ TargetSolidusVersion: ~
10
+
11
+ Solidus/ClassEvalDecorator:
12
+ Description: 'Checks if Class.class_eval is being used in the code'
13
+ Enabled: true
14
+ VersionAdded: '0.1.0'
15
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/21'
16
+
17
+ Solidus/ReimbursementHookDeprecated:
18
+ Description: 'Checks if reimbursement_success_hooks and reimbursement_failed_hooks is being used'
19
+ Enabled: true
20
+ VersionAdded: '0.1.0'
21
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/27'
22
+
23
+ Solidus/SpreeCalculatorFreeShippingDeprecated:
24
+ Description: 'Checks if Spree::Calculator::FreeShipping is being used and add deprecation message'
25
+ Enabled: true
26
+ VersionAdded: '0.1.0'
27
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/29'
28
+
29
+ Solidus/SpreeCalculatorPercentPerItemDeprecated:
30
+ Description: 'Checks if Spree::Calculator::PercentPerItem is being used and add deprecation message'
31
+ Enabled: true
32
+ VersionAdded: '0.1.0'
33
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/29'
34
+
35
+ Solidus/SpreeCalculatorPriceSackDeprecated:
36
+ Description: 'Checks if Spree::Calculator::PriceSack is being used and add deprecation message'
37
+ Enabled: true
38
+ VersionAdded: '0.1.0'
39
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/29'
40
+
41
+ Solidus/SpreeDefaultCreditCardDeprecated:
42
+ Description: 'Checks if user.default_credit_card is used and suggest using user.wallet.default_wallet_payment_source'
43
+ Enabled: true
44
+ VersionAdded: '0.1.0'
45
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/33'
46
+
47
+ Solidus/SpreeGatewayBogusDeprecated:
48
+ Description: 'Checks if SpreeGatewayBogus is being used and replaces it with Spree::PaymentMethod::BogusCreditCard'
49
+ Enabled: true
50
+ VersionAdded: '0.1.0'
51
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/26'
52
+
53
+ Solidus/SpreeIconDeprecated:
54
+ Description: 'Checks if icon helper is being used and suggest `solidus_icon`'
55
+ Enabled: true
56
+ VersionAdded: '0.1.0'
57
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/32'
58
+
59
+ Solidus/SpreeRefundCallPerform:
60
+ Description: 'Checks if Spree::Refund.create is being used and require calling .perform!'
61
+ Enabled: true
62
+ VersionAdded: '0.1.0'
63
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/28'
64
+
65
+ Solidus/SpreeTDeprecated:
66
+ Description: 'Checks if Spree.t is being used and replaces it with I18n.t.'
67
+ Enabled: true
68
+ VersionAdded: '0.1.0'
69
+ Reference: 'https://github.com/nebulab/rubocop-solidus/issues/22'
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This module provides a way to specify a minimum Solidus version for a cop.
6
+ # It also provides a way to check if the current Solidus version is affected.
7
+ # The minimum Solidus version can be specified in the cop itself in this way:
8
+ # include TargetSolidusVersion
9
+ # minimum_solidus_version 2.11
10
+ module TargetSolidusVersion
11
+ DEFAULT_SOLIDUS_VERSION = 3.0
12
+
13
+ def self.included(target)
14
+ target.extend(ClassMethods)
15
+ end
16
+
17
+ # These class methods are automatically added to the cop when the module
18
+ # is included. They are used to specify the minimum Solidus version for the cop.
19
+ module ClassMethods
20
+ def minimum_solidus_version(version)
21
+ @minimum_solidus_version = version
22
+ end
23
+
24
+ def targeted_solidus_version?(version)
25
+ @minimum_solidus_version <= version
26
+ end
27
+ end
28
+
29
+ # This method overrides the one in RuboCop::Cop::Base.
30
+ # Since this method is called for every offense, we can use it to check
31
+ # if the Solidus version is affected and skip the offense if it's not.
32
+ def add_offense(*args, **kwargs, &block)
33
+ return unless affected_solidus_version?
34
+
35
+ if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('3')
36
+ super(*args, **kwargs, &block)
37
+ else
38
+ super(*args, &block)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def affected_solidus_version?
45
+ self.class.targeted_solidus_version?(target_solidus_version)
46
+ end
47
+
48
+ def target_solidus_version
49
+ @target_solidus_version ||=
50
+ config.for_all_cops['TargetSolidusVersion']&.to_f || solidus_version_from_lock_file || DEFAULT_SOLIDUS_VERSION
51
+ end
52
+
53
+ def solidus_version_from_lock_file
54
+ @solidus_version_from_lock_file ||= read_solidus_version_from_lock_file
55
+ end
56
+
57
+ def read_solidus_version_from_lock_file
58
+ lock_file_path = config.bundler_lock_file_path
59
+ return nil unless lock_file_path
60
+
61
+ File.foreach(lock_file_path) do |line|
62
+ # If Solidus (or one of its frameworks) is in Gemfile.lock, there should be a line like:
63
+ # solidus_core (X.X.X)
64
+ result = line.match(/^\s+solidus_core\s+\((\d+\.\d+)/)
65
+ return result.captures.first.to_f if result
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # TODO: Write cop description and example of bad / good code. For every
7
+ # `SupportedStyle` and unique configuration, there needs to be examples.
8
+ # Examples must have valid Ruby syntax. Do not use upticks.
9
+ #
10
+ #
11
+ # @example EnforcedStyle: SpreeClass
12
+ # # Description of the `SpreeClass` style.
13
+ #
14
+ # # bad
15
+ # SpreeClass.class_eval do
16
+ # .
17
+ # .
18
+ # end
19
+ #
20
+ #
21
+ # # good
22
+ # module SpreeClassDecorator
23
+ # .
24
+ # .
25
+ # end
26
+ #
27
+ class ClassEvalDecorator < Base
28
+ MSG = 'Do not use `class_eval` flag. Use a decorator module instead. Check this link for an example https://guides.solidus.io/cookbook/redefining-checkout-steps'
29
+
30
+ # TODO: Don't call `on_send` unless the method name is in this list
31
+ # If you don't need `on_send` in the cop you created, remove it.
32
+ RESTRICT_ON_SEND = %i[class_eval].freeze
33
+
34
+ # @!method on_class_eval?(node)
35
+ def_node_matcher :on_class_eval?, <<~PATTERN
36
+ (send ($...) :class_eval)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ return unless on_class_eval?(node)
41
+
42
+ add_offense(node)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds reimbursement_success_hooks and reimbursement_failed_hooks calls and
7
+ # asks to remove them and subscribe to reimbursement_reimbursed event instead.
8
+ class ReimbursementHookDeprecated < Base
9
+ include TargetSolidusVersion
10
+ minimum_solidus_version 2.11
11
+
12
+ MSG = 'Please remove reimbursement_success_hooks and reimbursement_failed_hooks. ' \
13
+ 'Subscribe to reimbursement_reimbursed event instead.'
14
+
15
+ def_node_matcher :success_hook?, <<~PATTERN
16
+ (send (send nil? :reimbursement_success_hooks) ...)
17
+ PATTERN
18
+
19
+ def_node_matcher :fail_hook?, <<~PATTERN
20
+ (send (send nil? :reimbursement_failed_hooks) ...)
21
+ PATTERN
22
+
23
+ def on_send(node)
24
+ return unless success_hook?(node) || fail_hook?(node)
25
+
26
+ add_offense(node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree::Calculator::FreeShipping calls.
7
+ # This cop is needed as they have been deprecated in future version.
8
+ #
9
+ class SpreeCalculatorFreeShippingDeprecated < Base
10
+ MSG = 'Spree::Calculator::FreeShipping is deprecated.'
11
+
12
+ def_node_matcher :free_shipping?, <<~PATTERN
13
+ (send (... (... :Calculator) :FreeShipping) $_)
14
+ PATTERN
15
+
16
+ def on_send(node)
17
+ return unless free_shipping?(node)
18
+
19
+ add_offense(node)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree::Calculator::PercentPerItem calls.
7
+ # This cop is needed as they have been deprecated in future version.
8
+ #
9
+ class SpreeCalculatorPercentPerItemDeprecated < Base
10
+ extend AutoCorrector
11
+
12
+ MSG = 'Spree::Calculator::PercentPerItem is deprecated.'
13
+
14
+ def_node_matcher :percent_per_item?, <<~PATTERN
15
+ (send (... (... :Calculator) :PercentPerItem) $_)
16
+ PATTERN
17
+
18
+ def on_send(node)
19
+ percent_per_item?(node) do |method_used|
20
+ add_offense(node, message: MSG) do |corrector|
21
+ corrector.replace(node, "Spree::Calculator::PercentOnLineItem.#{method_used}")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree::Calculator::PriceSack calls.
7
+ # This cop is needed as they have been deprecated in future version.
8
+ #
9
+ class SpreeCalculatorPriceSackDeprecated < Base
10
+ MSG = 'Spree::Calculator::PriceSack is deprecated.'
11
+
12
+ def_node_matcher :price_sack?, <<~PATTERN
13
+ (send (... (... :Calculator) :PriceSack) $_)
14
+ PATTERN
15
+
16
+ def on_send(node)
17
+ return unless price_sack?(node)
18
+
19
+ add_offense(node)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds user.default_credit_card suggest using user.wallet.default_wallet_payment_source
7
+ #
8
+ # @example EnforcedStyle:
9
+ # # bad
10
+ # user.default_credit_card
11
+ #
12
+ # # good
13
+ # user.wallet.default_wallet_payment_source
14
+ #
15
+ #
16
+ class SpreeDefaultCreditCardDeprecated < Base
17
+ extend AutoCorrector
18
+ include TargetSolidusVersion
19
+ minimum_solidus_version 2.2
20
+
21
+ MSG = 'user.default_credit_card is deprecated. Please use user.wallet.default_wallet_payment_source instead.'
22
+
23
+ # @!method bad_method?(node)
24
+ def_node_matcher :default_credit_card?, <<~PATTERN
25
+ (send ... :default_credit_card)
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ return unless default_credit_card?(node)
30
+
31
+ add_offense(node) do |corrector|
32
+ corrector.replace(node, node.source.gsub('default_credit_card', 'wallet.default_wallet_payment_source'))
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree::Gateway::Bogus calls and replaces them with the Spree::PaymentMethod::BogusCreditCard call
7
+ # This cop is needed as the Spree::Gateway::Bogus has been deprecated in future version.
8
+ #
9
+ # @example EnforcedStyle:
10
+ # # bad
11
+ # Spree::Gateway::Bogus.new
12
+ #
13
+ # # good
14
+ # Spree::PaymentMethod::BogusCreditCard.new
15
+ #
16
+ # # bad
17
+ # Spree::Gateway::Bogus.create
18
+ #
19
+ # # good
20
+ # Spree::PaymentMethod::BogusCreditCard.create
21
+ #
22
+ # # bad
23
+ # Spree::Gateway::Bogus.create!
24
+ #
25
+ # # good
26
+ # Spree::PaymentMethod::BogusCreditCard.create!
27
+ #
28
+ #
29
+ class SpreeGatewayBogusDeprecated < Base
30
+ extend AutoCorrector
31
+ include TargetSolidusVersion
32
+ minimum_solidus_version 2.10
33
+
34
+ MSG = 'Spree::Gateway::Bogus is deprecated. Please use Spree::PaymentMethod::BogusCreditCard instead.'
35
+
36
+ # @!method bad_method?(node)
37
+ def_node_matcher :bad_class?, <<~PATTERN
38
+ (send (... (... :Gateway) :Bogus) $_)
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ bad_class?(node) do |method_used|
43
+ add_offense(node, message: MSG) do |corrector|
44
+ corrector.replace(node, "Spree::PaymentMethod::BogusCreditCard.#{method_used}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds icon helper calls and suggest using solidus_icon
7
+ #
8
+ # @example EnforcedStyle:
9
+ # # bad
10
+ # helper.icon('example')
11
+ #
12
+ # # good
13
+ # helper.solidus_icon('example')
14
+ #
15
+ #
16
+ class SpreeIconDeprecated < Base
17
+ extend AutoCorrector
18
+ include TargetSolidusVersion
19
+ minimum_solidus_version 2.3
20
+
21
+ MSG = 'In Solidus 2.3, `icon` helper has been deprecated in favor of `solidus_icon`'
22
+
23
+ RESTRICT_ON_SEND = %i[icon].freeze
24
+
25
+ def_node_matcher :icon?, <<~PATTERN
26
+ (send (send nil? ...) :icon ...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return unless icon?(node)
31
+
32
+ add_offense(node) do |corrector|
33
+ corrector.replace(node, node.source.gsub('icon', 'solidus_icon'))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree::Refund.create(your: attributes) calls and
7
+ # replaces them with the Spree::Refund.create(your: attributes, perform_after_create: false).perform! call
8
+ #
9
+ class SpreeRefundCallPerform < Base
10
+ include TargetSolidusVersion
11
+ minimum_solidus_version 2.11
12
+
13
+ MSG = 'From Solidus v3.0 onwards, #perform! will need to be explicitly called when creating new refunds. ' \
14
+ 'Please, change your code from `Spree::Refund.create(your: attributes)` ' \
15
+ 'to `Spree::Refund.create(your: attributes, perform_after_create: false).perform!`'
16
+
17
+ RESTRICT_ON_SEND = %i[create].freeze
18
+
19
+ # @!method bad_method?(node)
20
+ def_node_matcher :create_refund?, <<~PATTERN
21
+ (send (const (const nil? :Spree) :Refund) :create ...)
22
+ PATTERN
23
+
24
+ def on_send(node)
25
+ return unless create_refund?(node)
26
+
27
+ add_offense(node)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solidus
6
+ # This cop finds Spree.t method calls and replaces them with the I18n,t method call
7
+ # This cop is needed as the Spree.t version has been deprecated in future version.
8
+ #
9
+ #
10
+ # @example EnforcedStyle: bar (default)
11
+ # # bad
12
+ # Spree.t(:bar)
13
+ #
14
+ # # good
15
+ # I18n.t(:bar, scope: :spree)
16
+ #
17
+ # # bad
18
+ # Spree.t(:bar, scope: [:city])
19
+ #
20
+ # # good
21
+ # I18n.t(:bar, scope: [:spree, :city])
22
+ #
23
+ # # bad
24
+ # Spree.t(:bar, scope: [:city], email: email)
25
+ #
26
+ # # good
27
+ # I18n.t(:bar, scope: [:spree, :city], email: email)
28
+ #
29
+ # # bad
30
+ # Spree.t('bar', scope: 'admin.city')
31
+ #
32
+ # # good
33
+ # I18n.t('bar', scope: 'spree.admin.city')
34
+ #
35
+ #
36
+ class SpreeTDeprecated < Base
37
+ extend AutoCorrector
38
+ MSG = 'Use I18n.t instead of Spree.t which has been deprecated in future versions.'
39
+
40
+ RESTRICT_ON_SEND = %i[t].freeze
41
+
42
+ # @!method spree_t?(node)
43
+ def_node_matcher :spree_t?, <<~PATTERN
44
+ (send ($...) :t ...)
45
+ PATTERN
46
+
47
+ def on_send(node)
48
+ return unless spree_t?(node)
49
+
50
+ return unless spree_t?(node).include?(:Spree)
51
+
52
+ add_offense(node) do |corrector|
53
+ corrector.replace(node, corrected_statement(node))
54
+ end
55
+ end
56
+
57
+ # rubocop:disable Metrics/MethodLength
58
+ def corrected_statement(node)
59
+ arguments = node.arguments
60
+
61
+ new_statement = 'I18n.t('
62
+ new_statement += arguments.first.source
63
+ new_statement += ', scope: :spree' if scope_missing?(arguments)
64
+
65
+ node.arguments.drop(1).each do |argument|
66
+ if argument.source.include? 'scope:'
67
+ new_argument = add_spree_scope(argument)
68
+ new_statement += ", #{new_argument}"
69
+ else
70
+ new_statement += ", #{argument.source}"
71
+ end
72
+ end
73
+ new_statement += ')'
74
+ new_statement
75
+ end
76
+ # rubocop:enable Metrics/MethodLength
77
+
78
+ def scope_missing?(arguments)
79
+ arguments.each do |argument|
80
+ return false if argument.source.include? 'scope:'
81
+ end
82
+ true
83
+ end
84
+
85
+ # rubocop:disable Metrics/MethodLength
86
+ def add_spree_scope(argument)
87
+ modified_argument = ''
88
+
89
+ argument.each_pair do |key, value|
90
+ if key.source == 'scope'
91
+ modified_argument = if value.type == :array
92
+ "#{key.source}: #{value.source.gsub('[', '[:spree, ')}"
93
+ else
94
+ "#{key.source}: 'spree.#{value.source.gsub("'", '')}'"
95
+ end
96
+ else
97
+ modified_argument += ", #{key.source}: #{value.source}"
98
+ end
99
+ end
100
+
101
+ modified_argument
102
+ end
103
+ # rubocop:enable Metrics/MethodLength
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mixin/target_solidus_version'
4
+
5
+ require_relative 'solidus/class_eval_decorator'
6
+ require_relative 'solidus/reimbursement_hook_deprecated'
7
+ require_relative 'solidus/spree_calculator_free_shipping_deprecated'
8
+ require_relative 'solidus/spree_calculator_percent_per_item_deprecated'
9
+ require_relative 'solidus/spree_calculator_price_sack_deprecated'
10
+ require_relative 'solidus/spree_default_credit_card_deprecated'
11
+ require_relative 'solidus/spree_gateway_bogus_deprecated'
12
+ require_relative 'solidus/spree_icon_deprecated'
13
+ require_relative 'solidus/spree_refund_call_perform'
14
+ require_relative 'solidus/spree_t_deprecated'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The original code is from https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
4
+ # See https://github.com/rubocop/rubocop-rspec/blob/master/MIT-LICENSE.md
5
+ module RuboCop
6
+ module Solidus
7
+ # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
8
+ # bit of our configuration.
9
+ module Inject
10
+ def self.defaults!
11
+ path = CONFIG_DEFAULT.to_s
12
+ hash = ConfigLoader.send(:load_yaml_configuration, path)
13
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
14
+ puts "configuration from #{path}" if ConfigLoader.debug?
15
+ config = ConfigLoader.merge_with_default(config, path)
16
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Solidus
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'solidus/version'
4
+
5
+ module RuboCop
6
+ # RuboCop Solidus project namespace
7
+ module Solidus
8
+ class Error < StandardError; end
9
+ # Your code goes here...
10
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
11
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
12
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
13
+
14
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ require_relative 'rubocop/solidus'
6
+ require_relative 'rubocop/solidus/version'
7
+ require_relative 'rubocop/solidus/inject'
8
+
9
+ RuboCop::Solidus::Inject.defaults!
10
+
11
+ require_relative 'rubocop/cop/solidus_cops'
@@ -0,0 +1,6 @@
1
+ module Rubocop
2
+ module Solidus
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ autoload :Changelog, "#{__dir__}/changelog"
4
+
5
+ namespace :changelog do
6
+ %i[new fix change].each do |type|
7
+ desc "Create a Changelog entry (#{type})"
8
+ task type, [:id] do |_task, args|
9
+ ref_type = :pull if args[:id]
10
+ path = Changelog::Entry.new(type: type, ref_id: args[:id], ref_type: ref_type).write
11
+ cmd = "git add #{path}"
12
+ system cmd
13
+ puts "Entry '#{path}' created and added to git index"
14
+ end
15
+ end
16
+
17
+ desc 'Merge entries and delete them'
18
+ task :merge do
19
+ raise 'No entries!' unless Changelog.pending?
20
+
21
+ Changelog.new.merge!.and_delete!
22
+ cmd = "git commit -a -m 'Update Changelog'"
23
+ puts cmd
24
+ system cmd
25
+ end
26
+
27
+ task :check_clean do
28
+ next unless Changelog.pending?
29
+
30
+ puts '*** Pending changelog entries!'
31
+ puts 'Do `bundle exec rake changelog:merge`'
32
+ exit(1)
33
+ end
34
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Changelog utility
4
+ class Changelog
5
+ ENTRIES_PATH = 'changelog/'
6
+ FIRST_HEADER = /#{Regexp.escape("## master (unreleased)\n")}/m.freeze
7
+ ENTRIES_PATH_TEMPLATE = "#{ENTRIES_PATH}%<type>s_%<name>s.md"
8
+ TYPE_REGEXP = /#{Regexp.escape(ENTRIES_PATH)}([a-z]+)_/.freeze
9
+ TYPE_TO_HEADER = { new: 'New features', fix: 'Bug fixes', change: 'Changes' }.freeze
10
+ HEADER = /### (.*)/.freeze
11
+ PATH = 'CHANGELOG.md'
12
+ REF_URL = 'https://github.com/nebulab/rubocop-solidus'
13
+ MAX_LENGTH = 40
14
+ CONTRIBUTOR = '[@%<user>s]: https://github.com/%<user>s'
15
+ SIGNATURE = Regexp.new(format(Regexp.escape('[@%<user>s][]'), user: '([\w-]+)'))
16
+ EOF = "\n"
17
+
18
+ # New entry
19
+ Entry = Struct.new(:type, :body, :ref_type, :ref_id, :user, keyword_init: true) do
20
+ def initialize(type:, body: last_commit_title, ref_type: nil, ref_id: nil, user: github_user)
21
+ id, body = extract_id(body)
22
+ ref_id ||= id || 'x'
23
+ ref_type ||= id ? :issues : :pull
24
+ super
25
+ end
26
+
27
+ def write
28
+ File.write(path, content)
29
+ path
30
+ end
31
+
32
+ def path
33
+ format(ENTRIES_PATH_TEMPLATE, type: type, name: str_to_filename(body))
34
+ end
35
+
36
+ def content
37
+ period = '.' unless body.end_with? '.'
38
+ "* #{ref}: #{body}#{period} ([@#{user}][])\n"
39
+ end
40
+
41
+ def ref
42
+ "[##{ref_id}](#{REF_URL}/#{ref_type}/#{ref_id})"
43
+ end
44
+
45
+ def last_commit_title
46
+ `git log -1 --pretty=%B`.lines.first.chomp
47
+ end
48
+
49
+ def extract_id(body)
50
+ /^\[Fix(?:es)? #(\d+)\] (.*)/.match(body)&.captures || [nil, body]
51
+ end
52
+
53
+ def str_to_filename(str)
54
+ str
55
+ .split
56
+ .reject(&:empty?)
57
+ .map { |s| prettify(s) }
58
+ .inject do |result, word|
59
+ s = "#{result}_#{word}"
60
+ return result if s.length > MAX_LENGTH
61
+
62
+ s
63
+ end
64
+ end
65
+
66
+ def github_user
67
+ user = `git config --global credential.username`.chomp
68
+ warn 'Set your username with `git config --global credential.username "myusernamehere"`' if user.empty?
69
+
70
+ user
71
+ end
72
+
73
+ private
74
+
75
+ def prettify(str)
76
+ str.gsub!(/\W/, '_')
77
+
78
+ # Separate word boundaries by `_`.
79
+ str.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
80
+ (Regexp.last_match(1) || Regexp.last_match(2)) << '_'
81
+ end
82
+
83
+ str.gsub!(/\A_+|_+\z/, '')
84
+ str.downcase!
85
+ str
86
+ end
87
+ end
88
+
89
+ def self.pending?
90
+ entry_paths.any?
91
+ end
92
+
93
+ def self.entry_paths
94
+ Dir["#{ENTRIES_PATH}*"]
95
+ end
96
+
97
+ def self.read_entries
98
+ entry_paths.to_h { |path| [path, File.read(path)] }
99
+ end
100
+
101
+ attr_reader :header, :rest
102
+
103
+ def initialize(content: File.read(PATH), entries: Changelog.read_entries)
104
+ require 'strscan'
105
+
106
+ parse(content)
107
+ @entries = entries
108
+ end
109
+
110
+ def and_delete!
111
+ @entries.each_key { |path| File.delete(path) }
112
+ end
113
+
114
+ def merge!
115
+ File.write(PATH, merge_content)
116
+ self
117
+ end
118
+
119
+ def unreleased_content
120
+ entry_map = parse_entries(@entries)
121
+ merged_map = merge_entries(entry_map)
122
+ merged_map.flat_map { |header, things| ["### #{header}\n", *things, ''] }.join("\n")
123
+ end
124
+
125
+ def merge_content
126
+ merged_content = [@header, unreleased_content, @rest.chomp, *new_contributor_lines].join("\n")
127
+
128
+ merged_content << EOF
129
+ end
130
+
131
+ def new_contributor_lines
132
+ contributors
133
+ .map { |user| format(CONTRIBUTOR, user: user) }
134
+ .reject { |line| @rest.include?(line) }
135
+ end
136
+
137
+ def contributors
138
+ contributors = @entries.values.flat_map do |entry|
139
+ entry.match(/\. \((?<contributors>.+)\)\n/)[:contributors].split(',')
140
+ end
141
+
142
+ contributors.join.scan(SIGNATURE).flatten
143
+ end
144
+
145
+ private
146
+
147
+ def merge_entries(entry_map)
148
+ all = @unreleased.merge(entry_map) { |_k, v1, v2| v1.concat(v2) }
149
+ canonical = TYPE_TO_HEADER.values.to_h { |v| [v, nil] }
150
+ canonical.merge(all).compact
151
+ end
152
+
153
+ def parse(content)
154
+ ss = StringScanner.new(content)
155
+ @header = ss.scan_until(FIRST_HEADER)
156
+ @unreleased = parse_release(ss.scan_until(/\n(?=## )/m))
157
+ @rest = ss.rest
158
+ end
159
+
160
+ # @return [Hash<type, Array<String>]]
161
+ def parse_release(unreleased)
162
+ unreleased
163
+ .lines
164
+ .map(&:chomp)
165
+ .reject(&:empty?)
166
+ .slice_before(HEADER)
167
+ .to_h do |header, *entries|
168
+ [HEADER.match(header)[1], entries]
169
+ end
170
+ end
171
+
172
+ def parse_entries(path_content_map)
173
+ changes = Hash.new { |h, k| h[k] = [] }
174
+ path_content_map.each do |path, content|
175
+ header = TYPE_TO_HEADER.fetch(TYPE_REGEXP.match(path)[1].to_sym)
176
+ changes[header].concat(content.lines.map(&:chomp))
177
+ end
178
+ changes
179
+ end
180
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bump'
4
+
5
+ namespace :cut_release do
6
+ %w[major minor patch pre].each do |release_type|
7
+ desc "Cut a new #{release_type} release and create release notes."
8
+ task release_type => 'changelog:check_clean' do
9
+ run(release_type)
10
+ end
11
+ end
12
+
13
+ def add_header_to_changelog(version)
14
+ changelog = File.read('CHANGELOG.md')
15
+ head, tail = changelog.split("## master (unreleased)\n\n", 2)
16
+
17
+ File.open('CHANGELOG.md', 'w') do |f|
18
+ f << head
19
+ f << "## master (unreleased)\n\n"
20
+ f << "## #{version} (#{Time.now.strftime('%F')})\n\n"
21
+ f << tail
22
+ end
23
+ end
24
+
25
+ def create_release_notes(version)
26
+ release_notes = new_version_changes.strip
27
+ contributor_links = user_links(release_notes)
28
+
29
+ File.open("relnotes/v#{version}.md", 'w') do |file|
30
+ file << release_notes
31
+ file << "\n\n"
32
+ file << contributor_links
33
+ file << "\n"
34
+ end
35
+ end
36
+
37
+ def version_sans_patch(version)
38
+ version.split('.').take(2).join('.')
39
+ end
40
+
41
+ # Replace `<<next>>` (and variations) with version being cut.
42
+ def update_cop_versions(_old_version, new_version)
43
+ update_file('config/default.yml') do |default|
44
+ default.gsub(/['"]?<<\s*next\s*>>['"]?/i, "'#{version_sans_patch(new_version)}'")
45
+ end
46
+ end
47
+
48
+ def new_version_changes
49
+ changelog = File.read('CHANGELOG.md')
50
+ _, _, new_changes, _older_changes = changelog.split(/^## .*$/, 4)
51
+ new_changes
52
+ end
53
+
54
+ def update_file(path)
55
+ content = File.read(path)
56
+ File.write(path, yield(content))
57
+ end
58
+
59
+ def user_links(text)
60
+ names = text.scan(/\[@(\S+)\]\[\]/).map(&:first).uniq
61
+ names.map { |name| "[@#{name}]: https://github.com/#{name}" }.join("\n")
62
+ end
63
+
64
+ def run(release_type)
65
+ old_version = Bump::Bump.current
66
+ Bump::Bump.run(release_type, commit: false, bundle: false, tag: false)
67
+ new_version = Bump::Bump.current
68
+
69
+ update_cop_versions(old_version, new_version)
70
+ add_header_to_changelog(new_version)
71
+ create_release_notes(new_version)
72
+
73
+ puts "Changed version from #{old_version} to #{new_version}."
74
+ end
75
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-solidus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - piyushswain
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: |
28
+ Automatic Solidus code style checking tool.
29
+ A RuboCop extension focused on enforcing Solidus best practices and coding conventions.
30
+ email:
31
+ - danielepalombo@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CHANGELOG.md
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - config/default.yml
45
+ - lib/rubocop-solidus.rb
46
+ - lib/rubocop/cop/mixin/target_solidus_version.rb
47
+ - lib/rubocop/cop/solidus/class_eval_decorator.rb
48
+ - lib/rubocop/cop/solidus/reimbursement_hook_deprecated.rb
49
+ - lib/rubocop/cop/solidus/spree_calculator_free_shipping_deprecated.rb
50
+ - lib/rubocop/cop/solidus/spree_calculator_percent_per_item_deprecated.rb
51
+ - lib/rubocop/cop/solidus/spree_calculator_price_sack_deprecated.rb
52
+ - lib/rubocop/cop/solidus/spree_default_credit_card_deprecated.rb
53
+ - lib/rubocop/cop/solidus/spree_gateway_bogus_deprecated.rb
54
+ - lib/rubocop/cop/solidus/spree_icon_deprecated.rb
55
+ - lib/rubocop/cop/solidus/spree_refund_call_perform.rb
56
+ - lib/rubocop/cop/solidus/spree_t_deprecated.rb
57
+ - lib/rubocop/cop/solidus_cops.rb
58
+ - lib/rubocop/solidus.rb
59
+ - lib/rubocop/solidus/inject.rb
60
+ - lib/rubocop/solidus/version.rb
61
+ - sig/rubocop/solidus.rbs
62
+ - tasks/changelog.rake
63
+ - tasks/changelog.rb
64
+ - tasks/cut_release.rake
65
+ homepage: https://www.github.com/nebulab/rubocop-solidus
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://www.github.com/nebulab/rubocop-solidus
70
+ source_code_uri: https://www.github.com/nebulab/rubocop-solidus
71
+ changelog_uri: https://www.github.com/nebulab/rubocop-solidus
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 2.4.0
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.3.17
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Automatic Solidus code style checking tool.
91
+ test_files: []