packwerk 1.0.1 → 1.1.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +8 -7
  3. data/.github/workflows/ci.yml +14 -5
  4. data/.ruby-version +1 -1
  5. data/Gemfile +2 -1
  6. data/Gemfile.lock +130 -110
  7. data/README.md +8 -1
  8. data/USAGE.md +23 -2
  9. data/dev.yml +1 -1
  10. data/exe/packwerk +1 -1
  11. data/gemfiles/Gemfile-rails-6-0 +22 -0
  12. data/lib/packwerk.rb +4 -2
  13. data/lib/packwerk/application_load_paths.rb +68 -0
  14. data/lib/packwerk/application_validator.rb +94 -74
  15. data/lib/packwerk/association_inspector.rb +24 -9
  16. data/lib/packwerk/cache_deprecated_references.rb +55 -0
  17. data/lib/packwerk/checker.rb +3 -0
  18. data/lib/packwerk/checking_deprecated_references.rb +5 -2
  19. data/lib/packwerk/cli.rb +56 -55
  20. data/lib/packwerk/commands/detect_stale_violations_command.rb +60 -0
  21. data/lib/packwerk/commands/offense_progress_marker.rb +24 -0
  22. data/lib/packwerk/commands/result.rb +13 -0
  23. data/lib/packwerk/commands/update_deprecations_command.rb +81 -0
  24. data/lib/packwerk/configuration.rb +5 -37
  25. data/lib/packwerk/const_node_inspector.rb +28 -17
  26. data/lib/packwerk/dependency_checker.rb +13 -5
  27. data/lib/packwerk/deprecated_references.rb +23 -0
  28. data/lib/packwerk/detect_stale_deprecated_references.rb +14 -0
  29. data/lib/packwerk/file_processor.rb +4 -4
  30. data/lib/packwerk/formatters/offenses_formatter.rb +48 -0
  31. data/lib/packwerk/formatters/progress_formatter.rb +6 -2
  32. data/lib/packwerk/inflector.rb +17 -8
  33. data/lib/packwerk/node.rb +61 -38
  34. data/lib/packwerk/node_processor.rb +4 -4
  35. data/lib/packwerk/node_processor_factory.rb +39 -0
  36. data/lib/packwerk/node_visitor.rb +1 -1
  37. data/lib/packwerk/offense.rb +4 -6
  38. data/lib/packwerk/output_style.rb +20 -0
  39. data/lib/packwerk/output_styles/coloured.rb +29 -0
  40. data/lib/packwerk/output_styles/plain.rb +26 -0
  41. data/lib/packwerk/package_set.rb +9 -3
  42. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  43. data/lib/packwerk/parsers/erb.rb +4 -0
  44. data/lib/packwerk/parsers/factory.rb +10 -1
  45. data/lib/packwerk/privacy_checker.rb +23 -5
  46. data/lib/packwerk/run_context.rb +69 -46
  47. data/lib/packwerk/sanity_checker.rb +1 -1
  48. data/lib/packwerk/spring_command.rb +1 -1
  49. data/lib/packwerk/updating_deprecated_references.rb +2 -39
  50. data/lib/packwerk/version.rb +1 -1
  51. data/library.yml +1 -1
  52. data/packwerk.gemspec +1 -1
  53. data/service.yml +2 -2
  54. data/shipit.rubygems.yml +5 -1
  55. data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
  56. data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
  57. data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
  58. data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
  59. data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
  60. data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
  61. data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
  62. data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
  63. data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
  64. data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
  65. data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
  66. data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
  67. data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
  68. data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
  69. data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
  70. data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
  71. data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
  72. data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
  73. data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
  74. data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
  75. data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
  76. data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
  77. data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
  78. data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
  79. data/sorbet/rbi/gems/{parallel@1.19.1.rbi → parallel@1.20.1.rbi} +1 -1
  80. data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
  81. data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
  82. data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
  83. data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
  84. data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
  85. data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
  86. data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
  87. data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
  88. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +8 -0
  89. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
  90. data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
  91. data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
  92. data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
  93. data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
  94. data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
  95. data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
  96. data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
  97. data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
  98. data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
  99. data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
  100. data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
  101. data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
  102. data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
  103. data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
  104. data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
  105. data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
  106. metadata +66 -58
  107. data/lib/packwerk/output_styles.rb +0 -41
  108. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
  109. data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
  110. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
  111. data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
  112. data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
  113. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
  114. data/static/packwerk-check-demo.png +0 -0
  115. data/static/packwerk_check.gif +0 -0
  116. data/static/packwerk_check_violation.gif +0 -0
  117. data/static/packwerk_update.gif +0 -0
  118. data/static/packwerk_validate.gif +0 -0
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Packwerk
7
+ module Commands
8
+ class Result < T::Struct
9
+ prop :message, String
10
+ prop :status, T::Boolean
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,81 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "benchmark"
6
+
7
+ require "packwerk/commands/offense_progress_marker"
8
+ require "packwerk/commands/result"
9
+ require "packwerk/run_context"
10
+ require "packwerk/updating_deprecated_references"
11
+
12
+ module Packwerk
13
+ module Commands
14
+ class UpdateDeprecationsCommand
15
+ extend T::Sig
16
+ include OffenseProgressMarker
17
+
18
+ sig do
19
+ params(
20
+ files: T::Enumerable[String],
21
+ configuration: Configuration,
22
+ offenses_formatter: Formatters::OffensesFormatter,
23
+ progress_formatter: Formatters::ProgressFormatter
24
+ ).void
25
+ end
26
+ def initialize(files:, configuration:, offenses_formatter:, progress_formatter:)
27
+ @files = files
28
+ @configuration = configuration
29
+ @progress_formatter = progress_formatter
30
+ @offenses_formatter = offenses_formatter
31
+ @updating_deprecated_references = T.let(nil, T.nilable(UpdatingDeprecatedReferences))
32
+ @run_context = T.let(nil, T.nilable(RunContext))
33
+ end
34
+
35
+ sig { returns(Result) }
36
+ def run
37
+ @progress_formatter.started(@files)
38
+
39
+ all_offenses = T.let([], T.untyped)
40
+ execution_time = Benchmark.realtime do
41
+ all_offenses = @files.flat_map do |path|
42
+ run_context.process_file(file: path).tap do |offenses|
43
+ mark_progress(offenses: offenses, progress_formatter: @progress_formatter)
44
+ end
45
+ end
46
+
47
+ updating_deprecated_references.dump_deprecated_references_files
48
+ end
49
+
50
+ @progress_formatter.finished(execution_time)
51
+ calculate_result(all_offenses)
52
+ end
53
+
54
+ private
55
+
56
+ sig { returns(RunContext) }
57
+ def run_context
58
+ @run_context ||= RunContext.from_configuration(
59
+ @configuration,
60
+ reference_lister: updating_deprecated_references
61
+ )
62
+ end
63
+
64
+ sig { returns(UpdatingDeprecatedReferences) }
65
+ def updating_deprecated_references
66
+ @updating_deprecated_references ||= UpdatingDeprecatedReferences.new(@configuration.root_path)
67
+ end
68
+
69
+ sig { params(all_offenses: T::Array[T.nilable(::Packwerk::Offense)]).returns(Result) }
70
+ def calculate_result(all_offenses)
71
+ result_status = all_offenses.empty?
72
+ message = <<~EOS
73
+ #{@offenses_formatter.show_offenses(all_offenses)}
74
+ ✅ `deprecated_references.yml` has been updated.
75
+ EOS
76
+
77
+ Result.new(message: message, status: result_status)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -22,7 +22,10 @@ module Packwerk
22
22
  private
23
23
 
24
24
  def from_packwerk_config(path)
25
- new(YAML.load_file(path), config_path: path)
25
+ new(
26
+ YAML.load_file(path) || {},
27
+ config_path: path
28
+ )
26
29
  end
27
30
  end
28
31
 
@@ -42,45 +45,10 @@ module Packwerk
42
45
  @root_path = File.expand_path(root)
43
46
  @package_paths = configs["package_paths"] || "**/"
44
47
  @custom_associations = configs["custom_associations"] || []
45
- @load_paths = configs["load_paths"] || all_application_autoload_paths
48
+ @load_paths = configs["load_paths"] || []
46
49
  @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
47
50
 
48
51
  @config_path = config_path
49
52
  end
50
-
51
- def all_application_autoload_paths
52
- return [] unless defined?(::Rails)
53
-
54
- all_paths = Rails.application.railties
55
- .select { |railtie| railtie.is_a?(Rails::Engine) }
56
- .push(Rails.application)
57
- .flat_map do |engine|
58
- (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths).uniq
59
- end
60
-
61
- bundle_path_match = Bundler.bundle_path.join("**").to_s
62
-
63
- all_paths = all_paths.each_with_object([]) do |path_string, paths|
64
- # ignore paths inside gems
65
- path = Pathname.new(path_string)
66
-
67
- next unless path.exist?
68
- next if path.realpath.fnmatch(bundle_path_match)
69
-
70
- paths << path.relative_path_from(Rails.root).to_s
71
- end
72
-
73
- all_paths.tap do |paths|
74
- if paths.empty?
75
- raise <<~EOS
76
- No autoload paths have been set up in your Rails app. This is likely a bug, and
77
- packwerk is unlikely to work correctly without any autoload paths.
78
-
79
- You can follow the Rails guides on setting up load paths, or manually configure
80
- them in `packwerk.yml` with `load_paths`.
81
- EOS
82
- end
83
- end
84
- end
85
53
  end
86
54
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "packwerk/constant_name_inspector"
@@ -6,23 +6,21 @@ require "packwerk/constant_name_inspector"
6
6
  module Packwerk
7
7
  # Extracts a constant name from an AST node of type :const
8
8
  class ConstNodeInspector
9
+ extend T::Sig
9
10
  include ConstantNameInspector
10
11
 
12
+ sig do
13
+ override
14
+ .params(node: AST::Node, ancestors: T::Array[AST::Node])
15
+ .returns(T.nilable(String))
16
+ end
11
17
  def constant_name_from_node(node, ancestors:)
12
- return nil unless Node.type(node) == Node::CONSTANT
13
-
14
- # Only process the root `const` node for namespaced constant references. For example, in the
15
- # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
18
+ return nil unless Node.constant?(node)
16
19
  parent = ancestors.first
17
- return nil if parent && Node.type(parent) == Node::CONSTANT
20
+ return nil unless root_constant?(parent)
18
21
 
19
- if constant_in_module_or_class_definition?(node, parent: parent)
20
- # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
21
- # enclosing namespace
22
- name = Node.parent_module_name(ancestors: ancestors)
23
- name ||= Node.enclosing_namespace_path(node, ancestors: ancestors).push(Node.constant_name(node)).join("::")
24
-
25
- "::" + name
22
+ if parent && constant_in_module_or_class_definition?(node, parent: parent)
23
+ fully_qualify_constant(ancestors)
26
24
  else
27
25
  begin
28
26
  Node.constant_name(node)
@@ -34,11 +32,24 @@ module Packwerk
34
32
 
35
33
  private
36
34
 
35
+ # Only process the root `const` node for namespaced constant references. For example, in the
36
+ # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
37
+ sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
38
+ def root_constant?(parent)
39
+ !(parent && Node.constant?(parent))
40
+ end
41
+
42
+ sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
37
43
  def constant_in_module_or_class_definition?(node, parent:)
38
- if parent
39
- parent_name = Node.module_name_from_definition(parent)
40
- parent_name && parent_name == Node.constant_name(node)
41
- end
44
+ parent_name = Node.module_name_from_definition(parent)
45
+ parent_name && parent_name == Node.constant_name(node)
46
+ end
47
+
48
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
49
+ def fully_qualify_constant(ancestors)
50
+ # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
51
+ # enclosing namespace
52
+ "::" + Node.parent_module_name(ancestors: ancestors)
42
53
  end
43
54
  end
44
55
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "packwerk/violation_type"
@@ -6,20 +6,28 @@ require "packwerk/checker"
6
6
 
7
7
  module Packwerk
8
8
  class DependencyChecker
9
+ extend T::Sig
9
10
  include Checker
10
11
 
12
+ sig { override.returns(ViolationType) }
11
13
  def violation_type
12
14
  ViolationType::Dependency
13
15
  end
14
16
 
17
+ sig do
18
+ override
19
+ .params(reference: Packwerk::Reference, reference_lister: Packwerk::ReferenceLister)
20
+ .returns(T::Boolean)
21
+ end
15
22
  def invalid_reference?(reference, reference_lister)
16
- return unless reference.source_package
17
- return unless reference.source_package.enforce_dependencies?
18
- return if reference.source_package.dependency?(reference.constant.package)
19
- return if reference_lister.listed?(reference, violation_type: violation_type)
23
+ return false unless reference.source_package
24
+ return false unless reference.source_package.enforce_dependencies?
25
+ return false if reference.source_package.dependency?(reference.constant.package)
26
+ return false if reference_lister.listed?(reference, violation_type: violation_type)
20
27
  true
21
28
  end
22
29
 
30
+ sig { override.params(reference: Packwerk::Reference).returns(String) }
23
31
  def message_for(reference)
24
32
  "Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
25
33
  "'#{reference.source_package}' does not specify a dependency on " \
@@ -13,6 +13,7 @@ module Packwerk
13
13
  extend T::Sig
14
14
  include ReferenceLister
15
15
 
16
+ sig { params(package: Packwerk::Package, filepath: String).void }
16
17
  def initialize(package, filepath)
17
18
  @package = package
18
19
  @filepath = filepath
@@ -34,6 +35,7 @@ module Packwerk
34
35
  violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
35
36
  end
36
37
 
38
+ sig { params(reference: Packwerk::Reference, violation_type: String).void }
37
39
  def add_entries(reference, violation_type)
38
40
  package_violations = @new_entries.fetch(reference.constant.package.name, {})
39
41
  entries_for_file = package_violations[reference.constant.name] ||= {}
@@ -47,6 +49,25 @@ module Packwerk
47
49
  @new_entries[reference.constant.package.name] = package_violations
48
50
  end
49
51
 
52
+ sig { returns(T::Boolean) }
53
+ def stale_violations?
54
+ prepare_entries_for_dump
55
+ deprecated_references.any? do |package, package_violations|
56
+ package_violations.any? do |constant_name, entries_for_file|
57
+ new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
58
+ return true if new_entries_violation_types.nil?
59
+ if entries_for_file["violations"].all? { |type| new_entries_violation_types.include?(type) }
60
+ stale_violations =
61
+ entries_for_file["files"] - Array(@new_entries.dig(package, constant_name, "files"))
62
+ stale_violations.present?
63
+ else
64
+ return true
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ sig { void }
50
71
  def dump
51
72
  if @new_entries.empty?
52
73
  File.delete(@filepath) if File.exist?(@filepath)
@@ -69,6 +90,7 @@ module Packwerk
69
90
 
70
91
  private
71
92
 
93
+ sig { returns(Hash) }
72
94
  def prepare_entries_for_dump
73
95
  @new_entries.each do |package_name, package_violations|
74
96
  package_violations.each do |_, entries_for_file|
@@ -81,6 +103,7 @@ module Packwerk
81
103
  @new_entries = @new_entries.sort.to_h
82
104
  end
83
105
 
106
+ sig { returns(Hash) }
84
107
  def deprecated_references
85
108
  @deprecated_references ||= if File.exist?(@filepath)
86
109
  YAML.load_file(@filepath) || {}
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk/cache_deprecated_references"
5
+
6
+ module Packwerk
7
+ class DetectStaleDeprecatedReferences < CacheDeprecatedReferences
8
+ extend T::Sig
9
+ sig { returns(T::Boolean) }
10
+ def stale_violations?
11
+ @deprecated_references.values.any?(&:stale_violations?)
12
+ end
13
+ end
14
+ end
@@ -15,8 +15,8 @@ module Packwerk
15
15
  end
16
16
  end
17
17
 
18
- def initialize(run_context:, parser_factory: nil)
19
- @run_context = run_context
18
+ def initialize(node_processor_factory:, parser_factory: nil)
19
+ @node_processor_factory = node_processor_factory
20
20
  @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
21
21
  end
22
22
 
@@ -32,8 +32,8 @@ module Packwerk
32
32
 
33
33
  result = []
34
34
  if node
35
- @node_processor = @run_context.node_processor_for(filename: file_path, ast_node: node)
36
- node_visitor = Packwerk::NodeVisitor.new(node_processor: @node_processor)
35
+ node_processor = @node_processor_factory.for(filename: file_path, node: node)
36
+ node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
37
37
 
38
38
  node_visitor.visit(node, ancestors: [], result: result)
39
39
  end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
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
+ module Packwerk
12
+ module Formatters
13
+ class OffensesFormatter
14
+ extend T::Sig
15
+
16
+ sig { params(style: OutputStyle).void }
17
+ def initialize(style: OutputStyles::Plain.new)
18
+ @style = style
19
+ end
20
+
21
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
22
+ def show_offenses(offenses)
23
+ return "No offenses detected 🎉" if offenses.empty?
24
+
25
+ <<~EOS
26
+ #{offenses_list(offenses)}
27
+ #{offenses_summary(offenses)}
28
+ EOS
29
+ end
30
+
31
+ private
32
+
33
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
34
+ def offenses_list(offenses)
35
+ offenses
36
+ .compact
37
+ .map { |offense| offense.to_s(@style) }
38
+ .join("\n")
39
+ end
40
+
41
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
42
+ def offenses_summary(offenses)
43
+ offenses_string = Inflector.default.pluralize("offense", offenses.length)
44
+ "#{offenses.length} #{offenses_string} detected"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -4,12 +4,16 @@
4
4
  require "benchmark"
5
5
 
6
6
  require "packwerk/inflector"
7
- require "packwerk/output_styles"
7
+ require "packwerk/output_style"
8
+ require "packwerk/output_styles/plain"
8
9
 
9
10
  module Packwerk
10
11
  module Formatters
11
12
  class ProgressFormatter
12
- def initialize(out, style: OutputStyles::Plain)
13
+ extend T::Sig
14
+
15
+ sig { params(out: T.any(StringIO, IO), style: OutputStyle).void }
16
+ def initialize(out, style: OutputStyles::Plain.new)
13
17
  @out = out
14
18
  @style = style
15
19
  end
@@ -8,20 +8,31 @@ require "packwerk/inflections/custom"
8
8
  module Packwerk
9
9
  class Inflector
10
10
  class << self
11
+ extend T::Sig
12
+
11
13
  def default
12
- @default ||= new
14
+ @default ||= new(custom_inflector: Inflections::Custom.new)
15
+ end
16
+
17
+ sig { params(inflections_file: String).returns(::Packwerk::Inflector) }
18
+ def from_file(inflections_file)
19
+ new(custom_inflector: Inflections::Custom.new(inflections_file))
13
20
  end
14
21
  end
15
22
 
16
- # For #camelize, #classify, #pluralize, #singularize
17
- include ::ActiveSupport::Inflector
23
+ extend T::Sig
24
+ include ::ActiveSupport::Inflector # For #camelize, #classify, #pluralize, #singularize
18
25
 
19
- def initialize(custom_inflection_file: nil)
26
+ sig do
27
+ params(
28
+ custom_inflector: Inflections::Custom
29
+ ).void
30
+ end
31
+ def initialize(custom_inflector:)
20
32
  @inflections = ::ActiveSupport::Inflector::Inflections.new
21
33
 
22
34
  Inflections::Default.apply_to(@inflections)
23
-
24
- Inflections::Custom.new(custom_inflection_file).apply_to(@inflections)
35
+ custom_inflector.apply_to(@inflections)
25
36
  end
26
37
 
27
38
  def pluralize(word, count = nil)
@@ -32,8 +43,6 @@ module Packwerk
32
43
  end
33
44
  end
34
45
 
35
- private
36
-
37
46
  def inflections(_ = nil)
38
47
  @inflections
39
48
  end