rubocop-discourse 3.6.0 → 3.7.0

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -0
  3. data/config/default.yml +28 -0
  4. data/lib/rubocop/cop/discourse/fabricator_shorthand.rb +52 -0
  5. data/lib/rubocop/cop/discourse/plugins/call_requires_plugin.rb +71 -0
  6. data/lib/rubocop/cop/discourse/plugins/namespace_constants.rb +35 -0
  7. data/lib/rubocop/cop/discourse/plugins/namespace_methods.rb +37 -0
  8. data/lib/rubocop/cop/discourse/plugins/no_monkey_patching.rb +92 -0
  9. data/lib/rubocop/cop/discourse/plugins/use_plugin_instance_on.rb +42 -0
  10. data/lib/rubocop/cop/discourse/plugins/use_require_relative.rb +32 -0
  11. data/lib/rubocop/cop/discourse_cops.rb +1 -1
  12. data/lib/rubocop-discourse.rb +2 -0
  13. data/rubocop-capybara.yml +5 -0
  14. data/rubocop-core.yml +1 -0
  15. data/rubocop-discourse.gemspec +4 -1
  16. data/rubocop-factory_bot.yml +11 -0
  17. data/rubocop-rspec.yml +0 -15
  18. data/spec/fixtures/controllers/bad_controller.rb +5 -0
  19. data/spec/fixtures/controllers/base_controller.rb +11 -0
  20. data/spec/fixtures/controllers/good_controller.rb +4 -0
  21. data/spec/fixtures/controllers/inherit_from_outside_controller.rb +5 -0
  22. data/spec/fixtures/controllers/namespaced_parent_controller.rb +5 -0
  23. data/spec/fixtures/controllers/no_requires_plugin_controller.rb +5 -0
  24. data/spec/fixtures/controllers/requires_plugin_controller.rb +6 -0
  25. data/spec/lib/rubocop/cop/fabricator_shorthand_spec.rb +34 -0
  26. data/spec/lib/rubocop/cop/plugins/call_requires_plugin_spec.rb +81 -0
  27. data/spec/lib/rubocop/cop/plugins/namespace_constants_spec.rb +42 -0
  28. data/spec/lib/rubocop/cop/plugins/namespace_methods_spec.rb +86 -0
  29. data/spec/lib/rubocop/cop/plugins/no_monkey_patching_spec.rb +93 -0
  30. data/spec/lib/rubocop/cop/plugins/use_plugin_instance_on_spec.rb +39 -0
  31. data/spec/lib/rubocop/cop/plugins/use_require_relative_spec.rb +26 -0
  32. data/stree-compat.yml +8 -0
  33. metadata +67 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e57f05acccac952d699a535ba6d53c33e68c5e9e9872f35ade7272d9bf4c97f8
4
- data.tar.gz: 615179ee1ea8739f9fc4f1c650006a2ea59b0d54fe6286d251149392a1739e87
3
+ metadata.gz: 3d347009b4a77b64450a20ef3afc5ca102dd361c60e3fbde2e5fd0ea976469cd
4
+ data.tar.gz: c98b24bd73f783f154320e337e44f6859e050c43ff9219cfa0be6f63a891fc13
5
5
  SHA512:
6
- metadata.gz: 6c2e8ab893929e04877786425beead68633197d0b2dacde6e40ad814454dee38f54ec80646ca7b2bf9bde48fbe555fe7c3dffecd96ee5ac58a10f73c11d02c8e
7
- data.tar.gz: d3c6717f1bab2b3c6c5f83fe19d340de4713390c87052ca8ce63394858b28dfad9a6668f0a0b5978263f052f1b383a0ba507ef08f07b60cff5bd592bc8bac5b9
6
+ metadata.gz: 98c56b4b42703f8fc1fe0b3f68344507e2001d7a71d5530c89b8f21a29b5276fc518542b445e9dd9a902cb30be38ce0d4df18218b2cf08bfb50465411d7c7d77
7
+ data.tar.gz: f7f5f50325374fe530a26c20ed113b4cb827ed7541a8a9f6dbae1a81c0dca29be2cc7073c8f543b5200dab2e2238617984a7f4dbd87d838ff8a1d4cfa8da0db6
data/.rubocop.yml CHANGED
@@ -1,2 +1,9 @@
1
1
  inherit_from:
2
2
  - default.yml
3
+
4
+ AllCops:
5
+ inherit_mode:
6
+ merge:
7
+ - Exclude
8
+ Exclude:
9
+ - "**/spec/fixtures/**/*"
data/config/default.yml CHANGED
@@ -62,3 +62,31 @@ Discourse/NoMixingMultisiteAndStandardSpecs:
62
62
  Patterns:
63
63
  - _spec.rb
64
64
  - '(?:^|/)spec/'
65
+
66
+ Discourse/Plugins/CallRequiresPlugin:
67
+ Enabled: true
68
+ Include:
69
+ - 'app/controllers/**/*'
70
+
71
+ Discourse/Plugins/UsePluginInstanceOn:
72
+ Enabled: true
73
+
74
+ Discourse/Plugins/NamespaceMethods:
75
+ Enabled: true
76
+ Exclude:
77
+ - '**/spec/**/*'
78
+ - '**/tasks/**/*.rake'
79
+ - '**/db/fixtures/**/*'
80
+
81
+ Discourse/Plugins/NamespaceConstants:
82
+ Enabled: true
83
+ Exclude:
84
+ - '**/spec/**/*'
85
+ - '**/tasks/**/*.rake'
86
+ - '**/db/fixtures/**/*'
87
+
88
+ Discourse/Plugins/UseRequireRelative:
89
+ Enabled: true
90
+
91
+ Discourse/Plugins/NoMonkeyPatching:
92
+ Enabled: true
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ # When fabricating a record without custom attributes, we can use the
7
+ # fabricator shorthand as long as the identifier matches the fabricator
8
+ # name.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # fab!(:user) { Fabricate(:user) }
14
+ #
15
+ # # good
16
+ # fab!(:user)
17
+ #
18
+ # When using custom attributes or the identifier doesn't match, the
19
+ # shorthand can't be used.
20
+ #
21
+ # @example
22
+ #
23
+ # # good
24
+ # fab!(:user) { Fabricate(:user, trust_level: TrustLevel[0]) }
25
+ #
26
+ # # good
27
+ # fab!(:another_user) { Fabricate(:user) }
28
+ class FabricatorShorthand < Base
29
+ def_node_matcher :offending_fabricator?, <<-MATCHER
30
+ (block
31
+ (send nil? :fab!
32
+ (sym $_identifier))
33
+ (args)
34
+ (send nil? :Fabricate
35
+ (sym $_identifier)))
36
+ MATCHER
37
+
38
+ def on_block(node)
39
+ offending_fabricator?(node) do |identifier|
40
+ add_offense(node, message: message(identifier))
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def message(identifier)
47
+ "Use the fabricator shorthand: `fab!(:#{identifier})`"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Plugin controllers must call `requires_plugin` to prevent routes from
8
+ # being accessible when the plugin is disabled.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # class MyController
13
+ # def my_action
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ # class MyController
19
+ # requires_plugin PLUGIN_NAME
20
+ # def my_action
21
+ # end
22
+ # end
23
+ #
24
+ class CallRequiresPlugin < Base
25
+ MSG =
26
+ "Use `requires_plugin` in controllers to prevent routes from being accessible when plugin is disabled."
27
+
28
+ def_node_matcher :requires_plugin_present?, <<~MATCHER
29
+ (class _ _
30
+ {
31
+ (begin <(send nil? :requires_plugin _) ...>)
32
+ <(send nil? :requires_plugin _) ...>
33
+ }
34
+ )
35
+ MATCHER
36
+
37
+ def on_class(node)
38
+ return if requires_plugin_present?(node)
39
+ return if requires_plugin_present_in_parent_classes(node)
40
+ add_offense(node, message: MSG)
41
+ end
42
+
43
+ private
44
+
45
+ def requires_plugin_present_in_parent_classes(node)
46
+ return unless processed_source.path
47
+ controller_path =
48
+ base_controller_path(node.parent_class&.const_name.to_s)
49
+ return unless controller_path
50
+ Commissioner
51
+ .new([self.class.new(config, @options)])
52
+ .investigate(parse(controller_path.read, controller_path.to_s))
53
+ .offenses
54
+ .empty?
55
+ end
56
+
57
+ def base_controller_path(base_class)
58
+ return if base_class.blank?
59
+ base_path = "#{base_class.underscore}.rb"
60
+ path = Pathname.new("#{processed_source.path}/../").cleanpath
61
+ until path.root?
62
+ controller_path = path.join(base_path)
63
+ return controller_path if controller_path.exist?
64
+ path = path.join("..").cleanpath
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Constants must be defined inside the plugin namespace (module or class).
8
+ #
9
+ # @example
10
+ # # bad
11
+ # MY_CONSTANT = :value
12
+ #
13
+ # # good
14
+ # module MyPlugin
15
+ # MY_CONSTANT = :value
16
+ # end
17
+ #
18
+ class NamespaceConstants < Base
19
+ MSG = "Don’t define constants outside a class or a module."
20
+
21
+ def on_casgn(node)
22
+ return if inside_namespace?(node)
23
+ add_offense(node, message: MSG)
24
+ end
25
+
26
+ private
27
+
28
+ def inside_namespace?(node)
29
+ node.each_ancestor.detect { _1.class_type? || _1.module_type? }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Methods must be defined inside the plugin namespace (module or class).
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def my_method
12
+ # end
13
+ #
14
+ # # good
15
+ # module MyPlugin
16
+ # def my_method
17
+ # end
18
+ # end
19
+ #
20
+ class NamespaceMethods < Base
21
+ MSG = "Don’t define methods outside a class or a module."
22
+
23
+ def on_def(node)
24
+ return if inside_namespace?(node)
25
+ add_offense(node, message: MSG)
26
+ end
27
+
28
+ private
29
+
30
+ def inside_namespace?(node)
31
+ node.each_ancestor.detect { _1.class_type? || _1.module_type? }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Don’t monkey-patch classes directly in `plugin.rb`. Instead, define
8
+ # additional methods in a dedicated mixin (an ActiveSupport concern for
9
+ # example) and use `prepend` (this allows calling `super` from the mixin).
10
+ #
11
+ # If you’re just adding new methods to an existing serializer, then use
12
+ # `add_to_serializer` instead.
13
+ #
14
+ # @example generic monkey-patching
15
+ # # bad
16
+ # ::Topic.class_eval do
17
+ # has_many :new_items
18
+ #
19
+ # def new_method
20
+ # end
21
+ # end
22
+ #
23
+ # # good
24
+ # module MyPlugin::TopicExtension
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # prepended do
28
+ # has_many :new_items
29
+ # end
30
+ #
31
+ # def new_method
32
+ # end
33
+ # end
34
+ #
35
+ # reloadable_patch { ::Topic.prepend(MyPlugin::TopicExtension) }
36
+ #
37
+ # @example for serializers
38
+ # # bad
39
+ # UserSerializer.class_eval do
40
+ # def new_method
41
+ # do_processing
42
+ # end
43
+ # end
44
+ #
45
+ # # good
46
+ # add_to_serializer(:user, :new_method) { do_processing }
47
+ #
48
+ class NoMonkeyPatching < Base
49
+ MSG =
50
+ "Don’t reopen existing classes. Instead, create a mixin and use `prepend`."
51
+ MSG_CLASS_EVAL =
52
+ "Don’t call `class_eval`. Instead, create a mixin and use `prepend`."
53
+ MSG_CLASS_EVAL_SERIALIZERS =
54
+ "Don’t call `class_eval` on a serializer. If you’re adding new methods, use `add_to_serializer`. Otherwise, create a mixin and use `prepend`."
55
+ MSG_SERIALIZERS =
56
+ "Don’t reopen serializers. Instead, use `add_to_serializer`."
57
+ RESTRICT_ON_SEND = [:class_eval].freeze
58
+
59
+ def_node_matcher :existing_class?, <<~MATCHER
60
+ (class (const (cbase) _) ...)
61
+ MATCHER
62
+
63
+ def_node_matcher :serializer?, <<~MATCHER
64
+ ({class send} (const _ /Serializer$/) ...)
65
+ MATCHER
66
+
67
+ def on_send(node)
68
+ if serializer?(node)
69
+ return add_offense(node, message: MSG_CLASS_EVAL_SERIALIZERS)
70
+ end
71
+ add_offense(node, message: MSG_CLASS_EVAL)
72
+ end
73
+
74
+ def on_class(node)
75
+ return unless in_plugin_rb_file?
76
+ return unless existing_class?(node)
77
+ if serializer?(node)
78
+ return add_offense(node, message: MSG_SERIALIZERS)
79
+ end
80
+ add_offense(node, message: MSG)
81
+ end
82
+
83
+ private
84
+
85
+ def in_plugin_rb_file?
86
+ processed_source.path.split("/").last == "plugin.rb"
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Using `DiscourseEvent.on` leaves the handler enabled when the plugin is disabled.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # DiscourseEvent.on(:event) { do_something }
12
+ #
13
+ # # good
14
+ # on(:event) { do_something }
15
+ #
16
+ class UsePluginInstanceOn < Base
17
+ MSG =
18
+ "Use `on` instead of `DiscourseEvent.on` as the latter will listen to events even if the plugin is disabled."
19
+ NOT_OUTSIDE_PLUGIN_RB =
20
+ "Don’t call `DiscourseEvent.on` outside `plugin.rb`."
21
+ RESTRICT_ON_SEND = [:on].freeze
22
+
23
+ def_node_matcher :discourse_event_on?, <<~MATCHER
24
+ (send (const nil? :DiscourseEvent) :on _)
25
+ MATCHER
26
+
27
+ def on_send(node)
28
+ return unless discourse_event_on?(node)
29
+ return add_offense(node, message: MSG) if in_plugin_rb_file?
30
+ add_offense(node, message: NOT_OUTSIDE_PLUGIN_RB)
31
+ end
32
+
33
+ private
34
+
35
+ def in_plugin_rb_file?
36
+ processed_source.path.split("/").last == "plugin.rb"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Plugins
7
+ # Use `require_relative` to load dependencies.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # load File.expand_path("../lib/my_file.rb", __FILE__)
12
+ #
13
+ # # good
14
+ # require_relative "lib/my_file"
15
+ #
16
+ class UseRequireRelative < Base
17
+ MSG = "Use `require_relative` instead of `load`."
18
+ RESTRICT_ON_SEND = [:load].freeze
19
+
20
+ def_node_matcher :load_called?, <<~MATCHER
21
+ (send nil? :load _)
22
+ MATCHER
23
+
24
+ def on_send(node)
25
+ return unless load_called?(node)
26
+ add_offense(node, message: MSG)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- path = File.join(__dir__, "discourse", "*.rb")
3
+ path = File.join(__dir__, "discourse", "**/*.rb")
4
4
  Dir[path].each { |file| require file }
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rubocop"
4
+ require "active_support"
5
+ require "active_support/core_ext/string/inflections"
4
6
  require_relative "rubocop/discourse"
5
7
  require_relative "rubocop/discourse/inject"
6
8
 
@@ -0,0 +1,5 @@
1
+ require:
2
+ - rubocop-capybara
3
+
4
+ Capybara/CurrentPathExpectation:
5
+ Enabled: true
data/rubocop-core.yml CHANGED
@@ -136,6 +136,7 @@ Lint/RedundantSafeNavigation:
136
136
 
137
137
  Lint/EmptyConditionalBody:
138
138
  Enabled: true
139
+ AutoCorrect: false # it can break the code
139
140
 
140
141
  Lint/SafeNavigationConsistency:
141
142
  Enabled: true
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "rubocop-discourse"
5
- s.version = "3.6.0"
5
+ s.version = "3.7.0"
6
6
  s.summary = "Custom rubocop cops used by Discourse"
7
7
  s.authors = ["Discourse Team"]
8
8
  s.license = "MIT"
@@ -11,8 +11,11 @@ Gem::Specification.new do |s|
11
11
  s.files = `git ls-files`.split($/)
12
12
  s.require_paths = ["lib"]
13
13
 
14
+ s.add_runtime_dependency "activesupport", ">= 6.1"
14
15
  s.add_runtime_dependency "rubocop", ">= 1.59.0"
15
16
  s.add_runtime_dependency "rubocop-rspec", ">= 2.25.0"
17
+ s.add_runtime_dependency "rubocop-factory_bot", ">= 2.0.0"
18
+ s.add_runtime_dependency "rubocop-capybara", ">= 2.0.0"
16
19
 
17
20
  s.add_development_dependency "rake", "~> 13.1.0"
18
21
  s.add_development_dependency "rspec", "~> 3.12.0"
@@ -0,0 +1,11 @@
1
+ require:
2
+ - rubocop-factory_bot
3
+
4
+ FactoryBot/AttributeDefinedStatically:
5
+ Enabled: true
6
+
7
+ FactoryBot/CreateList:
8
+ Enabled: true
9
+
10
+ FactoryBot/FactoryClassName:
11
+ Enabled: true
data/rubocop-rspec.yml CHANGED
@@ -223,23 +223,8 @@ RSpec/VoidExpect:
223
223
  RSpec/Yield:
224
224
  Enabled: true
225
225
 
226
- Capybara/CurrentPathExpectation:
227
- Enabled: true
228
-
229
226
  RSpec/Capybara/FeatureMethods:
230
227
  Enabled: true
231
228
 
232
- Capybara/VisibilityMatcher:
233
- Enabled: true
234
-
235
- FactoryBot/AttributeDefinedStatically:
236
- Enabled: true
237
-
238
- FactoryBot/CreateList:
239
- Enabled: true
240
-
241
- FactoryBot/FactoryClassName:
242
- Enabled: true
243
-
244
229
  RSpec/Rails/HttpStatus:
245
230
  Enabled: true
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BadController < NoRequiresPluginController
4
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/CallRequiresPlugin: Use `requires_plugin` in controllers to prevent routes from being accessible when plugin is disabled.
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BaseController
4
+ class << self
5
+ def requires_plugin(*)
6
+ end
7
+
8
+ def requires_login
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GoodController < RequiresPluginController
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class InheritFromOutsideController < ApplicationController
4
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/CallRequiresPlugin: Use `requires_plugin` in controllers to prevent routes from being accessible when plugin is disabled.
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class InheritFromOutsideController < MyPlugin::ApplicationController
4
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/CallRequiresPlugin: Use `requires_plugin` in controllers to prevent routes from being accessible when plugin is disabled.
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NoRequiresPluginController < BaseController
4
+ requires_login
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RequiresPluginController < BaseController
4
+ requires_plugin "my_plugin"
5
+ requires_login
6
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe RuboCop::Cop::Discourse::FabricatorShorthand, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ it "registers an offense when not using the fabricator shorthand" do
11
+ expect_offense(<<~RUBY)
12
+ RSpec.describe "Foo" do
13
+ fab!(:foo) { Fabricate(:foo) }
14
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/FabricatorShorthand: Use the fabricator shorthand: `fab!(:foo)`
15
+ end
16
+ RUBY
17
+ end
18
+
19
+ it "does not register an offense when the fabricator has attributes" do
20
+ expect_no_offenses(<<~RUBY)
21
+ RSpec.describe "Foo" do
22
+ fab!(:foo) { Fabricate(:foo, bar: 1) }
23
+ end
24
+ RUBY
25
+ end
26
+
27
+ it "does not register an offense when the identifier doesn't match" do
28
+ expect_no_offenses(<<~RUBY)
29
+ RSpec.describe "Foo" do
30
+ fab!(:bar) { Fabricate(:foo) }
31
+ end
32
+ RUBY
33
+ end
34
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::CallRequiresPlugin, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when `requires_plugin` is missing" do
11
+ it "registers an offense" do
12
+ expect_offense(<<~RUBY)
13
+ class MyController < ApplicationController
14
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/CallRequiresPlugin: Use `requires_plugin` in controllers to prevent routes from being accessible when plugin is disabled.
15
+ requires_login
16
+ end
17
+ RUBY
18
+ end
19
+ end
20
+
21
+ context "when `requires_plugin` is not missing" do
22
+ it "does not register an offense" do
23
+ expect_no_offenses(<<~RUBY)
24
+ class MyController
25
+ requires_plugin MyPlugin::PLUGIN_NAME
26
+ requires_login
27
+ end
28
+ RUBY
29
+ end
30
+ end
31
+
32
+ context "when inheriting" do
33
+ let(:controllers_path) do
34
+ Pathname.new("#{__dir__}/../../../../fixtures/controllers").cleanpath
35
+ end
36
+
37
+ before do
38
+ # As we’re providing real files, we need to get rid of the default config
39
+ # restricting the cop to `app/controllers/*`
40
+ configuration.for_cop(cop).delete("Include")
41
+ end
42
+
43
+ context "when `requires_plugin` is called in a parent controller" do
44
+ let(:good_controller) { controllers_path.join("good_controller.rb") }
45
+
46
+ it "does not register an offense" do
47
+ expect_no_offenses(good_controller.read, good_controller.to_s)
48
+ end
49
+ end
50
+
51
+ context "when `requires_plugin` is not called in a parent controller" do
52
+ let(:bad_controller) { controllers_path.join("bad_controller.rb") }
53
+
54
+ it "registers an offense" do
55
+ expect_offense(bad_controller.read, bad_controller.to_s)
56
+ end
57
+ end
58
+
59
+ context "when parent controller can’t be located" do
60
+ context "when parent controller is namespaced" do
61
+ let(:controller) do
62
+ controllers_path.join("namespaced_parent_controller.rb")
63
+ end
64
+
65
+ it "registers an offense" do
66
+ expect_offense(controller.read, controller.to_s)
67
+ end
68
+ end
69
+
70
+ context "when parent controller is not namespaced" do
71
+ let(:controller) do
72
+ controllers_path.join("inherit_from_outside_controller.rb")
73
+ end
74
+
75
+ it "registers an offense" do
76
+ expect_offense(controller.read, controller.to_s)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::NamespaceConstants, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when defining a constant outside any namespace" do
11
+ it "registers an offense" do
12
+ expect_offense(<<~RUBY)
13
+ MY_CONSTANT = "my_value"
14
+ ^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/NamespaceConstants: Don’t define constants outside a class or a module.
15
+
16
+ class MyClass
17
+ MY_CONSTANT = "my_value"
18
+ end
19
+ RUBY
20
+ end
21
+ end
22
+
23
+ context "when defining a constant inside a class" do
24
+ it "does not register an offense" do
25
+ expect_no_offenses(<<~RUBY)
26
+ class MyClass
27
+ MY_CONSTANT = "my_value"
28
+ end
29
+ RUBY
30
+ end
31
+ end
32
+
33
+ context "when defining a constant inside a module" do
34
+ it "does not register an offense" do
35
+ expect_no_offenses(<<~RUBY)
36
+ module MyModule
37
+ MY_CONSTANT = "my_value"
38
+ end
39
+ RUBY
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::NamespaceMethods, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when defining a method outside any namespace" do
11
+ it "registers an offense" do
12
+ expect_offense(<<~RUBY)
13
+ def my_method
14
+ ^^^^^^^^^^^^^ Discourse/Plugins/NamespaceMethods: Don’t define methods outside a class or a module.
15
+ "my_value"
16
+ end
17
+
18
+ class MyClass
19
+ def my_method
20
+ "my_method"
21
+ end
22
+ end
23
+ RUBY
24
+ end
25
+ end
26
+
27
+ context "when defining a method inside a class" do
28
+ context "when defining an instance method" do
29
+ it "does not register an offense" do
30
+ expect_no_offenses(<<~RUBY)
31
+ class MyClass
32
+ def my_method
33
+ "my_value"
34
+ end
35
+ end
36
+ RUBY
37
+ end
38
+ end
39
+
40
+ context "when defining a class method" do
41
+ it "does not register an offense" do
42
+ expect_no_offenses(<<~RUBY)
43
+ class MyClass
44
+ class << self
45
+ def my_method
46
+ "my_value"
47
+ end
48
+
49
+ def another_method
50
+ "plop"
51
+ end
52
+ end
53
+ end
54
+ RUBY
55
+ end
56
+ end
57
+ end
58
+
59
+ context "when defining a method inside a module" do
60
+ context "when defining an instance method" do
61
+ it "does not register an offense" do
62
+ expect_no_offenses(<<~RUBY)
63
+ module MyModule
64
+ def my_method
65
+ "my_value"
66
+ end
67
+ end
68
+ RUBY
69
+ end
70
+ end
71
+
72
+ context "when defining a class method" do
73
+ it "does not register an offense" do
74
+ expect_no_offenses(<<~RUBY)
75
+ module MyModule
76
+ class << self
77
+ def my_method
78
+ "my_value"
79
+ end
80
+ end
81
+ end
82
+ RUBY
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::NoMonkeyPatching, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when outside `plugin.rb`" do
11
+ it "does not register an offense" do
12
+ expect_no_offenses(<<~RUBY, "my_class.rb")
13
+ class ::MyClass
14
+ def my_method
15
+ "my_value"
16
+ end
17
+ end
18
+
19
+ class AnotherClass
20
+ def my_method
21
+ "my_value"
22
+ end
23
+ end
24
+ RUBY
25
+ end
26
+ end
27
+
28
+ context "when inside `plugin.rb`" do
29
+ context "when opening an existing class" do
30
+ it "registers an offense" do
31
+ expect_offense(<<~RUBY, "plugin.rb")
32
+ after_initialize do
33
+ module MyPlugin
34
+ class Engine < Rails::Engine
35
+ isolate_namespace MyPlugin
36
+ end
37
+ end
38
+
39
+ class ::Topic
40
+ ^^^^^^^^^^^^^ Discourse/Plugins/NoMonkeyPatching: Don’t reopen existing classes. [...]
41
+ def self.new_method
42
+ :new_value
43
+ end
44
+
45
+ def my_new_method
46
+ "my_new_value"
47
+ end
48
+ end
49
+ end
50
+ RUBY
51
+ end
52
+ end
53
+
54
+ context "when opening an existing serializer" do
55
+ it "registers an offense" do
56
+ expect_offense(<<~RUBY, "plugin.rb")
57
+ class ::TopicSerializer
58
+ ^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/NoMonkeyPatching: Don’t reopen serializers. [...]
59
+ def new_attribute
60
+ "my_attribute"
61
+ end
62
+ end
63
+ RUBY
64
+ end
65
+ end
66
+
67
+ context "when calling `.class_eval` on a class" do
68
+ it "registers an offense" do
69
+ expect_offense(<<~RUBY)
70
+ User.class_eval do
71
+ ^^^^^^^^^^^^^^^ Discourse/Plugins/NoMonkeyPatching: Don’t call `class_eval`. [...]
72
+ def a_new_method
73
+ :new_value
74
+ end
75
+ end
76
+ RUBY
77
+ end
78
+ end
79
+
80
+ context "when calling `.class_eval` on a serializer" do
81
+ it "registers an offense" do
82
+ expect_offense(<<~RUBY)
83
+ UserSerializer.class_eval do
84
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/NoMonkeyPatching: Don’t call `class_eval` on a serializer. [...]
85
+ def a_new_method
86
+ :new_value
87
+ end
88
+ end
89
+ RUBY
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::UsePluginInstanceOn, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when outside `plugin.rb`" do
11
+ context "when `DiscourseEvent.on` is called" do
12
+ it "registers an offense" do
13
+ expect_offense(<<~RUBY, "another_file.rb")
14
+ DiscourseEvent.on(:topic_status_updated) { do_something }
15
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/UsePluginInstanceOn: Don’t call `DiscourseEvent.on` outside `plugin.rb`.
16
+ RUBY
17
+ end
18
+ end
19
+ end
20
+
21
+ context "when inside `plugin.rb`" do
22
+ context "when `DiscourseEvent.on` is called" do
23
+ it "registers an offense" do
24
+ expect_offense(<<~RUBY, "plugin.rb")
25
+ DiscourseEvent.on(:topic_status_updated) { do_something }
26
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/UsePluginInstanceOn: Use `on` instead of `DiscourseEvent.on` [...]
27
+ RUBY
28
+ end
29
+ end
30
+
31
+ context "when `on` is called" do
32
+ it "does not register an offense" do
33
+ expect_no_offenses(<<~RUBY, "plugin.rb")
34
+ on(:topic_status_updated) { do_something }
35
+ RUBY
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Plugins::UseRequireRelative, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when using `load`" do
11
+ it "registers an offense" do
12
+ expect_offense(<<~RUBY)
13
+ load File.expand_path("../app/jobs/onceoff/voting_ensure_consistency.rb", __FILE__)
14
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Discourse/Plugins/UseRequireRelative: Use `require_relative` instead of `load`.
15
+ RUBY
16
+ end
17
+ end
18
+
19
+ context "when using `require_relative`" do
20
+ it "does not register an offense" do
21
+ expect_no_offenses(<<~RUBY)
22
+ require_relative "app/controllers/encrypt_controller.rb"
23
+ RUBY
24
+ end
25
+ end
26
+ end
data/stree-compat.yml CHANGED
@@ -3,10 +3,13 @@ require:
3
3
 
4
4
  inherit_from:
5
5
  - ./rubocop-core.yml
6
+ - ./rubocop-capybara.yml
7
+ - ./rubocop-factory_bot.yml
6
8
  - ./rubocop-rspec.yml
7
9
 
8
10
  AllCops:
9
11
  TargetRubyVersion: 3.2
12
+ SuggestExtensions: false
10
13
  DisabledByDefault: true
11
14
  Exclude:
12
15
  - 'db/schema.rb'
@@ -19,3 +22,8 @@ AllCops:
19
22
 
20
23
  Discourse:
21
24
  Enabled: true
25
+
26
+ Discourse/FabricatorShorthand:
27
+ Enabled: true
28
+ Include:
29
+ - 'spec/**/*_spec.rb'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-discourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Discourse Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-15 00:00:00.000000000 Z
11
+ date: 2024-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rubocop
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,34 @@ dependencies:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: 2.25.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-factory_bot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-capybara
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.0
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: rake
43
85
  requirement: !ruby/object:Gem::Requirement
@@ -82,6 +124,7 @@ files:
82
124
  - config/default.yml
83
125
  - default.yml
84
126
  - lib/rubocop-discourse.rb
127
+ - lib/rubocop/cop/discourse/fabricator_shorthand.rb
85
128
  - lib/rubocop/cop/discourse/no_add_reference_active_record_migrations.rb
86
129
  - lib/rubocop/cop/discourse/no_chdir.rb
87
130
  - lib/rubocop/cop/discourse/no_direct_multisite_manipulation.rb
@@ -93,19 +136,41 @@ files:
93
136
  - lib/rubocop/cop/discourse/no_time_new_without_args.rb
94
137
  - lib/rubocop/cop/discourse/no_uri_escape_encode.rb
95
138
  - lib/rubocop/cop/discourse/only_top_level_multisite_specs.rb
139
+ - lib/rubocop/cop/discourse/plugins/call_requires_plugin.rb
140
+ - lib/rubocop/cop/discourse/plugins/namespace_constants.rb
141
+ - lib/rubocop/cop/discourse/plugins/namespace_methods.rb
142
+ - lib/rubocop/cop/discourse/plugins/no_monkey_patching.rb
143
+ - lib/rubocop/cop/discourse/plugins/use_plugin_instance_on.rb
144
+ - lib/rubocop/cop/discourse/plugins/use_require_relative.rb
96
145
  - lib/rubocop/cop/discourse/time_eq_matcher.rb
97
146
  - lib/rubocop/cop/discourse_cops.rb
98
147
  - lib/rubocop/discourse.rb
99
148
  - lib/rubocop/discourse/inject.rb
149
+ - rubocop-capybara.yml
100
150
  - rubocop-core.yml
101
151
  - rubocop-discourse.gemspec
152
+ - rubocop-factory_bot.yml
102
153
  - rubocop-layout.yml
103
154
  - rubocop-rspec.yml
155
+ - spec/fixtures/controllers/bad_controller.rb
156
+ - spec/fixtures/controllers/base_controller.rb
157
+ - spec/fixtures/controllers/good_controller.rb
158
+ - spec/fixtures/controllers/inherit_from_outside_controller.rb
159
+ - spec/fixtures/controllers/namespaced_parent_controller.rb
160
+ - spec/fixtures/controllers/no_requires_plugin_controller.rb
161
+ - spec/fixtures/controllers/requires_plugin_controller.rb
162
+ - spec/lib/rubocop/cop/fabricator_shorthand_spec.rb
104
163
  - spec/lib/rubocop/cop/no_add_reference_active_record_migrations_spec.rb
105
164
  - spec/lib/rubocop/cop/no_mixing_multisite_and_standard_specs_spec.rb
106
165
  - spec/lib/rubocop/cop/no_mocking_jobs_enqueue_spec.rb
107
166
  - spec/lib/rubocop/cop/no_reset_column_information_migrations_spec.rb
108
167
  - spec/lib/rubocop/cop/only_top_level_multisite_specs_spec.rb
168
+ - spec/lib/rubocop/cop/plugins/call_requires_plugin_spec.rb
169
+ - spec/lib/rubocop/cop/plugins/namespace_constants_spec.rb
170
+ - spec/lib/rubocop/cop/plugins/namespace_methods_spec.rb
171
+ - spec/lib/rubocop/cop/plugins/no_monkey_patching_spec.rb
172
+ - spec/lib/rubocop/cop/plugins/use_plugin_instance_on_spec.rb
173
+ - spec/lib/rubocop/cop/plugins/use_require_relative_spec.rb
109
174
  - spec/lib/rubocop/cop/time_eq_matcher_spec.rb
110
175
  - spec/spec_helper.rb
111
176
  - stree-compat.yml