packwerk 2.2.2 → 3.0.0
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 +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
|