packwerk 2.2.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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