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
@@ -0,0 +1,90 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "constant_resolver"
|
5
|
+
require "pathname"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
module Packwerk
|
9
|
+
module Validator
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
abstract!
|
14
|
+
|
15
|
+
class << self
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
sig { params(base: Class).void }
|
19
|
+
def included(base)
|
20
|
+
@validators ||= T.let(@validators, T.nilable(T::Array[Class]))
|
21
|
+
@validators ||= []
|
22
|
+
@validators << base
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { returns(T::Array[Validator]) }
|
26
|
+
def all
|
27
|
+
T.unsafe(@validators).map(&:new)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { abstract.returns(T::Array[String]) }
|
32
|
+
def permitted_keys
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { abstract.params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result) }
|
36
|
+
def call(package_set, configuration)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { params(configuration: Configuration, setting: T.untyped).returns(T.untyped) }
|
40
|
+
def package_manifests_settings_for(configuration, setting)
|
41
|
+
package_manifests(configuration).map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
42
|
+
end
|
43
|
+
|
44
|
+
sig do
|
45
|
+
params(configuration: Configuration,
|
46
|
+
glob_pattern: T.nilable(T.any(T::Array[String], String))).returns(T::Array[String])
|
47
|
+
end
|
48
|
+
def package_manifests(configuration, glob_pattern = nil)
|
49
|
+
glob_pattern ||= package_glob(configuration)
|
50
|
+
PackageSet.package_paths(configuration.root_path, glob_pattern, configuration.exclude)
|
51
|
+
.map { |f| File.realpath(f) }
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(configuration: Configuration).returns(T.any(T::Array[String], String)) }
|
55
|
+
def package_glob(configuration)
|
56
|
+
configuration.package_paths
|
57
|
+
end
|
58
|
+
|
59
|
+
sig do
|
60
|
+
params(
|
61
|
+
results: T::Array[Validator::Result],
|
62
|
+
separator: String,
|
63
|
+
before_errors: String,
|
64
|
+
after_errors: String,
|
65
|
+
).returns(Validator::Result)
|
66
|
+
end
|
67
|
+
def merge_results(results, separator: "\n", before_errors: "", after_errors: "")
|
68
|
+
results.reject!(&:ok?)
|
69
|
+
|
70
|
+
if results.empty?
|
71
|
+
Validator::Result.new(ok: true)
|
72
|
+
else
|
73
|
+
Validator::Result.new(
|
74
|
+
ok: false,
|
75
|
+
error_value: [
|
76
|
+
before_errors,
|
77
|
+
separator.lstrip,
|
78
|
+
results.map(&:error_value).join(separator),
|
79
|
+
after_errors,
|
80
|
+
].join,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { params(configuration: Configuration, path: String).returns(Pathname) }
|
86
|
+
def relative_path(configuration, path)
|
87
|
+
Pathname.new(path).relative_path_from(configuration.root_path)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module Validators
|
6
|
+
class DependencyValidator
|
7
|
+
extend T::Sig
|
8
|
+
include Validator
|
9
|
+
|
10
|
+
sig do
|
11
|
+
override.params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result)
|
12
|
+
end
|
13
|
+
def call(package_set, configuration)
|
14
|
+
results = [
|
15
|
+
check_package_manifest_syntax(configuration),
|
16
|
+
check_acyclic_graph(package_set),
|
17
|
+
check_valid_package_dependencies(configuration),
|
18
|
+
]
|
19
|
+
|
20
|
+
merge_results(results)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.returns(T::Array[String]) }
|
24
|
+
def permitted_keys
|
25
|
+
[
|
26
|
+
"enforce_dependencies",
|
27
|
+
"dependencies",
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
sig { params(configuration: Configuration).returns(Validator::Result) }
|
34
|
+
def check_package_manifest_syntax(configuration)
|
35
|
+
errors = []
|
36
|
+
|
37
|
+
valid_settings = [true, false, "strict"]
|
38
|
+
package_manifests_settings_for(configuration, "enforce_dependencies").each do |config, setting|
|
39
|
+
next if setting.nil?
|
40
|
+
|
41
|
+
unless valid_settings.include?(setting)
|
42
|
+
errors << "\tInvalid 'enforce_dependencies' option: #{setting.inspect} in #{config.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
package_manifests_settings_for(configuration, "dependencies").each do |config, setting|
|
47
|
+
next if setting.nil?
|
48
|
+
|
49
|
+
unless setting.is_a?(Array)
|
50
|
+
errors << "\tInvalid 'dependencies' option: #{setting.inspect} in #{config.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if errors.empty?
|
55
|
+
Validator::Result.new(ok: true)
|
56
|
+
else
|
57
|
+
merge_results(
|
58
|
+
errors.map { |error| Validator::Result.new(ok: false, error_value: error) },
|
59
|
+
separator: "\n",
|
60
|
+
before_errors: "Malformed syntax in the following manifests:\n\n",
|
61
|
+
after_errors: "\n",
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(package_set: PackageSet).returns(Validator::Result) }
|
67
|
+
def check_acyclic_graph(package_set)
|
68
|
+
edges = package_set.flat_map do |package|
|
69
|
+
package.dependencies.map do |dependency|
|
70
|
+
[package.name, package_set.fetch(dependency)&.name]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
dependency_graph = Graph.new(edges)
|
75
|
+
|
76
|
+
cycle_strings = build_cycle_strings(dependency_graph.cycles)
|
77
|
+
|
78
|
+
if dependency_graph.acyclic?
|
79
|
+
Validator::Result.new(ok: true)
|
80
|
+
else
|
81
|
+
Validator::Result.new(
|
82
|
+
ok: false,
|
83
|
+
error_value: <<~EOS
|
84
|
+
Expected the package dependency graph to be acyclic, but it contains the following circular dependencies:
|
85
|
+
|
86
|
+
#{cycle_strings.join("\n")}
|
87
|
+
EOS
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
sig { params(configuration: Configuration).returns(Validator::Result) }
|
93
|
+
def check_valid_package_dependencies(configuration)
|
94
|
+
packages_dependencies = package_manifests_settings_for(configuration, "dependencies")
|
95
|
+
.delete_if { |_, deps| deps.nil? }
|
96
|
+
|
97
|
+
packages_with_invalid_dependencies =
|
98
|
+
packages_dependencies.each_with_object([]) do |(package, dependencies), invalid_packages|
|
99
|
+
invalid_dependencies = if dependencies.is_a?(Array)
|
100
|
+
dependencies.filter { |path| invalid_package_path?(configuration, path) }
|
101
|
+
else
|
102
|
+
[]
|
103
|
+
end
|
104
|
+
invalid_packages << [package, invalid_dependencies] if invalid_dependencies.any?
|
105
|
+
end
|
106
|
+
|
107
|
+
if packages_with_invalid_dependencies.empty?
|
108
|
+
Validator::Result.new(ok: true)
|
109
|
+
else
|
110
|
+
error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
|
111
|
+
package ||= configuration.root_path
|
112
|
+
package_path = Pathname.new(package).relative_path_from(configuration.root_path)
|
113
|
+
all_invalid_dependencies = invalid_dependencies.map { |d| " - #{d}" }
|
114
|
+
|
115
|
+
<<~EOS
|
116
|
+
\t#{package_path}:
|
117
|
+
\t#{all_invalid_dependencies.join("\n\t")}
|
118
|
+
EOS
|
119
|
+
end
|
120
|
+
|
121
|
+
Validator::Result.new(
|
122
|
+
ok: false,
|
123
|
+
error_value: "These dependencies do not point to valid packages:\n\n#{error_locations.join("\n")}"
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(configuration: Configuration, path: T.untyped).returns(T::Boolean) }
|
129
|
+
def invalid_package_path?(configuration, path)
|
130
|
+
# Packages at the root can be implicitly specified as "."
|
131
|
+
return false if path == "."
|
132
|
+
|
133
|
+
package_path = File.join(configuration.root_path, path, PackageSet::PACKAGE_CONFIG_FILENAME)
|
134
|
+
!File.file?(package_path)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Convert the cycles:
|
138
|
+
#
|
139
|
+
# [[a, b, c], [b, c]]
|
140
|
+
#
|
141
|
+
# to the string:
|
142
|
+
#
|
143
|
+
# ["a -> b -> c -> a", "b -> c -> b"]
|
144
|
+
sig { params(cycles: T.untyped).returns(T::Array[String]) }
|
145
|
+
def build_cycle_strings(cycles)
|
146
|
+
cycles.map do |cycle|
|
147
|
+
cycle_strings = cycle.map(&:to_s)
|
148
|
+
cycle_strings << cycle.first.to_s
|
149
|
+
"\t- #{cycle_strings.join(" → ")}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/packwerk/version.rb
CHANGED
data/lib/packwerk.rb
CHANGED
@@ -7,45 +7,36 @@ require "fileutils"
|
|
7
7
|
|
8
8
|
# Provides String#pluralize
|
9
9
|
require "active_support/core_ext/string"
|
10
|
+
# Provides Object#to_json
|
11
|
+
require "active_support/core_ext/object/json"
|
10
12
|
|
11
13
|
module Packwerk
|
12
14
|
extend ActiveSupport::Autoload
|
13
15
|
|
14
|
-
|
15
|
-
autoload :
|
16
|
-
autoload :AssociationInspector
|
17
|
-
autoload :OffenseCollection
|
18
|
-
autoload :Cache
|
16
|
+
# Public APIs
|
17
|
+
autoload :Checker
|
19
18
|
autoload :Cli
|
20
19
|
autoload :Configuration
|
21
|
-
autoload :
|
22
|
-
autoload :ConstantNameInspector
|
23
|
-
autoload :ConstNodeInspector
|
24
|
-
autoload :DeprecatedReferences
|
25
|
-
autoload :FileProcessor
|
26
|
-
autoload :FilesForProcessing
|
27
|
-
autoload :Graph
|
20
|
+
autoload :ConstantContext
|
28
21
|
autoload :Node
|
29
|
-
autoload :NodeHelpers
|
30
|
-
autoload :NodeProcessor
|
31
|
-
autoload :NodeProcessorFactory
|
32
|
-
autoload :NodeVisitor
|
33
22
|
autoload :Offense
|
23
|
+
autoload :OffenseCollection
|
34
24
|
autoload :OffensesFormatter
|
35
25
|
autoload :OutputStyle
|
36
26
|
autoload :Package
|
37
27
|
autoload :PackageSet
|
38
|
-
autoload :
|
28
|
+
autoload :PackageTodo
|
39
29
|
autoload :Parsers
|
40
|
-
autoload :
|
41
|
-
autoload :UnresolvedReference
|
30
|
+
autoload :RailsLoadPaths
|
42
31
|
autoload :Reference
|
43
|
-
autoload :ReferenceExtractor
|
44
32
|
autoload :ReferenceOffense
|
45
|
-
autoload :
|
46
|
-
|
47
|
-
|
48
|
-
|
33
|
+
autoload :Validator
|
34
|
+
|
35
|
+
class Cli
|
36
|
+
extend ActiveSupport::Autoload
|
37
|
+
|
38
|
+
autoload :Result
|
39
|
+
end
|
49
40
|
|
50
41
|
module OutputStyles
|
51
42
|
extend ActiveSupport::Autoload
|
@@ -61,10 +52,37 @@ module Packwerk
|
|
61
52
|
module Formatters
|
62
53
|
extend ActiveSupport::Autoload
|
63
54
|
|
64
|
-
autoload :OffensesFormatter
|
65
55
|
autoload :ProgressFormatter
|
66
56
|
end
|
67
57
|
|
58
|
+
module Validator
|
59
|
+
extend ActiveSupport::Autoload
|
60
|
+
|
61
|
+
autoload :Result
|
62
|
+
end
|
63
|
+
|
64
|
+
# Private APIs
|
65
|
+
# Please submit an issue if you have a use-case for these
|
66
|
+
autoload :ApplicationValidator
|
67
|
+
autoload :AssociationInspector
|
68
|
+
autoload :Cache
|
69
|
+
autoload :ConstantDiscovery
|
70
|
+
autoload :ConstantNameInspector
|
71
|
+
autoload :ConstNodeInspector
|
72
|
+
autoload :ExtensionLoader
|
73
|
+
autoload :FileProcessor
|
74
|
+
autoload :FilesForProcessing
|
75
|
+
autoload :Graph
|
76
|
+
autoload :NodeHelpers
|
77
|
+
autoload :NodeProcessor
|
78
|
+
autoload :NodeProcessorFactory
|
79
|
+
autoload :NodeVisitor
|
80
|
+
autoload :ParsedConstantDefinitions
|
81
|
+
autoload :ParseRun
|
82
|
+
autoload :ReferenceExtractor
|
83
|
+
autoload :RunContext
|
84
|
+
autoload :UnresolvedReference
|
85
|
+
|
68
86
|
module Generators
|
69
87
|
extend ActiveSupport::Autoload
|
70
88
|
|
@@ -72,6 +90,8 @@ module Packwerk
|
|
72
90
|
autoload :RootPackage
|
73
91
|
end
|
74
92
|
|
93
|
+
private_constant :Generators
|
94
|
+
|
75
95
|
module ReferenceChecking
|
76
96
|
extend ActiveSupport::Autoload
|
77
97
|
|
@@ -80,9 +100,27 @@ module Packwerk
|
|
80
100
|
module Checkers
|
81
101
|
extend ActiveSupport::Autoload
|
82
102
|
|
83
|
-
autoload :Checker
|
84
103
|
autoload :DependencyChecker
|
85
104
|
autoload :PrivacyChecker
|
86
105
|
end
|
87
106
|
end
|
107
|
+
|
108
|
+
private_constant :ReferenceChecking
|
109
|
+
|
110
|
+
class ApplicationValidator
|
111
|
+
extend ActiveSupport::Autoload
|
112
|
+
|
113
|
+
autoload :Helpers
|
114
|
+
end
|
88
115
|
end
|
116
|
+
|
117
|
+
require "packwerk/version"
|
118
|
+
|
119
|
+
# Required to register the DefaultOffensesFormatter
|
120
|
+
# We put this at the *end* of the file to specify all autoloads first
|
121
|
+
require "packwerk/formatters/default_offenses_formatter"
|
122
|
+
|
123
|
+
# Required to register the default DependencyChecker
|
124
|
+
require "packwerk/reference_checking/checkers/dependency_checker"
|
125
|
+
# Required to register the default DependencyValidator
|
126
|
+
require "packwerk/validators/dependency_validator"
|
data/packwerk.gemspec
CHANGED
@@ -38,9 +38,9 @@ Gem::Specification.new do |spec|
|
|
38
38
|
end
|
39
39
|
spec.require_paths = ["lib"]
|
40
40
|
|
41
|
-
spec.required_ruby_version = ">= 2.
|
41
|
+
spec.required_ruby_version = ">= 2.7"
|
42
42
|
|
43
|
-
spec.add_dependency("activesupport", ">=
|
43
|
+
spec.add_dependency("activesupport", ">= 6.0")
|
44
44
|
spec.add_dependency("bundler")
|
45
45
|
spec.add_dependency("constant_resolver", ">= 0.2.0")
|
46
46
|
spec.add_dependency("parallel")
|
@@ -53,4 +53,6 @@ Gem::Specification.new do |spec|
|
|
53
53
|
|
54
54
|
# For ERB parsing
|
55
55
|
spec.add_dependency("better_html")
|
56
|
+
|
57
|
+
spec.add_development_dependency("railties")
|
56
58
|
end
|