packwerk 2.3.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/.ruby-version +1 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -95
- data/README.md +2 -7
- data/RESOLVING_VIOLATIONS.md +3 -8
- data/TROUBLESHOOT.md +2 -25
- data/UPGRADING.md +12 -0
- data/USAGE.md +136 -54
- data/dev.yml +1 -1
- data/exe/packwerk +4 -0
- data/gemfiles/Gemfile-rails-6-1 +1 -1
- data/lib/packwerk/application_validator.rb +54 -285
- data/lib/packwerk/association_inspector.rb +2 -0
- data/lib/packwerk/cache.rb +6 -5
- data/lib/packwerk/checker.rb +54 -0
- data/lib/packwerk/cli/result.rb +11 -0
- data/lib/packwerk/cli.rb +55 -40
- data/lib/packwerk/configuration.rb +61 -40
- data/lib/packwerk/const_node_inspector.rb +2 -0
- data/lib/packwerk/constant_context.rb +8 -0
- data/lib/packwerk/constant_discovery.rb +5 -6
- data/lib/packwerk/constant_name_inspector.rb +2 -0
- data/lib/packwerk/disable_sorbet.rb +41 -0
- data/lib/packwerk/extension_loader.rb +24 -0
- data/lib/packwerk/file_processor.rb +3 -1
- data/lib/packwerk/files_for_processing.rb +25 -12
- data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
- data/lib/packwerk/formatters/progress_formatter.rb +31 -12
- data/lib/packwerk/generators/configuration_file.rb +7 -2
- data/lib/packwerk/generators/root_package.rb +5 -1
- data/lib/packwerk/generators/templates/package.yml +0 -10
- data/lib/packwerk/graph.rb +10 -2
- data/lib/packwerk/node.rb +1 -1
- data/lib/packwerk/node_helpers.rb +14 -7
- data/lib/packwerk/node_processor.rb +2 -0
- data/lib/packwerk/node_processor_factory.rb +6 -4
- data/lib/packwerk/node_visitor.rb +10 -1
- data/lib/packwerk/offense_collection.rb +26 -18
- data/lib/packwerk/offenses_formatter.rb +59 -2
- data/lib/packwerk/package.rb +7 -35
- data/lib/packwerk/package_set.rb +1 -1
- data/lib/packwerk/package_todo.rb +19 -20
- data/lib/packwerk/parse_run.rb +27 -34
- data/lib/packwerk/parsed_constant_definitions.rb +28 -5
- data/lib/packwerk/parsers/erb.rb +23 -4
- data/lib/packwerk/parsers/factory.rb +11 -2
- data/lib/packwerk/parsers/parser_interface.rb +1 -1
- data/lib/packwerk/parsers/ruby.rb +13 -3
- data/lib/packwerk/parsers.rb +6 -2
- data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
- data/lib/packwerk/reference.rb +7 -1
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
- data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
- data/lib/packwerk/reference_extractor.rb +24 -12
- data/lib/packwerk/reference_offense.rb +2 -2
- data/lib/packwerk/run_context.rb +7 -10
- data/lib/packwerk/spring_command.rb +9 -2
- data/lib/packwerk/unresolved_reference.rb +9 -1
- data/lib/packwerk/validator/result.rb +18 -0
- data/lib/packwerk/validator.rb +90 -0
- data/lib/packwerk/validators/dependency_validator.rb +154 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +64 -26
- data/packwerk.gemspec +4 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
- data/sorbet/rbi/shims/minitest/test.rb +8 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
- data/sorbet/rbi/shims/parser.rbi +13 -0
- metadata +34 -15
- data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
- data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
- data/lib/packwerk/result.rb +0 -9
- data/lib/packwerk/sanity_checker.rb +0 -8
- data/lib/packwerk/violation_type.rb +0 -11
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
data/lib/packwerk/cli.rb
CHANGED
@@ -14,8 +14,8 @@ module Packwerk
|
|
14
14
|
out: T.any(StringIO, IO),
|
15
15
|
err_out: T.any(StringIO, IO),
|
16
16
|
environment: String,
|
17
|
-
style:
|
18
|
-
offenses_formatter: T.nilable(
|
17
|
+
style: OutputStyle,
|
18
|
+
offenses_formatter: T.nilable(OffensesFormatter)
|
19
19
|
).void
|
20
20
|
end
|
21
21
|
def initialize(
|
@@ -33,7 +33,7 @@ module Packwerk
|
|
33
33
|
@configuration = T.let(configuration || Configuration.from_path, Configuration)
|
34
34
|
@progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
|
35
35
|
@offenses_formatter = T.let(
|
36
|
-
offenses_formatter ||
|
36
|
+
offenses_formatter || @configuration.offenses_formatter,
|
37
37
|
OffensesFormatter
|
38
38
|
)
|
39
39
|
end
|
@@ -52,22 +52,13 @@ module Packwerk
|
|
52
52
|
init
|
53
53
|
when "check"
|
54
54
|
output_result(parse_run(args).check)
|
55
|
-
when "
|
56
|
-
output_result(parse_run(args).detect_stale_violations)
|
57
|
-
when "update-deprecations"
|
58
|
-
warning = <<~WARNING.squish
|
59
|
-
DEPRECATION WARNING: `update-deprecations` is deprecated in favor of
|
60
|
-
`update-todo`.
|
61
|
-
WARNING
|
62
|
-
|
63
|
-
warn(warning)
|
64
|
-
output_result(parse_run(args).update_todo)
|
65
|
-
when "update-todo"
|
66
|
-
output_result(parse_run(args).update_todo)
|
67
|
-
when "update"
|
55
|
+
when "update-todo", "update"
|
68
56
|
output_result(parse_run(args).update_todo)
|
69
57
|
when "validate"
|
70
58
|
validate(args)
|
59
|
+
when "version"
|
60
|
+
@out.puts(Packwerk::VERSION)
|
61
|
+
true
|
71
62
|
when nil, "help"
|
72
63
|
usage
|
73
64
|
else
|
@@ -89,12 +80,12 @@ module Packwerk
|
|
89
80
|
|
90
81
|
sig { returns(T::Boolean) }
|
91
82
|
def generate_configs
|
92
|
-
configuration_file =
|
83
|
+
configuration_file = Generators::ConfigurationFile.generate(
|
93
84
|
root: @configuration.root_path,
|
94
85
|
out: @out
|
95
86
|
)
|
96
87
|
|
97
|
-
root_package =
|
88
|
+
root_package = Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
|
98
89
|
|
99
90
|
success = configuration_file && root_package
|
100
91
|
|
@@ -124,8 +115,9 @@ module Packwerk
|
|
124
115
|
Subcommands:
|
125
116
|
init - set up packwerk
|
126
117
|
check - run all checks
|
127
|
-
update-
|
118
|
+
update-todo - update package_todo.yml files
|
128
119
|
validate - verify integrity of packwerk and package configuration
|
120
|
+
version - output packwerk version
|
129
121
|
help - display help information about packwerk
|
130
122
|
USAGE
|
131
123
|
true
|
@@ -142,72 +134,95 @@ module Packwerk
|
|
142
134
|
params(
|
143
135
|
relative_file_paths: T::Array[String],
|
144
136
|
ignore_nested_packages: T::Boolean
|
145
|
-
).returns(FilesForProcessing
|
137
|
+
).returns(FilesForProcessing)
|
146
138
|
end
|
147
139
|
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
148
|
-
|
140
|
+
files_for_processing = FilesForProcessing.fetch(
|
149
141
|
relative_file_paths: relative_file_paths,
|
150
142
|
ignore_nested_packages: ignore_nested_packages,
|
151
143
|
configuration: @configuration
|
152
144
|
)
|
153
|
-
|
154
|
-
|
155
|
-
|
145
|
+
@out.puts(<<~MSG.squish) if files_for_processing.files.empty?
|
146
|
+
No files found or given.
|
147
|
+
Specify files or check the include and exclude glob in the config file.
|
148
|
+
MSG
|
149
|
+
|
150
|
+
files_for_processing
|
156
151
|
end
|
157
152
|
|
158
153
|
sig { params(_paths: T::Array[String]).returns(T::Boolean) }
|
159
154
|
def validate(_paths)
|
155
|
+
result = T.let(nil, T.nilable(Validator::Result))
|
156
|
+
|
160
157
|
@progress_formatter.started_validation do
|
161
|
-
result =
|
158
|
+
result = validator.check_all(package_set, @configuration)
|
162
159
|
|
163
160
|
list_validation_errors(result)
|
164
|
-
|
165
|
-
return result.ok?
|
166
161
|
end
|
162
|
+
|
163
|
+
T.must(result).ok?
|
167
164
|
end
|
168
165
|
|
169
166
|
sig { returns(ApplicationValidator) }
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
167
|
+
def validator
|
168
|
+
ApplicationValidator.new
|
169
|
+
end
|
170
|
+
|
171
|
+
sig { returns(PackageSet) }
|
172
|
+
def package_set
|
173
|
+
PackageSet.load_all_from(
|
174
|
+
@configuration.root_path,
|
175
|
+
package_pathspec: @configuration.package_paths
|
175
176
|
)
|
176
177
|
end
|
177
178
|
|
178
|
-
sig { params(result:
|
179
|
+
sig { params(result: Validator::Result).void }
|
179
180
|
def list_validation_errors(result)
|
180
181
|
@out.puts
|
181
182
|
if result.ok?
|
182
183
|
@out.puts("Validation successful 🎉")
|
183
184
|
else
|
184
185
|
@out.puts("Validation failed ❗")
|
186
|
+
@out.puts
|
185
187
|
@out.puts(result.error_value)
|
186
188
|
end
|
187
189
|
end
|
188
190
|
|
189
|
-
sig { params(
|
190
|
-
def parse_run(
|
191
|
+
sig { params(args: T::Array[String]).returns(ParseRun) }
|
192
|
+
def parse_run(args)
|
191
193
|
relative_file_paths = T.let([], T::Array[String])
|
192
194
|
ignore_nested_packages = nil
|
195
|
+
formatter = @offenses_formatter
|
193
196
|
|
194
|
-
if
|
197
|
+
if args.any? { |arg| arg.include?("--packages") }
|
195
198
|
OptionParser.new do |parser|
|
196
199
|
parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
|
197
200
|
relative_file_paths = p
|
198
201
|
end
|
199
|
-
end.parse!(
|
202
|
+
end.parse!(args)
|
200
203
|
ignore_nested_packages = true
|
201
204
|
else
|
202
|
-
relative_file_paths =
|
205
|
+
relative_file_paths = args
|
203
206
|
ignore_nested_packages = false
|
204
207
|
end
|
205
208
|
|
209
|
+
if args.any? { |arg| arg.include?("--offenses-formatter") }
|
210
|
+
OptionParser.new do |parser|
|
211
|
+
parser.on("--offenses-formatter=FORMATTER", String,
|
212
|
+
"identifier of offenses formatter to use") do |formatter_identifier|
|
213
|
+
formatter = OffensesFormatter.find(formatter_identifier)
|
214
|
+
end
|
215
|
+
end.parse!(args)
|
216
|
+
end
|
217
|
+
|
218
|
+
files_for_processing = fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
219
|
+
|
206
220
|
ParseRun.new(
|
207
|
-
relative_file_set:
|
221
|
+
relative_file_set: files_for_processing.files,
|
222
|
+
file_set_specified: files_for_processing.files_specified?,
|
208
223
|
configuration: @configuration,
|
209
224
|
progress_formatter: @progress_formatter,
|
210
|
-
offenses_formatter:
|
225
|
+
offenses_formatter: formatter
|
211
226
|
)
|
212
227
|
end
|
213
228
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "pathname"
|
@@ -6,7 +6,12 @@ require "yaml"
|
|
6
6
|
|
7
7
|
module Packwerk
|
8
8
|
class Configuration
|
9
|
+
extend T::Sig
|
10
|
+
|
9
11
|
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(path: String).returns(Configuration) }
|
10
15
|
def from_path(path = Dir.pwd)
|
11
16
|
raise ArgumentError, "#{File.expand_path(path)} does not exist" unless File.exist?(path)
|
12
17
|
|
@@ -21,6 +26,7 @@ module Packwerk
|
|
21
26
|
|
22
27
|
private
|
23
28
|
|
29
|
+
sig { params(path: String).returns(Configuration) }
|
24
30
|
def from_packwerk_config(path)
|
25
31
|
new(
|
26
32
|
YAML.load_file(path) || {},
|
@@ -30,63 +36,78 @@ module Packwerk
|
|
30
36
|
end
|
31
37
|
|
32
38
|
DEFAULT_CONFIG_PATH = "packwerk.yml"
|
33
|
-
DEFAULT_INCLUDE_GLOBS = ["**/*.{rb,rake,erb}"]
|
34
|
-
DEFAULT_EXCLUDE_GLOBS = ["{bin,node_modules,script,tmp,vendor}/**/*"]
|
39
|
+
DEFAULT_INCLUDE_GLOBS = T.let(["**/*.{rb,rake,erb}"], T::Array[String])
|
40
|
+
DEFAULT_EXCLUDE_GLOBS = T.let(["{bin,node_modules,script,tmp,vendor}/**/*"], T::Array[String])
|
35
41
|
|
36
|
-
|
37
|
-
|
38
|
-
)
|
42
|
+
sig { returns(T::Array[String]) }
|
43
|
+
attr_reader(:include)
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
@exclude = configs["exclude"] || DEFAULT_EXCLUDE_GLOBS
|
43
|
-
root = config_path ? File.dirname(config_path) : "."
|
44
|
-
@root_path = File.expand_path(root)
|
45
|
-
@package_paths = configs["package_paths"] || "**/"
|
46
|
-
@custom_associations = configs["custom_associations"] || []
|
47
|
-
@parallel = configs.key?("parallel") ? configs["parallel"] : true
|
48
|
-
@cache_enabled = configs.key?("cache") ? configs["cache"] : false
|
49
|
-
@cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
|
50
|
-
@config_path = config_path
|
45
|
+
sig { returns(T::Array[String]) }
|
46
|
+
attr_reader(:exclude)
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
DEPRECATION WARNING: The 'load_paths' key in `packwerk.yml` is deprecated.
|
55
|
-
This value is no longer cached, and you can remove the key from `packwerk.yml`.
|
56
|
-
WARNING
|
48
|
+
sig { returns(String) }
|
49
|
+
attr_reader(:root_path)
|
57
50
|
|
58
|
-
|
59
|
-
|
51
|
+
sig { returns(T.any(String, T::Array[String])) }
|
52
|
+
attr_reader(:package_paths)
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
DEPRECATION WARNING: The 'inflections_file' key in `packwerk.yml` is deprecated.
|
64
|
-
This value is no longer cached, and you can remove the key from `packwerk.yml`.
|
65
|
-
You can also delete #{configs["inflections_file"]}.
|
66
|
-
WARNING
|
54
|
+
sig { returns(T::Array[Symbol]) }
|
55
|
+
attr_reader(:custom_associations)
|
67
56
|
|
68
|
-
|
69
|
-
|
57
|
+
sig { returns(T.nilable(String)) }
|
58
|
+
attr_reader(:config_path)
|
59
|
+
|
60
|
+
sig { returns(Pathname) }
|
61
|
+
attr_reader(:cache_directory)
|
70
62
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
63
|
+
sig do
|
64
|
+
params(
|
65
|
+
configs: T::Hash[String, T.untyped],
|
66
|
+
config_path: T.nilable(String),
|
67
|
+
).void
|
68
|
+
end
|
69
|
+
def initialize(configs = {}, config_path: nil)
|
70
|
+
@include = T.let(configs["include"] || DEFAULT_INCLUDE_GLOBS, T::Array[String])
|
71
|
+
@exclude = T.let(configs["exclude"] || DEFAULT_EXCLUDE_GLOBS, T::Array[String])
|
72
|
+
root = config_path ? File.dirname(config_path) : "."
|
73
|
+
@root_path = T.let(File.expand_path(root), String)
|
74
|
+
@package_paths = T.let(configs["package_paths"] || "**/", T.any(String, T::Array[String]))
|
75
|
+
@custom_associations = T.let(configs["custom_associations"] || [], T::Array[Symbol])
|
76
|
+
@parallel = T.let(configs.key?("parallel") ? configs["parallel"] : true, T::Boolean)
|
77
|
+
@cache_enabled = T.let(configs.key?("cache") ? configs["cache"] : false, T::Boolean)
|
78
|
+
@cache_directory = T.let(Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk"), Pathname)
|
79
|
+
@config_path = config_path
|
77
80
|
|
78
|
-
|
81
|
+
@offenses_formatter_identifier = T.let(
|
82
|
+
configs["offenses_formatter"] || Formatters::DefaultOffensesFormatter::IDENTIFIER, String
|
83
|
+
)
|
84
|
+
|
85
|
+
if configs.key?("require")
|
86
|
+
configs["require"].each do |require_directive|
|
87
|
+
ExtensionLoader.load(require_directive, @root_path)
|
88
|
+
end
|
79
89
|
end
|
80
90
|
end
|
81
91
|
|
92
|
+
sig { returns(T::Hash[String, Module]) }
|
82
93
|
def load_paths
|
83
|
-
@load_paths ||=
|
94
|
+
@load_paths ||= T.let(
|
95
|
+
RailsLoadPaths.for(@root_path, environment: "test"),
|
96
|
+
T.nilable(T::Hash[String, Module]),
|
97
|
+
)
|
84
98
|
end
|
85
99
|
|
100
|
+
sig { returns(T::Boolean) }
|
86
101
|
def parallel?
|
87
102
|
@parallel
|
88
103
|
end
|
89
104
|
|
105
|
+
sig { returns(OffensesFormatter) }
|
106
|
+
def offenses_formatter
|
107
|
+
OffensesFormatter.find(@offenses_formatter_identifier)
|
108
|
+
end
|
109
|
+
|
110
|
+
sig { returns(T::Boolean) }
|
90
111
|
def cache_enabled?
|
91
112
|
@cache_enabled
|
92
113
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "constant_resolver"
|
@@ -17,8 +17,6 @@ module Packwerk
|
|
17
17
|
class ConstantDiscovery
|
18
18
|
extend T::Sig
|
19
19
|
|
20
|
-
ConstantContext = Struct.new(:name, :location, :package, :public?)
|
21
|
-
|
22
20
|
# @param constant_resolver [ConstantResolver]
|
23
21
|
# @param packages [Packwerk::PackageSet]
|
24
22
|
sig do
|
@@ -50,12 +48,12 @@ module Packwerk
|
|
50
48
|
# @param const_name [String] The unresolved constant's name.
|
51
49
|
# @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
|
52
50
|
# used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
|
53
|
-
# @return [
|
51
|
+
# @return [ConstantContext]
|
54
52
|
sig do
|
55
53
|
params(
|
56
54
|
const_name: String,
|
57
55
|
current_namespace_path: T.nilable(T::Array[String]),
|
58
|
-
).returns(T.nilable(
|
56
|
+
).returns(T.nilable(ConstantContext))
|
59
57
|
end
|
60
58
|
def context_for(const_name, current_namespace_path: [])
|
61
59
|
begin
|
@@ -71,8 +69,9 @@ module Packwerk
|
|
71
69
|
constant.name,
|
72
70
|
constant.location,
|
73
71
|
package,
|
74
|
-
package.public_path?(constant.location),
|
75
72
|
)
|
76
73
|
end
|
77
74
|
end
|
75
|
+
|
76
|
+
private_constant :ConstantDiscovery
|
78
77
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
begin
|
7
|
+
T::Configuration.default_checked_level = :never
|
8
|
+
|
9
|
+
T.singleton_class.prepend(
|
10
|
+
Module.new do
|
11
|
+
def cast(value, type, checked: true)
|
12
|
+
value
|
13
|
+
end
|
14
|
+
|
15
|
+
def let(value, type, checked: true)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
def must(arg)
|
20
|
+
arg
|
21
|
+
end
|
22
|
+
|
23
|
+
def absurd(value)
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def bind(value, type, checked: true)
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
)
|
32
|
+
rescue RuntimeError => error
|
33
|
+
# From https://github.com/sorbet/sorbet/blob/dcf1b069cfb0d6624c027e45e59f4c6ca33de970/gems/sorbet-runtime/lib/types/private/runtime_levels.rb#L54
|
34
|
+
# Sorbet has already evaluated a method call somewhere, so we can't disable it.
|
35
|
+
# In this case, we want to log a warning so Packwerk can still be used (but will be slower).
|
36
|
+
if /Set the default checked level earlier./.match?(error.message)
|
37
|
+
warn("Packwerk couldn't disable Sorbet. Please ensure it isn't being used before Packwerk is loaded.")
|
38
|
+
else
|
39
|
+
raise error
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
# This class handles loading extensions to packwerk using the `require` directive
|
6
|
+
# in the `packwerk.yml` configuration.
|
7
|
+
module ExtensionLoader
|
8
|
+
class << self
|
9
|
+
extend T::Sig
|
10
|
+
sig { params(require_directive: String, config_dir_path: String).void }
|
11
|
+
def load(require_directive, config_dir_path)
|
12
|
+
# We want to transform the require directive to behave differently
|
13
|
+
# if it's a specific local file being required versus a gem
|
14
|
+
if require_directive.start_with?(".")
|
15
|
+
require File.join(config_dir_path, require_directive)
|
16
|
+
else
|
17
|
+
require require_directive
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private_constant :ExtensionLoader
|
24
|
+
end
|
@@ -64,7 +64,7 @@ module Packwerk
|
|
64
64
|
references = []
|
65
65
|
|
66
66
|
node_processor = @node_processor_factory.for(relative_file: relative_file, node: node)
|
67
|
-
node_visitor =
|
67
|
+
node_visitor = NodeVisitor.new(node_processor: node_processor)
|
68
68
|
node_visitor.visit(node, ancestors: [], result: references)
|
69
69
|
|
70
70
|
references
|
@@ -82,4 +82,6 @@ module Packwerk
|
|
82
82
|
@parser_factory.for_path(file_path)
|
83
83
|
end
|
84
84
|
end
|
85
|
+
|
86
|
+
private_constant :FileProcessor
|
85
87
|
end
|
@@ -15,10 +15,10 @@ module Packwerk
|
|
15
15
|
relative_file_paths: T::Array[String],
|
16
16
|
configuration: Configuration,
|
17
17
|
ignore_nested_packages: T::Boolean
|
18
|
-
).returns(
|
18
|
+
).returns(FilesForProcessing)
|
19
19
|
end
|
20
20
|
def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
|
21
|
-
new(relative_file_paths, configuration, ignore_nested_packages)
|
21
|
+
new(relative_file_paths, configuration, ignore_nested_packages)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -33,37 +33,48 @@ module Packwerk
|
|
33
33
|
@relative_file_paths = relative_file_paths
|
34
34
|
@configuration = configuration
|
35
35
|
@ignore_nested_packages = ignore_nested_packages
|
36
|
-
@
|
36
|
+
@specified_files = T.let(nil, T.nilable(RelativeFileSet))
|
37
|
+
@files = T.let(nil, T.nilable(RelativeFileSet))
|
37
38
|
end
|
38
39
|
|
39
40
|
sig { returns(RelativeFileSet) }
|
40
41
|
def files
|
41
|
-
|
42
|
+
@files ||= files_for_processing
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(T::Boolean) }
|
46
|
+
def files_specified?
|
47
|
+
specified_files.any?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
sig { returns(RelativeFileSet) }
|
53
|
+
def files_for_processing
|
54
|
+
all_included_files = if specified_files.empty?
|
42
55
|
configured_included_files
|
43
56
|
else
|
44
|
-
configured_included_files &
|
57
|
+
configured_included_files & specified_files
|
45
58
|
end
|
46
59
|
|
47
|
-
|
60
|
+
all_included_files - configured_excluded_files
|
48
61
|
end
|
49
62
|
|
50
|
-
private
|
51
|
-
|
52
63
|
sig { returns(RelativeFileSet) }
|
53
|
-
def
|
54
|
-
@
|
64
|
+
def specified_files
|
65
|
+
@specified_files ||= Set.new(
|
55
66
|
@relative_file_paths.map do |relative_file_path|
|
56
67
|
if File.file?(relative_file_path)
|
57
68
|
relative_file_path
|
58
69
|
else
|
59
|
-
|
70
|
+
specified_included_files(relative_file_path)
|
60
71
|
end
|
61
72
|
end
|
62
73
|
).flatten
|
63
74
|
end
|
64
75
|
|
65
76
|
sig { params(relative_file_path: String).returns(RelativeFileSet) }
|
66
|
-
def
|
77
|
+
def specified_included_files(relative_file_path)
|
67
78
|
# Note, assuming include globs are always relative paths
|
68
79
|
relative_includes = @configuration.include
|
69
80
|
relative_files = Dir.glob([File.join(relative_file_path, "**", "*")]).select do |relative_path|
|
@@ -100,4 +111,6 @@ module Packwerk
|
|
100
111
|
Set.new(relative_globs.flat_map { |glob| Dir[glob] })
|
101
112
|
end
|
102
113
|
end
|
114
|
+
|
115
|
+
private_constant :FilesForProcessing
|
103
116
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module Formatters
|
6
|
+
class DefaultOffensesFormatter
|
7
|
+
include OffensesFormatter
|
8
|
+
|
9
|
+
IDENTIFIER = T.let("default", String)
|
10
|
+
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { override.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
|
14
|
+
def show_offenses(offenses)
|
15
|
+
return "No offenses detected" if offenses.empty?
|
16
|
+
|
17
|
+
<<~EOS
|
18
|
+
#{offenses_list(offenses)}
|
19
|
+
#{offenses_summary(offenses)}
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.params(offense_collection: OffenseCollection, fileset: T::Set[String]).returns(String) }
|
24
|
+
def show_stale_violations(offense_collection, fileset)
|
25
|
+
if offense_collection.stale_violations?(fileset)
|
26
|
+
"There were stale violations found, please run `packwerk update-todo`"
|
27
|
+
else
|
28
|
+
"No stale violations detected"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { override.returns(String) }
|
33
|
+
def identifier
|
34
|
+
IDENTIFIER
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { override.params(strict_mode_violations: T::Array[ReferenceOffense]).returns(String) }
|
38
|
+
def show_strict_mode_violations(strict_mode_violations)
|
39
|
+
if strict_mode_violations.any?
|
40
|
+
strict_mode_violations.compact.map { |offense| format_strict_mode_violation(offense) }.join("\n")
|
41
|
+
else
|
42
|
+
""
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
sig { returns(OutputStyle) }
|
49
|
+
def style
|
50
|
+
@style ||= T.let(Packwerk::OutputStyles::Coloured.new, T.nilable(Packwerk::OutputStyles::Coloured))
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(offense: ReferenceOffense).returns(String) }
|
54
|
+
def format_strict_mode_violation(offense)
|
55
|
+
reference_package = offense.reference.package
|
56
|
+
defining_package = offense.reference.constant.package
|
57
|
+
"#{reference_package} cannot have #{offense.violation_type} violations on #{defining_package} "\
|
58
|
+
"because strict mode is enabled for #{offense.violation_type} violations in "\
|
59
|
+
"the enforcing package's package.yml"
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
|
63
|
+
def offenses_list(offenses)
|
64
|
+
offenses
|
65
|
+
.compact
|
66
|
+
.map { |offense| offense.to_s(style) }
|
67
|
+
.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
|
71
|
+
def offenses_summary(offenses)
|
72
|
+
offenses_string = "offense".pluralize(offenses.length)
|
73
|
+
"#{offenses.length} #{offenses_string} detected"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|