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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/RESOLVING_VIOLATIONS.md +7 -7
  8. data/TROUBLESHOOT.md +1 -23
  9. data/USAGE.md +149 -59
  10. data/dev.yml +1 -1
  11. data/exe/packwerk +1 -0
  12. data/gemfiles/Gemfile-rails-6-1 +1 -1
  13. data/lib/packwerk/application_validator.rb +54 -285
  14. data/lib/packwerk/association_inspector.rb +2 -0
  15. data/lib/packwerk/cache.rb +6 -5
  16. data/lib/packwerk/checker.rb +54 -0
  17. data/lib/packwerk/cli/result.rb +11 -0
  18. data/lib/packwerk/cli.rb +56 -31
  19. data/lib/packwerk/configuration.rb +61 -40
  20. data/lib/packwerk/const_node_inspector.rb +2 -0
  21. data/lib/packwerk/constant_context.rb +8 -0
  22. data/lib/packwerk/constant_discovery.rb +5 -6
  23. data/lib/packwerk/constant_name_inspector.rb +2 -0
  24. data/lib/packwerk/disable_sorbet.rb +41 -0
  25. data/lib/packwerk/extension_loader.rb +24 -0
  26. data/lib/packwerk/file_processor.rb +3 -1
  27. data/lib/packwerk/files_for_processing.rb +25 -12
  28. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  29. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  30. data/lib/packwerk/generators/configuration_file.rb +7 -2
  31. data/lib/packwerk/generators/root_package.rb +5 -1
  32. data/lib/packwerk/generators/templates/package.yml +0 -10
  33. data/lib/packwerk/graph.rb +10 -2
  34. data/lib/packwerk/node.rb +1 -1
  35. data/lib/packwerk/node_helpers.rb +14 -7
  36. data/lib/packwerk/node_processor.rb +2 -0
  37. data/lib/packwerk/node_processor_factory.rb +6 -4
  38. data/lib/packwerk/node_visitor.rb +10 -1
  39. data/lib/packwerk/offense_collection.rb +43 -23
  40. data/lib/packwerk/offenses_formatter.rb +59 -2
  41. data/lib/packwerk/package.rb +7 -35
  42. data/lib/packwerk/package_set.rb +1 -1
  43. data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
  44. data/lib/packwerk/parse_run.rb +29 -36
  45. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  46. data/lib/packwerk/parsers/erb.rb +23 -4
  47. data/lib/packwerk/parsers/factory.rb +11 -2
  48. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  49. data/lib/packwerk/parsers/ruby.rb +13 -3
  50. data/lib/packwerk/parsers.rb +6 -2
  51. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  52. data/lib/packwerk/reference.rb +7 -1
  53. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  54. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  55. data/lib/packwerk/reference_extractor.rb +24 -12
  56. data/lib/packwerk/reference_offense.rb +2 -2
  57. data/lib/packwerk/run_context.rb +7 -10
  58. data/lib/packwerk/spring_command.rb +11 -2
  59. data/lib/packwerk/unresolved_reference.rb +9 -1
  60. data/lib/packwerk/validator/result.rb +18 -0
  61. data/lib/packwerk/validator.rb +90 -0
  62. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  63. data/lib/packwerk/version.rb +1 -1
  64. data/lib/packwerk.rb +64 -26
  65. data/packwerk.gemspec +4 -2
  66. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  67. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  68. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  69. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  70. data/sorbet/rbi/shims/parser.rbi +13 -0
  71. metadata +35 -16
  72. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  73. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  74. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  75. data/lib/packwerk/result.rb +0 -9
  76. data/lib/packwerk/sanity_checker.rb +0 -8
  77. data/lib/packwerk/violation_type.rb +0 -11
  78. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  79. 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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "2.2.2"
5
+ VERSION = "3.0.0"
6
6
  end
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
- autoload :ApplicationLoadPaths
15
- autoload :ApplicationValidator
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 :ConstantDiscovery
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 :ParsedConstantDefinitions
28
+ autoload :PackageTodo
39
29
  autoload :Parsers
40
- autoload :ParseRun
41
- autoload :UnresolvedReference
30
+ autoload :RailsLoadPaths
42
31
  autoload :Reference
43
- autoload :ReferenceExtractor
44
32
  autoload :ReferenceOffense
45
- autoload :Result
46
- autoload :RunContext
47
- autoload :Version
48
- autoload :ViolationType
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.6"
41
+ spec.required_ruby_version = ">= 2.7"
42
42
 
43
- spec.add_dependency("activesupport", ">= 5.2")
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