packwerk 1.1.1 → 1.1.2
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/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/exe/packwerk +1 -1
- data/lib/packwerk.rb +3 -1
- data/lib/packwerk/application_load_paths.rb +3 -2
- data/lib/packwerk/application_validator.rb +78 -57
- data/lib/packwerk/association_inspector.rb +23 -8
- data/lib/packwerk/cache_deprecated_references.rb +10 -2
- data/lib/packwerk/checker.rb +3 -0
- data/lib/packwerk/checking_deprecated_references.rb +5 -2
- data/lib/packwerk/cli.rb +12 -2
- data/lib/packwerk/commands/detect_stale_violations_command.rb +1 -1
- data/lib/packwerk/const_node_inspector.rb +13 -12
- data/lib/packwerk/dependency_checker.rb +13 -5
- data/lib/packwerk/deprecated_references.rb +5 -1
- data/lib/packwerk/formatters/offenses_formatter.rb +4 -5
- data/lib/packwerk/formatters/progress_formatter.rb +6 -2
- data/lib/packwerk/node.rb +3 -0
- data/lib/packwerk/offense.rb +4 -6
- data/lib/packwerk/output_style.rb +20 -0
- data/lib/packwerk/output_styles/coloured.rb +29 -0
- data/lib/packwerk/output_styles/plain.rb +26 -0
- data/lib/packwerk/package_set.rb +2 -1
- data/lib/packwerk/privacy_checker.rb +23 -5
- data/lib/packwerk/version.rb +1 -1
- metadata +5 -3
- data/lib/packwerk/output_styles.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd008f86d84fd0224aac9d8f92bc104123f23869e9381c947b3e51864523c947
|
4
|
+
data.tar.gz: debf7f89ce8f8419a02f2af84c3f1900c7bc2f2ac79cce535284e3e697133d74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bf12776a5e5cd4f3f9d06f3bbabb3d9b0c6d873325dba9a7bc43dc49ffedbc119678b77a2087cface6428bf564e037b75386abb8fca2d06a313e617461db53a
|
7
|
+
data.tar.gz: d6ca7a42dea1c5338c40e89b6fe6b14baa43372cf44e22af3f19763ee1048c03629e34f4466b632e7f5b6d82932b8e05b2d02d5e26ebf91852334b9f2ca6d5b2
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applications.
|
4
4
|
|
5
5
|
Packwerk can be used to:
|
6
|
-
* Combine
|
6
|
+
* Combine groups of files into packages
|
7
7
|
* Define package-level constant visibility (i.e. have publicly accessible constants)
|
8
8
|
* Enforce privacy (inbound) and dependency (outbound) boundaries between packages
|
9
9
|
* Help existing codebases to become more modular without obstructing development
|
data/exe/packwerk
CHANGED
data/lib/packwerk.rb
CHANGED
@@ -27,7 +27,9 @@ require "packwerk/graph"
|
|
27
27
|
require "packwerk/inflector"
|
28
28
|
require "packwerk/node_processor"
|
29
29
|
require "packwerk/node_visitor"
|
30
|
-
require "packwerk/
|
30
|
+
require "packwerk/output_style"
|
31
|
+
require "packwerk/output_styles/plain"
|
32
|
+
require "packwerk/output_styles/coloured"
|
31
33
|
require "packwerk/package"
|
32
34
|
require "packwerk/package_set"
|
33
35
|
require "packwerk/parsers"
|
@@ -28,8 +28,9 @@ module Packwerk
|
|
28
28
|
.select { |railtie| railtie.is_a?(Rails::Engine) }
|
29
29
|
.push(Rails.application)
|
30
30
|
.flat_map do |engine|
|
31
|
-
|
32
|
-
|
31
|
+
paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
|
32
|
+
paths.map(&:to_s).uniq
|
33
|
+
end
|
33
34
|
end
|
34
35
|
|
35
36
|
sig do
|
@@ -35,16 +35,7 @@ module Packwerk
|
|
35
35
|
check_root_package_exists,
|
36
36
|
]
|
37
37
|
|
38
|
-
results
|
39
|
-
|
40
|
-
if results.empty?
|
41
|
-
Result.new(true)
|
42
|
-
else
|
43
|
-
Result.new(
|
44
|
-
false,
|
45
|
-
results.map(&:error_value).join("\n===\n")
|
46
|
-
)
|
47
|
-
end
|
38
|
+
merge_results(results)
|
48
39
|
end
|
49
40
|
|
50
41
|
def check_autoload_path_cache
|
@@ -65,51 +56,37 @@ module Packwerk
|
|
65
56
|
def check_package_manifests_for_privacy
|
66
57
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
67
58
|
|
68
|
-
autoload_paths = @configuration.load_paths
|
69
|
-
|
70
59
|
resolver = ConstantResolver.new(
|
71
60
|
root_path: @configuration.root_path,
|
72
|
-
load_paths:
|
61
|
+
load_paths: @configuration.load_paths
|
73
62
|
)
|
74
63
|
|
75
|
-
|
64
|
+
results = []
|
76
65
|
|
77
|
-
privacy_settings.each do |
|
66
|
+
privacy_settings.each do |config_file_path, setting|
|
78
67
|
next unless setting.is_a?(Array)
|
68
|
+
constants = setting
|
79
69
|
|
80
|
-
|
81
|
-
# make sure the constant can be loaded
|
82
|
-
constant.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
83
|
-
context = resolver.resolve(constant)
|
70
|
+
assert_constants_can_be_loaded(constants)
|
84
71
|
|
85
|
-
|
86
|
-
errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
|
87
|
-
next
|
88
|
-
end
|
89
|
-
|
90
|
-
expected_filename = constant.underscore + ".rb"
|
72
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
91
73
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
"It should be in something like #{expected_filename.inspect}"
|
74
|
+
constant_locations.each do |name, location|
|
75
|
+
results << if location
|
76
|
+
check_private_constant_location(name, location, config_file_path)
|
77
|
+
else
|
78
|
+
private_constant_unresolvable(name, config_file_path)
|
79
|
+
end
|
99
80
|
end
|
100
81
|
end
|
101
82
|
|
102
|
-
|
103
|
-
Result.new(true)
|
104
|
-
else
|
105
|
-
Result.new(false, errors.join("\n---\n"))
|
106
|
-
end
|
83
|
+
merge_results(results, separator: "\n---\n")
|
107
84
|
end
|
108
85
|
|
109
86
|
def check_package_manifest_syntax
|
110
87
|
errors = []
|
111
88
|
|
112
|
-
package_manifests
|
89
|
+
package_manifests.each do |f|
|
113
90
|
hash = YAML.load_file(f)
|
114
91
|
next unless hash
|
115
92
|
|
@@ -193,24 +170,16 @@ module Packwerk
|
|
193
170
|
end
|
194
171
|
end
|
195
172
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
Result.new(
|
202
|
-
false,
|
203
|
-
"Inflections specified in #{inflections_file} don't line up with application!\n" +
|
204
|
-
errors.map(&:error_value).join("\n")
|
205
|
-
)
|
206
|
-
end
|
173
|
+
merge_results(
|
174
|
+
results,
|
175
|
+
separator: "\n",
|
176
|
+
errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
|
177
|
+
)
|
207
178
|
end
|
208
179
|
|
209
180
|
def check_acyclic_graph
|
210
|
-
|
211
|
-
|
212
|
-
edges = packages.flat_map do |package|
|
213
|
-
package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
|
181
|
+
edges = package_set.flat_map do |package|
|
182
|
+
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
214
183
|
end
|
215
184
|
dependency_graph = Packwerk::Graph.new(*edges)
|
216
185
|
|
@@ -316,8 +285,7 @@ module Packwerk
|
|
316
285
|
private
|
317
286
|
|
318
287
|
def package_manifests_settings_for(setting)
|
319
|
-
package_manifests(
|
320
|
-
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
288
|
+
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
321
289
|
end
|
322
290
|
|
323
291
|
def format_yaml_strings(list)
|
@@ -328,13 +296,17 @@ module Packwerk
|
|
328
296
|
@configuration.package_paths || "**"
|
329
297
|
end
|
330
298
|
|
331
|
-
def package_manifests(glob_pattern)
|
299
|
+
def package_manifests(glob_pattern = package_glob)
|
332
300
|
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
333
301
|
.map { |f| File.realpath(f) }
|
334
302
|
end
|
335
303
|
|
336
304
|
def relative_paths(paths)
|
337
|
-
paths.map { |path|
|
305
|
+
paths.map { |path| relative_path(path) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def relative_path(path)
|
309
|
+
Pathname.new(path).relative_path_from(@configuration.root_path)
|
338
310
|
end
|
339
311
|
|
340
312
|
def invalid_package_path?(path)
|
@@ -344,5 +316,54 @@ module Packwerk
|
|
344
316
|
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
345
317
|
!File.file?(package_path)
|
346
318
|
end
|
319
|
+
|
320
|
+
def assert_constants_can_be_loaded(constants)
|
321
|
+
constants.each(&:constantize)
|
322
|
+
nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def private_constant_unresolvable(name, config_file_path)
|
326
|
+
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
327
|
+
|
328
|
+
Result.new(
|
329
|
+
false,
|
330
|
+
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
331
|
+
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
332
|
+
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
333
|
+
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
334
|
+
)
|
335
|
+
end
|
336
|
+
|
337
|
+
def check_private_constant_location(name, location, config_file_path)
|
338
|
+
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
339
|
+
constant_package = package_set.package_from_path(location)
|
340
|
+
|
341
|
+
if constant_package == declared_package
|
342
|
+
Result.new(true)
|
343
|
+
else
|
344
|
+
Result.new(
|
345
|
+
false,
|
346
|
+
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
347
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
348
|
+
)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def package_set
|
353
|
+
@package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
|
354
|
+
end
|
355
|
+
|
356
|
+
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
357
|
+
results.reject!(&:ok?)
|
358
|
+
|
359
|
+
if results.empty?
|
360
|
+
Result.new(true)
|
361
|
+
else
|
362
|
+
Result.new(
|
363
|
+
false,
|
364
|
+
errors_headline + results.map(&:error_value).join(separator)
|
365
|
+
)
|
366
|
+
end
|
367
|
+
end
|
347
368
|
end
|
348
369
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "packwerk/constant_name_inspector"
|
@@ -7,20 +7,32 @@ require "packwerk/node"
|
|
7
7
|
module Packwerk
|
8
8
|
# Extracts the implicit constant reference from an active record association
|
9
9
|
class AssociationInspector
|
10
|
+
extend T::Sig
|
10
11
|
include ConstantNameInspector
|
11
12
|
|
12
|
-
|
13
|
-
belongs_to
|
14
|
-
has_many
|
15
|
-
has_one
|
16
|
-
has_and_belongs_to_many
|
17
|
-
).to_set
|
13
|
+
CustomAssociations = T.type_alias { T.any(T::Array[Symbol], T::Set[Symbol]) }
|
18
14
|
|
15
|
+
RAILS_ASSOCIATIONS = T.let(
|
16
|
+
%i(
|
17
|
+
belongs_to
|
18
|
+
has_many
|
19
|
+
has_one
|
20
|
+
has_and_belongs_to_many
|
21
|
+
).to_set,
|
22
|
+
CustomAssociations
|
23
|
+
)
|
24
|
+
|
25
|
+
sig { params(inflector: Inflector, custom_associations: CustomAssociations).void }
|
19
26
|
def initialize(inflector:, custom_associations: Set.new)
|
20
27
|
@inflector = inflector
|
21
|
-
@associations = RAILS_ASSOCIATIONS + custom_associations
|
28
|
+
@associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
|
22
29
|
end
|
23
30
|
|
31
|
+
sig do
|
32
|
+
override
|
33
|
+
.params(node: AST::Node, ancestors: T::Array[AST::Node])
|
34
|
+
.returns(T.nilable(String))
|
35
|
+
end
|
24
36
|
def constant_name_from_node(node, ancestors:)
|
25
37
|
return unless Node.method_call?(node)
|
26
38
|
return unless association?(node)
|
@@ -38,11 +50,13 @@ module Packwerk
|
|
38
50
|
|
39
51
|
private
|
40
52
|
|
53
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
41
54
|
def association?(node)
|
42
55
|
method_name = Node.method_name(node)
|
43
56
|
@associations.include?(method_name)
|
44
57
|
end
|
45
58
|
|
59
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.nilable(AST::Node)) }
|
46
60
|
def custom_class_name(arguments)
|
47
61
|
association_options = arguments.detect { |n| Node.hash?(n) }
|
48
62
|
return unless association_options
|
@@ -50,6 +64,7 @@ module Packwerk
|
|
50
64
|
Node.value_from_hash(association_options, :class_name)
|
51
65
|
end
|
52
66
|
|
67
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.any(T.nilable(Symbol), T.nilable(String))) }
|
53
68
|
def association_name(arguments)
|
54
69
|
return unless Node.symbol?(arguments[0])
|
55
70
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
@@ -15,9 +15,15 @@ module Packwerk
|
|
15
15
|
include ReferenceLister
|
16
16
|
abstract!
|
17
17
|
|
18
|
+
sig do
|
19
|
+
params(
|
20
|
+
root_path: String,
|
21
|
+
deprecated_references: T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences]
|
22
|
+
).void
|
23
|
+
end
|
18
24
|
def initialize(root_path, deprecated_references = {})
|
19
25
|
@root_path = root_path
|
20
|
-
@deprecated_references = T.let(deprecated_references, T::Hash[
|
26
|
+
@deprecated_references = T.let(deprecated_references, T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences])
|
21
27
|
end
|
22
28
|
|
23
29
|
sig do
|
@@ -33,6 +39,7 @@ module Packwerk
|
|
33
39
|
|
34
40
|
private
|
35
41
|
|
42
|
+
sig { params(package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
|
36
43
|
def deprecated_references_for(package)
|
37
44
|
@deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
|
38
45
|
package,
|
@@ -40,6 +47,7 @@ module Packwerk
|
|
40
47
|
)
|
41
48
|
end
|
42
49
|
|
50
|
+
sig { params(package: Packwerk::Package).returns(String) }
|
43
51
|
def deprecated_references_file_for(package)
|
44
52
|
File.join(@root_path, package.name, "deprecated_references.yml")
|
45
53
|
end
|
data/lib/packwerk/checker.rb
CHANGED
@@ -11,6 +11,9 @@ module Packwerk
|
|
11
11
|
|
12
12
|
interface!
|
13
13
|
|
14
|
+
sig { returns(ViolationType).abstract }
|
15
|
+
def violation_type; end
|
16
|
+
|
14
17
|
sig { params(reference: Reference, reference_lister: ReferenceLister).returns(T::Boolean).abstract }
|
15
18
|
def invalid_reference?(reference, reference_lister); end
|
16
19
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
@@ -10,9 +10,10 @@ module Packwerk
|
|
10
10
|
extend T::Sig
|
11
11
|
include ReferenceLister
|
12
12
|
|
13
|
+
sig { params(root_path: String).void }
|
13
14
|
def initialize(root_path)
|
14
15
|
@root_path = root_path
|
15
|
-
@deprecated_references = {}
|
16
|
+
@deprecated_references = T.let({}, T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences])
|
16
17
|
end
|
17
18
|
|
18
19
|
sig do
|
@@ -26,6 +27,7 @@ module Packwerk
|
|
26
27
|
|
27
28
|
private
|
28
29
|
|
30
|
+
sig { params(source_package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
|
29
31
|
def deprecated_references_for(source_package)
|
30
32
|
@deprecated_references[source_package] ||= Packwerk::DeprecatedReferences.new(
|
31
33
|
source_package,
|
@@ -33,6 +35,7 @@ module Packwerk
|
|
33
35
|
)
|
34
36
|
end
|
35
37
|
|
38
|
+
sig { params(package: Packwerk::Package).returns(String) }
|
36
39
|
def deprecated_references_file_for(package)
|
37
40
|
File.join(@root_path, package.name, "deprecated_references.yml")
|
38
41
|
end
|
data/lib/packwerk/cli.rb
CHANGED
@@ -10,7 +10,8 @@ require "packwerk/files_for_processing"
|
|
10
10
|
require "packwerk/formatters/offenses_formatter"
|
11
11
|
require "packwerk/formatters/progress_formatter"
|
12
12
|
require "packwerk/inflector"
|
13
|
-
require "packwerk/
|
13
|
+
require "packwerk/output_style"
|
14
|
+
require "packwerk/output_styles/plain"
|
14
15
|
require "packwerk/run_context"
|
15
16
|
require "packwerk/updating_deprecated_references"
|
16
17
|
require "packwerk/checking_deprecated_references"
|
@@ -23,7 +24,16 @@ module Packwerk
|
|
23
24
|
extend T::Sig
|
24
25
|
include OffenseProgressMarker
|
25
26
|
|
26
|
-
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
run_context: T.nilable(Packwerk::RunContext),
|
30
|
+
configuration: T.nilable(Configuration),
|
31
|
+
out: T.any(StringIO, IO),
|
32
|
+
err_out: T.any(StringIO, IO),
|
33
|
+
style: Packwerk::OutputStyle
|
34
|
+
).void
|
35
|
+
end
|
36
|
+
def initialize(run_context: nil, configuration: nil, out: $stdout, err_out: $stderr, style: OutputStyles::Plain.new)
|
27
37
|
@out = out
|
28
38
|
@err_out = err_out
|
29
39
|
@style = style
|
@@ -49,7 +49,7 @@ module Packwerk
|
|
49
49
|
sig { returns(Result) }
|
50
50
|
def calculate_result
|
51
51
|
result_status = !reference_lister.stale_violations?
|
52
|
-
message = "There were stale violations found, please run `packwerk update`"
|
52
|
+
message = "There were stale violations found, please run `packwerk update-deprecations`"
|
53
53
|
if result_status
|
54
54
|
message = "No stale violations detected"
|
55
55
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "packwerk/constant_name_inspector"
|
@@ -6,15 +6,21 @@ require "packwerk/constant_name_inspector"
|
|
6
6
|
module Packwerk
|
7
7
|
# Extracts a constant name from an AST node of type :const
|
8
8
|
class ConstNodeInspector
|
9
|
+
extend T::Sig
|
9
10
|
include ConstantNameInspector
|
10
11
|
|
12
|
+
sig do
|
13
|
+
override
|
14
|
+
.params(node: AST::Node, ancestors: T::Array[AST::Node])
|
15
|
+
.returns(T.nilable(String))
|
16
|
+
end
|
11
17
|
def constant_name_from_node(node, ancestors:)
|
12
18
|
return nil unless Node.constant?(node)
|
13
19
|
parent = ancestors.first
|
14
20
|
return nil unless root_constant?(parent)
|
15
21
|
|
16
22
|
if parent && constant_in_module_or_class_definition?(node, parent: parent)
|
17
|
-
fully_qualify_constant(
|
23
|
+
fully_qualify_constant(ancestors)
|
18
24
|
else
|
19
25
|
begin
|
20
26
|
Node.constant_name(node)
|
@@ -28,27 +34,22 @@ module Packwerk
|
|
28
34
|
|
29
35
|
# Only process the root `const` node for namespaced constant references. For example, in the
|
30
36
|
# reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
|
37
|
+
sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
|
31
38
|
def root_constant?(parent)
|
32
39
|
!(parent && Node.constant?(parent))
|
33
40
|
end
|
34
41
|
|
42
|
+
sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
|
35
43
|
def constant_in_module_or_class_definition?(node, parent:)
|
36
44
|
parent_name = Node.module_name_from_definition(parent)
|
37
45
|
parent_name && parent_name == Node.constant_name(node)
|
38
46
|
end
|
39
47
|
|
40
|
-
|
48
|
+
sig { params(ancestors: T::Array[AST::Node]).returns(String) }
|
49
|
+
def fully_qualify_constant(ancestors)
|
41
50
|
# We're defining a class with this name, in which case the constant is implicitly fully qualified by its
|
42
51
|
# enclosing namespace
|
43
|
-
|
44
|
-
name ||= generate_qualified_constant(node, ancestors)
|
45
|
-
"::" + name
|
46
|
-
end
|
47
|
-
|
48
|
-
def generate_qualified_constant(node, ancestors:)
|
49
|
-
namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
|
50
|
-
constant_name = Node.constant_name(node)
|
51
|
-
namespace_path.push(constant_name).join("::")
|
52
|
+
"::" + Node.parent_module_name(ancestors: ancestors)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "packwerk/violation_type"
|
@@ -6,20 +6,28 @@ require "packwerk/checker"
|
|
6
6
|
|
7
7
|
module Packwerk
|
8
8
|
class DependencyChecker
|
9
|
+
extend T::Sig
|
9
10
|
include Checker
|
10
11
|
|
12
|
+
sig { override.returns(ViolationType) }
|
11
13
|
def violation_type
|
12
14
|
ViolationType::Dependency
|
13
15
|
end
|
14
16
|
|
17
|
+
sig do
|
18
|
+
override
|
19
|
+
.params(reference: Packwerk::Reference, reference_lister: Packwerk::ReferenceLister)
|
20
|
+
.returns(T::Boolean)
|
21
|
+
end
|
15
22
|
def invalid_reference?(reference, reference_lister)
|
16
|
-
return unless reference.source_package
|
17
|
-
return unless reference.source_package.enforce_dependencies?
|
18
|
-
return if reference.source_package.dependency?(reference.constant.package)
|
19
|
-
return if reference_lister.listed?(reference, violation_type: violation_type)
|
23
|
+
return false unless reference.source_package
|
24
|
+
return false unless reference.source_package.enforce_dependencies?
|
25
|
+
return false if reference.source_package.dependency?(reference.constant.package)
|
26
|
+
return false if reference_lister.listed?(reference, violation_type: violation_type)
|
20
27
|
true
|
21
28
|
end
|
22
29
|
|
30
|
+
sig { override.params(reference: Packwerk::Reference).returns(String) }
|
23
31
|
def message_for(reference)
|
24
32
|
"Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
|
25
33
|
"'#{reference.source_package}' does not specify a dependency on " \
|
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "yaml"
|
6
|
-
require "sorbet-runtime"
|
7
6
|
|
8
7
|
require "packwerk/reference"
|
9
8
|
require "packwerk/reference_lister"
|
@@ -14,6 +13,7 @@ module Packwerk
|
|
14
13
|
extend T::Sig
|
15
14
|
include ReferenceLister
|
16
15
|
|
16
|
+
sig { params(package: Packwerk::Package, filepath: String).void }
|
17
17
|
def initialize(package, filepath)
|
18
18
|
@package = package
|
19
19
|
@filepath = filepath
|
@@ -35,6 +35,7 @@ module Packwerk
|
|
35
35
|
violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
|
36
36
|
end
|
37
37
|
|
38
|
+
sig { params(reference: Packwerk::Reference, violation_type: String).void }
|
38
39
|
def add_entries(reference, violation_type)
|
39
40
|
package_violations = @new_entries.fetch(reference.constant.package.name, {})
|
40
41
|
entries_for_file = package_violations[reference.constant.name] ||= {}
|
@@ -66,6 +67,7 @@ module Packwerk
|
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
70
|
+
sig { void }
|
69
71
|
def dump
|
70
72
|
if @new_entries.empty?
|
71
73
|
File.delete(@filepath) if File.exist?(@filepath)
|
@@ -88,6 +90,7 @@ module Packwerk
|
|
88
90
|
|
89
91
|
private
|
90
92
|
|
93
|
+
sig { returns(Hash) }
|
91
94
|
def prepare_entries_for_dump
|
92
95
|
@new_entries.each do |package_name, package_violations|
|
93
96
|
package_violations.each do |_, entries_for_file|
|
@@ -100,6 +103,7 @@ module Packwerk
|
|
100
103
|
@new_entries = @new_entries.sort.to_h
|
101
104
|
end
|
102
105
|
|
106
|
+
sig { returns(Hash) }
|
103
107
|
def deprecated_references
|
104
108
|
@deprecated_references ||= if File.exist?(@filepath)
|
105
109
|
YAML.load_file(@filepath) || {}
|
@@ -5,17 +5,16 @@ require "benchmark"
|
|
5
5
|
require "sorbet-runtime"
|
6
6
|
|
7
7
|
require "packwerk/inflector"
|
8
|
-
require "packwerk/
|
8
|
+
require "packwerk/output_style"
|
9
|
+
require "packwerk/output_styles/plain"
|
9
10
|
|
10
11
|
module Packwerk
|
11
12
|
module Formatters
|
12
13
|
class OffensesFormatter
|
13
14
|
extend T::Sig
|
14
15
|
|
15
|
-
sig
|
16
|
-
|
17
|
-
end
|
18
|
-
def initialize(style: OutputStyles::Plain)
|
16
|
+
sig { params(style: OutputStyle).void }
|
17
|
+
def initialize(style: OutputStyles::Plain.new)
|
19
18
|
@style = style
|
20
19
|
end
|
21
20
|
|
@@ -4,12 +4,16 @@
|
|
4
4
|
require "benchmark"
|
5
5
|
|
6
6
|
require "packwerk/inflector"
|
7
|
-
require "packwerk/
|
7
|
+
require "packwerk/output_style"
|
8
|
+
require "packwerk/output_styles/plain"
|
8
9
|
|
9
10
|
module Packwerk
|
10
11
|
module Formatters
|
11
12
|
class ProgressFormatter
|
12
|
-
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(out: T.any(StringIO, IO), style: OutputStyle).void }
|
16
|
+
def initialize(out, style: OutputStyles::Plain.new)
|
13
17
|
@out = out
|
14
18
|
@style = style
|
15
19
|
end
|
data/lib/packwerk/node.rb
CHANGED
@@ -9,6 +9,8 @@ module Packwerk
|
|
9
9
|
Location = Struct.new(:line, :column)
|
10
10
|
|
11
11
|
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
12
14
|
def class_or_module_name(class_or_module_node)
|
13
15
|
case type_of(class_or_module_node)
|
14
16
|
when CLASS, MODULE
|
@@ -178,6 +180,7 @@ module Packwerk
|
|
178
180
|
class_node.children[1]
|
179
181
|
end
|
180
182
|
|
183
|
+
sig { params(ancestors: T::Array[AST::Node]).returns(String) }
|
181
184
|
def parent_module_name(ancestors:)
|
182
185
|
definitions = ancestors
|
183
186
|
.select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
|
data/lib/packwerk/offense.rb
CHANGED
@@ -4,7 +4,8 @@
|
|
4
4
|
require "parser/source/map"
|
5
5
|
require "sorbet-runtime"
|
6
6
|
|
7
|
-
require "packwerk/
|
7
|
+
require "packwerk/output_style"
|
8
|
+
require "packwerk/output_styles/plain"
|
8
9
|
|
9
10
|
module Packwerk
|
10
11
|
class Offense
|
@@ -23,11 +24,8 @@ module Packwerk
|
|
23
24
|
@message = message
|
24
25
|
end
|
25
26
|
|
26
|
-
sig
|
27
|
-
|
28
|
-
.returns(String)
|
29
|
-
end
|
30
|
-
def to_s(style = OutputStyles::Plain)
|
27
|
+
sig { params(style: OutputStyle).returns(String) }
|
28
|
+
def to_s(style = OutputStyles::Plain.new)
|
31
29
|
if location
|
32
30
|
<<~EOS
|
33
31
|
#{style.filename}#{file}#{style.reset}:#{location.line}:#{location.column}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyle
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
interface!
|
10
|
+
|
11
|
+
sig { abstract.returns(String) }
|
12
|
+
def reset; end
|
13
|
+
|
14
|
+
sig { abstract.returns(String) }
|
15
|
+
def filename; end
|
16
|
+
|
17
|
+
sig { abstract.returns(String) }
|
18
|
+
def error; end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyles
|
6
|
+
# See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
|
7
|
+
class Coloured
|
8
|
+
extend T::Sig
|
9
|
+
include OutputStyle
|
10
|
+
|
11
|
+
sig { override.returns(String) }
|
12
|
+
def reset
|
13
|
+
"\033[m"
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { override.returns(String) }
|
17
|
+
def filename
|
18
|
+
# 36 is foreground cyan
|
19
|
+
"\033[36m"
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.returns(String) }
|
23
|
+
def error
|
24
|
+
# 31 is foreground red
|
25
|
+
"\033[31m"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyles
|
6
|
+
class Plain
|
7
|
+
extend T::Sig
|
8
|
+
include OutputStyle
|
9
|
+
|
10
|
+
sig { override.returns(String) }
|
11
|
+
def reset
|
12
|
+
""
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.returns(String) }
|
16
|
+
def filename
|
17
|
+
""
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { override.returns(String) }
|
21
|
+
def error
|
22
|
+
""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/packwerk/package_set.rb
CHANGED
@@ -60,7 +60,8 @@ module Packwerk
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def package_from_path(file_path)
|
63
|
-
|
63
|
+
path_string = file_path.to_s
|
64
|
+
@packages.values.find { |package| package.package_path?(path_string) }
|
64
65
|
end
|
65
66
|
end
|
66
67
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "packwerk/violation_type"
|
@@ -6,26 +6,34 @@ require "packwerk/checker"
|
|
6
6
|
|
7
7
|
module Packwerk
|
8
8
|
class PrivacyChecker
|
9
|
+
extend T::Sig
|
9
10
|
include Checker
|
10
11
|
|
12
|
+
sig { override.returns(Packwerk::ViolationType) }
|
11
13
|
def violation_type
|
12
14
|
ViolationType::Privacy
|
13
15
|
end
|
14
16
|
|
17
|
+
sig do
|
18
|
+
override
|
19
|
+
.params(reference: Packwerk::Reference, reference_lister: Packwerk::ReferenceLister)
|
20
|
+
.returns(T::Boolean)
|
21
|
+
end
|
15
22
|
def invalid_reference?(reference, reference_lister)
|
16
|
-
return if reference.constant.public?
|
23
|
+
return false if reference.constant.public?
|
17
24
|
|
18
25
|
privacy_option = reference.constant.package.enforce_privacy
|
19
|
-
return if enforcement_disabled?(privacy_option)
|
26
|
+
return false if enforcement_disabled?(privacy_option)
|
20
27
|
|
21
|
-
return unless privacy_option == true ||
|
28
|
+
return false unless privacy_option == true ||
|
22
29
|
explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_option)
|
23
30
|
|
24
|
-
return if reference_lister.listed?(reference, violation_type: violation_type)
|
31
|
+
return false if reference_lister.listed?(reference, violation_type: violation_type)
|
25
32
|
|
26
33
|
true
|
27
34
|
end
|
28
35
|
|
36
|
+
sig { override.params(reference: Packwerk::Reference).returns(String) }
|
29
37
|
def message_for(reference)
|
30
38
|
source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
|
31
39
|
"Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
|
@@ -35,12 +43,22 @@ module Packwerk
|
|
35
43
|
|
36
44
|
private
|
37
45
|
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
constant: ConstantDiscovery::ConstantContext,
|
49
|
+
explicitly_private_constants: T::Array[String]
|
50
|
+
).returns(T::Boolean)
|
51
|
+
end
|
38
52
|
def explicitly_private_constant?(constant, explicitly_private_constants:)
|
39
53
|
explicitly_private_constants.include?(constant.name) ||
|
40
54
|
# nested constants
|
41
55
|
explicitly_private_constants.any? { |epc| constant.name.start_with?(epc + "::") }
|
42
56
|
end
|
43
57
|
|
58
|
+
sig do
|
59
|
+
params(privacy_option: T.nilable(T.any(T::Boolean, T::Array[String])))
|
60
|
+
.returns(T::Boolean)
|
61
|
+
end
|
44
62
|
def enforcement_disabled?(privacy_option)
|
45
63
|
[false, nil].include?(privacy_option)
|
46
64
|
end
|
data/lib/packwerk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -223,7 +223,9 @@ files:
|
|
223
223
|
- lib/packwerk/node_processor_factory.rb
|
224
224
|
- lib/packwerk/node_visitor.rb
|
225
225
|
- lib/packwerk/offense.rb
|
226
|
-
- lib/packwerk/
|
226
|
+
- lib/packwerk/output_style.rb
|
227
|
+
- lib/packwerk/output_styles/coloured.rb
|
228
|
+
- lib/packwerk/output_styles/plain.rb
|
227
229
|
- lib/packwerk/package.rb
|
228
230
|
- lib/packwerk/package_set.rb
|
229
231
|
- lib/packwerk/parsed_constant_definitions.rb
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Packwerk
|
5
|
-
module OutputStyles
|
6
|
-
class Plain
|
7
|
-
class << self
|
8
|
-
def reset
|
9
|
-
""
|
10
|
-
end
|
11
|
-
|
12
|
-
def filename
|
13
|
-
""
|
14
|
-
end
|
15
|
-
|
16
|
-
def error
|
17
|
-
""
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
|
23
|
-
class Coloured
|
24
|
-
class << self
|
25
|
-
def reset
|
26
|
-
"\033[m"
|
27
|
-
end
|
28
|
-
|
29
|
-
def filename
|
30
|
-
# 36 is foreground cyan
|
31
|
-
"\033[36m"
|
32
|
-
end
|
33
|
-
|
34
|
-
def error
|
35
|
-
# 31 is foreground red
|
36
|
-
"\033[31m"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|