rubocop-packs 0.0.10 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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!