rubocop-openproject 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: 32626e1f367406ced02e2379b7a390a9c8d9012ce29174a7aefa3cfae33375d9
4
+ data.tar.gz: 31d990582ddf0efa6ae5f3905d2024bb4dc525160061315a983ea510f45f9fe2
5
+ SHA512:
6
+ metadata.gz: 2b488bcfd9c6dd063ef385c69afe15ba12e5a7ea0419065796e392e8564d1c42e02ca9f78288e9f7b6fcb7c8ed0e81ea03a179c886f0742faf42ca4dd8346d43
7
+ data.tar.gz: 0b913ebb1d53d3c90f9faa66cc21c044d4b076d23495dbd37042c5b91c78e9d41b212db7972b38502c7cf09dd7040104d15774094c312819eec15c379e1a8ff7
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.3
3
+ NewCops: enable
4
+
5
+ Naming/FileName:
6
+ Exclude:
7
+ - lib/rubocop-openproject.rb
8
+
9
+ Style/StringLiterals:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Metrics/BlockLength:
13
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-05
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Christophe Bliard
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,35 @@
1
+ # rubocop-openproject
2
+
3
+ This is a collection of the cops written to be used in the
4
+ [OpenProject repository](https://github.com/opf/openproject).
5
+
6
+ ## Installation
7
+
8
+ Add this gem to the `Gemfile`:
9
+
10
+ ```ruby
11
+ gem "rubocop-openproject", require: false
12
+ ```
13
+
14
+ Run `bundle install`.
15
+
16
+ Finally in the `.rubocop.yml` file, add the following:
17
+
18
+ ```
19
+ require:
20
+ - rubocop-openproject
21
+ ```
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/opf/rubocop-openproject.
32
+
33
+ ## License
34
+
35
+ 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,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = FileList["spec/**/*_spec.rb"]
9
+ end
10
+
11
+ RuboCop::RakeTask.new
12
+
13
+ task default: %i[spec rubocop]
14
+
15
+ desc "Generate a new cop with a template"
16
+ task :new_cop, [:cop] do |_task, args|
17
+ require "rubocop"
18
+
19
+ cop_name = args.fetch(:cop) do
20
+ warn "usage: bundle exec rake new_cop[Department/Name]"
21
+ exit!
22
+ end
23
+
24
+ generator = RuboCop::Cop::Generator.new(cop_name)
25
+
26
+ generator.write_source
27
+ generator.write_spec
28
+ generator.inject_require(root_file_path: "lib/rubocop/cop/open_project_cops.rb")
29
+ generator.inject_config(config_file_path: "config/default.yml")
30
+
31
+ puts generator.todo
32
+ end
@@ -0,0 +1,14 @@
1
+ OpenProject/AddPreviewForViewComponent:
2
+ Description: 'Add a preview for the ViewComponent.'
3
+ Enabled: true
4
+ VersionAdded: '0.1.0'
5
+
6
+ OpenProject/NoDoEndBlockWithRSpecCapybaraMatcherInExpect:
7
+ Description: 'Use `{ .. }` syntax with Capybara rspec matchers instead of `do .. end`.'
8
+ Enabled: true
9
+ VersionAdded: '0.1.0'
10
+
11
+ OpenProject/UseServiceResultFactoryMethods:
12
+ Description: 'Use ServiceResult factory methods instead of ServiceResult.new.'
13
+ Enabled: true
14
+ VersionAdded: '0.1.0'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module OpenProject
6
+ # A lookbook preview must exist for each ViewComponent.
7
+ #
8
+ # Components are located in `app/components` and previews are searched in
9
+ # `lookbook/previews`.
10
+ class AddPreviewForViewComponent < Base
11
+ COMPONENT_PATH = "/app/components/"
12
+ PREVIEW_PATH = "/lookbook/previews/"
13
+
14
+ def on_class(node)
15
+ path = node.loc.expression.source_buffer.name
16
+ return unless path.include?(COMPONENT_PATH) && path.end_with?(".rb")
17
+
18
+ preview_path = path.sub(COMPONENT_PATH, PREVIEW_PATH).sub(".rb", "_preview.rb")
19
+
20
+ return if File.exist?(preview_path)
21
+
22
+ message = "Missing Lookbook preview for #{path}. Expected preview to exist at #{preview_path}."
23
+ add_offense(node.loc.name, message:)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module OpenProject
6
+ # As +do .. end+ block has less precedence than method call, a +do .. end+
7
+ # block at the end of a rspec matcher will be an argument to the +to+ method,
8
+ # which is not handled by Capybara matchers (teamcapybara/capybara/#2616).
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # expect(page).to have_selector("input") do |input|
14
+ # input.value == "hello world"
15
+ # end
16
+ #
17
+ # # good
18
+ # expect(page).to have_selector("input") { |input| input.value == "hello world" }
19
+ #
20
+ # # good
21
+ # expect(page).to have_selector("input", value: "hello world")
22
+ #
23
+ # # good
24
+ # match_input_with_hello_world = have_selector("input") do |input|
25
+ # input.value == "hello world"
26
+ # end
27
+ # expect(page).to match_input_with_hello_world
28
+ #
29
+ # # good
30
+ # expect(foo).to have_received(:bar) do |arg|
31
+ # arg == :baz
32
+ # end
33
+ #
34
+ class NoDoEndBlockWithRSpecCapybaraMatcherInExpect < Base
35
+ CAPYBARA_MATCHER_METHODS = %w[selector css xpath text title current_path link button
36
+ field checked_field unchecked_field select table
37
+ sibling ancestor].flat_map do |matcher_type|
38
+ ["have_#{matcher_type}", "have_no_#{matcher_type}"]
39
+ end
40
+
41
+ MSG = "The `do .. end` block is associated with `to` and not with Capybara matcher `%<matcher_method>s`."
42
+
43
+ def_node_matcher :expect_to_with_block?, <<~PATTERN
44
+ # ruby-parse output
45
+ (block
46
+ (send
47
+ (send nil? :expect ...)
48
+ :to
49
+ ...
50
+ )
51
+ ...
52
+ )
53
+ PATTERN
54
+
55
+ def_node_matcher :rspec_matcher, <<~PATTERN
56
+ (send
57
+ (send nil? :expect...)
58
+ :to
59
+ (:send nil? $_matcher_method ...)
60
+ )
61
+ PATTERN
62
+
63
+ def on_block(node)
64
+ return unless expect_to_with_block?(node)
65
+ return unless capybara_matcher?(node)
66
+
67
+ add_offense(offense_range(node), message: offense_message(node))
68
+ end
69
+
70
+ private
71
+
72
+ def capybara_matcher?(node)
73
+ matcher_name = node.send_node.arguments.first.method_name.to_s
74
+ CAPYBARA_MATCHER_METHODS.include?(matcher_name)
75
+ end
76
+
77
+ def offense_range(node)
78
+ node.send_node.loc.selector.join(node.loc.end)
79
+ end
80
+
81
+ def offense_message(node)
82
+ rspec_matcher(node.send_node) do |matcher_method|
83
+ format(MSG, matcher_method:)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module OpenProject
6
+ # Favor usage of ServiceResult factory methods instead of using
7
+ # `success: true/false` in constructor.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # ServiceResult.new(success: true, result: 'result')
12
+ #
13
+ # # bad
14
+ # ServiceResult.new(success: false, errors: ['error'])
15
+ #
16
+ # # good
17
+ # ServiceResult.success(result: 'result')
18
+ #
19
+ # # good
20
+ # ServiceResult.failure(errors: ['error'])
21
+ #
22
+ # # good
23
+ # ServiceResult.new(success: some_value)
24
+ #
25
+ # # good
26
+ # ServiceResult.new(**kwargs)
27
+ class UseServiceResultFactoryMethods < Base
28
+ extend AutoCorrector
29
+
30
+ MSG = "Use ServiceResult.%<factory_method>s(...) instead of ServiceResult.new(success: %<success_value>s, ...)."
31
+ MSG_IMPLICIT_FAILURE = "Use ServiceResult.failure instead of ServiceResult.new."
32
+
33
+ RESTRICT_ON_SEND = %i[new].freeze
34
+
35
+ def_node_matcher :service_result_constructor?, <<~PATTERN
36
+ (send
37
+ $(const nil? :ServiceResult) :new
38
+ ...
39
+ )
40
+ PATTERN
41
+
42
+ def_node_matcher :constructor_with_explicit_success_arg, <<~PATTERN
43
+ (send
44
+ (const nil? :ServiceResult) :new
45
+ (hash
46
+ <
47
+ $(pair (sym :success) ({true | false}))
48
+ ...
49
+ >
50
+ )
51
+ )
52
+ PATTERN
53
+
54
+ def on_send(node)
55
+ return unless service_result_constructor?(node)
56
+
57
+ if success_argument_present?(node)
58
+ add_offense_if_explicit_success_argument(node)
59
+ elsif success_argument_possibly_present?(node)
60
+ return # rubocop:disable Style/RedundantReturn
61
+ else
62
+ add_offense_for_implicit_failure(node)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def success_argument_present?(node)
69
+ hash_argument = node.arguments.find(&:hash_type?)
70
+ return false unless hash_argument
71
+
72
+ hash_argument.keys.any? { |key| key.sym_type? && key.value == :success }
73
+ end
74
+
75
+ def success_argument_possibly_present?(node)
76
+ return true if node.arguments.find(&:forwarded_args_type?)
77
+
78
+ hash_argument = node.arguments.find(&:hash_type?)
79
+ return false unless hash_argument
80
+
81
+ hash_argument.children.any?(&:kwsplat_type?)
82
+ end
83
+
84
+ def add_offense_if_explicit_success_argument(node)
85
+ constructor_with_explicit_success_arg(node) do |success_argument|
86
+ message = format(MSG, success_value: success_value(success_argument),
87
+ factory_method: factory_method(success_argument))
88
+ add_offense(success_argument, message:) do |corrector|
89
+ corrector.replace(node.loc.selector, factory_method(success_argument))
90
+ corrector.remove(removal_range(node, success_argument))
91
+ end
92
+ end
93
+ end
94
+
95
+ def add_offense_for_implicit_failure(node)
96
+ add_offense(node.loc.selector, message: MSG_IMPLICIT_FAILURE) do |corrector|
97
+ corrector.replace(node.loc.selector, "failure")
98
+ end
99
+ end
100
+
101
+ def success_value(success_argument)
102
+ success_argument.value.source
103
+ end
104
+
105
+ def factory_method(success_argument)
106
+ success_argument.value.source == "true" ? "success" : "failure"
107
+ end
108
+
109
+ def removal_range(node, success_argument)
110
+ if sole_argument?(success_argument)
111
+ all_parameters_range(node)
112
+ else
113
+ success_parameter_range(success_argument)
114
+ end
115
+ end
116
+
117
+ def sole_argument?(arg)
118
+ arg.parent.loc.expression == arg.loc.expression
119
+ end
120
+
121
+ def all_parameters_range(node)
122
+ node.loc.selector.end.join(node.loc.expression.end)
123
+ end
124
+
125
+ # rubocop:disable Metrics/AbcSize
126
+ def success_parameter_range(success_argument)
127
+ if success_argument.left_sibling
128
+ success_argument.left_sibling.loc.expression.end.join(success_argument.loc.expression.end)
129
+ else
130
+ success_argument.loc.expression.begin.join(success_argument.right_sibling.loc.expression.begin)
131
+ end
132
+ end
133
+ # rubocop:enable Metrics/AbcSize
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "open_project/add_preview_for_view_component"
4
+ require_relative "open_project/no_do_end_block_with_rspec_capybara_matcher_in_expect"
5
+ require_relative "open_project/use_service_result_factory_methods"
@@ -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 OpenProject
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 OpenProject
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "open_project/version"
4
+ require "yaml"
5
+
6
+ module RuboCop
7
+ # This module contains custom RuboCop cops and configuration for the OpenProject project.
8
+ # It loads the default configuration from a YAML file and sets up necessary constants.
9
+ module OpenProject
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
13
+ CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
14
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
15
+
16
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ require_relative "rubocop/open_project"
6
+ require_relative "rubocop/open_project/version"
7
+ require_relative "rubocop/open_project/inject"
8
+
9
+ RuboCop::OpenProject::Inject.defaults!
10
+
11
+ require_relative "rubocop/cop/open_project_cops"
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-openproject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - OpenProject GmbH
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-07-05 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: Custom cops written for OpenProject
28
+ email:
29
+ - info@openproject.org
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - CHANGELOG.md
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - config/default.yml
41
+ - lib/rubocop-openproject.rb
42
+ - lib/rubocop/cop/open_project/add_preview_for_view_component.rb
43
+ - lib/rubocop/cop/open_project/no_do_end_block_with_rspec_capybara_matcher_in_expect.rb
44
+ - lib/rubocop/cop/open_project/use_service_result_factory_methods.rb
45
+ - lib/rubocop/cop/open_project_cops.rb
46
+ - lib/rubocop/open_project.rb
47
+ - lib/rubocop/open_project/inject.rb
48
+ - lib/rubocop/open_project/version.rb
49
+ homepage: https://github.com/opf/rubocop-openproject
50
+ licenses:
51
+ - MIT
52
+ metadata:
53
+ homepage_uri: https://github.com/opf/rubocop-openproject
54
+ source_code_uri: https://github.com/opf/rubocop-openproject
55
+ changelog_uri: https://github.com/opf/rubocop-openproject/blob/main/CHANGELOG.md
56
+ rubygems_mfa_required: 'true'
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 3.3.0
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.5.14
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: RuboCop cops for OpenProject
76
+ test_files: []