packwerk 1.1.3 → 1.2.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +6 -5
  3. data/USAGE.md +17 -16
  4. data/lib/packwerk.rb +72 -36
  5. data/lib/packwerk/application_validator.rb +0 -5
  6. data/lib/packwerk/association_inspector.rb +0 -3
  7. data/lib/packwerk/checker.rb +2 -8
  8. data/lib/packwerk/cli.rb +22 -77
  9. data/lib/packwerk/configuration.rb +5 -0
  10. data/lib/packwerk/const_node_inspector.rb +0 -2
  11. data/lib/packwerk/constant_discovery.rb +2 -0
  12. data/lib/packwerk/constant_name_inspector.rb +0 -1
  13. data/lib/packwerk/dependency_checker.rb +2 -15
  14. data/lib/packwerk/deprecated_references.rb +3 -9
  15. data/lib/packwerk/file_processor.rb +0 -4
  16. data/lib/packwerk/formatters/offenses_formatter.rb +3 -8
  17. data/lib/packwerk/formatters/progress_formatter.rb +5 -4
  18. data/lib/packwerk/generators/configuration_file.rb +0 -1
  19. data/lib/packwerk/inflector.rb +0 -2
  20. data/lib/packwerk/node.rb +1 -0
  21. data/lib/packwerk/node_processor.rb +14 -32
  22. data/lib/packwerk/node_processor_factory.rb +0 -5
  23. data/lib/packwerk/node_visitor.rb +1 -4
  24. data/lib/packwerk/offense.rb +0 -4
  25. data/lib/packwerk/offense_collection.rb +84 -0
  26. data/lib/packwerk/offenses_formatter.rb +15 -0
  27. data/lib/packwerk/package.rb +8 -0
  28. data/lib/packwerk/package_set.rb +0 -2
  29. data/lib/packwerk/parse_run.rb +104 -0
  30. data/lib/packwerk/parsed_constant_definitions.rb +0 -2
  31. data/lib/packwerk/parsers.rb +0 -2
  32. data/lib/packwerk/parsers/erb.rb +0 -2
  33. data/lib/packwerk/parsers/factory.rb +0 -2
  34. data/lib/packwerk/privacy_checker.rb +2 -15
  35. data/lib/packwerk/reference_extractor.rb +0 -8
  36. data/lib/packwerk/reference_offense.rb +49 -0
  37. data/lib/packwerk/result.rb +9 -0
  38. data/lib/packwerk/run_context.rb +4 -21
  39. data/lib/packwerk/sanity_checker.rb +0 -2
  40. data/lib/packwerk/version.rb +1 -1
  41. data/lib/packwerk/violation_type.rb +0 -2
  42. data/packwerk.gemspec +1 -0
  43. data/service.yml +1 -4
  44. data/sorbet/rbi/gems/parallel@1.20.1.rbi +111 -2
  45. data/sorbet/tapioca/require.rb +1 -0
  46. metadata +22 -12
  47. data/lib/packwerk/cache_deprecated_references.rb +0 -55
  48. data/lib/packwerk/checking_deprecated_references.rb +0 -43
  49. data/lib/packwerk/commands/detect_stale_violations_command.rb +0 -60
  50. data/lib/packwerk/commands/offense_progress_marker.rb +0 -24
  51. data/lib/packwerk/commands/result.rb +0 -13
  52. data/lib/packwerk/commands/update_deprecations_command.rb +0 -81
  53. data/lib/packwerk/detect_stale_deprecated_references.rb +0 -14
  54. data/lib/packwerk/reference_lister.rb +0 -23
  55. data/lib/packwerk/updating_deprecated_references.rb +0 -14
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast"
5
- require "sorbet-runtime"
6
5
 
7
6
  module Packwerk
8
7
  # An interface describing some object that can extract a constant name from an AST node
@@ -1,9 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/violation_type"
5
- require "packwerk/checker"
6
-
7
4
  module Packwerk
8
5
  class DependencyChecker
9
6
  extend T::Sig
@@ -16,24 +13,14 @@ module Packwerk
16
13
 
17
14
  sig do
18
15
  override
19
- .params(reference: Packwerk::Reference, reference_lister: Packwerk::ReferenceLister)
16
+ .params(reference: Packwerk::Reference)
20
17
  .returns(T::Boolean)
21
18
  end
22
- def invalid_reference?(reference, reference_lister)
19
+ def invalid_reference?(reference)
23
20
  return false unless reference.source_package
24
21
  return false unless reference.source_package.enforce_dependencies?
25
22
  return false if reference.source_package.dependency?(reference.constant.package)
26
- return false if reference_lister.listed?(reference, violation_type: violation_type)
27
23
  true
28
24
  end
29
-
30
- sig { override.params(reference: Packwerk::Reference).returns(String) }
31
- def message_for(reference)
32
- "Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
33
- "'#{reference.source_package}' does not specify a dependency on " \
34
- "'#{reference.constant.package}'.\n" \
35
- "Are we missing an abstraction?\n" \
36
- "Is the code making the reference, and the referenced constant, in the right packages?\n"
37
- end
38
25
  end
39
26
  end
@@ -1,17 +1,11 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
4
  require "yaml"
6
5
 
7
- require "packwerk/reference"
8
- require "packwerk/reference_lister"
9
- require "packwerk/violation_type"
10
-
11
6
  module Packwerk
12
7
  class DeprecatedReferences
13
8
  extend T::Sig
14
- include ReferenceLister
15
9
 
16
10
  sig { params(package: Packwerk::Package, filepath: String).void }
17
11
  def initialize(package, filepath)
@@ -23,7 +17,6 @@ module Packwerk
23
17
  sig do
24
18
  params(reference: Packwerk::Reference, violation_type: ViolationType)
25
19
  .returns(T::Boolean)
26
- .override
27
20
  end
28
21
  def listed?(reference, violation_type:)
29
22
  violated_constants_found = deprecated_references.dig(reference.constant.package.name, reference.constant.name)
@@ -35,18 +28,19 @@ module Packwerk
35
28
  violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
36
29
  end
37
30
 
38
- sig { params(reference: Packwerk::Reference, violation_type: String).void }
31
+ sig { params(reference: Packwerk::Reference, violation_type: Packwerk::ViolationType).returns(T::Boolean) }
39
32
  def add_entries(reference, violation_type)
40
33
  package_violations = @new_entries.fetch(reference.constant.package.name, {})
41
34
  entries_for_file = package_violations[reference.constant.name] ||= {}
42
35
 
43
36
  entries_for_file["violations"] ||= []
44
- entries_for_file["violations"] << violation_type
37
+ entries_for_file["violations"] << violation_type.serialize
45
38
 
46
39
  entries_for_file["files"] ||= []
47
40
  entries_for_file["files"] << reference.relative_path.to_s
48
41
 
49
42
  @new_entries[reference.constant.package.name] = package_violations
43
+ listed?(reference, violation_type: violation_type)
50
44
  end
51
45
 
52
46
  sig { returns(T::Boolean) }
@@ -3,10 +3,6 @@
3
3
 
4
4
  require "ast/node"
5
5
 
6
- require "packwerk/node"
7
- require "packwerk/offense"
8
- require "packwerk/parsers"
9
-
10
6
  module Packwerk
11
7
  class FileProcessor
12
8
  class UnknownFileTypeResult < Offense
@@ -1,16 +1,11 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "benchmark"
5
- require "sorbet-runtime"
6
-
7
- require "packwerk/inflector"
8
- require "packwerk/output_style"
9
- require "packwerk/output_styles/plain"
10
-
11
4
  module Packwerk
12
5
  module Formatters
13
6
  class OffensesFormatter
7
+ include Packwerk::OffensesFormatter
8
+
14
9
  extend T::Sig
15
10
 
16
11
  sig { params(style: OutputStyle).void }
@@ -18,7 +13,7 @@ module Packwerk
18
13
  @style = style
19
14
  end
20
15
 
21
- sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
16
+ sig { override.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
22
17
  def show_offenses(offenses)
23
18
  return "No offenses detected 🎉" if offenses.empty?
24
19
 
@@ -3,10 +3,6 @@
3
3
 
4
4
  require "benchmark"
5
5
 
6
- require "packwerk/inflector"
7
- require "packwerk/output_style"
8
- require "packwerk/output_styles/plain"
9
-
10
6
  module Packwerk
11
7
  module Formatters
12
8
  class ProgressFormatter
@@ -45,6 +41,11 @@ module Packwerk
45
41
  @out.puts
46
42
  @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
47
43
  end
44
+
45
+ def interrupted
46
+ @out.puts
47
+ @out.puts("Manually interrupted. Violations caught so far are listed below:")
48
+ end
48
49
  end
49
50
  end
50
51
  end
@@ -1,7 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/configuration"
5
4
  require "erb"
6
5
 
7
6
  module Packwerk
@@ -2,8 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "active_support/inflector"
5
- require "packwerk/inflections/default"
6
- require "packwerk/inflections/custom"
7
5
 
8
6
  module Packwerk
9
7
  class Inflector
data/lib/packwerk/node.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "parser"
4
5
  require "parser/ast/node"
5
6
 
6
7
  module Packwerk
@@ -1,11 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/node"
5
- require "packwerk/offense"
6
- require "packwerk/checker"
7
- require "packwerk/reference_lister"
8
-
9
4
  module Packwerk
10
5
  class NodeProcessor
11
6
  extend T::Sig
@@ -13,49 +8,36 @@ module Packwerk
13
8
  sig do
14
9
  params(
15
10
  reference_extractor: ReferenceExtractor,
16
- reference_lister: ReferenceLister,
17
11
  filename: String,
18
12
  checkers: T::Array[Checker]
19
13
  ).void
20
14
  end
21
- def initialize(reference_extractor:, reference_lister:, filename:, checkers:)
15
+ def initialize(reference_extractor:, filename:, checkers:)
22
16
  @reference_extractor = reference_extractor
23
- @reference_lister = reference_lister
24
17
  @filename = filename
25
18
  @checkers = checkers
26
19
  end
27
20
 
28
- sig { params(node: Parser::AST::Node, ancestors: T::Array[Parser::AST::Node]).returns(T.nilable(Offense)) }
21
+ sig { params(node: Parser::AST::Node, ancestors: T::Array[Parser::AST::Node]).returns(T::Array[Offense]) }
29
22
  def call(node, ancestors)
30
- if Node.method_call?(node) || Node.constant?(node)
31
- reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
32
- check_reference(reference, node) if reference
33
- end
23
+ return [] unless Node.method_call?(node) || Node.constant?(node)
24
+ reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
25
+ check_reference(reference, node)
34
26
  end
35
27
 
36
28
  private
37
29
 
38
30
  def check_reference(reference, node)
39
- return nil unless (message = failed_check(reference))
40
-
41
- constant = reference.constant
42
-
43
- Packwerk::Offense.new(
44
- location: Node.location(node),
45
- file: reference.relative_path,
46
- message: <<~EOS
47
- #{message}
48
- Inference details: this is a reference to #{constant.name} which seems to be defined in #{constant.location}.
49
- To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
50
- EOS
51
- )
52
- end
53
-
54
- def failed_check(reference)
55
- failing_checker = @checkers.find do |checker|
56
- checker.invalid_reference?(reference, @reference_lister)
31
+ return [] unless reference
32
+ @checkers.each_with_object([]) do |checker, violations|
33
+ next unless checker.invalid_reference?(reference)
34
+ offense = Packwerk::ReferenceOffense.new(
35
+ location: Node.location(node),
36
+ reference: reference,
37
+ violation_type: checker.violation_type
38
+ )
39
+ violations << offense
57
40
  end
58
- failing_checker&.message_for(reference)
59
41
  end
60
42
  end
61
43
  end
@@ -1,15 +1,11 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/constant_name_inspector"
5
- require "packwerk/checker"
6
-
7
4
  module Packwerk
8
5
  class NodeProcessorFactory < T::Struct
9
6
  extend T::Sig
10
7
 
11
8
  const :root_path, String
12
- const :reference_lister, ReferenceLister
13
9
  const :context_provider, Packwerk::ConstantDiscovery
14
10
  const :constant_name_inspectors, T::Array[ConstantNameInspector]
15
11
  const :checkers, T::Array[Checker]
@@ -18,7 +14,6 @@ module Packwerk
18
14
  def for(filename:, node:)
19
15
  ::Packwerk::NodeProcessor.new(
20
16
  reference_extractor: reference_extractor(node: node),
21
- reference_lister: reference_lister,
22
17
  filename: filename,
23
18
  checkers: checkers,
24
19
  )
@@ -1,8 +1,6 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/node"
5
-
6
4
  module Packwerk
7
5
  class NodeVisitor
8
6
  def initialize(node_processor:)
@@ -10,8 +8,7 @@ module Packwerk
10
8
  end
11
9
 
12
10
  def visit(node, ancestors:, result:)
13
- offense = @node_processor.call(node, ancestors)
14
- result << offense if offense
11
+ result.concat(@node_processor.call(node, ancestors))
15
12
 
16
13
  child_ancestors = [node] + ancestors
17
14
  Node.each_child(node) do |child|
@@ -2,10 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser/source/map"
5
- require "sorbet-runtime"
6
-
7
- require "packwerk/output_style"
8
- require "packwerk/output_styles/plain"
9
5
 
10
6
  module Packwerk
11
7
  class Offense
@@ -0,0 +1,84 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ class OffenseCollection
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ sig do
10
+ params(
11
+ root_path: String,
12
+ deprecated_references: T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences]
13
+ ).void
14
+ end
15
+ def initialize(root_path, deprecated_references = {})
16
+ @root_path = root_path
17
+ @deprecated_references = T.let(deprecated_references, T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences])
18
+ @new_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
19
+ @errors = T.let([], T::Array[Packwerk::Offense])
20
+ end
21
+
22
+ sig { returns(T::Array[Packwerk::ReferenceOffense]) }
23
+ attr_reader :new_violations
24
+
25
+ sig { returns(T::Array[Packwerk::Offense]) }
26
+ attr_reader :errors
27
+
28
+ sig do
29
+ params(offense: Packwerk::Offense)
30
+ .returns(T::Boolean)
31
+ end
32
+ def listed?(offense)
33
+ return false unless offense.is_a?(ReferenceOffense)
34
+ reference = offense.reference
35
+ deprecated_references_for(reference.source_package).listed?(reference, violation_type: offense.violation_type)
36
+ end
37
+
38
+ sig do
39
+ params(offense: Packwerk::Offense).void
40
+ end
41
+ def add_offense(offense)
42
+ unless offense.is_a?(ReferenceOffense)
43
+ @errors << offense
44
+ return
45
+ end
46
+ deprecated_references = deprecated_references_for(offense.reference.source_package)
47
+ unless deprecated_references.add_entries(offense.reference, offense.violation_type)
48
+ new_violations << offense
49
+ end
50
+ end
51
+
52
+ sig { returns(T::Boolean) }
53
+ def stale_violations?
54
+ @deprecated_references.values.any?(&:stale_violations?)
55
+ end
56
+
57
+ sig { void }
58
+ def dump_deprecated_references_files
59
+ @deprecated_references.each do |_, deprecated_references_file|
60
+ deprecated_references_file.dump
61
+ end
62
+ end
63
+
64
+ sig { returns(T::Array[Packwerk::Offense]) }
65
+ def outstanding_offenses
66
+ errors + new_violations
67
+ end
68
+
69
+ private
70
+
71
+ sig { params(package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
72
+ def deprecated_references_for(package)
73
+ @deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
74
+ package,
75
+ deprecated_references_file_for(package),
76
+ )
77
+ end
78
+
79
+ sig { params(package: Packwerk::Package).returns(String) }
80
+ def deprecated_references_file_for(package)
81
+ File.join(@root_path, package.name, "deprecated_references.yml")
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OffensesFormatter
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ sig { abstract.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
12
+ def show_offenses(offenses)
13
+ end
14
+ end
15
+ end
@@ -52,6 +52,14 @@ module Packwerk
52
52
  name <=> other.name
53
53
  end
54
54
 
55
+ def eql?(other)
56
+ self == other
57
+ end
58
+
59
+ def hash
60
+ name.hash
61
+ end
62
+
55
63
  def to_s
56
64
  name
57
65
  end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require "pathname"
5
5
 
6
- require "packwerk/package"
7
-
8
6
  module Packwerk
9
7
  class PackageSet
10
8
  include Enumerable