packwerk 3.0.1 → 3.1.0
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/.github/workflows/ci.yml +36 -8
- data/.ruby-version +1 -1
- data/Gemfile.lock +11 -6
- data/README.md +4 -2
- data/Rakefile +10 -1
- data/USAGE.md +6 -0
- data/dev.yml +1 -1
- data/lib/packwerk/application_validator.rb +3 -0
- data/lib/packwerk/checker.rb +13 -4
- data/lib/packwerk/cli.rb +14 -177
- data/lib/packwerk/commands/base_command.rb +69 -0
- data/lib/packwerk/commands/check_command.rb +60 -0
- data/lib/packwerk/commands/help_command.rb +33 -0
- data/lib/packwerk/commands/init_command.rb +42 -0
- data/lib/packwerk/commands/lazy_loaded_entry.rb +37 -0
- data/lib/packwerk/commands/update_todo_command.rb +60 -0
- data/lib/packwerk/commands/uses_parse_run.rb +92 -0
- data/lib/packwerk/commands/validate_command.rb +46 -0
- data/lib/packwerk/commands/version_command.rb +18 -0
- data/lib/packwerk/commands.rb +54 -0
- data/lib/packwerk/configuration.rb +4 -1
- data/lib/packwerk/file_processor.rb +12 -1
- data/lib/packwerk/formatters/default_offenses_formatter.rb +3 -3
- data/lib/packwerk/formatters/progress_formatter.rb +11 -0
- data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
- data/lib/packwerk/offense_collection.rb +32 -12
- data/lib/packwerk/offenses_formatter.rb +13 -4
- data/lib/packwerk/package_todo.rb +87 -60
- data/lib/packwerk/parse_run.rb +42 -82
- data/lib/packwerk/validator.rb +18 -4
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +4 -28
- data/sorbet/rbi/gems/parser@3.2.2.0.rbi +7250 -0
- metadata +14 -5
- data/lib/packwerk/cli/result.rb +0 -11
- data/sorbet/rbi/gems/parser@3.1.2.1.rbi +0 -9029
@@ -7,16 +7,20 @@ module Packwerk
|
|
7
7
|
class PackageTodo
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
PackageName = T.type_alias { String }
|
11
|
+
ConstantName = T.type_alias { String }
|
12
|
+
FilePath = T.type_alias { String }
|
13
|
+
Entry = T.type_alias { T::Hash[ConstantName, T::Hash[ConstantName, T::Array[FilePath]]] }
|
14
|
+
Entries = T.type_alias do
|
15
|
+
T::Hash[PackageName, Entry]
|
12
16
|
end
|
13
17
|
|
14
|
-
sig { params(package: Packwerk::Package,
|
15
|
-
def initialize(package,
|
18
|
+
sig { params(package: Packwerk::Package, path: String).void }
|
19
|
+
def initialize(package, path)
|
16
20
|
@package = package
|
17
|
-
@
|
18
|
-
@new_entries = T.let({},
|
19
|
-
@
|
21
|
+
@path = path
|
22
|
+
@new_entries = T.let({}, Entries)
|
23
|
+
@old_entries = T.let(nil, T.nilable(Entries))
|
20
24
|
end
|
21
25
|
|
22
26
|
sig do
|
@@ -24,7 +28,7 @@ module Packwerk
|
|
24
28
|
.returns(T::Boolean)
|
25
29
|
end
|
26
30
|
def listed?(reference, violation_type:)
|
27
|
-
violated_constants_found =
|
31
|
+
violated_constants_found = old_entries.dig(reference.constant.package.name, reference.constant.name)
|
28
32
|
return false unless violated_constants_found
|
29
33
|
|
30
34
|
violated_constant_in_file = violated_constants_found.fetch("files", []).include?(reference.relative_path)
|
@@ -37,16 +41,16 @@ module Packwerk
|
|
37
41
|
params(reference: Packwerk::Reference, violation_type: String).returns(T::Boolean)
|
38
42
|
end
|
39
43
|
def add_entries(reference, violation_type)
|
40
|
-
package_violations =
|
44
|
+
package_violations = new_entries.fetch(reference.constant.package.name, {})
|
41
45
|
entries_for_constant = package_violations[reference.constant.name] ||= {}
|
42
46
|
|
43
47
|
entries_for_constant["violations"] ||= []
|
44
|
-
entries_for_constant
|
48
|
+
entries_for_constant.fetch("violations") << violation_type
|
45
49
|
|
46
50
|
entries_for_constant["files"] ||= []
|
47
|
-
entries_for_constant
|
51
|
+
entries_for_constant.fetch("files") << reference.relative_path.to_s
|
48
52
|
|
49
|
-
|
53
|
+
new_entries[reference.constant.package.name] = package_violations
|
50
54
|
listed?(reference, violation_type: violation_type)
|
51
55
|
end
|
52
56
|
|
@@ -54,44 +58,22 @@ module Packwerk
|
|
54
58
|
def stale_violations?(for_files)
|
55
59
|
prepare_entries_for_dump
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
entries_for_files = for_files & entries_for_constant["files"]
|
61
|
-
next if entries_for_files.none?
|
62
|
-
|
63
|
-
package_violations_for_files[constant_name] = {
|
64
|
-
"violations" => entries_for_constant["violations"],
|
65
|
-
"files" => entries_for_files.to_a,
|
66
|
-
}
|
67
|
-
end
|
61
|
+
old_entries.any? do |package, violations|
|
62
|
+
files = for_files + deleted_files_for(package)
|
63
|
+
violations_for_files = package_violations_for(violations, files: files)
|
68
64
|
|
69
65
|
# We `next false` because if we cannot find existing violations for `for_files` within
|
70
66
|
# the `package_todo.yml` file, then there are no violations that
|
71
67
|
# can be considered stale.
|
72
|
-
next false if
|
73
|
-
|
74
|
-
|
75
|
-
new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
|
76
|
-
# If there are no NEW entries that match the old entries `for_files`,
|
77
|
-
# @new_entries is from the list of violations we get when we check this file.
|
78
|
-
# If this list is empty, we also must have stale violations.
|
79
|
-
next true if new_entries_violation_types.nil?
|
80
|
-
|
81
|
-
if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
|
82
|
-
stale_violations =
|
83
|
-
entries_for_constant["files"] - Array(@new_entries.dig(package, constant_name, "files"))
|
84
|
-
stale_violations.any?
|
85
|
-
else
|
86
|
-
return true
|
87
|
-
end
|
88
|
-
end
|
68
|
+
next false if violations_for_files.empty?
|
69
|
+
|
70
|
+
stale_violation_for_package?(package, violations: violations_for_files)
|
89
71
|
end
|
90
72
|
end
|
91
73
|
|
92
74
|
sig { void }
|
93
75
|
def dump
|
94
|
-
if
|
76
|
+
if new_entries.empty?
|
95
77
|
delete_if_exists
|
96
78
|
else
|
97
79
|
prepare_entries_for_dump
|
@@ -104,45 +86,90 @@ module Packwerk
|
|
104
86
|
#
|
105
87
|
# bin/packwerk update-todo
|
106
88
|
MESSAGE
|
107
|
-
File.open(@
|
89
|
+
File.open(@path, "w") do |f|
|
108
90
|
f.write(message)
|
109
|
-
f.write(
|
91
|
+
f.write(new_entries.to_yaml)
|
110
92
|
end
|
111
93
|
end
|
112
94
|
end
|
113
95
|
|
114
96
|
sig { void }
|
115
97
|
def delete_if_exists
|
116
|
-
File.delete(@
|
98
|
+
File.delete(@path) if File.exist?(@path)
|
117
99
|
end
|
118
100
|
|
119
101
|
private
|
120
102
|
|
121
|
-
sig { returns(
|
103
|
+
sig { returns(Entries) }
|
104
|
+
attr_reader(:new_entries)
|
105
|
+
|
106
|
+
sig { params(package: String).returns(T::Array[String]) }
|
107
|
+
def deleted_files_for(package)
|
108
|
+
old_files = old_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
|
109
|
+
new_files = new_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
|
110
|
+
old_files - new_files
|
111
|
+
end
|
112
|
+
|
113
|
+
sig { params(package: String, violations: Entry).returns(T::Boolean) }
|
114
|
+
def stale_violation_for_package?(package, violations:)
|
115
|
+
violations.any? do |constant_name, entries_for_constant|
|
116
|
+
new_entries_violation_types = T.cast(
|
117
|
+
new_entries.dig(package, constant_name, "violations"),
|
118
|
+
T.nilable(T::Array[String]),
|
119
|
+
)
|
120
|
+
# If there are no NEW entries that match the old entries `for_files`,
|
121
|
+
# new_entries is from the list of violations we get when we check this file.
|
122
|
+
# If this list is empty, we also must have stale violations.
|
123
|
+
next true if new_entries_violation_types.nil?
|
124
|
+
|
125
|
+
if entries_for_constant.fetch("violations").all? { |type| new_entries_violation_types.include?(type) }
|
126
|
+
stale_violations =
|
127
|
+
entries_for_constant.fetch("files") - Array(new_entries.dig(package, constant_name, "files"))
|
128
|
+
stale_violations.any?
|
129
|
+
else
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
sig { params(package_violations: Entry, files: T::Set[String]).returns(Entry) }
|
136
|
+
def package_violations_for(package_violations, files:)
|
137
|
+
{}.tap do |package_violations_for_files|
|
138
|
+
package_violations_for_files = T.cast(package_violations_for_files, Entry)
|
139
|
+
|
140
|
+
package_violations.each do |constant_name, entries_for_constant|
|
141
|
+
entries_for_files = files & entries_for_constant.fetch("files")
|
142
|
+
next if entries_for_files.none?
|
143
|
+
|
144
|
+
package_violations_for_files[constant_name] = {
|
145
|
+
"violations" => entries_for_constant["violations"],
|
146
|
+
"files" => entries_for_files.to_a,
|
147
|
+
}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
sig { returns(Entries) }
|
122
153
|
def prepare_entries_for_dump
|
123
|
-
|
154
|
+
new_entries.each do |package_name, package_violations|
|
124
155
|
package_violations.each do |_, entries_for_constant|
|
125
|
-
entries_for_constant
|
126
|
-
entries_for_constant
|
156
|
+
entries_for_constant.fetch("violations").sort!.uniq!
|
157
|
+
entries_for_constant.fetch("files").sort!.uniq!
|
127
158
|
end
|
128
|
-
|
159
|
+
new_entries[package_name] = package_violations.sort.to_h
|
129
160
|
end
|
130
161
|
|
131
|
-
@new_entries =
|
162
|
+
@new_entries = new_entries.sort.to_h
|
132
163
|
end
|
133
164
|
|
134
|
-
sig { returns(
|
135
|
-
def
|
136
|
-
@
|
137
|
-
load_yaml(@filepath)
|
138
|
-
else
|
139
|
-
{}
|
140
|
-
end
|
165
|
+
sig { returns(Entries) }
|
166
|
+
def old_entries
|
167
|
+
@old_entries ||= load_yaml_file(@path)
|
141
168
|
end
|
142
169
|
|
143
|
-
sig { params(
|
144
|
-
def
|
145
|
-
YAML.load_file(
|
170
|
+
sig { params(path: String).returns(Entries) }
|
171
|
+
def load_yaml_file(path)
|
172
|
+
File.exist?(path) && YAML.load_file(path) || {}
|
146
173
|
rescue Psych::Exception
|
147
174
|
{}
|
148
175
|
end
|
data/lib/packwerk/parse_run.rb
CHANGED
@@ -14,93 +14,62 @@ module Packwerk
|
|
14
14
|
sig do
|
15
15
|
params(
|
16
16
|
relative_file_set: FilesForProcessing::RelativeFileSet,
|
17
|
-
|
18
|
-
file_set_specified: T::Boolean,
|
19
|
-
offenses_formatter: T.nilable(OffensesFormatter),
|
20
|
-
progress_formatter: Formatters::ProgressFormatter,
|
17
|
+
parallel: T::Boolean,
|
21
18
|
).void
|
22
19
|
end
|
23
|
-
def initialize(
|
24
|
-
relative_file_set:,
|
25
|
-
configuration:,
|
26
|
-
file_set_specified: false,
|
27
|
-
offenses_formatter: nil,
|
28
|
-
progress_formatter: Formatters::ProgressFormatter.new(StringIO.new)
|
29
|
-
)
|
30
|
-
|
31
|
-
@configuration = configuration
|
32
|
-
@progress_formatter = progress_formatter
|
33
|
-
@offenses_formatter = T.let(offenses_formatter || configuration.offenses_formatter, Packwerk::OffensesFormatter)
|
20
|
+
def initialize(relative_file_set:, parallel:)
|
34
21
|
@relative_file_set = relative_file_set
|
35
|
-
@
|
22
|
+
@parallel = parallel
|
36
23
|
end
|
37
24
|
|
38
|
-
sig
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
run_context = RunContext.from_configuration(@configuration)
|
49
|
-
offense_collection = find_offenses(run_context)
|
50
|
-
offense_collection.persist_package_todo_files(run_context.package_set)
|
51
|
-
|
52
|
-
message = <<~EOS
|
53
|
-
#{@offenses_formatter.show_offenses(offense_collection.errors)}
|
54
|
-
✅ `package_todo.yml` has been updated.
|
55
|
-
EOS
|
56
|
-
|
57
|
-
Cli::Result.new(message: message, status: offense_collection.errors.empty?)
|
25
|
+
sig do
|
26
|
+
params(
|
27
|
+
run_context: RunContext,
|
28
|
+
on_interrupt: T.nilable(T.proc.void),
|
29
|
+
block: T.nilable(T.proc.params(
|
30
|
+
offenses: T::Array[Packwerk::Offense],
|
31
|
+
).void)
|
32
|
+
).returns(T::Array[Offense])
|
58
33
|
end
|
34
|
+
def find_offenses(run_context, on_interrupt: nil, &block)
|
35
|
+
process_file_proc = process_file_proc(run_context, &block)
|
59
36
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
messages = [
|
66
|
-
@offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
|
67
|
-
@offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
|
68
|
-
@offenses_formatter.show_strict_mode_violations(offense_collection.strict_mode_violations),
|
69
|
-
]
|
70
|
-
|
71
|
-
result_status = offense_collection.outstanding_offenses.empty? &&
|
72
|
-
!offense_collection.stale_violations?(@relative_file_set) && offense_collection.strict_mode_violations.empty?
|
37
|
+
offenses = if @parallel
|
38
|
+
Parallel.flat_map(@relative_file_set, &process_file_proc)
|
39
|
+
else
|
40
|
+
serial_find_offenses(on_interrupt: on_interrupt, &process_file_proc)
|
41
|
+
end
|
73
42
|
|
74
|
-
|
43
|
+
offenses
|
75
44
|
end
|
76
45
|
|
77
46
|
private
|
78
47
|
|
79
|
-
sig
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
serial_find_offenses(&process_file)
|
95
|
-
end
|
48
|
+
sig do
|
49
|
+
params(
|
50
|
+
run_context: RunContext,
|
51
|
+
block: T.nilable(T.proc.params(offenses: T::Array[Offense]).void)
|
52
|
+
).returns(ProcessFileProc)
|
53
|
+
end
|
54
|
+
def process_file_proc(run_context, &block)
|
55
|
+
if block
|
56
|
+
T.let(proc do |relative_file|
|
57
|
+
run_context.process_file(relative_file: relative_file).tap(&block)
|
58
|
+
end, ProcessFileProc)
|
59
|
+
else
|
60
|
+
T.let(proc do |relative_file|
|
61
|
+
run_context.process_file(relative_file: relative_file)
|
62
|
+
end, ProcessFileProc)
|
96
63
|
end
|
97
|
-
|
98
|
-
all_offenses.each { |offense| offense_collection.add_offense(offense) }
|
99
|
-
offense_collection
|
100
64
|
end
|
101
65
|
|
102
|
-
sig
|
103
|
-
|
66
|
+
sig do
|
67
|
+
params(
|
68
|
+
on_interrupt: T.nilable(T.proc.void),
|
69
|
+
block: ProcessFileProc
|
70
|
+
).returns(T::Array[Offense])
|
71
|
+
end
|
72
|
+
def serial_find_offenses(on_interrupt: nil, &block)
|
104
73
|
all_offenses = T.let([], T::Array[Offense])
|
105
74
|
begin
|
106
75
|
@relative_file_set.each do |relative_file|
|
@@ -108,20 +77,11 @@ module Packwerk
|
|
108
77
|
all_offenses.concat(offenses)
|
109
78
|
end
|
110
79
|
rescue Interrupt
|
111
|
-
|
80
|
+
on_interrupt&.call
|
112
81
|
all_offenses
|
113
82
|
end
|
114
83
|
all_offenses
|
115
84
|
end
|
116
|
-
|
117
|
-
sig { params(failed: T::Boolean).void }
|
118
|
-
def update_progress(failed: false)
|
119
|
-
if failed
|
120
|
-
@progress_formatter.mark_as_failed
|
121
|
-
else
|
122
|
-
@progress_formatter.mark_as_inspected
|
123
|
-
end
|
124
|
-
end
|
125
85
|
end
|
126
86
|
|
127
87
|
private_constant :ParseRun
|
data/lib/packwerk/validator.rb
CHANGED
@@ -9,6 +9,9 @@ module Packwerk
|
|
9
9
|
module Validator
|
10
10
|
extend T::Sig
|
11
11
|
extend T::Helpers
|
12
|
+
extend ActiveSupport::Autoload
|
13
|
+
|
14
|
+
autoload :Result
|
12
15
|
|
13
16
|
abstract!
|
14
17
|
|
@@ -17,14 +20,25 @@ module Packwerk
|
|
17
20
|
|
18
21
|
sig { params(base: Class).void }
|
19
22
|
def included(base)
|
20
|
-
|
21
|
-
@validators ||= []
|
22
|
-
@validators << base
|
23
|
+
validators << base
|
23
24
|
end
|
24
25
|
|
25
26
|
sig { returns(T::Array[Validator]) }
|
26
27
|
def all
|
27
|
-
|
28
|
+
load_defaults
|
29
|
+
T.cast(validators.map(&:new), T::Array[Validator])
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
sig { void }
|
35
|
+
def load_defaults
|
36
|
+
require("packwerk/validators/dependency_validator")
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(T::Array[Class]) }
|
40
|
+
def validators
|
41
|
+
@validators ||= T.let([], T.nilable(T::Array[Class]))
|
28
42
|
end
|
29
43
|
end
|
30
44
|
|
data/lib/packwerk/version.rb
CHANGED
data/lib/packwerk.rb
CHANGED
@@ -18,6 +18,7 @@ module Packwerk
|
|
18
18
|
autoload :Cli
|
19
19
|
autoload :Configuration
|
20
20
|
autoload :ConstantContext
|
21
|
+
autoload :Commands
|
21
22
|
autoload :Node
|
22
23
|
autoload :Offense
|
23
24
|
autoload :OffenseCollection
|
@@ -32,12 +33,6 @@ module Packwerk
|
|
32
33
|
autoload :ReferenceOffense
|
33
34
|
autoload :Validator
|
34
35
|
|
35
|
-
class Cli
|
36
|
-
extend ActiveSupport::Autoload
|
37
|
-
|
38
|
-
autoload :Result
|
39
|
-
end
|
40
|
-
|
41
36
|
module OutputStyles
|
42
37
|
extend ActiveSupport::Autoload
|
43
38
|
|
@@ -45,20 +40,17 @@ module Packwerk
|
|
45
40
|
autoload :Plain
|
46
41
|
end
|
47
42
|
|
48
|
-
autoload_under "commands" do
|
49
|
-
autoload :OffenseProgressMarker
|
50
|
-
end
|
51
|
-
|
52
43
|
module Formatters
|
53
44
|
extend ActiveSupport::Autoload
|
54
45
|
|
46
|
+
autoload :DefaultOffensesFormatter
|
55
47
|
autoload :ProgressFormatter
|
56
48
|
end
|
57
49
|
|
58
|
-
module
|
50
|
+
module Validators
|
59
51
|
extend ActiveSupport::Autoload
|
60
52
|
|
61
|
-
autoload :
|
53
|
+
autoload :DependencyValidator
|
62
54
|
end
|
63
55
|
|
64
56
|
# Private APIs
|
@@ -101,26 +93,10 @@ module Packwerk
|
|
101
93
|
extend ActiveSupport::Autoload
|
102
94
|
|
103
95
|
autoload :DependencyChecker
|
104
|
-
autoload :PrivacyChecker
|
105
96
|
end
|
106
97
|
end
|
107
98
|
|
108
99
|
private_constant :ReferenceChecking
|
109
|
-
|
110
|
-
class ApplicationValidator
|
111
|
-
extend ActiveSupport::Autoload
|
112
|
-
|
113
|
-
autoload :Helpers
|
114
|
-
end
|
115
100
|
end
|
116
101
|
|
117
102
|
require "packwerk/version"
|
118
|
-
|
119
|
-
# Required to register the DefaultOffensesFormatter
|
120
|
-
# We put this at the *end* of the file to specify all autoloads first
|
121
|
-
require "packwerk/formatters/default_offenses_formatter"
|
122
|
-
|
123
|
-
# Required to register the default DependencyChecker
|
124
|
-
require "packwerk/reference_checking/checkers/dependency_checker"
|
125
|
-
# Required to register the default DependencyValidator
|
126
|
-
require "packwerk/validators/dependency_validator"
|