betterlint 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eba1c8a88e6cefd6fc8db168dfe4b9f41f30989c4716fdc0338e99614a40315d
4
- data.tar.gz: 9b176cbf47cf1f1d65547624b44531dcb0328832a1dfc62c12b3f33c0222f1e3
3
+ metadata.gz: 834ddcd8893844db2ccbff0a1c0379f40e47f12fbbff9859831c342df1266d34
4
+ data.tar.gz: 8b8b7c4d49d7a961f042fc6941f6f55085b77a502441eb1c6ba7dfb8aa1af9cc
5
5
  SHA512:
6
- metadata.gz: 00b1ed1bdec96493c6c9332228a2cff4a1134926c27d72ea0c13439f454af5668258a1722ef53520ffa2786472c59b1c09e12ee5abdce8e7fdc1d1d91ecb09df
7
- data.tar.gz: 7d159d6ee74e8a6f5b30f3e922e9d13ae1d5a26c779c12df0750daad6e82b68d2caecda1a8e473c7b983af45d96aca3635239906d03592e3e790fbf09952dec5
6
+ metadata.gz: d1db7904622a80b0271e05f33f36c4667f720a9fd13253422dba34c70ecd2ef0f6973255d5506475cd942e3ab72b353a61121da4ed4fa871d2b546228881324f
7
+ data.tar.gz: 3cf27adfc7561156c5a11a5a54c58b1bb471e5e944aed955220aaa8c383bf0e155eeb56aa514d537da317e08943ac9b330bb510d8fcf85cc8f6bbe6d7dcbc1dc
data/README.md CHANGED
@@ -130,6 +130,50 @@ end
130
130
  ```
131
131
 
132
132
  All three `params.permit` calls will be flagged.
133
+ ### Betterment/InternalsProtection
134
+
135
+ This cop is not enabled by default, and must be enabled from your `.rubocop.yml` file:
136
+
137
+ ```yaml
138
+ Betterment/InternalsProtection:
139
+ Enabled: true
140
+ ```
141
+
142
+ This cop enforces constants defined within `Internals` modules from being referenced from other modules.
143
+
144
+ For example, if you have a `Widget` ActiveRecord class that you don't want to be used directly,
145
+ you can place it into an Internals module such as:
146
+ ```ruby
147
+ class Widgets::Internals::Widget < ApplicationRecord
148
+ end
149
+ ```
150
+
151
+ Code outside of the `Widgets` module will not be allowed to reference Widget:
152
+ ```ruby
153
+ class WidgetsController < ApplicationController
154
+ def create
155
+ Widgets::Internals::Widget.create!
156
+ ^^^^^^^^^^^^^^^^^^ Internal constants may only be referenced from code within its containing module. [...]
157
+ end
158
+ end
159
+ ```
160
+
161
+ Non-Internal code within the module can be used to manage access.
162
+ ```ruby
163
+ class Widgets::WidgetCreation
164
+ def save!
165
+ Widgets::Internals::Widget.create!
166
+ end
167
+ end
168
+
169
+ class WidgetsController < ApplicationController
170
+ def create
171
+ Widgets::WidgetCreation.new.save!
172
+ end
173
+ end
174
+ ```
175
+
176
+ This cop inspects all direct constant references, and the `class_name` attribute on ActiveRecord associations.
133
177
 
134
178
  ### Betterment/UnsafeJob
135
179
 
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Betterment
6
+ class InternalsProtection < Base
7
+ MSG = <<~END.gsub(/\s+/, " ")
8
+ Internal constants may only be referenced from code within its containing module.
9
+ Constants defined within a module's Internals submodule may only be referenced by code in that module,
10
+ or nested classes and modules
11
+ (e.g. MyModule::Internals::MyClass may only be referenced from code in MyModule or MyModule::MyPublicClass).
12
+ END
13
+
14
+ # @!method association_with_class_name(node)
15
+ def_node_matcher :association_with_class_name, <<-PATTERN
16
+ (send nil? {:has_many :has_one :belongs_to} ... (hash <(pair (sym :class_name) ${str}) ...>))
17
+ PATTERN
18
+
19
+ # @!method rspec_describe(node)
20
+ def_node_matcher :rspec_describe, <<-PATTERN
21
+ (block (send (const nil? :RSpec) :describe ${const | str}) ...)
22
+ PATTERN
23
+
24
+ def on_const(node)
25
+ if node.children[1] == :Internals
26
+ module_path = const_path(node)
27
+
28
+ ensure_allowed_reference!(node, module_path)
29
+ end
30
+ end
31
+
32
+ def on_send(node)
33
+ class_name_node = association_with_class_name(node)
34
+ return unless class_name_node
35
+
36
+ full_path = string_path(class_name_node)
37
+ internals_index = full_path.find_index(:Internals)
38
+ if internals_index
39
+ module_path = full_path.take(internals_index)
40
+ ensure_allowed_reference!(class_name_node, module_path)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def ensure_allowed_reference!(node, module_path)
47
+ return if module_path.empty?
48
+
49
+ unless definition_context_path(node).each_cons(module_path.size).any?(module_path)
50
+ add_offense(node)
51
+ end
52
+ end
53
+
54
+ def const_path(const_node)
55
+ const_node.each_descendant(:const, :cbase).map { |n| n.children[1] }.reverse
56
+ end
57
+
58
+ def string_path(string_node)
59
+ string_node.children[0].split('::').map { |name| name == '' ? nil : name.to_sym }
60
+ end
61
+
62
+ def definition_context_path(node)
63
+ rspec_context_path(node) || module_class_definition_context_path(node)
64
+ end
65
+
66
+ def module_class_definition_context_path(node)
67
+ node.each_ancestor(:class, :module).flat_map { |anc|
68
+ anc.children[0].each_node(:const, :cbase).map { |c| c.children[1] }
69
+ }.push(nil).reverse
70
+ end
71
+
72
+ def rspec_context_path(node)
73
+ rspec_described_class = node.each_ancestor(:block).filter_map { |ancestor|
74
+ rspec_describe(ancestor)
75
+ }.first
76
+ case rspec_described_class&.type
77
+ when :const then const_path(rspec_described_class)
78
+ when :str then string_path(rspec_described_class)
79
+ else nil # rubocop:disable Style/EmptyElse
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -10,6 +10,7 @@ require 'rubocop/cop/betterment/dynamic_params'
10
10
  require 'rubocop/cop/betterment/unscoped_find'
11
11
  require 'rubocop/cop/betterment/unsafe_job'
12
12
  require 'rubocop/cop/betterment/timeout'
13
+ require 'rubocop/cop/betterment/internals_protection'
13
14
  require 'rubocop/cop/betterment/memoization_with_arguments'
14
15
  require 'rubocop/cop/betterment/non_standard_actions'
15
16
  require 'rubocop/cop/betterment/site_prism_loaded'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: betterlint
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 1.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Development
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-26 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -113,6 +113,7 @@ files:
113
113
  - lib/rubocop/cop/betterment/fetch_boolean.rb
114
114
  - lib/rubocop/cop/betterment/hardcoded_id.rb
115
115
  - lib/rubocop/cop/betterment/implicit_redirect_type.rb
116
+ - lib/rubocop/cop/betterment/internals_protection.rb
116
117
  - lib/rubocop/cop/betterment/memoization_with_arguments.rb
117
118
  - lib/rubocop/cop/betterment/non_standard_actions.rb
118
119
  - lib/rubocop/cop/betterment/redirect_status.rb
@@ -133,10 +134,10 @@ licenses:
133
134
  - MIT
134
135
  metadata:
135
136
  homepage_uri: https://github.com/Betterment/betterlint
136
- source_code_uri: https://github.com/Betterment/betterlint/tree/v1.14.0
137
- changelog_uri: https://github.com/Betterment/betterlint/blob/v1.14.0/CHANGELOG.md
137
+ source_code_uri: https://github.com/Betterment/betterlint/tree/v1.15.0
138
+ changelog_uri: https://github.com/Betterment/betterlint/blob/v1.15.0/CHANGELOG.md
138
139
  bug_tracker_uri: https://github.com/Betterment/betterlint/issues
139
- documentation_uri: https://www.rubydoc.info/gems/betterlint/1.14.0
140
+ documentation_uri: https://www.rubydoc.info/gems/betterlint/1.15.0
140
141
  rubygems_mfa_required: 'true'
141
142
  post_install_message:
142
143
  rdoc_options: []
@@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
154
  - !ruby/object:Gem::Version
154
155
  version: '0'
155
156
  requirements: []
156
- rubygems_version: 3.5.18
157
+ rubygems_version: 3.5.21
157
158
  signing_key:
158
159
  specification_version: 4
159
160
  summary: Betterment rubocop configuration