rubocop-packs 0.1.0 → 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/config/default.yml +2 -0
- data/lib/rubocop/cop/packs/class_methods_as_public_apis.rb +2 -4
- data/lib/rubocop/cop/packs/documented_public_apis.rb +205 -13
- data/lib/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api.rb +36 -19
- data/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb +4 -6
- data/lib/rubocop/cop/packs/typed_public_apis.rb +2 -2
- data/lib/rubocop/cop/packwerk_lite/constant_resolver.rb +31 -13
- data/lib/rubocop/cop/packwerk_lite/dependency_checker.rb +2 -3
- data/lib/rubocop/cop/packwerk_lite/privacy_checker.rb +2 -3
- data/lib/rubocop/cop/packwerk_lite/private.rb +3 -4
- data/lib/rubocop/packs/plugin.rb +3 -5
- data/lib/rubocop/packs/private/configuration.rb +4 -6
- data/lib/rubocop/packs/private.rb +3 -6
- data/lib/rubocop/packs/version.rb +2 -2
- data/lib/rubocop/packs.rb +4 -6
- data/lib/rubocop/packwerk_lite.rb +0 -1
- metadata +9 -52
- 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/config/default.yml
CHANGED
|
@@ -23,6 +23,8 @@ Packs/DocumentedPublicApis:
|
|
|
23
23
|
Description: 'Requires that each public API method (in `app/public`) has a documentation comment.'
|
|
24
24
|
Enabled: false
|
|
25
25
|
VersionAdded: '0.0.2'
|
|
26
|
+
AllowedMethods: []
|
|
27
|
+
RequireForNonPublicMethods: false
|
|
26
28
|
|
|
27
29
|
PackwerkLite/Privacy:
|
|
28
30
|
Description: 'Detects references to the private implementation of another pack instead of its public API.'
|
|
@@ -29,9 +29,7 @@ module RuboCop
|
|
|
29
29
|
# end
|
|
30
30
|
#
|
|
31
31
|
class ClassMethodsAsPublicApis < Base
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
sig { params(node: T.untyped).void }
|
|
32
|
+
#: (RuboCop::AST::DefNode node) -> void
|
|
35
33
|
def on_def(node)
|
|
36
34
|
# This cop only applies for ruby files in `app/public`
|
|
37
35
|
return if !processed_source.file_path.include?('app/public/')
|
|
@@ -59,7 +57,7 @@ module RuboCop
|
|
|
59
57
|
|
|
60
58
|
private
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
#: ((RuboCop::AST::ClassNode | RuboCop::AST::ModuleNode)? node) -> bool
|
|
63
61
|
def node_includes_acceptable_mixin?(node)
|
|
64
62
|
acceptable_mixins = cop_config['AcceptableMixins'] || []
|
|
65
63
|
return false if node.nil?
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
@@ -30,22 +31,155 @@ 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
|
-
|
|
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
|
|
173
|
+
end
|
|
174
|
+
alias_method :on_defs, :on_def
|
|
175
|
+
|
|
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
|
|
46
179
|
def check(node)
|
|
47
180
|
# This cop only applies for ruby files in `app/public`
|
|
48
181
|
return if !processed_source.file_path.include?('app/public')
|
|
182
|
+
return if non_public_method?(node) && !require_for_non_public_methods?
|
|
49
183
|
|
|
50
184
|
left_sibling = node.left_sibling
|
|
51
185
|
|
|
@@ -67,19 +201,77 @@ module RuboCop
|
|
|
67
201
|
add_offense(node)
|
|
68
202
|
end
|
|
69
203
|
|
|
70
|
-
|
|
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
|
|
71
229
|
def node_is_sorbet_signature?(node)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
245
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
270
|
|
|
81
|
-
|
|
82
|
-
|
|
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)
|
|
83
275
|
end
|
|
84
276
|
end
|
|
85
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
|
|
|
@@ -52,22 +66,25 @@ module RuboCop
|
|
|
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
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)
|
|
@@ -83,10 +82,9 @@ module RuboCop
|
|
|
83
82
|
|
|
84
83
|
private
|
|
85
84
|
|
|
86
|
-
|
|
85
|
+
#: -> DesiredZeitwerkApi
|
|
87
86
|
def desired_zeitwerk_api
|
|
88
|
-
@desired_zeitwerk_api ||=
|
|
89
|
-
@desired_zeitwerk_api ||= DesiredZeitwerkApi.new
|
|
87
|
+
@desired_zeitwerk_api ||= DesiredZeitwerkApi.new #: DesiredZeitwerkApi?
|
|
90
88
|
end
|
|
91
89
|
end
|
|
92
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,23 +13,37 @@ 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
49
|
# For now, we put it here and only support the public folder (and not specific private constants).
|
|
@@ -33,7 +51,7 @@ module RuboCop
|
|
|
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('::')
|
|
@@ -68,13 +86,13 @@ module RuboCop
|
|
|
68
86
|
|
|
69
87
|
return if !expected_pack_contains_constant
|
|
70
88
|
|
|
71
|
-
found_file = found_files.
|
|
89
|
+
found_file = found_files.fetch(0)
|
|
72
90
|
|
|
73
91
|
ConstantReference.new(
|
|
74
92
|
constant_name:,
|
|
75
93
|
global_namespace:,
|
|
76
94
|
source_package: expected_containing_pack,
|
|
77
|
-
constant_definition_location:
|
|
95
|
+
constant_definition_location: found_file,
|
|
78
96
|
referencing_file: Pathname.new(processed_source.path).relative_path_from(Pathname.pwd)
|
|
79
97
|
)
|
|
80
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,9 +38,7 @@ module RuboCop
|
|
|
37
38
|
# # - packs/bar
|
|
38
39
|
#
|
|
39
40
|
class Dependency < Base
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
sig { params(node: RuboCop::AST::ConstNode).void }
|
|
41
|
+
#: (RuboCop::AST::ConstNode node) -> void
|
|
43
42
|
def on_const(node)
|
|
44
43
|
return if Private.partial_const_reference?(node)
|
|
45
44
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RuboCop
|
|
4
5
|
module Cop
|
|
@@ -38,9 +39,7 @@ module RuboCop
|
|
|
38
39
|
# end
|
|
39
40
|
#
|
|
40
41
|
class Privacy < Base
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
sig { params(node: RuboCop::AST::ConstNode).void }
|
|
42
|
+
#: (RuboCop::AST::ConstNode node) -> void
|
|
44
43
|
def on_const(node)
|
|
45
44
|
# See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/lint/constant_resolution.rb source code as an example
|
|
46
45
|
return if Private.partial_const_reference?(node)
|
|
@@ -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|
|
data/lib/rubocop/packs/plugin.rb
CHANGED
|
@@ -7,9 +7,7 @@ module RuboCop
|
|
|
7
7
|
module Packs
|
|
8
8
|
# A plugin that integrates rubocop-packs with RuboCop's plugin system.
|
|
9
9
|
class Plugin < LintRoller::Plugin
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sig { returns(LintRoller::About) }
|
|
10
|
+
#: -> LintRoller::About
|
|
13
11
|
def about
|
|
14
12
|
LintRoller::About.new(
|
|
15
13
|
name: 'rubocop-packs',
|
|
@@ -19,12 +17,12 @@ module RuboCop
|
|
|
19
17
|
)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
#: (LintRoller::Context context) -> bool
|
|
23
21
|
def supported?(context)
|
|
24
22
|
context.engine == :rubocop
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
#: (LintRoller::Context _context) -> LintRoller::Rules
|
|
28
26
|
def rules(_context)
|
|
29
27
|
LintRoller::Rules.new(
|
|
30
28
|
type: :path,
|
|
@@ -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,23 +5,21 @@ require 'rubocop/packs/private'
|
|
|
5
5
|
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Packs
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sig { void }
|
|
8
|
+
#: -> void
|
|
11
9
|
def self.bust_cache!
|
|
12
10
|
config.bust_cache!
|
|
13
11
|
Private.bust_cache!
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
#: { (Private::Configuration arg0) -> void } -> void
|
|
17
15
|
def self.configure(&blk)
|
|
18
16
|
yield(config)
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
#: -> Private::Configuration
|
|
22
20
|
def self.config
|
|
23
21
|
Private.load_client_configuration
|
|
24
|
-
@config ||=
|
|
22
|
+
@config ||= Private::Configuration.new #: Private::Configuration?
|
|
25
23
|
end
|
|
26
24
|
end
|
|
27
25
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-packs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gusto Engineers
|
|
@@ -15,54 +15,26 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: base64
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - ">="
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
18
|
+
version: '7.1'
|
|
33
19
|
type: :runtime
|
|
34
20
|
prerelease: false
|
|
35
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
22
|
requirements:
|
|
37
23
|
- - ">="
|
|
38
24
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: bigdecimal
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
25
|
+
version: '7.1'
|
|
54
26
|
- !ruby/object:Gem::Dependency
|
|
55
27
|
name: lint_roller
|
|
56
28
|
requirement: !ruby/object:Gem::Requirement
|
|
57
29
|
requirements:
|
|
58
|
-
- - "
|
|
30
|
+
- - ">="
|
|
59
31
|
- !ruby/object:Gem::Version
|
|
60
32
|
version: '1.1'
|
|
61
33
|
type: :runtime
|
|
62
34
|
prerelease: false
|
|
63
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
36
|
requirements:
|
|
65
|
-
- - "
|
|
37
|
+
- - ">="
|
|
66
38
|
- !ruby/object:Gem::Version
|
|
67
39
|
version: '1.1'
|
|
68
40
|
- !ruby/object:Gem::Dependency
|
|
@@ -97,14 +69,14 @@ dependencies:
|
|
|
97
69
|
name: rubocop
|
|
98
70
|
requirement: !ruby/object:Gem::Requirement
|
|
99
71
|
requirements:
|
|
100
|
-
- - "
|
|
72
|
+
- - ">="
|
|
101
73
|
- !ruby/object:Gem::Version
|
|
102
74
|
version: '1.72'
|
|
103
75
|
type: :runtime
|
|
104
76
|
prerelease: false
|
|
105
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
78
|
requirements:
|
|
107
|
-
- - "
|
|
79
|
+
- - ">="
|
|
108
80
|
- !ruby/object:Gem::Version
|
|
109
81
|
version: '1.72'
|
|
110
82
|
- !ruby/object:Gem::Dependency
|
|
@@ -121,20 +93,6 @@ dependencies:
|
|
|
121
93
|
- - ">="
|
|
122
94
|
- !ruby/object:Gem::Version
|
|
123
95
|
version: 0.9.0
|
|
124
|
-
- !ruby/object:Gem::Dependency
|
|
125
|
-
name: sorbet-runtime
|
|
126
|
-
requirement: !ruby/object:Gem::Requirement
|
|
127
|
-
requirements:
|
|
128
|
-
- - ">="
|
|
129
|
-
- !ruby/object:Gem::Version
|
|
130
|
-
version: '0'
|
|
131
|
-
type: :runtime
|
|
132
|
-
prerelease: false
|
|
133
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
-
requirements:
|
|
135
|
-
- - ">="
|
|
136
|
-
- !ruby/object:Gem::Version
|
|
137
|
-
version: '0'
|
|
138
96
|
- !ruby/object:Gem::Dependency
|
|
139
97
|
name: bundler
|
|
140
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -181,14 +139,14 @@ dependencies:
|
|
|
181
139
|
name: rspec
|
|
182
140
|
requirement: !ruby/object:Gem::Requirement
|
|
183
141
|
requirements:
|
|
184
|
-
- - "
|
|
142
|
+
- - ">="
|
|
185
143
|
- !ruby/object:Gem::Version
|
|
186
144
|
version: '3.0'
|
|
187
145
|
type: :development
|
|
188
146
|
prerelease: false
|
|
189
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
190
148
|
requirements:
|
|
191
|
-
- - "
|
|
149
|
+
- - ">="
|
|
192
150
|
- !ruby/object:Gem::Version
|
|
193
151
|
version: '3.0'
|
|
194
152
|
- !ruby/object:Gem::Dependency
|
|
@@ -298,7 +256,6 @@ files:
|
|
|
298
256
|
- lib/rubocop/packs/plugin.rb
|
|
299
257
|
- lib/rubocop/packs/private.rb
|
|
300
258
|
- lib/rubocop/packs/private/configuration.rb
|
|
301
|
-
- lib/rubocop/packs/private/offense.rb
|
|
302
259
|
- lib/rubocop/packs/version.rb
|
|
303
260
|
- lib/rubocop/packwerk_lite.rb
|
|
304
261
|
homepage: https://github.com/rubyatscale/rubocop-packs
|
|
@@ -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
|