rubocop-packs 0.0.10 → 0.0.12

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: 3aa6a83103635cffaf57bbe3187211f14b0981633a55cf04cd73f6261df3d83a
4
- data.tar.gz: 8816eab16fe3dd069a3327b8f1569e910ea07a012bb97bead1baf0f7f6cdfc66
3
+ metadata.gz: 23ba4ef8b1719f4a38ac54f304efb11c88df874e3ba15888ad83fd6e3d0b78e1
4
+ data.tar.gz: 1b2c369b9f7f34dfa6439724a5b2a4dc39addeb1843f8d24d001a9dc55be1860
5
5
  SHA512:
6
- metadata.gz: 37e828c802da1563049ce07a05e224187bfe8a3d61861f0cc05e774c311a3627d66d6ff116480d44100a9d31ef756bde1ea16777e02e5517e8f31fb0aab77595
7
- data.tar.gz: 2e54cf17766e69bfd81663a488f1171a74335dd71c55fbfc55f9d1334f6eeae5d0b0fd5e7134349b42fb61c749520a760fd1902f0dfe19c457c311cba55f10ca
6
+ metadata.gz: 16a8330e4f3649d6e37178759e23eb500dec1d152b13b8a46e1c0b33ebb8c6f4369c1e4683d1c8ab44ca58b987cedcb567a7e0692aae80744022cc0496d781c8
7
+ data.tar.gz: c022df83da7b9c811a0add0c6a13950fbb65b7289f2e687093df1aa2c3900b23aacac0e50f72d3d89deb350d94c055b98a198001692498a394ee31136c3e3e65
data/README.md CHANGED
@@ -61,6 +61,22 @@ Packs/NamespacedUnderPackageName:
61
61
  Exclude:
62
62
  - lib/example.rb
63
63
  ```
64
+
65
+ ## Other Utilities
66
+ `rubocop-packs` also has some API that help you use rubocop in a pack-based context.
67
+
68
+ ### `RuboCop::Packs.auto_generate_rubocop_todo(packs: ParsePackwerk.all)`
69
+ This API will auto-generate a `packs/some_pack/.rubocop_todo.yml`. This allows a pack to own its own exception list. Note that you need to configure `rubocop-packs` with an allow list of cops that can live in per-pack `.rubocop_todo.yml` files:
70
+ ```
71
+ # `config/rubocop_packs.rb`
72
+ RuboCop::Packs.configure do |config|
73
+ # For example:
74
+ config.permitted_pack_level_cops = ['Packs/NamespaceConvention']
75
+ end
76
+ ```
77
+
78
+ There is a supporting validation to ensure these `packs/*/.rubocop_todo.yml` files only add exceptions to the allow listed set of rules. Run this validation with `RuboCop::packs.validate`, which returns an array of errors.
79
+
64
80
  ## Contributing
65
81
 
66
82
  Bug reports and pull requests are welcome on GitHub at https://github.com/rubyatscale/rubocop-packs. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code Of Conduct](CODE_OF_CONDUCT.MD).
data/config/default.yml CHANGED
@@ -1,3 +1,5 @@
1
+ inherit_from: ./pack_config.yml
2
+
1
3
  Packs/ClassMethodsAsPublicApis:
2
4
  Enabled: true
3
5
  AcceptableParentClasses:
@@ -14,3 +16,11 @@ Packs/TypedPublicApi:
14
16
 
15
17
  Packs/RequireDocumentedPublicApis:
16
18
  Enabled: true
19
+
20
+ PackwerkLite/Privacy:
21
+ # It is recommended to use packwerk
22
+ Enabled: false
23
+
24
+ PackwerkLite/Dependency:
25
+ # It is recommended to use packwerk
26
+ Enabled: false
@@ -0,0 +1,6 @@
1
+ # Relevant documentation:
2
+ # - Inheriting config from a gem:
3
+ # - https://docs.rubocop.org/rubocop/configuration.html#inheriting-configuration-from-a-dependency-gem
4
+ # - ERB in a .rubocop.yml file
5
+ # - https://docs.rubocop.org/rubocop/configuration.html#pre-processing
6
+ <%= RuboCop::Packs.pack_based_rubocop_todos %>
@@ -6,6 +6,8 @@ module RuboCop
6
6
  class NamespaceConvention < Base
7
7
  #
8
8
  # This is a private class that represents API that we would prefer to be available somehow in Zeitwerk.
9
+ # However, the boundaries between systems (packwerk/zeitwerk, rubocop/zeitwerk) are poor in this class, so
10
+ # that would need to be separated prior to proposing any API changes in zeitwerk.
9
11
  #
10
12
  class DesiredZeitwerkApi
11
13
  extend T::Sig
@@ -4,22 +4,21 @@ module RuboCop
4
4
  module Cop
5
5
  module Packs
6
6
  # This cop helps ensure that each pack has a documented public API
7
+ # The following examples assume this basic setup.
7
8
  #
8
9
  # @example
9
10
  #
10
11
  # # bad
11
12
  # # packs/foo/app/public/foo.rb
12
13
  # class Foo
13
- # sig { void }
14
- # def bar
14
+ # def bar; end
15
15
  # end
16
16
  #
17
17
  # # packs/foo/app/public/foo.rb
18
18
  # class Foo
19
19
  # # This is a documentation comment.
20
20
  # # It can live below or below a sorbet type signature.
21
- # sig { void }
22
- # def bar
21
+ # def bar; end
23
22
  # end
24
23
  #
25
24
  class RequireDocumentedPublicApis < Style::DocumentationMethod
@@ -33,6 +33,13 @@ module RuboCop
33
33
  # We can apply this same pattern if we want to use other cops in the context of package protections and prevent clashing.
34
34
  #
35
35
  extend T::Sig
36
+
37
+ sig { params(processed_source: T.untyped).void }
38
+ def investigate(processed_source)
39
+ return unless processed_source.path.include?('app/public')
40
+
41
+ super
42
+ end
36
43
  end
37
44
  end
38
45
  end
@@ -0,0 +1,84 @@
1
+ # typed: strict
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module PackwerkLite
6
+ #
7
+ # This is a private class that represents API that we would prefer to be available somehow in Zeitwerk.
8
+ # However, the boundaries between systems (packwerk/zeitwerk, rubocop/zeitwerk) are poor in this class, so
9
+ # that would need to be separated prior to proposing any API changes in zeitwerk.
10
+ #
11
+ class ConstantResolver
12
+ extend T::Sig
13
+
14
+ class ConstantReference < T::Struct
15
+ extend T::Sig
16
+
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
22
+
23
+ sig { returns(ParsePackwerk::Package) }
24
+ def referencing_package
25
+ ParsePackwerk.package_from_path(referencing_file)
26
+ end
27
+
28
+ sig { returns(T::Boolean) }
29
+ def public_api?
30
+ # ParsePackwerk::Package 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
+ constant_definition_location.to_s.include?('/public/')
33
+ end
34
+
35
+ sig { params(node: RuboCop::AST::ConstNode, processed_source: RuboCop::AST::ProcessedSource).returns(T.nilable(ConstantReference)) }
36
+ def self.resolve(node, processed_source)
37
+ constant_name = node.const_name
38
+ namespaces = constant_name.split('::')
39
+ global_namespace = namespaces.first
40
+
41
+ expected_containing_pack_last_name = global_namespace.underscore
42
+
43
+ # We don't use ParsePackwerk.find(...) here because we want to look for nested packs, and this pack could be a child pack in a nested pack too.
44
+ # In the future, we might want `find` to be able to take a glob or a regex to look for packs with a specific name structure.
45
+ expected_containing_pack = ParsePackwerk.all.find { |p| p.name.include?("/#{expected_containing_pack_last_name}") }
46
+ return if expected_containing_pack.nil?
47
+
48
+ if namespaces.count == 1
49
+ found_files = expected_containing_pack.directory.glob("app/*/#{expected_containing_pack_last_name}.rb")
50
+ else
51
+ expected_location_in_pack = namespaces[1..].map(&:underscore).join('/')
52
+ found_files = expected_containing_pack.directory.glob("app/*/#{expected_containing_pack_last_name}/#{expected_location_in_pack}.rb")
53
+ end
54
+
55
+ # Because of how Zietwerk works, we know two things:
56
+ # 1) Since namespaces map one to one with files, Zeitwerk does not permit multiple files to define the same fully-qualified class/module.
57
+ # (Note it does permit multiple files to open up portions of other namespaces)
58
+ # 2) If a file *could* define a fully qualified constant, then it *must* define that constant!
59
+ #
60
+ # Therefore when we've found possible files, we can sanity check there is only one,
61
+ # and then assume the found pack defines the constant!
62
+ raise if found_files.count > 1
63
+
64
+ expected_pack_contains_constant = found_files.any?
65
+
66
+ return if !expected_pack_contains_constant
67
+
68
+ found_file = found_files.first
69
+
70
+ ConstantReference.new(
71
+ constant_name: constant_name,
72
+ global_namespace: global_namespace,
73
+ source_package: expected_containing_pack,
74
+ constant_definition_location: T.must(found_file),
75
+ referencing_file: Pathname.new(processed_source.path).relative_path_from(Pathname.pwd)
76
+ )
77
+ end
78
+ end
79
+ end
80
+
81
+ private_constant :ConstantResolver
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,77 @@
1
+ # typed: strict
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module PackwerkLite
6
+ # This cop helps ensure that packs are depending on packs explicitly.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # # packs/foo/app/services/foo.rb
12
+ # class Foo
13
+ # def bar
14
+ # Bar
15
+ # end
16
+ # end
17
+ #
18
+ # # packs/foo/package.yml
19
+ # # enforces_dependencies: true
20
+ # # enforces_privacy: false
21
+ # # dependencies:
22
+ # # - packs/baz
23
+ #
24
+ # # good
25
+ # # packs/foo/app/services/foo.rb
26
+ # class Foo
27
+ # def bar
28
+ # Bar
29
+ # end
30
+ # end
31
+ #
32
+ # # packs/foo/package.yml
33
+ # # enforces_dependencies: true
34
+ # # enforces_privacy: false
35
+ # # dependencies:
36
+ # # - packs/baz
37
+ # # - packs/bar
38
+ #
39
+ 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 }
48
+ def on_const(node)
49
+ return if Private.partial_const_reference?(node)
50
+
51
+ constant_reference = ConstantResolver::ConstantReference.resolve(node, processed_source)
52
+ return if constant_reference.nil?
53
+ return if constant_reference.referencing_package.name == constant_reference.source_package.name
54
+
55
+ # These are cases that don't work yet!!
56
+ # I'll need to look into this more. It's related to inflections but not sure how yet!
57
+ return if constant_reference.constant_name.include?('PncApi')
58
+
59
+ is_new_violation = [
60
+ !constant_reference.referencing_package.dependencies.include?(constant_reference.source_package.name),
61
+ constant_reference.referencing_package.enforces_dependencies?,
62
+ !Private.violation_in_deprecated_references_yml?(constant_reference, type: 'dependency')
63
+ ].all?
64
+
65
+ if is_new_violation
66
+ add_offense(
67
+ node.source_range,
68
+ message: format(
69
+ 'Dependency violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
70
+ )
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,78 @@
1
+ # typed: strict
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module PackwerkLite
6
+ # This cop helps ensure that packs are using public API of other systems
7
+ # The following examples assume this basic setup.
8
+ #
9
+ # @example
10
+ # # packs/bar/app/public/bar.rb
11
+ # class Bar
12
+ # def my_public_api; end
13
+ # end
14
+ #
15
+ # # packs/bar/app/services/private.rb
16
+ # class Private
17
+ # def my_private_api; end
18
+ # end
19
+ #
20
+ # # packs/bar/package.yml
21
+ # # enforces_dependencies: false
22
+ # # enforces_privacy: true
23
+ #
24
+ # # bad
25
+ # # packs/foo/app/services/foo.rb
26
+ # class Foo
27
+ # def bar
28
+ # Private.my_private_api
29
+ # end
30
+ # end
31
+ #
32
+ # # good
33
+ # # packs/foo/app/services/foo.rb
34
+ # class Bar
35
+ # def bar
36
+ # Bar.my_public_api
37
+ # end
38
+ # end
39
+ #
40
+ 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 }
49
+ def on_const(node)
50
+ # See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/lint/constant_resolution.rb source code as an example
51
+ return if Private.partial_const_reference?(node)
52
+
53
+ constant_reference = ConstantResolver::ConstantReference.resolve(node, processed_source)
54
+
55
+ # If we can't determine a constant reference, we can just early return. This could be beacuse the constant is defined
56
+ # in a gem OR because it's not abiding by the namespace convention we've established.
57
+ return if constant_reference.nil?
58
+ return if constant_reference.referencing_package.name == constant_reference.source_package.name
59
+
60
+ is_new_violation = [
61
+ !constant_reference.public_api?,
62
+ constant_reference.source_package.enforces_privacy?,
63
+ !Private.violation_in_deprecated_references_yml?(constant_reference)
64
+ ].all?
65
+
66
+ if is_new_violation
67
+ add_offense(
68
+ node.source_range,
69
+ message: format(
70
+ 'Privacy violation detected. See https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md for help'
71
+ )
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module PackwerkLite
6
+ module Private
7
+ extend T::Sig
8
+
9
+ sig { params(node: RuboCop::AST::ConstNode).returns(T::Boolean) }
10
+ def self.partial_const_reference?(node)
11
+ # 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
+ # One with `Foo`, one with `Foo::Bar`, and one with `Foo::Bar::Baz`.
13
+ # As far as I can tell, there is no way to direct Rubocop to only look at the full constant name.
14
+ # In order to ensure we're only operating on fully constant names, I check the "right sibling" of the `node`, which is the portion of the AST
15
+ # immediately following the node.
16
+ # If that right sibling is `nil` OR it's a lowercase string, we assume that it's the full constant.
17
+ # If the right sibling is a non-nil capitalized string, we assume it's a part of the constant, because by convention, constants
18
+ # start with capital letters and methods start with lowercase letters.
19
+ # RegularRateOfPay::Types::HourlyEarningWithDate
20
+ right_sibling = node.right_sibling
21
+ return false if right_sibling.nil?
22
+
23
+ right_sibling.to_s[0].capitalize == right_sibling.to_s[0]
24
+ end
25
+
26
+ sig { params(constant_reference: ConstantResolver::ConstantReference, type: String).returns(T::Boolean) }
27
+ def self.violation_in_deprecated_references_yml?(constant_reference, type: 'privacy')
28
+ existing_violations = ParsePackwerk::DeprecatedReferences.for(constant_reference.referencing_package).violations
29
+ existing_violations.any? do |v|
30
+ v.class_name == "::#{constant_reference.constant_name}" && (type == 'privacy' ? v.privacy? : v.dependency?)
31
+ end
32
+ end
33
+ end
34
+
35
+ private_constant :Private
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Packs
6
+ module Private
7
+ class Configuration
8
+ extend T::Sig
9
+
10
+ sig { returns(T::Array[String]) }
11
+ attr_accessor :permitted_pack_level_cops
12
+
13
+ sig { void }
14
+ def initialize
15
+ @permitted_pack_level_cops = T.let([], T::Array[String])
16
+ end
17
+
18
+ sig { void }
19
+ def bust_cache!
20
+ @permitted_pack_level_cops = []
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,9 +1,39 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'rubocop/packs/private/configuration'
5
+
4
6
  module RuboCop
5
7
  module Packs
6
8
  module Private
9
+ extend T::Sig
10
+
11
+ sig { void }
12
+ def self.bust_cache!
13
+ @rubocop_todo_ymls = nil
14
+ @loaded_client_configuration = nil
15
+ end
16
+
17
+ sig { void }
18
+ def self.load_client_configuration
19
+ @loaded_client_configuration ||= T.let(false, T.nilable(T::Boolean))
20
+ return if @loaded_client_configuration
21
+
22
+ @loaded_client_configuration = true
23
+ client_configuration = Pathname.pwd.join('config/rubocop_packs.rb')
24
+ require client_configuration.to_s if client_configuration.exist?
25
+ end
26
+
27
+ sig { returns(T::Array[T::Hash[T.untyped, T.untyped]]) }
28
+ def self.rubocop_todo_ymls
29
+ @rubocop_todo_ymls = T.let(@rubocop_todo_ymls, T.nilable(T::Array[T::Hash[T.untyped, T.untyped]]))
30
+ @rubocop_todo_ymls ||= begin
31
+ todo_files = Pathname.glob('**/.rubocop_todo.yml')
32
+ todo_files.map do |todo_file|
33
+ YAML.load_file(todo_file)
34
+ end
35
+ end
36
+ end
7
37
  end
8
38
 
9
39
  private_constant :Private
data/lib/rubocop/packs.rb CHANGED
@@ -14,5 +14,155 @@ module RuboCop
14
14
  CONFIG = T.let(YAML.safe_load(CONFIG_DEFAULT.read).freeze, T.untyped)
15
15
 
16
16
  private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
17
+
18
+ #
19
+ # Ideally, this is API that is available to us via `rubocop` itself.
20
+ # That is: the ability to preserve the location of `.rubocop_todo.yml` files and associate
21
+ # exclusions with the closest ancestor `.rubocop_todo.yml`
22
+ #
23
+ sig { params(packs: T::Array[ParsePackwerk::Package]).void }
24
+ def self.auto_generate_rubocop_todo(packs:)
25
+ pack_arguments = packs.map(&:name).join(' ')
26
+ cop_arguments = config.permitted_pack_level_cops.join(',')
27
+ command = "bundle exec rubocop #{pack_arguments} --only=#{cop_arguments} --format=json"
28
+ puts "Executing: #{command}"
29
+ json = JSON.parse(`#{command}`)
30
+ new_rubocop_todo_exclusions = {}
31
+ json['files'].each do |file_hash|
32
+ filepath = file_hash['path']
33
+ pack = ParsePackwerk.package_from_path(filepath)
34
+ next if pack.name == ParsePackwerk::ROOT_PACKAGE_NAME
35
+
36
+ file_hash['offenses'].each do |offense_hash|
37
+ cop_name = offense_hash['cop_name']
38
+ next unless config.permitted_pack_level_cops.include?(cop_name)
39
+
40
+ new_rubocop_todo_exclusions[pack.name] ||= {}
41
+ new_rubocop_todo_exclusions[pack.name][filepath] ||= []
42
+ new_rubocop_todo_exclusions[pack.name][filepath] << cop_name
43
+ end
44
+ end
45
+
46
+ new_rubocop_todo_exclusions.each do |pack_name, file_hash|
47
+ pack = T.must(ParsePackwerk.find(pack_name))
48
+ rubocop_todo_yml = pack.directory.join('.rubocop_todo.yml')
49
+ if rubocop_todo_yml.exist?
50
+ rubocop_todo = YAML.load_file(rubocop_todo_yml)
51
+ else
52
+ rubocop_todo = {}
53
+ end
54
+ file_hash.each do |file, failing_cops|
55
+ failing_cops.each do |failing_cop|
56
+ rubocop_todo[failing_cop] ||= { 'Exclude' => [] }
57
+ rubocop_todo[failing_cop]['Exclude'] << file
58
+ end
59
+ end
60
+
61
+ next if rubocop_todo.empty?
62
+
63
+ rubocop_todo_yml.write(YAML.dump(rubocop_todo))
64
+ end
65
+ end
66
+
67
+ sig { params(root_pathname: String).returns(String) }
68
+ # It would be great if rubocop (upstream) could take in a glob for `inherit_from`, which
69
+ # would allow us to delete this method and this additional complexity.
70
+ def self.pack_based_rubocop_todos(root_pathname: Bundler.root)
71
+ rubocop_todos = {}
72
+ # We do this because when the ERB is evaluated Dir.pwd is at the directory containing the YML.
73
+ # Ideally rubocop wouldn't change the PWD before invoking this method.
74
+ Dir.chdir(root_pathname) do
75
+ ParsePackwerk.all.each do |package|
76
+ next if package.name == ParsePackwerk::ROOT_PACKAGE_NAME
77
+
78
+ rubocop_todo = package.directory.join('.rubocop_todo.yml')
79
+ next unless rubocop_todo.exist?
80
+
81
+ loaded_rubocop_todo = YAML.load_file(rubocop_todo)
82
+ loaded_rubocop_todo.each do |protection_key, key_config|
83
+ rubocop_todos[protection_key] ||= { 'Exclude' => [] }
84
+ rubocop_todos[protection_key]['Exclude'] += key_config['Exclude']
85
+ end
86
+ end
87
+ end
88
+
89
+ YAML.dump(rubocop_todos)
90
+ end
91
+
92
+ sig { void }
93
+ def self.bust_cache!
94
+ config.bust_cache!
95
+ Private.bust_cache!
96
+ end
97
+
98
+ sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
99
+ def self.configure(&blk)
100
+ yield(config)
101
+ end
102
+
103
+ sig { returns(Private::Configuration) }
104
+ def self.config
105
+ Private.load_client_configuration
106
+ @config = T.let(@config, T.nilable(Private::Configuration))
107
+ @config ||= Private::Configuration.new
108
+ end
109
+
110
+ sig { params(rule: String).returns(T::Set[String]) }
111
+ def self.exclude_for_rule(rule)
112
+ excludes = T.let(Set.new, T::Set[String])
113
+
114
+ Private.rubocop_todo_ymls.each do |todo_yml|
115
+ next if !todo_yml
116
+
117
+ config = todo_yml[rule]
118
+ next if config.nil?
119
+
120
+ exclude_list = config['Exclude']
121
+ next if exclude_list.nil?
122
+
123
+ excludes += exclude_list
124
+ end
125
+
126
+ excludes
127
+ end
128
+
129
+ sig { returns(T::Array[String]) }
130
+ def self.validate
131
+ errors = []
132
+ ParsePackwerk.all.each do |package|
133
+ next if package.name == ParsePackwerk::ROOT_PACKAGE_NAME
134
+
135
+ rubocop_todo = package.directory.join('.rubocop_todo.yml')
136
+ next unless rubocop_todo.exist?
137
+
138
+ loaded_rubocop_todo = YAML.load_file(rubocop_todo)
139
+ loaded_rubocop_todo.each_key do |key|
140
+ if !config.permitted_pack_level_cops.include?(key)
141
+ errors << <<~ERROR_MESSAGE
142
+ #{rubocop_todo} contains invalid configuration for #{key}.
143
+ Please ensure the only configuration is for package protection exclusions, which are one of the following cops: #{config.permitted_pack_level_cops.inspect}"
144
+ For ignoring other cops, please instead modify the top-level .rubocop_todo.yml file.
145
+ ERROR_MESSAGE
146
+ elsif loaded_rubocop_todo[key].keys != ['Exclude']
147
+ errors << <<~ERROR_MESSAGE
148
+ #{rubocop_todo} contains invalid configuration for #{key}.
149
+ Please ensure the only configuration for #{key} is `Exclude`
150
+ ERROR_MESSAGE
151
+ else
152
+ loaded_rubocop_todo[key]['Exclude'].each do |filepath|
153
+ next unless ParsePackwerk.package_from_path(filepath).name != package.name
154
+
155
+ errors << <<~ERROR_MESSAGE
156
+ #{rubocop_todo} contains invalid configuration for #{key}.
157
+ #{filepath} does not belong to #{package.name}. Please ensure you only add exclusions
158
+ for files within this pack.
159
+ ERROR_MESSAGE
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ errors
166
+ end
17
167
  end
18
168
  end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'rubocop/cop/packwerk_lite/private'
5
+ require 'rubocop/cop/packwerk_lite/constant_resolver'
6
+ require 'rubocop/cop/packwerk_lite/privacy_checker'
7
+ require 'rubocop/cop/packwerk_lite/dependency_checker'
8
+
9
+ module RuboCop
10
+ # See docs/packwerk_lite.md
11
+ module PackwerkLite
12
+ class Error < StandardError; end
13
+ extend T::Sig
14
+ end
15
+ end
data/lib/rubocop-packs.rb CHANGED
@@ -6,6 +6,7 @@ require 'parse_packwerk'
6
6
 
7
7
  require_relative 'rubocop/packs'
8
8
  require_relative 'rubocop/packs/inject'
9
+ require_relative 'rubocop/packwerk_lite'
9
10
 
10
11
  require 'rubocop/cop/packs/namespace_convention'
11
12
  require 'rubocop/cop/packs/typed_public_api'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-packs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2022-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -187,15 +187,22 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - README.md
189
189
  - config/default.yml
190
+ - config/pack_config.yml
190
191
  - lib/rubocop-packs.rb
191
192
  - lib/rubocop/cop/packs/class_methods_as_public_apis.rb
192
193
  - lib/rubocop/cop/packs/namespace_convention.rb
193
194
  - lib/rubocop/cop/packs/namespace_convention/desired_zeitwerk_api.rb
194
195
  - lib/rubocop/cop/packs/require_documented_public_apis.rb
195
196
  - lib/rubocop/cop/packs/typed_public_api.rb
197
+ - lib/rubocop/cop/packwerk_lite/constant_resolver.rb
198
+ - lib/rubocop/cop/packwerk_lite/dependency_checker.rb
199
+ - lib/rubocop/cop/packwerk_lite/privacy_checker.rb
200
+ - lib/rubocop/cop/packwerk_lite/private.rb
196
201
  - lib/rubocop/packs.rb
197
202
  - lib/rubocop/packs/inject.rb
198
203
  - lib/rubocop/packs/private.rb
204
+ - lib/rubocop/packs/private/configuration.rb
205
+ - lib/rubocop/packwerk_lite.rb
199
206
  - sorbet/config
200
207
  - sorbet/rbi/gems/activesupport@7.0.4.rbi
201
208
  - sorbet/rbi/gems/ast@2.4.2.rbi
@@ -259,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
266
  - !ruby/object:Gem::Version
260
267
  version: '0'
261
268
  requirements: []
262
- rubygems_version: 3.3.7
269
+ rubygems_version: 3.1.6
263
270
  signing_key:
264
271
  specification_version: 4
265
272
  summary: Fill this out!