rubocop-packs 0.0.10 → 0.0.11

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: caa9570071b5d3b21d447db0959789fbdf5747b352f5b1d5705bd95f0552f722
4
+ data.tar.gz: 3e739760cb2850057189f4f355417921b0be120cb723f8444e567bdf86ad7392
5
5
  SHA512:
6
- metadata.gz: 37e828c802da1563049ce07a05e224187bfe8a3d61861f0cc05e774c311a3627d66d6ff116480d44100a9d31ef756bde1ea16777e02e5517e8f31fb0aab77595
7
- data.tar.gz: 2e54cf17766e69bfd81663a488f1171a74335dd71c55fbfc55f9d1334f6eeae5d0b0fd5e7134349b42fb61c749520a760fd1902f0dfe19c457c311cba55f10ca
6
+ metadata.gz: 851f5ca37b72f15b5b763a5272094c0996cd4ae2c1daf6924209eaefc4787e0c9a68c9336c71db949995f7fc95eae89d0597a36af486127d87665b5d7c8431fd
7
+ data.tar.gz: ebac8a9f6f263d7a3898f12dce5ace40980f52789fe7f6763e56d54ad9de7238b7b0b56eb16bbdbe18af13ddda4b698903c37a11946530c1d2fd4763a4265c96
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:
@@ -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 %>
@@ -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
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.11
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-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -187,6 +187,7 @@ 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
@@ -196,6 +197,7 @@ files:
196
197
  - lib/rubocop/packs.rb
197
198
  - lib/rubocop/packs/inject.rb
198
199
  - lib/rubocop/packs/private.rb
200
+ - lib/rubocop/packs/private/configuration.rb
199
201
  - sorbet/config
200
202
  - sorbet/rbi/gems/activesupport@7.0.4.rbi
201
203
  - sorbet/rbi/gems/ast@2.4.2.rbi