rubocop-packs 0.0.45 → 0.1.1
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.
- checksums.yaml +4 -4
- data/README.md +6 -2
- data/config/default.yml +14 -0
- data/lib/rubocop/cop/packs/class_methods_as_public_apis.rb +6 -15
- data/lib/rubocop/cop/packs/documented_public_apis.rb +209 -11
- data/lib/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api.rb +38 -21
- data/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb +19 -20
- data/lib/rubocop/cop/packs/typed_public_apis.rb +2 -2
- data/lib/rubocop/cop/packwerk_lite/constant_resolver.rb +39 -19
- data/lib/rubocop/cop/packwerk_lite/dependency_checker.rb +4 -12
- data/lib/rubocop/cop/packwerk_lite/privacy_checker.rb +6 -14
- data/lib/rubocop/cop/packwerk_lite/private.rb +3 -4
- data/lib/rubocop/packs/plugin.rb +35 -0
- data/lib/rubocop/packs/private/configuration.rb +4 -6
- data/lib/rubocop/packs/private.rb +3 -6
- data/lib/rubocop/packs/version.rb +8 -0
- data/lib/rubocop/packs.rb +4 -11
- data/lib/rubocop/packwerk_lite.rb +0 -1
- data/lib/rubocop-packs.rb +2 -3
- metadata +57 -45
- data/lib/rubocop/packs/inject.rb +0 -24
- data/lib/rubocop/packs/private/offense.rb +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6fb879c4fde42e92de3719345a90fd8782cfd9239ec7845869df3ef942ed8b0
|
|
4
|
+
data.tar.gz: 1c2a8ad04b824ed3f5c4a5ea69032164c60057a12a45b8d2d1985b2baaa863c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f628b8204e0124712c8a760bc52c1f5a6b15dafe7f9fa48d6d05d9e40bc0b6e86c46f43441c535936e9182314718e27a4afc33d0a7eed33297f7a14d3317be8
|
|
7
|
+
data.tar.gz: 3d88bf6ee823725ba651add5983792096e59bd7ebe3229028edb03dbb1029002270f96a3cc64d3fa47e6963e7b2b97828f64b0583ff60ee0f1a65920971ff041
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Just install the `rubocop-packs` gem
|
|
|
9
9
|
```sh
|
|
10
10
|
gem install rubocop-packs
|
|
11
11
|
```
|
|
12
|
-
or, if you use `Bundler`, add this line your application's `Gemfile`:
|
|
12
|
+
or, if you use `Bundler`, add this line to your application's `Gemfile`:
|
|
13
13
|
|
|
14
14
|
```ruby
|
|
15
15
|
gem 'rubocop-packs', require: false
|
|
@@ -24,13 +24,17 @@ You need to tell RuboCop to load the Packs extension. There are three ways to do
|
|
|
24
24
|
Put this into your `.rubocop.yml`:
|
|
25
25
|
|
|
26
26
|
```yaml
|
|
27
|
-
|
|
27
|
+
plugins:
|
|
28
28
|
- rubocop-packs
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Now you can run `rubocop` and it will automatically load the RuboCop Packs cops together with the standard cops.
|
|
32
32
|
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`.
|
|
35
|
+
|
|
33
36
|
## The Cops
|
|
37
|
+
|
|
34
38
|
All cops are located under [`lib/rubocop/cop/packs`](lib/rubocop/cop/packs), and contain examples/documentation.
|
|
35
39
|
|
|
36
40
|
In your `.rubocop.yml`, you may treat the Packs cops just like any other cop. For example:
|
data/config/default.yml
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Packs/ClassMethodsAsPublicApis:
|
|
2
|
+
Description: 'Requires public API methods (in `app/public`) to be defined as class methods, which are more easily statically analyzable and typically hold less state.'
|
|
2
3
|
Enabled: false
|
|
4
|
+
VersionAdded: '0.0.2'
|
|
3
5
|
AcceptableParentClasses:
|
|
4
6
|
- T::Enum
|
|
5
7
|
- T::Struct
|
|
@@ -8,18 +10,30 @@ Packs/ClassMethodsAsPublicApis:
|
|
|
8
10
|
AcceptableMixins: []
|
|
9
11
|
|
|
10
12
|
Packs/RootNamespaceIsPackName:
|
|
13
|
+
Description: 'Requires that each file is namespaced under its pack name, so that each pack exposes exactly one root namespace.'
|
|
11
14
|
Enabled: false
|
|
15
|
+
VersionAdded: '0.0.2'
|
|
12
16
|
|
|
13
17
|
Packs/TypedPublicApis:
|
|
18
|
+
Description: "Requires that each pack's public API (in `app/public`) is `typed: strict`."
|
|
14
19
|
Enabled: false
|
|
20
|
+
VersionAdded: '0.0.2'
|
|
15
21
|
|
|
16
22
|
Packs/DocumentedPublicApis:
|
|
23
|
+
Description: 'Requires that each public API method (in `app/public`) has a documentation comment.'
|
|
17
24
|
Enabled: false
|
|
25
|
+
VersionAdded: '0.0.2'
|
|
26
|
+
AllowedMethods: []
|
|
27
|
+
RequireForNonPublicMethods: false
|
|
18
28
|
|
|
19
29
|
PackwerkLite/Privacy:
|
|
30
|
+
Description: 'Detects references to the private implementation of another pack instead of its public API.'
|
|
20
31
|
# It is recommended to use packwerk
|
|
21
32
|
Enabled: false
|
|
33
|
+
VersionAdded: '0.0.12'
|
|
22
34
|
|
|
23
35
|
PackwerkLite/Dependency:
|
|
36
|
+
Description: 'Detects references to a pack that is not listed as an explicit dependency.'
|
|
24
37
|
# It is recommended to use packwerk
|
|
25
38
|
Enabled: false
|
|
39
|
+
VersionAdded: '0.0.12'
|
|
@@ -29,19 +29,12 @@ module RuboCop
|
|
|
29
29
|
# end
|
|
30
30
|
#
|
|
31
31
|
class ClassMethodsAsPublicApis < Base
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
sig { returns(T::Boolean) }
|
|
35
|
-
def support_autocorrect?
|
|
36
|
-
false
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
sig { params(node: T.untyped).void }
|
|
32
|
+
#: (RuboCop::AST::DefNode node) -> void
|
|
40
33
|
def on_def(node)
|
|
41
34
|
# This cop only applies for ruby files in `app/public`
|
|
42
35
|
return if !processed_source.file_path.include?('app/public/')
|
|
43
36
|
|
|
44
|
-
# Looked at https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Lint/MissingSuper source code as inspiration for
|
|
37
|
+
# Looked at https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Lint/MissingSuper source code as inspiration for this part.
|
|
45
38
|
class_node = node.each_ancestor(:class).first
|
|
46
39
|
module_node = node.each_ancestor(:module).first
|
|
47
40
|
parent_class = class_node&.parent_class || module_node&.parent
|
|
@@ -52,21 +45,19 @@ module RuboCop
|
|
|
52
45
|
class_is_allowed_to_have_instance_methods = acceptable_parent_classes.include?(parent_class&.const_name)
|
|
53
46
|
return if uses_implicit_static_methods || class_is_allowed_to_have_instance_methods
|
|
54
47
|
|
|
55
|
-
is_sorbet_interface_or_abstract_class = !module_node.nil? && module_node.descendants.any? { |d| d.is_a?(RuboCop::AST::SendNode) && (
|
|
48
|
+
is_sorbet_interface_or_abstract_class = !module_node.nil? && module_node.descendants.any? { |d| d.is_a?(RuboCop::AST::SendNode) && %i(interface! abstract!).include?(d.method_name) }
|
|
56
49
|
return if is_sorbet_interface_or_abstract_class
|
|
57
50
|
return if node_includes_acceptable_mixin?(class_node || module_node)
|
|
58
51
|
|
|
59
52
|
add_offense(
|
|
60
53
|
node.source_range,
|
|
61
|
-
message:
|
|
62
|
-
"Public API method must be a class method (e.g. `self.#{node.method_name}(...)`)"
|
|
63
|
-
)
|
|
54
|
+
message: "Public API method must be a class method (e.g. `self.#{node.method_name}(...)`)"
|
|
64
55
|
)
|
|
65
56
|
end
|
|
66
57
|
|
|
67
58
|
private
|
|
68
59
|
|
|
69
|
-
|
|
60
|
+
#: ((RuboCop::AST::ClassNode | RuboCop::AST::ModuleNode)? node) -> bool
|
|
70
61
|
def node_includes_acceptable_mixin?(node)
|
|
71
62
|
acceptable_mixins = cop_config['AcceptableMixins'] || []
|
|
72
63
|
return false if node.nil?
|
|
@@ -74,7 +65,7 @@ module RuboCop
|
|
|
74
65
|
node.descendants.any? do |d|
|
|
75
66
|
d.is_a?(RuboCop::AST::SendNode) &&
|
|
76
67
|
d.method_name == :include &&
|
|
77
|
-
d.arguments.
|
|
68
|
+
d.arguments.one? &&
|
|
78
69
|
d.arguments.first.is_a?(RuboCop::AST::ConstNode) &&
|
|
79
70
|
acceptable_mixins.include?(d.arguments.first.const_name)
|
|
80
71
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
@@ -30,32 +31,162 @@ module RuboCop
|
|
|
30
31
|
# def bar; end
|
|
31
32
|
# end
|
|
32
33
|
#
|
|
34
|
+
# # bad - RBS signatures and Sorbet annotations are not documentation
|
|
35
|
+
# # packs/foo/app/public/foo.rb
|
|
36
|
+
# class Foo
|
|
37
|
+
# # @override
|
|
38
|
+
# #: -> void
|
|
39
|
+
# def bar; end
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # good
|
|
43
|
+
# # packs/foo/app/public/foo.rb
|
|
44
|
+
# class Foo
|
|
45
|
+
# # This is a documentation comment.
|
|
46
|
+
# # @override
|
|
47
|
+
# #: -> void
|
|
48
|
+
# def bar; end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# @example RequireForNonPublicMethods: false (default)
|
|
52
|
+
#
|
|
53
|
+
# # good
|
|
54
|
+
# # packs/foo/app/public/foo.rb
|
|
55
|
+
# class Foo
|
|
56
|
+
# private
|
|
57
|
+
#
|
|
58
|
+
# def bar; end
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# # good
|
|
62
|
+
# # packs/foo/app/public/foo.rb
|
|
63
|
+
# class Foo
|
|
64
|
+
# protected
|
|
65
|
+
#
|
|
66
|
+
# def bar; end
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# # good
|
|
70
|
+
# # packs/foo/app/public/foo.rb
|
|
71
|
+
# class Foo
|
|
72
|
+
# def self.bar; end
|
|
73
|
+
# private_class_method :bar
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# # bad - `private` does not apply to singleton methods, so `bar` is public
|
|
77
|
+
# # packs/foo/app/public/foo.rb
|
|
78
|
+
# class Foo
|
|
79
|
+
# private
|
|
80
|
+
#
|
|
81
|
+
# def self.bar; end
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# @example RequireForNonPublicMethods: true
|
|
85
|
+
#
|
|
86
|
+
# # bad
|
|
87
|
+
# # packs/foo/app/public/foo.rb
|
|
88
|
+
# class Foo
|
|
89
|
+
# private
|
|
90
|
+
#
|
|
91
|
+
# def bar; end
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# # bad
|
|
95
|
+
# # packs/foo/app/public/foo.rb
|
|
96
|
+
# class Foo
|
|
97
|
+
# protected
|
|
98
|
+
#
|
|
99
|
+
# def bar; end
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# # good
|
|
103
|
+
# # packs/foo/app/public/foo.rb
|
|
104
|
+
# class Foo
|
|
105
|
+
# private
|
|
106
|
+
#
|
|
107
|
+
# # This is a documentation comment.
|
|
108
|
+
# def bar; end
|
|
109
|
+
# end
|
|
110
|
+
#
|
|
111
|
+
# # good
|
|
112
|
+
# # packs/foo/app/public/foo.rb
|
|
113
|
+
# class Foo
|
|
114
|
+
# protected
|
|
115
|
+
#
|
|
116
|
+
# # This is a documentation comment.
|
|
117
|
+
# def bar; end
|
|
118
|
+
# end
|
|
119
|
+
#
|
|
120
|
+
# @example AllowedMethods: ['method_missing', 'respond_to_missing?']
|
|
121
|
+
#
|
|
122
|
+
# # good
|
|
123
|
+
# # packs/foo/app/public/foo.rb
|
|
124
|
+
# class Foo
|
|
125
|
+
# def method_missing(name, *args); end
|
|
126
|
+
#
|
|
127
|
+
# def respond_to_missing?(symbol, include_private); end
|
|
128
|
+
# end
|
|
129
|
+
#
|
|
130
|
+
# # bad
|
|
131
|
+
# # packs/foo/app/public/foo.rb
|
|
132
|
+
# class Foo
|
|
133
|
+
# def bar; end
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
33
136
|
class DocumentedPublicApis < Style::DocumentationMethod
|
|
34
137
|
# This cop does two things:
|
|
35
138
|
# 1) It only activates for things in the public folder
|
|
36
|
-
# 2) It allows `Style/DocumentationMethod` to work with sigs as expected.
|
|
139
|
+
# 2) It allows `Style/DocumentationMethod` to work with sigs, RBS comment signatures and Sorbet annotations as expected.
|
|
37
140
|
#
|
|
38
141
|
# An alternate approach would be to monkey patch the existing cop, as `rubocop-sorbet` did here:
|
|
39
142
|
# https://github.com/Shopify/rubocop-sorbet/blob/6634f033611604cd76eeb73eae6d8728ec82d504/lib/rubocop/cop/sorbet/mutable_constant_sorbet_aware_behaviour.rb
|
|
40
143
|
# This monkey-patched cop could/should probably be upstreamed to `rubocop-sorbet`, and then `config/default.yml` could simply set `packs/*/app/public/**/*`
|
|
41
144
|
# in the default include paths. However, that strategy has the downside of resulting in more configuration for the consumer if they only want to
|
|
42
145
|
# support this for some packs.
|
|
43
|
-
extend T::Sig
|
|
44
146
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
147
|
+
# The lines of an RBS method signature as Sorbet reads them: `#:` starts one, and `#|` continues a
|
|
148
|
+
# multi-line one. https://sorbet.org/docs/rbs-support
|
|
149
|
+
RBS_SIGNATURE = /\A#[:|]/
|
|
150
|
+
|
|
151
|
+
# The comments Sorbet reads in place of a `sig`'s modifiers, such as `# @override` for `override`,
|
|
152
|
+
# plus `@without_runtime`, which `spoom srb sigs translate` writes for `T::Sig::WithoutRuntime.sig`.
|
|
153
|
+
SORBET_METHOD_ANNOTATIONS = [
|
|
154
|
+
'@abstract',
|
|
155
|
+
'@final',
|
|
156
|
+
'@overridable',
|
|
157
|
+
'@override',
|
|
158
|
+
'@override(allow_incompatible: true)',
|
|
159
|
+
'@override(allow_incompatible: false)',
|
|
160
|
+
'@override(allow_incompatible: :visibility)',
|
|
161
|
+
'@without_runtime',
|
|
162
|
+
].freeze
|
|
163
|
+
|
|
164
|
+
# rubocop < 1.91 checks AllowedMethods inside `#check`, which this cop
|
|
165
|
+
# overrides. Drop when the gemspec floor is rubocop >= 1.91; until then the
|
|
166
|
+
# minimum-rubocop CI job covers it. Re-alias `on_defs` so it binds to this
|
|
167
|
+
# method, not the parent's.
|
|
168
|
+
#: (RuboCop::AST::DefNode node) -> void
|
|
169
|
+
def on_def(node)
|
|
170
|
+
return if allowed_methods.include?(node.method_name)
|
|
171
|
+
|
|
172
|
+
super
|
|
48
173
|
end
|
|
174
|
+
alias_method :on_defs, :on_def
|
|
49
175
|
|
|
50
|
-
|
|
176
|
+
# `node` is the enclosing send for a definition passed to `module_function` or
|
|
177
|
+
# `ruby2_keywords`, and the definition itself otherwise.
|
|
178
|
+
#: ((RuboCop::AST::DefNode | RuboCop::AST::SendNode) node) -> void
|
|
51
179
|
def check(node)
|
|
52
180
|
# This cop only applies for ruby files in `app/public`
|
|
53
181
|
return if !processed_source.file_path.include?('app/public')
|
|
54
|
-
return if
|
|
182
|
+
return if non_public_method?(node) && !require_for_non_public_methods?
|
|
55
183
|
|
|
56
184
|
left_sibling = node.left_sibling
|
|
57
185
|
|
|
58
|
-
|
|
186
|
+
# A `Symbol` sibling means the definition was passed to a modifier, as in
|
|
187
|
+
# `private def foo` or `private_class_method def self.foo`, so both the sig and the
|
|
188
|
+
# documentation comment sit above the enclosing send rather than above the definition.
|
|
189
|
+
if left_sibling.is_a?(Symbol)
|
|
59
190
|
if node_is_sorbet_signature?(node.parent.left_sibling)
|
|
60
191
|
return if documentation_comment?(node.parent.left_sibling)
|
|
61
192
|
elsif documentation_comment?(node.parent)
|
|
@@ -70,10 +201,77 @@ module RuboCop
|
|
|
70
201
|
add_offense(node)
|
|
71
202
|
end
|
|
72
203
|
|
|
73
|
-
|
|
204
|
+
# `DefNode#non_public?` treats a singleton method after a bare `private` or `protected`
|
|
205
|
+
# as non-public, but those only change the visibility of instance methods. A singleton
|
|
206
|
+
# method is non-public only when `private_class_method` wraps it or later names it.
|
|
207
|
+
#: ((RuboCop::AST::DefNode | RuboCop::AST::SendNode) node) -> bool
|
|
208
|
+
def non_public_method?(node)
|
|
209
|
+
if node.is_a?(RuboCop::AST::DefNode) && node.defs_type?
|
|
210
|
+
non_public_modifier?(node.parent) || made_private_after_definition?(node)
|
|
211
|
+
else
|
|
212
|
+
non_public?(node)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
#: (RuboCop::AST::DefNode node) -> bool
|
|
217
|
+
def made_private_after_definition?(node)
|
|
218
|
+
node.right_siblings.any? do |sibling|
|
|
219
|
+
sibling.is_a?(RuboCop::AST::SendNode) &&
|
|
220
|
+
(sibling.receiver.nil? || sibling.receiver.self_type?) &&
|
|
221
|
+
sibling.method?(:private_class_method) &&
|
|
222
|
+
sibling.arguments.any? do |argument|
|
|
223
|
+
argument.is_a?(RuboCop::AST::SymbolNode) && argument.value == node.method_name
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
#: (top node) -> bool
|
|
74
229
|
def node_is_sorbet_signature?(node)
|
|
75
|
-
|
|
76
|
-
|
|
230
|
+
case node
|
|
231
|
+
when RuboCop::AST::Node
|
|
232
|
+
# A node's `source` is `nil` when it has no source range. That is the case for the empty
|
|
233
|
+
# `args` node of a block written without parameters, as in `Struct.new(:a) do ... end`,
|
|
234
|
+
# which is the left sibling of a method defined as the block's only statement.
|
|
235
|
+
node_source = node.source
|
|
236
|
+
|
|
237
|
+
# Is there a better way to check if a node is a sorbet signature? Probably!
|
|
238
|
+
!!(node_source && (node_source.include?('sig do') || node_source.include?('sig {')))
|
|
239
|
+
else
|
|
240
|
+
# A sibling is not necessarily an AST node. It is a `Symbol` when a method definition is
|
|
241
|
+
# passed to a modifier, such as the `:private` in `private def foo`.
|
|
242
|
+
false
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# A copy of `RuboCop::Cop::DocumentationComment#documentation_comment?` with two changes. Like the
|
|
247
|
+
# original, it ignores annotation keywords (`# TODO:`) and directives (`# rubocop:disable`), and it
|
|
248
|
+
# also ignores RBS comments, which, like a `sig`, are not documentation. And it reads only the
|
|
249
|
+
# comments directly above the method: the original accepts any comment above a method once one is
|
|
250
|
+
# adjacent, which an RBS signature always is, so a section heading a blank line above would count.
|
|
251
|
+
#: (RuboCop::AST::Node node) -> bool
|
|
252
|
+
def documentation_comment?(node)
|
|
253
|
+
preceding_lines = preceding_lines(node)
|
|
254
|
+
|
|
255
|
+
return false unless preceding_comment?(node, preceding_lines.last)
|
|
256
|
+
|
|
257
|
+
adjacent_comments(preceding_lines).any? do |comment|
|
|
258
|
+
!AnnotationComment.new(comment, annotation_keywords).annotation? &&
|
|
259
|
+
!interpreter_directive_comment?(comment) &&
|
|
260
|
+
!rubocop_directive_comment?(comment) &&
|
|
261
|
+
!rbs_comment?(comment)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# The unbroken run of `comments` that ends with the last one, from the bottom up.
|
|
266
|
+
#: (Array[Parser::Source::Comment] comments) -> Array[Parser::Source::Comment]
|
|
267
|
+
def adjacent_comments(comments)
|
|
268
|
+
comments.reverse.chunk_while { |below, above| below.loc.line - above.loc.last_line == 1 }.first.to_a
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
#: (Parser::Source::Comment comment) -> bool
|
|
272
|
+
def rbs_comment?(comment)
|
|
273
|
+
comment.text.match?(RBS_SIGNATURE) ||
|
|
274
|
+
SORBET_METHOD_ANNOTATIONS.include?(comment.text.delete_prefix('#').strip)
|
|
77
275
|
end
|
|
78
276
|
end
|
|
79
277
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
@@ -10,13 +11,26 @@ module RuboCop
|
|
|
10
11
|
# that would need to be separated prior to proposing any API changes in zeitwerk.
|
|
11
12
|
#
|
|
12
13
|
class DesiredZeitwerkApi
|
|
13
|
-
|
|
14
|
+
class NamespaceContext
|
|
15
|
+
#: String
|
|
16
|
+
attr_reader :current_namespace
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
#: String
|
|
19
|
+
attr_reader :current_fully_qualified_constant
|
|
20
|
+
|
|
21
|
+
#: String
|
|
22
|
+
attr_reader :expected_namespace
|
|
23
|
+
|
|
24
|
+
#: String
|
|
25
|
+
attr_reader :expected_filepath
|
|
26
|
+
|
|
27
|
+
#: (current_namespace: String, current_fully_qualified_constant: String, expected_namespace: String, expected_filepath: String) -> void
|
|
28
|
+
def initialize(current_namespace:, current_fully_qualified_constant:, expected_namespace:, expected_filepath:)
|
|
29
|
+
@current_namespace = current_namespace
|
|
30
|
+
@current_fully_qualified_constant = current_fully_qualified_constant
|
|
31
|
+
@expected_namespace = expected_namespace
|
|
32
|
+
@expected_filepath = expected_filepath
|
|
33
|
+
end
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
#
|
|
@@ -28,7 +42,7 @@ module RuboCop
|
|
|
28
42
|
# Likely this means that our own cop should determine the desired namespace and pass that in
|
|
29
43
|
# and this can determine actual namespace and how to get to expected.
|
|
30
44
|
#
|
|
31
|
-
|
|
45
|
+
#: (String relative_filename, ::Packs::Pack package_for_path) -> NamespaceContext?
|
|
32
46
|
def for_file(relative_filename, package_for_path)
|
|
33
47
|
package_name = package_for_path.name
|
|
34
48
|
|
|
@@ -48,26 +62,29 @@ module RuboCop
|
|
|
48
62
|
# Therefore, for our implementation, we substitute out the non-namespace producing portions of the filename to count the number of namespaces.
|
|
49
63
|
# Note this will *not work* properly in applications that have different assumptions about autoloading.
|
|
50
64
|
|
|
51
|
-
path_without_package_base = relative_filename.gsub(%r
|
|
65
|
+
path_without_package_base = relative_filename.gsub(%r(#{package_name}/app/), '')
|
|
52
66
|
if path_without_package_base.include?('/concerns/')
|
|
53
67
|
autoload_folder_name = path_without_package_base.split('/').first(2).join('/')
|
|
54
68
|
else
|
|
55
|
-
autoload_folder_name = path_without_package_base.split('/').
|
|
69
|
+
autoload_folder_name = path_without_package_base.split('/').fetch(0)
|
|
56
70
|
end
|
|
57
71
|
|
|
58
|
-
remaining_file_path = path_without_package_base.gsub(%r
|
|
72
|
+
remaining_file_path = path_without_package_base.gsub(%r(\A#{autoload_folder_name}/), '')
|
|
59
73
|
actual_namespace = get_actual_namespace(remaining_file_path, package_name)
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
app_or_lib =
|
|
76
|
+
if relative_filename.include?('app/')
|
|
77
|
+
'app'
|
|
78
|
+
elsif relative_filename.include?('lib/')
|
|
79
|
+
'lib'
|
|
80
|
+
else
|
|
81
|
+
raise ArgumentError, "#{relative_filename} is in neither app/ nor lib/"
|
|
82
|
+
end
|
|
66
83
|
|
|
67
84
|
absolute_desired_path = root_pathname.join(
|
|
68
85
|
package_name,
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
app_or_lib,
|
|
87
|
+
autoload_folder_name,
|
|
71
88
|
package_for_path.last_name,
|
|
72
89
|
remaining_file_path
|
|
73
90
|
)
|
|
@@ -75,26 +92,26 @@ module RuboCop
|
|
|
75
92
|
relative_desired_path = absolute_desired_path.relative_path_from(root_pathname)
|
|
76
93
|
|
|
77
94
|
NamespaceContext.new(
|
|
78
|
-
current_namespace:
|
|
95
|
+
current_namespace: actual_namespace.split('::').fetch(0),
|
|
79
96
|
current_fully_qualified_constant: actual_namespace,
|
|
80
97
|
expected_namespace: get_pack_based_namespace(package_for_path),
|
|
81
98
|
expected_filepath: relative_desired_path.to_s
|
|
82
99
|
)
|
|
83
100
|
end
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
#: (::Packs::Pack pack) -> String
|
|
86
103
|
def get_pack_based_namespace(pack)
|
|
87
104
|
pack.last_name.camelize
|
|
88
105
|
end
|
|
89
106
|
|
|
90
107
|
private
|
|
91
108
|
|
|
92
|
-
|
|
109
|
+
#: -> Pathname
|
|
93
110
|
def root_pathname
|
|
94
111
|
Pathname.pwd
|
|
95
112
|
end
|
|
96
113
|
|
|
97
|
-
|
|
114
|
+
#: (String remaining_file_path, String package_name) -> String
|
|
98
115
|
def get_actual_namespace(remaining_file_path, package_name)
|
|
99
116
|
# If the remaining file path is a ruby file (not a directory), then it establishes a global namespace
|
|
100
117
|
# Otherwise, per Zeitwerk's conventions as listed above, its a directory that establishes another global namespace
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# For String#camelize
|
|
4
5
|
require 'active_support/core_ext/string/inflections'
|
|
@@ -24,11 +25,9 @@ module RuboCop
|
|
|
24
25
|
# class Foo::Blah::Bar; end
|
|
25
26
|
#
|
|
26
27
|
class RootNamespaceIsPackName < Base
|
|
27
|
-
extend T::Sig
|
|
28
|
-
|
|
29
28
|
include RangeHelp
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
#: -> void
|
|
32
31
|
def on_new_investigation
|
|
33
32
|
absolute_filepath = Pathname.new(processed_source.file_path)
|
|
34
33
|
relative_filepath = absolute_filepath.relative_path_from(Pathname.pwd)
|
|
@@ -44,12 +43,16 @@ module RuboCop
|
|
|
44
43
|
return if package_for_path.nil?
|
|
45
44
|
|
|
46
45
|
namespace_context = desired_zeitwerk_api.for_file(relative_filename, package_for_path)
|
|
46
|
+
# :nocov: defensive: for_file always returns a context for the `app/` paths that reach here
|
|
47
47
|
return if namespace_context.nil?
|
|
48
|
+
# :nocov:
|
|
48
49
|
|
|
49
|
-
allowed_global_namespaces = Set.new(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
allowed_global_namespaces = Set.new(
|
|
51
|
+
[
|
|
52
|
+
namespace_context.expected_namespace,
|
|
53
|
+
*RuboCop::Packs.config.globally_permitted_namespaces,
|
|
54
|
+
]
|
|
55
|
+
)
|
|
53
56
|
|
|
54
57
|
package_name = package_for_path.name
|
|
55
58
|
actual_namespace = namespace_context.current_namespace
|
|
@@ -63,29 +66,25 @@ module RuboCop
|
|
|
63
66
|
add_offense(
|
|
64
67
|
source_range(processed_source.buffer, 1, 0),
|
|
65
68
|
message: format(
|
|
66
|
-
'Based on the filepath, this file defines
|
|
67
|
-
package_name
|
|
68
|
-
expected_namespace
|
|
69
|
+
'Based on the filepath, this file defines `%{current_fully_qualified_constant}`, but it should be namespaced as `%{expected_namespace}::%{current_fully_qualified_constant}` with path `%{expected_path}`.',
|
|
70
|
+
package_name:,
|
|
71
|
+
expected_namespace:,
|
|
69
72
|
expected_path: relative_desired_path,
|
|
70
|
-
current_fully_qualified_constant:
|
|
73
|
+
current_fully_qualified_constant:
|
|
71
74
|
)
|
|
72
75
|
)
|
|
73
76
|
end
|
|
74
77
|
end
|
|
75
78
|
|
|
76
|
-
# In the future, we'd love this to support auto-correct
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
def support_autocorrect?
|
|
80
|
-
false
|
|
81
|
-
end
|
|
79
|
+
# In the future, we'd love this to support auto-correct,
|
|
80
|
+
# perhaps by automatically renamespacing the file and changing its location.
|
|
81
|
+
# That would mean extending `AutoCorrector` and implementing the correction.
|
|
82
82
|
|
|
83
83
|
private
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
#: -> DesiredZeitwerkApi
|
|
86
86
|
def desired_zeitwerk_api
|
|
87
|
-
@desired_zeitwerk_api ||=
|
|
88
|
-
@desired_zeitwerk_api ||= DesiredZeitwerkApi.new
|
|
87
|
+
@desired_zeitwerk_api ||= DesiredZeitwerkApi.new #: DesiredZeitwerkApi?
|
|
89
88
|
end
|
|
90
89
|
end
|
|
91
90
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require 'rubocop-sorbet'
|
|
4
5
|
|
|
@@ -32,9 +33,8 @@ module RuboCop
|
|
|
32
33
|
#
|
|
33
34
|
# We can apply this same pattern if we want to use other cops in the context of package protections and prevent clashing.
|
|
34
35
|
#
|
|
35
|
-
extend T::Sig
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
#: -> void
|
|
38
38
|
def on_new_investigation
|
|
39
39
|
return unless processed_source.path.include?('app/public')
|
|
40
40
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# For String#underscore
|
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
|
2
6
|
|
|
3
7
|
module RuboCop
|
|
4
8
|
module Cop
|
|
@@ -9,31 +13,45 @@ module RuboCop
|
|
|
9
13
|
# that would need to be separated prior to proposing any API changes in zeitwerk.
|
|
10
14
|
#
|
|
11
15
|
class ConstantResolver
|
|
12
|
-
|
|
16
|
+
class ConstantReference
|
|
17
|
+
#: String
|
|
18
|
+
attr_reader :constant_name
|
|
19
|
+
|
|
20
|
+
#: String
|
|
21
|
+
attr_reader :global_namespace
|
|
22
|
+
|
|
23
|
+
#: ParsePackwerk::Package
|
|
24
|
+
attr_reader :source_package
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
#: Pathname
|
|
27
|
+
attr_reader :constant_definition_location
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
#: Pathname
|
|
30
|
+
attr_reader :referencing_file
|
|
31
|
+
|
|
32
|
+
#: (constant_name: String, global_namespace: String, source_package: ParsePackwerk::Package, constant_definition_location: Pathname, referencing_file: Pathname) -> void
|
|
33
|
+
def initialize(constant_name:, global_namespace:, source_package:, constant_definition_location:, referencing_file:)
|
|
34
|
+
@constant_name = constant_name
|
|
35
|
+
@global_namespace = global_namespace
|
|
36
|
+
@source_package = source_package
|
|
37
|
+
@constant_definition_location = constant_definition_location
|
|
38
|
+
@referencing_file = referencing_file
|
|
39
|
+
end
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
#: -> ParsePackwerk::Package
|
|
24
42
|
def referencing_package
|
|
25
43
|
ParsePackwerk.package_from_path(referencing_file)
|
|
26
44
|
end
|
|
27
45
|
|
|
28
|
-
|
|
46
|
+
#: -> bool
|
|
29
47
|
def public_api?
|
|
30
48
|
# PackwerkExtensions should have a method to take in a path and determine if the file is public.
|
|
31
|
-
# For now we put it here and only support the public folder (and not specific private constants).
|
|
32
|
-
# However if we declare that dependency we may want to extract this into `rubocop-packwerk_lite` or something
|
|
49
|
+
# For now, we put it here and only support the public folder (and not specific private constants).
|
|
50
|
+
# However, if we declare that dependency we may want to extract this into `rubocop-packwerk_lite` or something like that!
|
|
33
51
|
constant_definition_location.to_s.include?('/public/')
|
|
34
52
|
end
|
|
35
53
|
|
|
36
|
-
|
|
54
|
+
#: (RuboCop::AST::ConstNode node, RuboCop::AST::ProcessedSource processed_source) -> ConstantReference?
|
|
37
55
|
def self.resolve(node, processed_source)
|
|
38
56
|
constant_name = node.const_name
|
|
39
57
|
namespaces = constant_name.split('::')
|
|
@@ -46,33 +64,35 @@ module RuboCop
|
|
|
46
64
|
expected_containing_pack = ParsePackwerk.all.find { |p| p.name.include?("/#{expected_containing_pack_last_name}") }
|
|
47
65
|
return if expected_containing_pack.nil?
|
|
48
66
|
|
|
49
|
-
if namespaces.
|
|
67
|
+
if namespaces.one?
|
|
50
68
|
found_files = expected_containing_pack.directory.glob("app/*/#{expected_containing_pack_last_name}.rb")
|
|
51
69
|
else
|
|
52
70
|
expected_location_in_pack = namespaces[1..].map(&:underscore).join('/')
|
|
53
71
|
found_files = expected_containing_pack.directory.glob("app/*/#{expected_containing_pack_last_name}/#{expected_location_in_pack}.rb")
|
|
54
72
|
end
|
|
55
73
|
|
|
56
|
-
# Because of how
|
|
74
|
+
# Because of how Zeitwerk works, we know two things:
|
|
57
75
|
# 1) Since namespaces map one to one with files, Zeitwerk does not permit multiple files to define the same fully-qualified class/module.
|
|
58
76
|
# (Note it does permit multiple files to open up portions of other namespaces)
|
|
59
77
|
# 2) If a file *could* define a fully qualified constant, then it *must* define that constant!
|
|
60
78
|
#
|
|
61
79
|
# Therefore when we've found possible files, we can sanity check there is only one,
|
|
62
80
|
# and then assume the found pack defines the constant!
|
|
81
|
+
# :nocov: defensive invariant: Zeitwerk guarantees a constant maps to at most one file
|
|
63
82
|
raise if found_files.count > 1
|
|
83
|
+
# :nocov:
|
|
64
84
|
|
|
65
85
|
expected_pack_contains_constant = found_files.any?
|
|
66
86
|
|
|
67
87
|
return if !expected_pack_contains_constant
|
|
68
88
|
|
|
69
|
-
found_file = found_files.
|
|
89
|
+
found_file = found_files.fetch(0)
|
|
70
90
|
|
|
71
91
|
ConstantReference.new(
|
|
72
|
-
constant_name
|
|
73
|
-
global_namespace
|
|
92
|
+
constant_name:,
|
|
93
|
+
global_namespace:,
|
|
74
94
|
source_package: expected_containing_pack,
|
|
75
|
-
constant_definition_location:
|
|
95
|
+
constant_definition_location: found_file,
|
|
76
96
|
referencing_file: Pathname.new(processed_source.path).relative_path_from(Pathname.pwd)
|
|
77
97
|
)
|
|
78
98
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
@@ -37,14 +38,7 @@ module RuboCop
|
|
|
37
38
|
# # - packs/bar
|
|
38
39
|
#
|
|
39
40
|
class Dependency < Base
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
sig { returns(T::Boolean) }
|
|
43
|
-
def support_autocorrect?
|
|
44
|
-
false
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
sig { params(node: RuboCop::AST::ConstNode).void }
|
|
41
|
+
#: (RuboCop::AST::ConstNode node) -> void
|
|
48
42
|
def on_const(node)
|
|
49
43
|
return if Private.partial_const_reference?(node)
|
|
50
44
|
|
|
@@ -59,15 +53,13 @@ module RuboCop
|
|
|
59
53
|
is_new_violation = [
|
|
60
54
|
!constant_reference.referencing_package.dependencies.include?(constant_reference.source_package.name),
|
|
61
55
|
constant_reference.referencing_package.enforces_dependencies?,
|
|
62
|
-
!Private.violation_in_package_todo_yml?(constant_reference, type: 'dependency')
|
|
56
|
+
!Private.violation_in_package_todo_yml?(constant_reference, type: 'dependency'),
|
|
63
57
|
].all?
|
|
64
58
|
|
|
65
59
|
if is_new_violation
|
|
66
60
|
add_offense(
|
|
67
61
|
node.source_range,
|
|
68
|
-
message:
|
|
69
|
-
'Dependency violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
|
|
70
|
-
)
|
|
62
|
+
message: 'Dependency violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
|
|
71
63
|
)
|
|
72
64
|
end
|
|
73
65
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
5
6
|
module PackwerkLite
|
|
6
|
-
# This cop helps ensure that packs are using public API of other systems
|
|
7
|
+
# This cop helps ensure that packs are using the public API of other systems
|
|
7
8
|
# The following examples assume this basic setup.
|
|
8
9
|
#
|
|
9
10
|
# @example
|
|
@@ -38,21 +39,14 @@ module RuboCop
|
|
|
38
39
|
# end
|
|
39
40
|
#
|
|
40
41
|
class Privacy < Base
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
sig { returns(T::Boolean) }
|
|
44
|
-
def support_autocorrect?
|
|
45
|
-
false
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
sig { params(node: RuboCop::AST::ConstNode).void }
|
|
42
|
+
#: (RuboCop::AST::ConstNode node) -> void
|
|
49
43
|
def on_const(node)
|
|
50
44
|
# See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/lint/constant_resolution.rb source code as an example
|
|
51
45
|
return if Private.partial_const_reference?(node)
|
|
52
46
|
|
|
53
47
|
constant_reference = ConstantResolver::ConstantReference.resolve(node, processed_source)
|
|
54
48
|
|
|
55
|
-
# If we can't determine a constant reference, we can just early return. This could be
|
|
49
|
+
# If we can't determine a constant reference, we can just early return. This could be because the constant is defined
|
|
56
50
|
# in a gem OR because it's not abiding by the namespace convention we've established.
|
|
57
51
|
return if constant_reference.nil?
|
|
58
52
|
return if constant_reference.referencing_package.name == constant_reference.source_package.name
|
|
@@ -60,15 +54,13 @@ module RuboCop
|
|
|
60
54
|
is_new_violation = [
|
|
61
55
|
!constant_reference.public_api?,
|
|
62
56
|
constant_reference.source_package.enforces_privacy?,
|
|
63
|
-
!Private.violation_in_package_todo_yml?(constant_reference)
|
|
57
|
+
!Private.violation_in_package_todo_yml?(constant_reference),
|
|
64
58
|
].all?
|
|
65
59
|
|
|
66
60
|
if is_new_violation
|
|
67
61
|
add_offense(
|
|
68
62
|
node.source_range,
|
|
69
|
-
message:
|
|
70
|
-
'Privacy violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
|
|
71
|
-
)
|
|
63
|
+
message: 'Privacy violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
|
|
72
64
|
)
|
|
73
65
|
end
|
|
74
66
|
end
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
5
6
|
module PackwerkLite
|
|
6
7
|
module Private
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
sig { params(node: RuboCop::AST::ConstNode).returns(T::Boolean) }
|
|
8
|
+
#: (RuboCop::AST::ConstNode node) -> bool
|
|
10
9
|
def self.partial_const_reference?(node)
|
|
11
10
|
# This is a bit whacky, but if I have a reference in the code like this: Foo::Bar::Baz.any_method, `on_const` will be called three times:
|
|
12
11
|
# One with `Foo`, one with `Foo::Bar`, and one with `Foo::Bar::Baz`.
|
|
@@ -23,7 +22,7 @@ module RuboCop
|
|
|
23
22
|
right_sibling.to_s[0].capitalize == right_sibling.to_s[0]
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
#: (ConstantResolver::ConstantReference constant_reference, ?type: String) -> bool
|
|
27
26
|
def self.violation_in_package_todo_yml?(constant_reference, type: 'privacy')
|
|
28
27
|
existing_violations = ParsePackwerk::PackageTodo.for(constant_reference.referencing_package).violations
|
|
29
28
|
existing_violations.any? do |v|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'lint_roller'
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Packs
|
|
8
|
+
# A plugin that integrates rubocop-packs with RuboCop's plugin system.
|
|
9
|
+
class Plugin < LintRoller::Plugin
|
|
10
|
+
#: -> LintRoller::About
|
|
11
|
+
def about
|
|
12
|
+
LintRoller::About.new(
|
|
13
|
+
name: 'rubocop-packs',
|
|
14
|
+
version: VERSION,
|
|
15
|
+
homepage: 'https://github.com/rubyatscale/rubocop-packs',
|
|
16
|
+
description: 'A collection of Rubocop rules for gradually modularizing a ruby codebase'
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#: (LintRoller::Context context) -> bool
|
|
21
|
+
def supported?(context)
|
|
22
|
+
context.engine == :rubocop
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: (LintRoller::Context _context) -> LintRoller::Rules
|
|
26
|
+
def rules(_context)
|
|
27
|
+
LintRoller::Rules.new(
|
|
28
|
+
type: :path,
|
|
29
|
+
config_format: :rubocop,
|
|
30
|
+
value: Pathname.new(__dir__).join('../../../config/default.yml')
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -5,17 +5,15 @@ module RuboCop
|
|
|
5
5
|
module Packs
|
|
6
6
|
module Private
|
|
7
7
|
class Configuration
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sig { returns(T::Array[String]) }
|
|
8
|
+
#: Array[String]
|
|
11
9
|
attr_accessor :globally_permitted_namespaces
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
#: -> void
|
|
14
12
|
def initialize
|
|
15
|
-
@globally_permitted_namespaces =
|
|
13
|
+
@globally_permitted_namespaces = [] #: Array[String]
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
#: -> void
|
|
19
17
|
def bust_cache!
|
|
20
18
|
@globally_permitted_namespaces = []
|
|
21
19
|
end
|
|
@@ -2,21 +2,18 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'rubocop/packs/private/configuration'
|
|
5
|
-
require 'rubocop/packs/private/offense'
|
|
6
5
|
|
|
7
6
|
module RuboCop
|
|
8
7
|
module Packs
|
|
9
8
|
module Private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sig { void }
|
|
9
|
+
#: -> void
|
|
13
10
|
def self.bust_cache!
|
|
14
11
|
@loaded_client_configuration = nil
|
|
15
12
|
end
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
#: -> void
|
|
18
15
|
def self.load_client_configuration
|
|
19
|
-
@loaded_client_configuration ||=
|
|
16
|
+
@loaded_client_configuration ||= false #: bool?
|
|
20
17
|
return if @loaded_client_configuration
|
|
21
18
|
|
|
22
19
|
@loaded_client_configuration = true
|
data/lib/rubocop/packs.rb
CHANGED
|
@@ -5,28 +5,21 @@ require 'rubocop/packs/private'
|
|
|
5
5
|
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Packs
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
PROJECT_ROOT = T.let(Pathname.new(__dir__).parent.parent.expand_path.freeze, Pathname)
|
|
11
|
-
CONFIG_DEFAULT = T.let(PROJECT_ROOT.join('config', 'default.yml').freeze, Pathname)
|
|
12
|
-
|
|
13
|
-
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
|
14
|
-
|
|
15
|
-
sig { void }
|
|
8
|
+
#: -> void
|
|
16
9
|
def self.bust_cache!
|
|
17
10
|
config.bust_cache!
|
|
18
11
|
Private.bust_cache!
|
|
19
12
|
end
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
#: { (Private::Configuration arg0) -> void } -> void
|
|
22
15
|
def self.configure(&blk)
|
|
23
16
|
yield(config)
|
|
24
17
|
end
|
|
25
18
|
|
|
26
|
-
|
|
19
|
+
#: -> Private::Configuration
|
|
27
20
|
def self.config
|
|
28
21
|
Private.load_client_configuration
|
|
29
|
-
@config ||=
|
|
22
|
+
@config ||= Private::Configuration.new #: Private::Configuration?
|
|
30
23
|
end
|
|
31
24
|
end
|
|
32
25
|
end
|
data/lib/rubocop-packs.rb
CHANGED
|
@@ -5,12 +5,11 @@ require 'rubocop'
|
|
|
5
5
|
require 'packs-specification'
|
|
6
6
|
|
|
7
7
|
require_relative 'rubocop/packs'
|
|
8
|
-
require_relative 'rubocop/packs/
|
|
8
|
+
require_relative 'rubocop/packs/version'
|
|
9
|
+
require_relative 'rubocop/packs/plugin'
|
|
9
10
|
require_relative 'rubocop/packwerk_lite'
|
|
10
11
|
|
|
11
12
|
require 'rubocop/cop/packs/root_namespace_is_pack_name'
|
|
12
13
|
require 'rubocop/cop/packs/typed_public_apis'
|
|
13
14
|
require 'rubocop/cop/packs/class_methods_as_public_apis'
|
|
14
15
|
require 'rubocop/cop/packs/documented_public_apis'
|
|
15
|
-
|
|
16
|
-
RuboCop::Packs::Inject.defaults!
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-packs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gusto Engineers
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -16,30 +15,30 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '7.1'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
25
|
+
version: '7.1'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: lint_roller
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
32
|
+
version: '1.1'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
39
|
+
version: '1.1'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: packs-specification
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
@@ -53,7 +52,7 @@ dependencies:
|
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: parse_packwerk
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
58
|
- - ">="
|
|
@@ -67,55 +66,55 @@ dependencies:
|
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: '0'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
69
|
+
name: rubocop
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
72
|
- - ">="
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
74
|
+
version: '1.72'
|
|
76
75
|
type: :runtime
|
|
77
76
|
prerelease: false
|
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
78
|
requirements:
|
|
80
79
|
- - ">="
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
81
|
+
version: '1.72'
|
|
83
82
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rubocop
|
|
83
|
+
name: rubocop-sorbet
|
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
|
86
85
|
requirements:
|
|
87
|
-
- - "
|
|
86
|
+
- - ">="
|
|
88
87
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
88
|
+
version: 0.9.0
|
|
90
89
|
type: :runtime
|
|
91
90
|
prerelease: false
|
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
92
|
requirements:
|
|
94
|
-
- - "
|
|
93
|
+
- - ">="
|
|
95
94
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
95
|
+
version: 0.9.0
|
|
97
96
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
97
|
+
name: bundler
|
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
|
100
99
|
requirements:
|
|
101
100
|
- - ">="
|
|
102
101
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: 0
|
|
104
|
-
type: :
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :development
|
|
105
104
|
prerelease: false
|
|
106
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
106
|
requirements:
|
|
108
107
|
- - ">="
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: 0
|
|
109
|
+
version: '0'
|
|
111
110
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
111
|
+
name: parser
|
|
113
112
|
requirement: !ruby/object:Gem::Requirement
|
|
114
113
|
requirements:
|
|
115
114
|
- - ">="
|
|
116
115
|
- !ruby/object:Gem::Version
|
|
117
116
|
version: '0'
|
|
118
|
-
type: :
|
|
117
|
+
type: :development
|
|
119
118
|
prerelease: false
|
|
120
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
120
|
requirements:
|
|
@@ -123,35 +122,35 @@ dependencies:
|
|
|
123
122
|
- !ruby/object:Gem::Version
|
|
124
123
|
version: '0'
|
|
125
124
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
125
|
+
name: rake
|
|
127
126
|
requirement: !ruby/object:Gem::Requirement
|
|
128
127
|
requirements:
|
|
129
|
-
- - "
|
|
128
|
+
- - ">="
|
|
130
129
|
- !ruby/object:Gem::Version
|
|
131
|
-
version:
|
|
130
|
+
version: '0'
|
|
132
131
|
type: :development
|
|
133
132
|
prerelease: false
|
|
134
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
134
|
requirements:
|
|
136
|
-
- - "
|
|
135
|
+
- - ">="
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
|
-
version:
|
|
137
|
+
version: '0'
|
|
139
138
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
139
|
+
name: rspec
|
|
141
140
|
requirement: !ruby/object:Gem::Requirement
|
|
142
141
|
requirements:
|
|
143
142
|
- - ">="
|
|
144
143
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
144
|
+
version: '3.0'
|
|
146
145
|
type: :development
|
|
147
146
|
prerelease: false
|
|
148
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
148
|
requirements:
|
|
150
149
|
- - ">="
|
|
151
150
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
151
|
+
version: '3.0'
|
|
153
152
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
153
|
+
name: rubocop-extension-generator
|
|
155
154
|
requirement: !ruby/object:Gem::Requirement
|
|
156
155
|
requirements:
|
|
157
156
|
- - ">="
|
|
@@ -165,21 +164,21 @@ dependencies:
|
|
|
165
164
|
- !ruby/object:Gem::Version
|
|
166
165
|
version: '0'
|
|
167
166
|
- !ruby/object:Gem::Dependency
|
|
168
|
-
name:
|
|
167
|
+
name: rubocop-gusto
|
|
169
168
|
requirement: !ruby/object:Gem::Requirement
|
|
170
169
|
requirements:
|
|
171
|
-
- - "
|
|
170
|
+
- - ">="
|
|
172
171
|
- !ruby/object:Gem::Version
|
|
173
|
-
version: '
|
|
172
|
+
version: '0'
|
|
174
173
|
type: :development
|
|
175
174
|
prerelease: false
|
|
176
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
176
|
requirements:
|
|
178
|
-
- - "
|
|
177
|
+
- - ">="
|
|
179
178
|
- !ruby/object:Gem::Version
|
|
180
|
-
version: '
|
|
179
|
+
version: '0'
|
|
181
180
|
- !ruby/object:Gem::Dependency
|
|
182
|
-
name:
|
|
181
|
+
name: simplecov
|
|
183
182
|
requirement: !ruby/object:Gem::Requirement
|
|
184
183
|
requirements:
|
|
185
184
|
- - ">="
|
|
@@ -220,6 +219,20 @@ dependencies:
|
|
|
220
219
|
- - ">="
|
|
221
220
|
- !ruby/object:Gem::Version
|
|
222
221
|
version: '0'
|
|
222
|
+
- !ruby/object:Gem::Dependency
|
|
223
|
+
name: yard
|
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
|
225
|
+
requirements:
|
|
226
|
+
- - ">="
|
|
227
|
+
- !ruby/object:Gem::Version
|
|
228
|
+
version: '0'
|
|
229
|
+
type: :development
|
|
230
|
+
prerelease: false
|
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
232
|
+
requirements:
|
|
233
|
+
- - ">="
|
|
234
|
+
- !ruby/object:Gem::Version
|
|
235
|
+
version: '0'
|
|
223
236
|
description: A collection of Rubocop rules for gradually modularizing a ruby codebase
|
|
224
237
|
email:
|
|
225
238
|
- dev@gusto.com
|
|
@@ -240,10 +253,10 @@ files:
|
|
|
240
253
|
- lib/rubocop/cop/packwerk_lite/privacy_checker.rb
|
|
241
254
|
- lib/rubocop/cop/packwerk_lite/private.rb
|
|
242
255
|
- lib/rubocop/packs.rb
|
|
243
|
-
- lib/rubocop/packs/
|
|
256
|
+
- lib/rubocop/packs/plugin.rb
|
|
244
257
|
- lib/rubocop/packs/private.rb
|
|
245
258
|
- lib/rubocop/packs/private/configuration.rb
|
|
246
|
-
- lib/rubocop/packs/
|
|
259
|
+
- lib/rubocop/packs/version.rb
|
|
247
260
|
- lib/rubocop/packwerk_lite.rb
|
|
248
261
|
homepage: https://github.com/rubyatscale/rubocop-packs
|
|
249
262
|
licenses:
|
|
@@ -253,7 +266,7 @@ metadata:
|
|
|
253
266
|
source_code_uri: https://github.com/rubyatscale/rubocop-packs
|
|
254
267
|
changelog_uri: https://github.com/rubyatscale/rubocop-packs/releases
|
|
255
268
|
allowed_push_host: https://rubygems.org
|
|
256
|
-
|
|
269
|
+
default_lint_roller_plugin: RuboCop::Packs::Plugin
|
|
257
270
|
rdoc_options: []
|
|
258
271
|
require_paths:
|
|
259
272
|
- lib
|
|
@@ -261,15 +274,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
261
274
|
requirements:
|
|
262
275
|
- - ">="
|
|
263
276
|
- !ruby/object:Gem::Version
|
|
264
|
-
version: '
|
|
277
|
+
version: '3.3'
|
|
265
278
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
279
|
requirements:
|
|
267
280
|
- - ">="
|
|
268
281
|
- !ruby/object:Gem::Version
|
|
269
282
|
version: '0'
|
|
270
283
|
requirements: []
|
|
271
|
-
rubygems_version: 3.
|
|
272
|
-
signing_key:
|
|
284
|
+
rubygems_version: 3.6.9
|
|
273
285
|
specification_version: 4
|
|
274
286
|
summary: A collection of Rubocop rules for gradually modularizing a ruby codebase
|
|
275
287
|
test_files: []
|
data/lib/rubocop/packs/inject.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# The original code is from https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
|
5
|
-
# See https://github.com/rubocop/rubocop-rspec/blob/master/MIT-LICENSE.md
|
|
6
|
-
module RuboCop
|
|
7
|
-
module Packs
|
|
8
|
-
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
|
9
|
-
# bit of our configuration.
|
|
10
|
-
module Inject
|
|
11
|
-
extend T::Sig
|
|
12
|
-
|
|
13
|
-
sig { void }
|
|
14
|
-
def self.defaults!
|
|
15
|
-
path = CONFIG_DEFAULT.to_s
|
|
16
|
-
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
|
17
|
-
config = Config.new(hash, path).tap(&:make_excludes_absolute)
|
|
18
|
-
puts "configuration from #{path}" if ConfigLoader.debug?
|
|
19
|
-
config = ConfigLoader.merge_with_default(config, path)
|
|
20
|
-
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Packs
|
|
5
|
-
module Private
|
|
6
|
-
class Offense < T::Struct
|
|
7
|
-
extend T::Sig
|
|
8
|
-
|
|
9
|
-
const :cop_name, String
|
|
10
|
-
const :filepath, String
|
|
11
|
-
|
|
12
|
-
sig { returns(T.nilable(::Packs::Pack)) }
|
|
13
|
-
def pack
|
|
14
|
-
::Packs.for_file(filepath)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|