packwerk 3.2.1 → 3.2.3

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/packwerk/application_validator.rb +2 -1
  3. data/lib/packwerk/association_inspector.rb +17 -4
  4. data/lib/packwerk/configuration.rb +4 -0
  5. data/lib/packwerk/const_node_inspector.rb +2 -2
  6. data/lib/packwerk/constant_name_inspector.rb +2 -2
  7. data/lib/packwerk/graph.rb +15 -56
  8. data/lib/packwerk/package.rb +1 -1
  9. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +1 -2
  10. data/lib/packwerk/reference_extractor.rb +29 -1
  11. data/lib/packwerk/run_context.rb +20 -4
  12. data/lib/packwerk/validators/dependency_validator.rb +5 -4
  13. data/lib/packwerk/version.rb +1 -1
  14. data/lib/packwerk.rb +1 -0
  15. data/sorbet/config +1 -0
  16. data/sorbet/rbi/gems/{actionpack@7.0.3.1.rbi → actionpack@7.0.8.7.rbi} +1338 -1227
  17. data/sorbet/rbi/gems/{actionview@7.0.3.1.rbi → actionview@7.0.8.7.rbi} +548 -503
  18. data/sorbet/rbi/gems/{activesupport@7.0.3.1.rbi → activesupport@7.0.8.7.rbi} +714 -635
  19. data/sorbet/rbi/gems/{better_html@2.0.1.rbi → better_html@2.1.1.rbi} +21 -21
  20. data/sorbet/rbi/gems/{concurrent-ruby@1.1.10.rbi → concurrent-ruby@1.3.5.rbi} +1390 -1366
  21. data/sorbet/rbi/gems/{constant_resolver@0.2.0.rbi → constant_resolver@0.3.0.rbi} +22 -13
  22. data/sorbet/rbi/gems/{erubi@1.11.0.rbi → erubi@1.13.1.rbi} +28 -17
  23. data/sorbet/rbi/gems/{i18n@1.12.0.rbi → i18n@1.14.7.rbi} +234 -172
  24. data/sorbet/rbi/gems/{json@2.6.2.rbi → json@2.7.2.rbi} +94 -74
  25. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  26. data/sorbet/rbi/gems/{loofah@2.18.0.rbi → loofah@2.24.0.rbi} +470 -243
  27. data/sorbet/rbi/gems/{minitest@5.16.2.rbi → minitest@5.25.4.rbi} +577 -472
  28. data/sorbet/rbi/gems/{mocha@1.14.0.rbi → mocha@2.5.0.rbi} +468 -684
  29. data/sorbet/rbi/gems/{nokogiri@1.15.3.rbi → nokogiri@1.18.4.rbi} +1756 -869
  30. data/sorbet/rbi/gems/{parallel@1.24.0.rbi → parallel@1.25.1.rbi} +26 -20
  31. data/sorbet/rbi/gems/{racc@1.7.1.rbi → racc@1.8.1.rbi} +36 -36
  32. data/sorbet/rbi/gems/{rack-test@2.0.2.rbi → rack-test@2.2.0.rbi} +87 -114
  33. data/sorbet/rbi/gems/{rack@2.2.4.rbi → rack@2.2.13.rbi} +243 -195
  34. data/sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi +754 -0
  35. data/sorbet/rbi/gems/rails-html-sanitizer@1.6.2.rbi +764 -0
  36. data/sorbet/rbi/gems/{railties@7.0.3.1.rbi → railties@7.0.8.7.rbi} +146 -140
  37. data/sorbet/rbi/gems/{regexp_parser@2.5.0.rbi → regexp_parser@2.9.2.rbi} +947 -542
  38. data/sorbet/rbi/gems/{rexml@3.2.5.rbi → rexml@3.3.9.rbi} +452 -312
  39. data/sorbet/rbi/gems/{rubocop-ast@1.21.0.rbi → rubocop-ast@1.31.3.rbi} +717 -588
  40. data/sorbet/rbi/gems/{rubocop@1.34.1.rbi → rubocop@1.64.1.rbi} +10916 -4406
  41. data/sorbet/rbi/gems/{ruby-progressbar@1.11.0.rbi → ruby-progressbar@1.13.0.rbi} +359 -281
  42. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
  43. data/sorbet/rbi/gems/{tzinfo@2.0.5.rbi → tzinfo@2.0.6.rbi} +144 -141
  44. data/sorbet/rbi/gems/{unicode-display_width@2.2.0.rbi → unicode-display_width@2.5.0.rbi} +24 -7
  45. metadata +37 -56
  46. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +0 -8
  47. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +0 -8
  48. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +0 -455
  49. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +0 -542
  50. data/sorbet/rbi/gems/ruby-lsp@0.2.3.rbi +0 -11
  51. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +0 -8
  52. /data/sorbet/rbi/gems/{builder@3.2.4.rbi → builder@3.3.0.rbi} +0 -0
  53. /data/sorbet/rbi/gems/{parser@3.3.1.0.rbi → parser@3.3.3.0.rbi} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8766d43b92fcc7bedad92604cde48e11433afdf474b21515f363b595c7fae23
4
- data.tar.gz: 0d386a1d690b6c07d41a6d63b0907f38d57fffd87d2ab370ee580cbcd0826a17
3
+ metadata.gz: 9b01578e447c49b16c57f33b6bfb111f65f91046b12465a433da90c30d6d1f7e
4
+ data.tar.gz: 731840a1431c1ac5305a30489379cd36a8c345e52589282eed80d694d2cc9b9a
5
5
  SHA512:
6
- metadata.gz: a64ccf65829b48691341b0abc09013213bd266ca9d2ec7a37fa1735856ae6b707d904ad75012c91bcd5a6d3ec4991ed6d7f1545140232d34f6d27452f5ac5a27
7
- data.tar.gz: f231695cf34fc0fb109d70efcff6874d5268b70c4af80688813734448f5c3d1ee3a705dbe3ece0bbb156733585f581f941917ed43f1666bb58f9617932858e18
6
+ metadata.gz: 26ff296eba5296f6674ebd963d4a5a3868a3a18d2464420d6ca3523745f8b5bfe6652c193cd43e6aa0304fe53bbba642857a956e89ae7b94e893edd47e47fb95
7
+ data.tar.gz: 742bdcfab120757f90621cea4301b84036cfdf0747809d1116a972c9076cd448451723f7b8fa1f62baeebfe0e539be71c1e4fdb319925130c9103af22780d143
@@ -72,7 +72,8 @@ module Packwerk
72
72
  def check_application_structure(configuration)
73
73
  resolver = ConstantResolver.new(
74
74
  root_path: configuration.root_path.to_s,
75
- load_paths: configuration.load_paths
75
+ load_paths: configuration.load_paths,
76
+ exclude: configuration.exclude,
76
77
  )
77
78
 
78
79
  begin
@@ -19,19 +19,27 @@ module Packwerk
19
19
  CustomAssociations
20
20
  )
21
21
 
22
- sig { params(inflector: T.class_of(ActiveSupport::Inflector), custom_associations: CustomAssociations).void }
23
- def initialize(inflector:, custom_associations: Set.new)
22
+ sig do
23
+ params(
24
+ inflector: T.class_of(ActiveSupport::Inflector),
25
+ custom_associations: CustomAssociations,
26
+ excluded_files: T::Set[String]
27
+ ).void
28
+ end
29
+ def initialize(inflector:, custom_associations: Set.new, excluded_files: Set.new)
24
30
  @inflector = inflector
25
31
  @associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
32
+ @excluded_files = T.let(excluded_files, T::Set[String])
26
33
  end
27
34
 
28
35
  sig do
29
36
  override
30
- .params(node: AST::Node, ancestors: T::Array[AST::Node])
37
+ .params(node: AST::Node, ancestors: T::Array[AST::Node], relative_file: String)
31
38
  .returns(T.nilable(String))
32
39
  end
33
- def constant_name_from_node(node, ancestors:)
40
+ def constant_name_from_node(node, ancestors:, relative_file:)
34
41
  return unless NodeHelpers.method_call?(node)
42
+ return if excluded?(relative_file)
35
43
  return unless association?(node)
36
44
 
37
45
  arguments = NodeHelpers.method_arguments(node)
@@ -48,6 +56,11 @@ module Packwerk
48
56
 
49
57
  private
50
58
 
59
+ sig { params(relative_file: String).returns(T::Boolean) }
60
+ def excluded?(relative_file)
61
+ @excluded_files.include?(relative_file)
62
+ end
63
+
51
64
  sig { params(node: AST::Node).returns(T::Boolean) }
52
65
  def association?(node)
53
66
  method_name = NodeHelpers.method_name(node)
@@ -54,6 +54,9 @@ module Packwerk
54
54
  sig { returns(T::Array[Symbol]) }
55
55
  attr_reader(:custom_associations)
56
56
 
57
+ sig { returns(T::Array[String]) }
58
+ attr_reader(:associations_exclude)
59
+
57
60
  sig { returns(T.nilable(String)) }
58
61
  attr_reader(:config_path)
59
62
 
@@ -76,6 +79,7 @@ module Packwerk
76
79
  @root_path = T.let(File.expand_path(root), String)
77
80
  @package_paths = T.let(configs["package_paths"] || "**/", T.any(String, T::Array[String]))
78
81
  @custom_associations = T.let((configs["custom_associations"] || []).map(&:to_sym), T::Array[Symbol])
82
+ @associations_exclude = T.let(configs["associations_exclude"] || [], T::Array[String])
79
83
  @parallel = T.let(configs.key?("parallel") ? configs["parallel"] : true, T::Boolean)
80
84
  @cache_enabled = T.let(configs.key?("cache") ? configs["cache"] : false, T::Boolean)
81
85
  @cache_directory = T.let(Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk"), Pathname)
@@ -9,10 +9,10 @@ module Packwerk
9
9
 
10
10
  sig do
11
11
  override
12
- .params(node: AST::Node, ancestors: T::Array[AST::Node])
12
+ .params(node: AST::Node, ancestors: T::Array[AST::Node], relative_file: String)
13
13
  .returns(T.nilable(String))
14
14
  end
15
- def constant_name_from_node(node, ancestors:)
15
+ def constant_name_from_node(node, ancestors:, relative_file:)
16
16
  return nil unless NodeHelpers.constant?(node)
17
17
 
18
18
  parent = ancestors.first
@@ -13,10 +13,10 @@ module Packwerk
13
13
 
14
14
  sig do
15
15
  abstract
16
- .params(node: ::AST::Node, ancestors: T::Array[::AST::Node])
16
+ .params(node: ::AST::Node, ancestors: T::Array[::AST::Node], relative_file: String)
17
17
  .returns(T.nilable(String))
18
18
  end
19
- def constant_name_from_node(node, ancestors:); end
19
+ def constant_name_from_node(node, ancestors:, relative_file:); end
20
20
  end
21
21
 
22
22
  private_constant :ConstantNameInspector
@@ -1,82 +1,41 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "tsort"
5
+
4
6
  module Packwerk
5
7
  # A general implementation of a graph data structure with the ability to check for - and list - cycles.
6
8
  class Graph
9
+ include TSort
10
+
7
11
  extend T::Sig
8
12
  sig do
9
13
  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)]]
14
+ # The edges of the graph; represented as an Hash of Arrays.
15
+ edges: T::Hash[T.any(String, Integer, NilClass), T::Array[T.any(String, Integer, NilClass)]]
12
16
  ).void
13
17
  end
14
18
  def initialize(edges)
15
- @edges = edges.uniq
16
- @cycles = Set.new
17
- process
19
+ @edges = edges
18
20
  end
19
21
 
20
22
  def cycles
21
- @cycles.dup
23
+ @cycles ||= strongly_connected_components.reject { _1.size == 1 }
22
24
  end
23
25
 
24
26
  def acyclic?
25
- @cycles.empty?
26
- end
27
-
28
- private
29
-
30
- def nodes
31
- @edges.flatten.uniq
32
- end
33
-
34
- def process
35
- # See https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
36
- @processed ||= begin
37
- nodes.each { |node| visit(node) }
38
- true
39
- end
40
- end
41
-
42
- def visit(node, visited_nodes: Set.new, path: [])
43
- # Already visited, short circuit to avoid unnecessary processing
44
- return if visited_nodes.include?(node)
45
-
46
- # We've returned to a node that we've already visited, so we've found a cycle!
47
- if path.include?(node)
48
- # Filter out the part of the path that isn't a cycle. For example, with the following path:
49
- #
50
- # a -> b -> c -> d -> b
51
- #
52
- # "a" isn't part of the cycle. The cycle should only appear once in the path, so we reject
53
- # everything from the beginning to the first instance of the current node.
54
- add_cycle(path.drop_while { |n| n != node })
55
- return
56
- end
57
-
58
- path << node
59
- neighbours(node).each do |neighbour|
60
- visit(neighbour, visited_nodes: visited_nodes, path: path)
61
- end
62
- path.pop
63
- ensure
64
- visited_nodes << node
27
+ cycles.empty?
65
28
  end
66
29
 
67
- def neighbours(node)
68
- @edges
69
- .lazy
70
- .select { |src, _dst| src == node }
71
- .map { |_src, dst| dst }
30
+ private def tsort_each_node(&block)
31
+ @edges.each_key(&block)
72
32
  end
73
33
 
74
- def add_cycle(cycle)
75
- # Ensure that the lexicographically smallest item is the first one labeled in a cycle
76
- min_node = cycle.min
77
- cycle.rotate! until cycle.first == min_node
34
+ EMPTY_ARRAY = [].freeze
35
+ private_constant :EMPTY_ARRAY
78
36
 
79
- @cycles << cycle
37
+ private def tsort_each_child(node, &block)
38
+ (@edges[node] || EMPTY_ARRAY).each(&block)
80
39
  end
81
40
  end
82
41
 
@@ -42,7 +42,7 @@ module Packwerk
42
42
  def package_path?(path)
43
43
  return true if root?
44
44
 
45
- path.start_with?(@name)
45
+ path.start_with?(@name + "/")
46
46
  end
47
47
 
48
48
  sig { params(other: T.untyped).returns(T.nilable(Integer)) }
@@ -40,8 +40,7 @@ module Packwerk
40
40
 
41
41
  <<~EOS
42
42
  Dependency violation: #{const_name} belongs to '#{const_package}', but '#{ref_package}' does not specify a dependency on '#{const_package}'.
43
- Are we missing an abstraction?
44
- Is the code making the reference, and the referenced constant, in the right packages?
43
+ Are the constant and its references in the right packages?
45
44
 
46
45
  #{standard_help_message(reference)}
47
46
  EOS
@@ -80,7 +80,12 @@ module Packwerk
80
80
  constant_name = T.let(nil, T.nilable(String))
81
81
 
82
82
  @constant_name_inspectors.each do |inspector|
83
- constant_name = inspector.constant_name_from_node(node, ancestors: ancestors)
83
+ constant_name = inspect_node(
84
+ inspector,
85
+ node: node,
86
+ ancestors: ancestors,
87
+ relative_file: relative_file
88
+ )
84
89
 
85
90
  break if constant_name
86
91
  end
@@ -97,6 +102,29 @@ module Packwerk
97
102
 
98
103
  private
99
104
 
105
+ sig do
106
+ params(
107
+ inspector: ConstantNameInspector,
108
+ node: Parser::AST::Node,
109
+ ancestors: T::Array[Parser::AST::Node],
110
+ relative_file: String
111
+ ).returns(T.nilable(String))
112
+ end
113
+ def inspect_node(inspector, node:, ancestors:, relative_file:)
114
+ inspector.constant_name_from_node(node, ancestors: ancestors, relative_file: relative_file)
115
+ rescue ArgumentError => error
116
+ if error.message == "unknown keyword: :relative_file"
117
+ T.unsafe(inspector).constant_name_from_node(node, ancestors: ancestors).tap do
118
+ warn(<<~MSG.squish)
119
+ #{T.cast(inspector, Object).class}#reference_from_node without a relative_file: keyword
120
+ argument is deprecated and will be required in Packwerk 3.1.1.
121
+ MSG
122
+ end
123
+ else
124
+ raise
125
+ end
126
+ end
127
+
100
128
  sig do
101
129
  params(
102
130
  constant_name: String,
@@ -15,14 +15,14 @@ module Packwerk
15
15
  params(configuration: Configuration).returns(RunContext)
16
16
  end
17
17
  def from_configuration(configuration)
18
- inflector = ActiveSupport::Inflector
19
-
20
18
  new(
21
19
  root_path: configuration.root_path,
22
20
  load_paths: configuration.load_paths,
23
21
  package_paths: configuration.package_paths,
24
- inflector: inflector,
22
+ inflector: ActiveSupport::Inflector,
25
23
  custom_associations: configuration.custom_associations,
24
+ associations_exclude: configuration.associations_exclude,
25
+ exclude: configuration.exclude,
26
26
  cache_enabled: configuration.cache_enabled?,
27
27
  cache_directory: configuration.cache_directory,
28
28
  config_path: configuration.config_path,
@@ -39,6 +39,8 @@ module Packwerk
39
39
  config_path: T.nilable(String),
40
40
  package_paths: T.nilable(T.any(T::Array[String], String)),
41
41
  custom_associations: AssociationInspector::CustomAssociations,
42
+ associations_exclude: T::Array[String],
43
+ exclude: T::Array[String],
42
44
  checkers: T::Array[Checker],
43
45
  cache_enabled: T::Boolean,
44
46
  ).void
@@ -51,6 +53,8 @@ module Packwerk
51
53
  config_path: nil,
52
54
  package_paths: nil,
53
55
  custom_associations: [],
56
+ associations_exclude: [],
57
+ exclude: [],
54
58
  checkers: Checker.all,
55
59
  cache_enabled: false
56
60
  )
@@ -59,10 +63,12 @@ module Packwerk
59
63
  @package_paths = package_paths
60
64
  @inflector = inflector
61
65
  @custom_associations = custom_associations
66
+ @associations_exclude = associations_exclude
62
67
  @checkers = checkers
63
68
  @cache_enabled = cache_enabled
64
69
  @cache_directory = cache_directory
65
70
  @config_path = config_path
71
+ @exclude = exclude
66
72
 
67
73
  @file_processor = T.let(nil, T.nilable(FileProcessor))
68
74
  @context_provider = T.let(nil, T.nilable(ConstantDiscovery))
@@ -121,6 +127,7 @@ module Packwerk
121
127
  root_path: @root_path,
122
128
  load_paths: @load_paths,
123
129
  inflector: @inflector,
130
+ exclude: @exclude,
124
131
  )
125
132
  end
126
133
 
@@ -128,9 +135,18 @@ module Packwerk
128
135
  def constant_name_inspectors
129
136
  [
130
137
  ConstNodeInspector.new,
131
- AssociationInspector.new(inflector: @inflector, custom_associations: @custom_associations),
138
+ AssociationInspector.new(
139
+ inflector: @inflector,
140
+ custom_associations: @custom_associations,
141
+ excluded_files: relative_files_for_globs(@associations_exclude),
142
+ ),
132
143
  ]
133
144
  end
145
+
146
+ sig { params(relative_globs: T::Array[String]).returns(FilesForProcessing::RelativeFileSet) }
147
+ def relative_files_for_globs(relative_globs)
148
+ Set.new(relative_globs.flat_map { |glob| Dir[glob] })
149
+ end
134
150
  end
135
151
 
136
152
  private_constant :RunContext
@@ -65,10 +65,11 @@ module Packwerk
65
65
 
66
66
  sig { params(package_set: PackageSet).returns(Validator::Result) }
67
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
68
+ edges = package_set.to_h do |package|
69
+ [
70
+ package.name,
71
+ package.dependencies.map { |dependency| package_set.fetch(dependency)&.name },
72
+ ]
72
73
  end
73
74
 
74
75
  dependency_graph = Graph.new(edges)
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "3.2.1"
5
+ VERSION = "3.2.3"
6
6
  end
data/lib/packwerk.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  require "sorbet-runtime"
5
5
  require "active_support"
6
6
  require "fileutils"
7
+ require "stringio"
7
8
 
8
9
  # Provides String#pluralize
9
10
  require "active_support/core_ext/string"
data/sorbet/config CHANGED
@@ -1,3 +1,4 @@
1
1
  --dir
2
2
  .
3
3
  --enable-experimental-requires-ancestor
4
+ --ignore=/vendor/bundle