packwerk 2.2.2 → 3.0.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 +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 +7 -7
- data/TROUBLESHOOT.md +1 -23
- data/USAGE.md +149 -59
- data/dev.yml +1 -1
- data/exe/packwerk +1 -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 +56 -31
- 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 +43 -23
- 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/{deprecated_references.rb → package_todo.rb} +29 -13
- data/lib/packwerk/parse_run.rb +29 -36
- 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 +11 -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 +35 -16
- 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
@@ -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
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "benchmark"
|
@@ -14,37 +14,56 @@ module Packwerk
|
|
14
14
|
@style = style
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
files_size = target_files.size
|
19
|
-
files_string = "file".pluralize(files_size)
|
20
|
-
@out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
|
21
|
-
end
|
22
|
-
|
17
|
+
sig { params(block: T.proc.void).void }
|
23
18
|
def started_validation(&block)
|
24
|
-
|
19
|
+
start_validation
|
25
20
|
|
26
21
|
execution_time = Benchmark.realtime(&block)
|
27
22
|
finished(execution_time)
|
23
|
+
end
|
28
24
|
|
29
|
-
|
25
|
+
sig { params(target_files: FilesForProcessing::RelativeFileSet, block: T.proc.void).void }
|
26
|
+
def started_inspection(target_files, &block)
|
27
|
+
start_inspection(target_files)
|
28
|
+
|
29
|
+
execution_time = Benchmark.realtime(&block)
|
30
|
+
finished(execution_time)
|
30
31
|
end
|
31
32
|
|
33
|
+
sig { void }
|
32
34
|
def mark_as_inspected
|
33
35
|
@out.print(".")
|
34
36
|
end
|
35
37
|
|
38
|
+
sig { void }
|
36
39
|
def mark_as_failed
|
37
40
|
@out.print("#{@style.error}E#{@style.reset}")
|
38
41
|
end
|
39
42
|
|
43
|
+
sig { void }
|
44
|
+
def interrupted
|
45
|
+
@out.puts
|
46
|
+
@out.puts("Manually interrupted. Violations caught so far are listed below:")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { params(execution_time: Float).void }
|
40
52
|
def finished(execution_time)
|
41
53
|
@out.puts
|
42
54
|
@out.puts("📦 Finished in #{execution_time.round(2)} seconds")
|
43
55
|
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
@out.puts("
|
57
|
+
sig { void }
|
58
|
+
def start_validation
|
59
|
+
@out.puts("📦 Packwerk is running validation...")
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(target_files: FilesForProcessing::RelativeFileSet).void }
|
63
|
+
def start_inspection(target_files)
|
64
|
+
files_size = target_files.size
|
65
|
+
files_string = "file".pluralize(files_size)
|
66
|
+
@out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
|
48
67
|
end
|
49
68
|
end
|
50
69
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "erb"
|
@@ -11,6 +11,9 @@ module Packwerk
|
|
11
11
|
CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
|
12
12
|
|
13
13
|
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
|
14
17
|
def generate(root:, out:)
|
15
18
|
new(root: root, out: out).generate
|
16
19
|
end
|
@@ -25,7 +28,7 @@ module Packwerk
|
|
25
28
|
sig { returns(T::Boolean) }
|
26
29
|
def generate
|
27
30
|
@out.puts("📦 Generating Packwerk configuration file...")
|
28
|
-
default_config_path = File.join(@root,
|
31
|
+
default_config_path = File.join(@root, Configuration::DEFAULT_CONFIG_PATH)
|
29
32
|
|
30
33
|
if File.exist?(default_config_path)
|
31
34
|
@out.puts("⚠️ Packwerk configuration file already exists.")
|
@@ -40,10 +43,12 @@ module Packwerk
|
|
40
43
|
|
41
44
|
private
|
42
45
|
|
46
|
+
sig { returns(String) }
|
43
47
|
def render
|
44
48
|
ERB.new(template, trim_mode: "-").result(binding)
|
45
49
|
end
|
46
50
|
|
51
|
+
sig { returns(String) }
|
47
52
|
def template
|
48
53
|
template_file_path = File.join(__dir__, CONFIGURATION_TEMPLATE_FILE_PATH)
|
49
54
|
File.read(template_file_path)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -7,11 +7,15 @@ module Packwerk
|
|
7
7
|
extend T::Sig
|
8
8
|
|
9
9
|
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
|
10
13
|
def generate(root:, out:)
|
11
14
|
new(root: root, out: out).generate
|
12
15
|
end
|
13
16
|
end
|
14
17
|
|
18
|
+
sig { params(root: String, out: T.any(IO, StringIO)).void }
|
15
19
|
def initialize(root:, out: $stdout)
|
16
20
|
@root = root
|
17
21
|
@out = out
|
@@ -5,16 +5,6 @@
|
|
5
5
|
# Turn on dependency checks for this package
|
6
6
|
enforce_dependencies: true
|
7
7
|
|
8
|
-
# Turn on privacy checks for this package
|
9
|
-
# enforcing privacy is often not useful for the root package, because it would require defining a public interface
|
10
|
-
# for something that should only be a thin wrapper in the first place.
|
11
|
-
# We recommend enabling this for any new packages you create to aid with encapsulation.
|
12
|
-
enforce_privacy: false
|
13
|
-
|
14
|
-
# By default the public path will be app/public/, however this may not suit all applications' architecture so
|
15
|
-
# this allows you to modify what your package's public path is.
|
16
|
-
# public_path: app/public/
|
17
|
-
|
18
8
|
# A list of this package's dependencies
|
19
9
|
# Note that packages in this list require their own `package.yml` file
|
20
10
|
# dependencies:
|
data/lib/packwerk/graph.rb
CHANGED
@@ -4,8 +4,14 @@
|
|
4
4
|
module Packwerk
|
5
5
|
# A general implementation of a graph data structure with the ability to check for - and list - cycles.
|
6
6
|
class Graph
|
7
|
-
|
8
|
-
|
7
|
+
extend T::Sig
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
# The edges of the graph; An edge being represented as an Array of two nodes.
|
11
|
+
edges: T::Array[T::Array[T.any(String, Integer, NilClass)]]
|
12
|
+
).void
|
13
|
+
end
|
14
|
+
def initialize(edges)
|
9
15
|
@edges = edges.uniq
|
10
16
|
@cycles = Set.new
|
11
17
|
process
|
@@ -73,4 +79,6 @@ module Packwerk
|
|
73
79
|
@cycles << cycle
|
74
80
|
end
|
75
81
|
end
|
82
|
+
|
83
|
+
private_constant :Graph
|
76
84
|
end
|
data/lib/packwerk/node.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "parser"
|
@@ -59,11 +59,16 @@ module Packwerk
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
sig
|
63
|
-
|
64
|
-
|
62
|
+
sig do
|
63
|
+
params(
|
64
|
+
node: AST::Node,
|
65
|
+
block: T.nilable(T.proc.params(arg0: Parser::AST::Node).void),
|
66
|
+
).returns(T::Enumerable[AST::Node])
|
67
|
+
end
|
68
|
+
def each_child(node, &block)
|
69
|
+
if block
|
65
70
|
node.children.each do |child|
|
66
|
-
yield
|
71
|
+
yield(child) if child.is_a?(Parser::AST::Node)
|
67
72
|
end
|
68
73
|
else
|
69
74
|
enum_for(:each_child, node)
|
@@ -181,9 +186,9 @@ module Packwerk
|
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
184
|
-
sig { params(node:
|
189
|
+
sig { params(node: AST::Node).returns(T.nilable(Node::Location)) }
|
185
190
|
def name_location(node)
|
186
|
-
location = node.location
|
191
|
+
location = T.cast(node, Parser::AST::Node).location
|
187
192
|
|
188
193
|
if location.respond_to?(:name)
|
189
194
|
name = location.name
|
@@ -332,4 +337,6 @@ module Packwerk
|
|
332
337
|
end
|
333
338
|
end
|
334
339
|
end
|
340
|
+
|
341
|
+
private_constant :NodeHelpers
|
335
342
|
end
|