rubocop-packs 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/config/default.yml +2 -0
- data/config/pack_config.yml +6 -0
- data/lib/rubocop/packs/private/configuration.rb +25 -0
- data/lib/rubocop/packs/private.rb +30 -0
- data/lib/rubocop/packs.rb +150 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: caa9570071b5d3b21d447db0959789fbdf5747b352f5b1d5705bd95f0552f722
|
4
|
+
data.tar.gz: 3e739760cb2850057189f4f355417921b0be120cb723f8444e567bdf86ad7392
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -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.
|
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-
|
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
|