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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7697cef47d3e7da05b21e74efca28cbe80b3a7baeabf48675ceba6fe1e0c394f
4
- data.tar.gz: 25b5b3d3d20f5d621cae515533e26ba668df308dad2c3b0cfd0fac14f4a4ffb7
3
+ metadata.gz: c6fb879c4fde42e92de3719345a90fd8782cfd9239ec7845869df3ef942ed8b0
4
+ data.tar.gz: 1c2a8ad04b824ed3f5c4a5ea69032164c60057a12a45b8d2d1985b2baaa863c1
5
5
  SHA512:
6
- metadata.gz: 2660e307e4541b438d4d76a79a7a19d054564bc6aba6e33b3b438f4193efeac2498e7125d40be6eea6ba51df939388531d24e5c161f233f3cd87cca8dbf6265d
7
- data.tar.gz: e44adb56e5e53f1148b3949f7c870f80a17d9613d0a3f60938b08386b2990accea3300b59d6b3753f0ce1c9e56613539288f32d6eb98fe657eb38182bd6aefb4
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
- require:
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
- extend T::Sig
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 htis part.
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) && (d.method_name == :interface! || d.method_name == :abstract!) }
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: format(
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
- sig { params(node: T.untyped).returns(T::Boolean) }
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.count == 1 &&
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
- sig { returns(T::Boolean) }
46
- def support_autocorrect?
47
- false
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
- sig { params(node: T.untyped).void }
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 non_public?(node) && !require_for_non_public_methods?
182
+ return if non_public_method?(node) && !require_for_non_public_methods?
55
183
 
56
184
  left_sibling = node.left_sibling
57
185
 
58
- if left_sibling == :private_class_method
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
- sig { params(node: T.untyped).returns(T::Boolean) }
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
- # Is there a better way to check if a node is a sorbet signature? Probably!
76
- !!(node && (node.source.include?('sig do') || node.source.include?('sig {')))
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
- extend T::Sig
14
+ class NamespaceContext
15
+ #: String
16
+ attr_reader :current_namespace
14
17
 
15
- class NamespaceContext < T::Struct
16
- const :current_namespace, String
17
- const :current_fully_qualified_constant, String
18
- const :expected_namespace, String
19
- const :expected_filepath, String
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
- sig { params(relative_filename: String, package_for_path: ::Packs::Pack).returns(T.nilable(NamespaceContext)) }
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{#{package_name}/app/}, '')
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('/').first
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{\A#{autoload_folder_name}/}, '')
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
- if relative_filename.include?('app/')
62
- app_or_lib = 'app'
63
- elsif relative_filename.include?('lib/')
64
- app_or_lib = 'lib'
65
- end
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
- T.must(app_or_lib),
70
- T.must(autoload_folder_name),
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: T.must(actual_namespace.split('::').first),
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
- sig { params(pack: ::Packs::Pack).returns(String) }
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
- sig { returns(Pathname) }
109
+ #: -> Pathname
93
110
  def root_pathname
94
111
  Pathname.pwd
95
112
  end
96
113
 
97
- sig { params(remaining_file_path: String, package_name: String).returns(String) }
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
- sig { void }
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
- namespace_context.expected_namespace,
51
- *RuboCop::Packs.config.globally_permitted_namespaces
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 `%<current_fully_qualified_constant>s`, but it should be namespaced as `%<expected_namespace>s::%<current_fully_qualified_constant>s` with path `%<expected_path>s`.',
67
- package_name: package_name,
68
- expected_namespace: 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: 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
- # Perhaps by automatically renamespacing the file and changing its location?
78
- sig { returns(T::Boolean) }
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
- sig { returns(DesiredZeitwerkApi) }
85
+ #: -> DesiredZeitwerkApi
86
86
  def desired_zeitwerk_api
87
- @desired_zeitwerk_api ||= T.let(nil, T.nilable(DesiredZeitwerkApi))
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
- sig { void }
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
- extend T::Sig
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
- class ConstantReference < T::Struct
15
- extend T::Sig
26
+ #: Pathname
27
+ attr_reader :constant_definition_location
16
28
 
17
- const :constant_name, String
18
- const :global_namespace, String
19
- const :source_package, ParsePackwerk::Package
20
- const :constant_definition_location, Pathname
21
- const :referencing_file, Pathname
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
- sig { returns(ParsePackwerk::Package) }
41
+ #: -> ParsePackwerk::Package
24
42
  def referencing_package
25
43
  ParsePackwerk.package_from_path(referencing_file)
26
44
  end
27
45
 
28
- sig { returns(T::Boolean) }
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 liek that!
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
- sig { params(node: RuboCop::AST::ConstNode, processed_source: RuboCop::AST::ProcessedSource).returns(T.nilable(ConstantReference)) }
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.count == 1
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 Zietwerk works, we know two things:
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.first
89
+ found_file = found_files.fetch(0)
70
90
 
71
91
  ConstantReference.new(
72
- constant_name: constant_name,
73
- global_namespace: global_namespace,
92
+ constant_name:,
93
+ global_namespace:,
74
94
  source_package: expected_containing_pack,
75
- constant_definition_location: T.must(found_file),
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
- extend T::Sig
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: format(
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
- extend T::Sig
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 beacuse the constant is defined
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: format(
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
- extend T::Sig
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
- sig { params(constant_reference: ConstantResolver::ConstantReference, type: String).returns(T::Boolean) }
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
- extend T::Sig
9
-
10
- sig { returns(T::Array[String]) }
8
+ #: Array[String]
11
9
  attr_accessor :globally_permitted_namespaces
12
10
 
13
- sig { void }
11
+ #: -> void
14
12
  def initialize
15
- @globally_permitted_namespaces = T.let([], T::Array[String])
13
+ @globally_permitted_namespaces = [] #: Array[String]
16
14
  end
17
15
 
18
- sig { void }
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
- extend T::Sig
11
-
12
- sig { void }
9
+ #: -> void
13
10
  def self.bust_cache!
14
11
  @loaded_client_configuration = nil
15
12
  end
16
13
 
17
- sig { void }
14
+ #: -> void
18
15
  def self.load_client_configuration
19
- @loaded_client_configuration ||= T.let(false, T.nilable(T::Boolean))
16
+ @loaded_client_configuration ||= false #: bool?
20
17
  return if @loaded_client_configuration
21
18
 
22
19
  @loaded_client_configuration = true
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Packs
6
+ VERSION = '0.1.1'
7
+ end
8
+ end
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
- extend T::Sig
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
- sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
14
+ #: { (Private::Configuration arg0) -> void } -> void
22
15
  def self.configure(&blk)
23
16
  yield(config)
24
17
  end
25
18
 
26
- sig { returns(Private::Configuration) }
19
+ #: -> Private::Configuration
27
20
  def self.config
28
21
  Private.load_client_configuration
29
- @config ||= T.let(Private::Configuration.new, T.nilable(Private::Configuration))
22
+ @config ||= Private::Configuration.new #: Private::Configuration?
30
23
  end
31
24
  end
32
25
  end
@@ -11,6 +11,5 @@ module RuboCop
11
11
  # See docs/packwerk_lite.md
12
12
  module PackwerkLite
13
13
  class Error < StandardError; end
14
- extend T::Sig
15
14
  end
16
15
  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/inject'
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.0.45
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: 2024-10-01 00:00:00.000000000 Z
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: '0'
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: '0'
25
+ version: '7.1'
27
26
  - !ruby/object:Gem::Dependency
28
- name: base64
27
+ name: lint_roller
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - ">="
32
31
  - !ruby/object:Gem::Version
33
- version: '0'
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: '0'
39
+ version: '1.1'
41
40
  - !ruby/object:Gem::Dependency
42
- name: bigdecimal
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: packs-specification
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: parse_packwerk
69
+ name: rubocop
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
72
  - - ">="
74
73
  - !ruby/object:Gem::Version
75
- version: '0'
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: '0'
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: '1.0'
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: '1.0'
95
+ version: 0.9.0
97
96
  - !ruby/object:Gem::Dependency
98
- name: rubocop-sorbet
97
+ name: bundler
99
98
  requirement: !ruby/object:Gem::Requirement
100
99
  requirements:
101
100
  - - ">="
102
101
  - !ruby/object:Gem::Version
103
- version: 0.8.4
104
- type: :runtime
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.8.4
109
+ version: '0'
111
110
  - !ruby/object:Gem::Dependency
112
- name: sorbet-runtime
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: :runtime
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: bundler
125
+ name: rake
127
126
  requirement: !ruby/object:Gem::Requirement
128
127
  requirements:
129
- - - "~>"
128
+ - - ">="
130
129
  - !ruby/object:Gem::Version
131
- version: 2.2.16
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: 2.2.16
137
+ version: '0'
139
138
  - !ruby/object:Gem::Dependency
140
- name: parser
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: rake
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: rspec
167
+ name: rubocop-gusto
169
168
  requirement: !ruby/object:Gem::Requirement
170
169
  requirements:
171
- - - "~>"
170
+ - - ">="
172
171
  - !ruby/object:Gem::Version
173
- version: '3.0'
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: '3.0'
179
+ version: '0'
181
180
  - !ruby/object:Gem::Dependency
182
- name: rubocop-extension-generator
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/inject.rb
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/private/offense.rb
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
- post_install_message:
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: '2.7'
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.5.16
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: []
@@ -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