rubocop-discourse 3.1.0 → 3.9.3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +7 -0
  5. data/config/default.yml +51 -24
  6. data/lib/rubocop/cop/discourse/fabricator_shorthand.rb +62 -0
  7. data/lib/rubocop/cop/discourse/no_add_reference_active_record_migrations.rb +9 -7
  8. data/lib/rubocop/cop/discourse/no_chdir.rb +1 -1
  9. data/lib/rubocop/cop/discourse/no_direct_multisite_manipulation.rb +3 -2
  10. data/lib/rubocop/cop/discourse/no_json_parse_response.rb +2 -4
  11. data/lib/rubocop/cop/discourse/no_mixing_multisite_and_standard_specs.rb +4 -7
  12. data/lib/rubocop/cop/discourse/no_mocking_jobs.rb +3 -2
  13. data/lib/rubocop/cop/discourse/no_nokogiri_html_fragment.rb +1 -1
  14. data/lib/rubocop/cop/discourse/no_reset_column_information_in_migrations.rb +6 -5
  15. data/lib/rubocop/cop/discourse/no_time_new_without_args.rb +2 -4
  16. data/lib/rubocop/cop/discourse/no_uri_escape_encode.rb +11 -8
  17. data/lib/rubocop/cop/discourse/only_top_level_multisite_specs.rb +2 -6
  18. data/lib/rubocop/cop/discourse/plugins/call_requires_plugin.rb +71 -0
  19. data/lib/rubocop/cop/discourse/plugins/namespace_constants.rb +35 -0
  20. data/lib/rubocop/cop/discourse/plugins/namespace_methods.rb +37 -0
  21. data/lib/rubocop/cop/discourse/plugins/no_monkey_patching.rb +92 -0
  22. data/lib/rubocop/cop/discourse/plugins/use_plugin_instance_on.rb +42 -0
  23. data/lib/rubocop/cop/discourse/plugins/use_require_relative.rb +32 -0
  24. data/lib/rubocop/cop/discourse/services/empty_lines_around_blocks.rb +114 -0
  25. data/lib/rubocop/cop/discourse/services/group_keywords.rb +92 -0
  26. data/lib/rubocop/cop/discourse/time_eq_matcher.rb +2 -4
  27. data/lib/rubocop/cop/discourse_cops.rb +1 -1
  28. data/lib/rubocop/discourse.rb +3 -3
  29. data/lib/rubocop-discourse.rb +4 -0
  30. data/rubocop-capybara.yml +5 -0
  31. data/rubocop-core.yml +81 -4
  32. data/rubocop-discourse.gemspec +15 -10
  33. data/rubocop-factory_bot.yml +11 -0
  34. data/rubocop-layout.yml +4 -0
  35. data/rubocop-rails.yml +14 -0
  36. data/rubocop-rspec.yml +29 -23
  37. data/spec/fixtures/controllers/bad_controller.rb +5 -0
  38. data/spec/fixtures/controllers/base_controller.rb +11 -0
  39. data/spec/fixtures/controllers/good_controller.rb +4 -0
  40. data/spec/fixtures/controllers/inherit_from_outside_controller.rb +5 -0
  41. data/spec/fixtures/controllers/namespaced_parent_controller.rb +5 -0
  42. data/spec/fixtures/controllers/no_requires_plugin_controller.rb +5 -0
  43. data/spec/fixtures/controllers/requires_plugin_controller.rb +6 -0
  44. data/spec/lib/rubocop/cop/discourse/services/empty_lines_around_blocks_spec.rb +309 -0
  45. data/spec/lib/rubocop/cop/discourse/services/group_keywords_spec.rb +137 -0
  46. data/spec/lib/rubocop/cop/fabricator_shorthand_spec.rb +47 -0
  47. data/spec/lib/rubocop/cop/no_add_reference_active_record_migrations_spec.rb +13 -16
  48. data/spec/lib/rubocop/cop/no_mixing_multisite_and_standard_specs_spec.rb +10 -14
  49. data/spec/lib/rubocop/cop/no_mocking_jobs_enqueue_spec.rb +8 -12
  50. data/spec/lib/rubocop/cop/no_reset_column_information_migrations_spec.rb +8 -10
  51. data/spec/lib/rubocop/cop/only_top_level_multisite_specs_spec.rb +14 -18
  52. data/spec/lib/rubocop/cop/plugins/call_requires_plugin_spec.rb +79 -0
  53. data/spec/lib/rubocop/cop/plugins/namespace_constants_spec.rb +40 -0
  54. data/spec/lib/rubocop/cop/plugins/namespace_methods_spec.rb +84 -0
  55. data/spec/lib/rubocop/cop/plugins/no_monkey_patching_spec.rb +91 -0
  56. data/spec/lib/rubocop/cop/plugins/use_plugin_instance_on_spec.rb +37 -0
  57. data/spec/lib/rubocop/cop/plugins/use_require_relative_spec.rb +24 -0
  58. data/spec/lib/rubocop/cop/time_eq_matcher_spec.rb +6 -10
  59. data/spec/spec_helper.rb +4 -4
  60. data/stree-compat.yml +10 -1
  61. metadata +110 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41746c69c5012edbafa4e2f85d902a55a72235316897c0a5afdff69a7b7abbda
4
- data.tar.gz: b0128a77bf8387b94c7797c6e3df26a0eb1b0ffb68a87122b005e34aab13a448
3
+ metadata.gz: 4003717b78bca02f538f48bc23fdd1b0e913bb6816c5d6a1e87401c53c167a14
4
+ data.tar.gz: acba524c9e7e399a5b69974ce94dfefa33fbd61c5c2197ecb425b1ed22418e23
5
5
  SHA512:
6
- metadata.gz: abf20aff53a75d624eeb703a3bb86036486ebb6b5dd67b5255312eb8eb7e2ffb04cd57f3cf6ae173bff02a8bee6042fe93acaf038851c2a06011a082e91fd382
7
- data.tar.gz: 53b2e0e2bed1e95f94531819aa4f0c5595e1627231f53f626e6f557c59ab42d854f58362211ae8ff23fa60b5ebea4454185de2d9c4ce0322c71f2ed4e89c91e9
6
+ metadata.gz: 9fd47066d7d8066da4adb2f17d95de3420fbaae18599d07925671a28c58a95a2d39b4ba8319813a0daf2106e7ecce4407a99470a93f2d99f335147bad3142269
7
+ data.tar.gz: 311df91f7d4cf9b5e296d3dcbf625482a0fdea6ac1da33fe389b7db41ec0339b4918434ce97a13c023ba86e3a54eb81edcb512aba7cb9abd0a21b19bb5b5527d
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Setup ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: '2.6'
19
+ ruby-version: "3.3"
20
20
  bundler-cache: true
21
21
 
22
22
  - name: Rubocop
@@ -34,7 +34,7 @@ jobs:
34
34
  - uses: actions/checkout@v3
35
35
 
36
36
  - name: Release Gem
37
- uses: discourse/publish-rubygems-action@v2
37
+ uses: discourse/publish-rubygems-action@v3
38
38
  env:
39
39
  RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
40
40
  GIT_EMAIL: team@discourse.org
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require 'spec_helper'
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
@@ -1,8 +1,7 @@
1
1
  Discourse/NoChdir:
2
2
  Enabled: true
3
3
  Exclude:
4
- - 'spec/**/*' # Specs are run sequentially, so chdir can be used
5
- - 'plugins/*/spec/**/*'
4
+ - "**/spec/**/*" # Specs are run sequentially, so chdir can be used
6
5
 
7
6
  Discourse/NoTimeNewWithoutArgs:
8
7
  Enabled: true
@@ -13,8 +12,8 @@ Discourse/NoURIEscapeEncode:
13
12
  Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
14
13
  Enabled: true
15
14
  Include:
16
- - "**/db/migrate/*"
17
- - "**/db/post_migrate/*"
15
+ - "**/db/migrate/*"
16
+ - "**/db/post_migrate/*"
18
17
 
19
18
  Discourse/NoNokogiriHtmlFragment:
20
19
  Enabled: true
@@ -22,43 +21,71 @@ Discourse/NoNokogiriHtmlFragment:
22
21
  Discourse/NoResetColumnInformationInMigrations:
23
22
  Enabled: false
24
23
  Include:
25
- - "**/db/migrate/*"
26
- - "**/db/post_migrate/*"
24
+ - "**/db/migrate/*"
25
+ - "**/db/post_migrate/*"
27
26
 
28
27
  # Specs
29
28
 
30
29
  Discourse/NoDirectMultisiteManipulation:
31
30
  Enabled: true
32
- Patterns:
33
- - _spec.rb
34
- - "(?:^|/)spec/"
31
+ Include:
32
+ - "**/spec/**/*"
35
33
 
36
34
  Discourse/TimeEqMatcher:
37
35
  Enabled: true
38
- Patterns:
39
- - _spec.rb
40
- - "(?:^|/)spec/"
36
+ Include:
37
+ - "**/spec/**/*"
41
38
 
42
39
  Discourse/NoJsonParseResponse:
43
40
  Enabled: false
44
- Patterns:
45
- - _spec.rb
46
- - "(?:^|/)spec/"
41
+ Include:
42
+ - "**/spec/**/*"
47
43
 
48
44
  Discourse/NoMockingJobs:
49
45
  Enabled: true
50
- Patterns:
51
- - _spec.rb
52
- - "(?:^|/)spec/"
46
+ Include:
47
+ - "**/spec/**/*"
53
48
 
54
49
  Discourse/OnlyTopLevelMultisiteSpecs:
55
50
  Enabled: true
56
- Patterns:
57
- - _spec.rb
58
- - "(?:^|/)spec/"
51
+ Include:
52
+ - "**/spec/**/*"
59
53
 
60
54
  Discourse/NoMixingMultisiteAndStandardSpecs:
61
55
  Enabled: true
62
- Patterns:
63
- - _spec.rb
64
- - "(?:^|/)spec/"
56
+ Include:
57
+ - "**/spec/**/*"
58
+
59
+ Discourse/Plugins/CallRequiresPlugin:
60
+ Enabled: true
61
+ Include:
62
+ - "app/controllers/**/*"
63
+
64
+ Discourse/Plugins/UsePluginInstanceOn:
65
+ Enabled: true
66
+
67
+ Discourse/Plugins/NamespaceMethods:
68
+ Enabled: true
69
+ Exclude:
70
+ - "**/spec/**/*"
71
+ - "**/tasks/**/*.rake"
72
+ - "**/db/fixtures/**/*"
73
+
74
+ Discourse/Plugins/NamespaceConstants:
75
+ Enabled: true
76
+ Exclude:
77
+ - "**/spec/**/*"
78
+ - "**/tasks/**/*.rake"
79
+ - "**/db/fixtures/**/*"
80
+
81
+ Discourse/Plugins/UseRequireRelative:
82
+ Enabled: true
83
+
84
+ Discourse/Plugins/NoMonkeyPatching:
85
+ Enabled: true
86
+
87
+ Discourse/Services/GroupKeywords:
88
+ Enabled: true
89
+
90
+ Discourse/Services/EmptyLinesAroundBlocks:
91
+ Enabled: true
@@ -0,0 +1,62 @@
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
+ extend AutoCorrector
30
+ include IgnoredNode
31
+
32
+ def_node_matcher :offending_fabricator?, <<-MATCHER
33
+ (block
34
+ $(send nil? :fab!
35
+ (sym $_identifier))
36
+ (args)
37
+ (send nil? :Fabricate
38
+ (sym $_identifier)))
39
+ MATCHER
40
+
41
+ def on_block(node)
42
+ offending_fabricator?(node) do |expression, _identifier|
43
+ add_offense(
44
+ node,
45
+ message: message(expression.source)
46
+ ) do |corrector|
47
+ next if part_of_ignored_node?(node)
48
+ corrector.replace(node, expression.source)
49
+ end
50
+ ignore_node(node)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def message(expression)
57
+ "Use the fabricator shorthand: `#{expression}`"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -44,7 +44,7 @@ module RuboCop
44
44
  # index_posts_on_image_upload_id ON posts USING btree (image_upload_id)
45
45
  # SQL
46
46
  # end
47
- class NoAddReferenceOrAliasesActiveRecordMigration < Cop
47
+ class NoAddReferenceOrAliasesActiveRecordMigration < Base
48
48
  MSG = <<~MSG
49
49
  AR methods add_reference, add_belongs_to, t.references, and t.belongs_to are
50
50
  high-risk for large tables and have too many background magic operations.
@@ -70,12 +70,14 @@ module RuboCop
70
70
  MATCHER
71
71
 
72
72
  def on_send(node)
73
- return if [
74
- using_add_reference?(node),
75
- using_add_belongs_to?(node),
76
- using_t_references?(node),
77
- using_t_belongs_to?(node)
78
- ].none?
73
+ if [
74
+ using_add_reference?(node),
75
+ using_add_belongs_to?(node),
76
+ using_t_references?(node),
77
+ using_t_belongs_to?(node),
78
+ ].none?
79
+ return
80
+ end
79
81
  add_offense(node, message: MSG)
80
82
  end
81
83
  end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  # @example
14
14
  # # bad
15
15
  # Dir.chdir("test")
16
- class NoChdir < Cop
16
+ class NoChdir < Base
17
17
  MSG = "Chdir is not thread safe."
18
18
 
19
19
  def_node_matcher :using_dir_chdir?, <<-MATCHER
@@ -16,8 +16,9 @@ module RuboCop
16
16
  # it "works", type: :multisite do
17
17
  # do_something
18
18
  # end
19
- class NoDirectMultisiteManipulation < Cop
20
- MSG = "Use `type: :multisite` example setting instead of modifying `Rails.configuration.multisite`."
19
+ class NoDirectMultisiteManipulation < Base
20
+ MSG =
21
+ "Use `type: :multisite` example setting instead of modifying `Rails.configuration.multisite`."
21
22
 
22
23
  def_node_matcher :multisite_setter?, <<-MATCHER
23
24
  (send
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # expect(response.parsed_body).to eq({})
14
- class NoJsonParseResponse < Cop
14
+ class NoJsonParseResponse < Base
15
15
  MSG = "Use `response.parsed_body` instead of `JSON.parse(response.body)` in specs."
16
16
 
17
17
  def_node_matcher :json_parse_body?, <<-MATCHER
@@ -28,9 +28,7 @@ module RuboCop
28
28
  end
29
29
 
30
30
  def autocorrect(node)
31
- lambda do |corrector|
32
- corrector.replace(node.loc.expression, "response.parsed_body")
33
- end
31
+ lambda { |corrector| corrector.replace(node.loc.expression, "response.parsed_body") }
34
32
  end
35
33
  end
36
34
  end
@@ -23,8 +23,9 @@ module RuboCop
23
23
  # # x_multisite_spec.rb
24
24
  # describe "x", type: :multisite do
25
25
  # end
26
- class NoMixingMultisiteAndStandardSpecs < Cop
27
- MSG = "Do not mix multisite and standard specs. Consider moving multisite describes to a separate file."
26
+ class NoMixingMultisiteAndStandardSpecs < Base
27
+ MSG =
28
+ "Do not mix multisite and standard specs. Consider moving multisite describes to a separate file."
28
29
 
29
30
  def initialize(config = nil, options = nil)
30
31
  super
@@ -64,11 +65,7 @@ module RuboCop
64
65
  MATCHER
65
66
 
66
67
  def top_level?(node)
67
- if node.parent&.begin_type?
68
- node.parent.root?
69
- else
70
- node.root?
71
- end
68
+ node.parent&.begin_type? ? node.parent.root? : node.root?
72
69
  end
73
70
  end
74
71
  end
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Discourse
6
- class NoMockingJobs < Cop
7
- MSG = "Use the test helpers provided by Sidekiq instead of mocking `Jobs.expects(:enqueue)`."
6
+ class NoMockingJobs < Base
7
+ MSG =
8
+ "Use the test helpers provided by Sidekiq instead of mocking `Jobs.expects(:enqueue)`."
8
9
 
9
10
  def_node_matcher :mocking_jobs_enqueue?, <<~MATCHER
10
11
  (send (const nil? :Jobs) :expects (:sym :enqueue))
@@ -12,7 +12,7 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # Nokogiri::HTML5.fragment("<p>test</p>")
15
- class NoNokogiriHtmlFragment < Cop
15
+ class NoNokogiriHtmlFragment < Base
16
16
  MSG = "Nokogiri::HTML.fragment is deprecated and should not be used."
17
17
 
18
18
  def_node_matcher :using_nokogiri_html_fragment?, <<-MATCHER
@@ -7,11 +7,12 @@ module RuboCop
7
7
  # migrations. The method is not thread safe and we run migrations
8
8
  # concurrently for multisites. Also, we don't encourage the use of
9
9
  # ActiveRecord methods in migrations and prefer to write SQL directly.
10
- class NoResetColumnInformationInMigrations < Cop
11
- MSG = "ActiveRecord::ModelSchema.reset_column_information is not thread-safe " \
12
- "and we run migrations concurrently on multisite clusters. Using this " \
13
- "method also means ActiveRecord methods are being used in migration "\
14
- "which is discouraged at Discourse. Instead, you should write SQL in your migrations instead."
10
+ class NoResetColumnInformationInMigrations < Base
11
+ MSG =
12
+ "ActiveRecord::ModelSchema.reset_column_information is not thread-safe " \
13
+ "and we run migrations concurrently on multisite clusters. Using this " \
14
+ "method also means ActiveRecord methods are being used in migration " \
15
+ "which is discouraged at Discourse. Instead, you should write SQL in your migrations instead."
15
16
 
16
17
  def on_send(node)
17
18
  return if node.method_name != :reset_column_information
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # now = Time.zone.now
14
- class NoTimeNewWithoutArgs < Cop
14
+ class NoTimeNewWithoutArgs < Base
15
15
  MSG = "Use `Time.zone.now` instead of `Time.new` without arguments."
16
16
 
17
17
  def_node_matcher :time_new_without_args?, <<-MATCHER
@@ -25,9 +25,7 @@ module RuboCop
25
25
  end
26
26
 
27
27
  def autocorrect(node)
28
- lambda do |corrector|
29
- corrector.replace(node.loc.expression, "Time.zone.now")
30
- end
28
+ lambda { |corrector| corrector.replace(node.loc.expression, "Time.zone.now") }
31
29
  end
32
30
  end
33
31
  end
@@ -15,8 +15,9 @@ module RuboCop
15
15
  # # good
16
16
  # UrlHelper.encode("https://a%20a.com?a='a%22")
17
17
  # Addressable::URI.encode("https://a%20a.com?a='a%22")
18
- class NoURIEscapeEncode < Cop
19
- MSG = "URI.escape, URI.encode, URI.unescape, URI.decode are deprecated and should not be used."
18
+ class NoURIEscapeEncode < Base
19
+ MSG =
20
+ "URI.escape, URI.encode, URI.unescape, URI.decode are deprecated and should not be used."
20
21
 
21
22
  def_node_matcher :using_uri_escape?, <<-MATCHER
22
23
  (send (const nil? :URI) :escape ...)
@@ -35,12 +36,14 @@ module RuboCop
35
36
  MATCHER
36
37
 
37
38
  def on_send(node)
38
- return if [
39
- using_uri_escape?(node),
40
- using_uri_encode?(node),
41
- using_uri_unescape?(node),
42
- using_uri_decode?(node)
43
- ].none?
39
+ if [
40
+ using_uri_escape?(node),
41
+ using_uri_encode?(node),
42
+ using_uri_unescape?(node),
43
+ using_uri_decode?(node),
44
+ ].none?
45
+ return
46
+ end
44
47
  add_offense(node, message: MSG)
45
48
  end
46
49
  end
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # it "does X" do
26
26
  # end
27
27
  # end
28
- class OnlyTopLevelMultisiteSpecs < Cop
28
+ class OnlyTopLevelMultisiteSpecs < Base
29
29
  MSG = "Use `type: :multisite` only on a top-level `describe`"
30
30
 
31
31
  def on_block(node)
@@ -47,11 +47,7 @@ module RuboCop
47
47
  MATCHER
48
48
 
49
49
  def top_level?(node)
50
- if node.parent&.begin_type?
51
- node.parent.root?
52
- else
53
- node.root?
54
- end
50
+ node.parent&.begin_type? ? node.parent.root? : node.root?
55
51
  end
56
52
  end
57
53
  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