packwerk 1.1.0 → 1.3.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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +17 -8
  3. data/.ruby-version +1 -1
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +129 -111
  6. data/README.md +10 -3
  7. data/TROUBLESHOOT.md +2 -2
  8. data/USAGE.md +30 -30
  9. data/bin/m +29 -0
  10. data/bin/rake +29 -0
  11. data/bin/rubocop +29 -0
  12. data/bin/srb +29 -0
  13. data/bin/tapioca +29 -0
  14. data/dev.yml +7 -7
  15. data/exe/packwerk +1 -1
  16. data/gemfiles/Gemfile-rails-6-0 +22 -0
  17. data/lib/packwerk.rb +72 -34
  18. data/lib/packwerk/application_load_paths.rb +21 -10
  19. data/lib/packwerk/application_validator.rb +104 -84
  20. data/lib/packwerk/association_inspector.rb +23 -11
  21. data/lib/packwerk/checker.rb +4 -7
  22. data/lib/packwerk/cli.rb +36 -129
  23. data/lib/packwerk/configuration.rb +10 -2
  24. data/lib/packwerk/const_node_inspector.rb +13 -14
  25. data/lib/packwerk/constant_discovery.rb +2 -0
  26. data/lib/packwerk/constant_name_inspector.rb +0 -1
  27. data/lib/packwerk/dependency_checker.rb +12 -17
  28. data/lib/packwerk/deprecated_references.rb +8 -10
  29. data/lib/packwerk/file_processor.rb +0 -4
  30. data/lib/packwerk/formatters/offenses_formatter.rb +52 -0
  31. data/lib/packwerk/formatters/progress_formatter.rb +9 -4
  32. data/lib/packwerk/generators/configuration_file.rb +0 -1
  33. data/lib/packwerk/inflector.rb +0 -2
  34. data/lib/packwerk/node.rb +9 -2
  35. data/lib/packwerk/node_processor.rb +15 -32
  36. data/lib/packwerk/node_processor_factory.rb +0 -5
  37. data/lib/packwerk/node_visitor.rb +1 -4
  38. data/lib/packwerk/offense.rb +2 -8
  39. data/lib/packwerk/offense_collection.rb +84 -0
  40. data/lib/packwerk/offenses_formatter.rb +19 -0
  41. data/lib/packwerk/output_style.rb +20 -0
  42. data/lib/packwerk/output_styles/coloured.rb +29 -0
  43. data/lib/packwerk/output_styles/plain.rb +26 -0
  44. data/lib/packwerk/package.rb +17 -1
  45. data/lib/packwerk/package_set.rb +2 -3
  46. data/lib/packwerk/parse_run.rb +106 -0
  47. data/lib/packwerk/parsed_constant_definitions.rb +2 -4
  48. data/lib/packwerk/parsers.rb +0 -2
  49. data/lib/packwerk/parsers/erb.rb +0 -2
  50. data/lib/packwerk/parsers/factory.rb +1 -3
  51. data/lib/packwerk/privacy_checker.rb +22 -17
  52. data/lib/packwerk/reference_extractor.rb +0 -8
  53. data/lib/packwerk/reference_offense.rb +49 -0
  54. data/lib/packwerk/result.rb +9 -0
  55. data/lib/packwerk/run_context.rb +4 -21
  56. data/lib/packwerk/sanity_checker.rb +1 -3
  57. data/lib/packwerk/version.rb +1 -1
  58. data/lib/packwerk/violation_type.rb +0 -2
  59. data/library.yml +1 -1
  60. data/packwerk.gemspec +1 -0
  61. data/service.yml +1 -4
  62. data/shipit.rubygems.yml +5 -1
  63. data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
  64. data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
  65. data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
  66. data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
  67. data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
  68. data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
  69. data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
  70. data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
  71. data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
  72. data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
  73. data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
  74. data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
  75. data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
  76. data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
  77. data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
  78. data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
  79. data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
  80. data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
  81. data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
  82. data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
  83. data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
  84. data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
  85. data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
  86. data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
  87. data/sorbet/rbi/gems/parallel@1.20.1.rbi +117 -0
  88. data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
  89. data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
  90. data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
  91. data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
  92. data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
  93. data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
  94. data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
  95. data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
  96. data/sorbet/rbi/gems/{parallel@1.19.1.rbi → regexp_parser@2.1.1.rbi} +2 -2
  97. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
  98. data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
  99. data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
  100. data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
  101. data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
  102. data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
  103. data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
  104. data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
  105. data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
  106. data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
  107. data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
  108. data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
  109. data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
  110. data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
  111. data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
  112. data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
  113. data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
  114. data/sorbet/tapioca/require.rb +1 -0
  115. metadata +83 -65
  116. data/lib/packwerk/cache_deprecated_references.rb +0 -47
  117. data/lib/packwerk/checking_deprecated_references.rb +0 -40
  118. data/lib/packwerk/commands/detect_stale_violations_command.rb +0 -63
  119. data/lib/packwerk/commands/offense_progress_marker.rb +0 -24
  120. data/lib/packwerk/detect_stale_deprecated_references.rb +0 -14
  121. data/lib/packwerk/generators/application_validation.rb +0 -62
  122. data/lib/packwerk/generators/templates/packwerk +0 -23
  123. data/lib/packwerk/generators/templates/packwerk_validator_test.rb +0 -11
  124. data/lib/packwerk/output_styles.rb +0 -41
  125. data/lib/packwerk/reference_lister.rb +0 -23
  126. data/lib/packwerk/spring_command.rb +0 -28
  127. data/lib/packwerk/updating_deprecated_references.rb +0 -14
  128. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
  129. data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
  130. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
  131. data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
  132. data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
  133. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
@@ -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,10 +45,15 @@ 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"]
48
+ @load_paths = configs["load_paths"] || []
46
49
  @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
50
+ @parallel = configs.key?("parallel") ? configs["parallel"] : true
47
51
 
48
52
  @config_path = config_path
49
53
  end
54
+
55
+ def parallel?
56
+ @parallel
57
+ end
50
58
  end
51
59
  end
@@ -1,20 +1,24 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/constant_name_inspector"
5
-
6
4
  module Packwerk
7
5
  # Extracts a constant name from an AST node of type :const
8
6
  class ConstNodeInspector
7
+ extend T::Sig
9
8
  include ConstantNameInspector
10
9
 
10
+ sig do
11
+ override
12
+ .params(node: AST::Node, ancestors: T::Array[AST::Node])
13
+ .returns(T.nilable(String))
14
+ end
11
15
  def constant_name_from_node(node, ancestors:)
12
16
  return nil unless Node.constant?(node)
13
17
  parent = ancestors.first
14
18
  return nil unless root_constant?(parent)
15
19
 
16
20
  if parent && constant_in_module_or_class_definition?(node, parent: parent)
17
- fully_qualify_constant(node, ancestors: ancestors)
21
+ fully_qualify_constant(ancestors)
18
22
  else
19
23
  begin
20
24
  Node.constant_name(node)
@@ -28,27 +32,22 @@ module Packwerk
28
32
 
29
33
  # Only process the root `const` node for namespaced constant references. For example, in the
30
34
  # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
35
+ sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
31
36
  def root_constant?(parent)
32
37
  !(parent && Node.constant?(parent))
33
38
  end
34
39
 
40
+ sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
35
41
  def constant_in_module_or_class_definition?(node, parent:)
36
42
  parent_name = Node.module_name_from_definition(parent)
37
43
  parent_name && parent_name == Node.constant_name(node)
38
44
  end
39
45
 
40
- def fully_qualify_constant(node, ancestors:)
46
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
47
+ def fully_qualify_constant(ancestors)
41
48
  # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
42
49
  # enclosing namespace
43
- name = Node.parent_module_name(ancestors: ancestors)
44
- name ||= generate_qualified_constant(node, ancestors)
45
- "::" + name
46
- end
47
-
48
- def generate_qualified_constant(node, ancestors:)
49
- namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
50
- constant_name = Node.constant_name(node)
51
- namespace_path.push(constant_name).join("::")
50
+ "::" + Node.parent_module_name(ancestors: ancestors)
52
51
  end
53
52
  end
54
53
  end
@@ -1,6 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "constant_resolver"
5
+
4
6
  module Packwerk
5
7
  # Get information about (partially qualified) constants without loading the application code.
6
8
  # Information gathered: Fully qualified name, path to file containing the definition, package,
@@ -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,31 +1,26 @@
1
- # typed: true
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
6
+ extend T::Sig
9
7
  include Checker
10
8
 
9
+ sig { override.returns(ViolationType) }
11
10
  def violation_type
12
11
  ViolationType::Dependency
13
12
  end
14
13
 
15
- 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)
20
- true
14
+ sig do
15
+ override
16
+ .params(reference: Packwerk::Reference)
17
+ .returns(T::Boolean)
21
18
  end
22
-
23
- def message_for(reference)
24
- "Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
25
- "'#{reference.source_package}' does not specify a dependency on " \
26
- "'#{reference.constant.package}'.\n" \
27
- "Are we missing an abstraction?\n" \
28
- "Is the code making the reference, and the referenced constant, in the right packages?\n"
19
+ def invalid_reference?(reference)
20
+ return false unless reference.source_package
21
+ return false unless reference.source_package.enforce_dependencies?
22
+ return false if reference.source_package.dependency?(reference.constant.package)
23
+ true
29
24
  end
30
25
  end
31
26
  end
@@ -1,19 +1,13 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
4
  require "yaml"
6
- require "sorbet-runtime"
7
-
8
- require "packwerk/reference"
9
- require "packwerk/reference_lister"
10
- require "packwerk/violation_type"
11
5
 
12
6
  module Packwerk
13
7
  class DeprecatedReferences
14
8
  extend T::Sig
15
- include ReferenceLister
16
9
 
10
+ sig { params(package: Packwerk::Package, filepath: String).void }
17
11
  def initialize(package, filepath)
18
12
  @package = package
19
13
  @filepath = 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,17 +28,19 @@ module Packwerk
35
28
  violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
36
29
  end
37
30
 
31
+ sig { params(reference: Packwerk::Reference, violation_type: Packwerk::ViolationType).returns(T::Boolean) }
38
32
  def add_entries(reference, violation_type)
39
33
  package_violations = @new_entries.fetch(reference.constant.package.name, {})
40
34
  entries_for_file = package_violations[reference.constant.name] ||= {}
41
35
 
42
36
  entries_for_file["violations"] ||= []
43
- entries_for_file["violations"] << violation_type
37
+ entries_for_file["violations"] << violation_type.serialize
44
38
 
45
39
  entries_for_file["files"] ||= []
46
40
  entries_for_file["files"] << reference.relative_path.to_s
47
41
 
48
42
  @new_entries[reference.constant.package.name] = package_violations
43
+ listed?(reference, violation_type: violation_type)
49
44
  end
50
45
 
51
46
  sig { returns(T::Boolean) }
@@ -66,6 +61,7 @@ module Packwerk
66
61
  end
67
62
  end
68
63
 
64
+ sig { void }
69
65
  def dump
70
66
  if @new_entries.empty?
71
67
  File.delete(@filepath) if File.exist?(@filepath)
@@ -77,7 +73,7 @@ module Packwerk
77
73
  #
78
74
  # You can regenerate this file using the following command:
79
75
  #
80
- # bundle exec packwerk update-deprecations #{@package.name}
76
+ # packwerk update-deprecations #{@package.name}
81
77
  MESSAGE
82
78
  File.open(@filepath, "w") do |f|
83
79
  f.write(message)
@@ -88,6 +84,7 @@ module Packwerk
88
84
 
89
85
  private
90
86
 
87
+ sig { returns(Hash) }
91
88
  def prepare_entries_for_dump
92
89
  @new_entries.each do |package_name, package_violations|
93
90
  package_violations.each do |_, entries_for_file|
@@ -100,6 +97,7 @@ module Packwerk
100
97
  @new_entries = @new_entries.sort.to_h
101
98
  end
102
99
 
100
+ sig { returns(Hash) }
103
101
  def deprecated_references
104
102
  @deprecated_references ||= if File.exist?(@filepath)
105
103
  YAML.load_file(@filepath) || {}
@@ -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
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Formatters
6
+ class OffensesFormatter
7
+ include Packwerk::OffensesFormatter
8
+
9
+ extend T::Sig
10
+
11
+ sig { params(style: OutputStyle).void }
12
+ def initialize(style: OutputStyles::Plain.new)
13
+ @style = style
14
+ end
15
+
16
+ sig { override.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
17
+ def show_offenses(offenses)
18
+ return "No offenses detected" if offenses.empty?
19
+
20
+ <<~EOS
21
+ #{offenses_list(offenses)}
22
+ #{offenses_summary(offenses)}
23
+ EOS
24
+ end
25
+
26
+ sig { override.params(offense_collection: Packwerk::OffenseCollection).returns(String) }
27
+ def show_stale_violations(offense_collection)
28
+ if offense_collection.stale_violations?
29
+ "There were stale violations found, please run `packwerk update-deprecations`"
30
+ else
31
+ "No stale violations detected"
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
38
+ def offenses_list(offenses)
39
+ offenses
40
+ .compact
41
+ .map { |offense| offense.to_s(@style) }
42
+ .join("\n")
43
+ end
44
+
45
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
46
+ def offenses_summary(offenses)
47
+ offenses_string = Inflector.default.pluralize("offense", offenses.length)
48
+ "#{offenses.length} #{offenses_string} detected"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -3,13 +3,13 @@
3
3
 
4
4
  require "benchmark"
5
5
 
6
- require "packwerk/inflector"
7
- require "packwerk/output_styles"
8
-
9
6
  module Packwerk
10
7
  module Formatters
11
8
  class ProgressFormatter
12
- def initialize(out, style: OutputStyles::Plain)
9
+ extend T::Sig
10
+
11
+ sig { params(out: T.any(StringIO, IO), style: OutputStyle).void }
12
+ def initialize(out, style: OutputStyles::Plain.new)
13
13
  @out = out
14
14
  @style = style
15
15
  end
@@ -41,6 +41,11 @@ module Packwerk
41
41
  @out.puts
42
42
  @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
43
43
  end
44
+
45
+ def interrupted
46
+ @out.puts
47
+ @out.puts("Manually interrupted. Violations caught so far are listed below:")
48
+ end
44
49
  end
45
50
  end
46
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
@@ -9,6 +10,8 @@ module Packwerk
9
10
  Location = Struct.new(:line, :column)
10
11
 
11
12
  class << self
13
+ extend T::Sig
14
+
12
15
  def class_or_module_name(class_or_module_node)
13
16
  case type_of(class_or_module_node)
14
17
  when CLASS, MODULE
@@ -27,7 +30,7 @@ module Packwerk
27
30
  case type_of(constant_node)
28
31
  when CONSTANT_ROOT_NAMESPACE
29
32
  ""
30
- when CONSTANT, CONSTANT_ASSIGNMENT
33
+ when CONSTANT, CONSTANT_ASSIGNMENT, SELF
31
34
  # (const nil :Foo)
32
35
  # "Foo"
33
36
  # (const (cbase) :Foo)
@@ -40,6 +43,8 @@ module Packwerk
40
43
  # "::Foo = 1"
41
44
  # (casgn (lvar :a) :Foo (int 1))
42
45
  # "a::Foo = 1"
46
+ # (casgn (self) :Foo (int 1))
47
+ # "self::Foo = 1"
43
48
  namespace, name = constant_node.children
44
49
  if namespace
45
50
  [constant_name(namespace), name].join("::")
@@ -176,6 +181,7 @@ module Packwerk
176
181
  class_node.children[1]
177
182
  end
178
183
 
184
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
179
185
  def parent_module_name(ancestors:)
180
186
  definitions = ancestors
181
187
  .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
@@ -204,12 +210,13 @@ module Packwerk
204
210
  HASH_PAIR = :pair
205
211
  METHOD_CALL = :send
206
212
  MODULE = :module
213
+ SELF = :self
207
214
  STRING = :str
208
215
  SYMBOL = :sym
209
216
 
210
217
  private_constant(
211
218
  :BLOCK, :CLASS, :CONSTANT, :CONSTANT_ASSIGNMENT, :CONSTANT_ROOT_NAMESPACE, :HASH, :HASH_PAIR, :METHOD_CALL,
212
- :MODULE, :STRING, :SYMBOL,
219
+ :MODULE, :SELF, :STRING, :SYMBOL,
213
220
  )
214
221
 
215
222
  def type_of(node)
@@ -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,48 +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
- def call(node, ancestors:)
29
- if Node.method_call?(node) || Node.constant?(node)
30
- reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
31
- check_reference(reference, node) if reference
32
- end
21
+ sig { params(node: Parser::AST::Node, ancestors: T::Array[Parser::AST::Node]).returns(T::Array[Offense]) }
22
+ def call(node, ancestors)
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)
33
26
  end
34
27
 
35
28
  private
36
29
 
37
30
  def check_reference(reference, node)
38
- return nil unless (message = failed_check(reference))
39
-
40
- constant = reference.constant
41
-
42
- Packwerk::Offense.new(
43
- location: Node.location(node),
44
- file: reference.relative_path,
45
- message: <<~EOS
46
- #{message}
47
- Inference details: this is a reference to #{constant.name} which seems to be defined in #{constant.location}.
48
- To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
49
- EOS
50
- )
51
- end
52
-
53
- def failed_check(reference)
54
- failing_checker = @checkers.find do |checker|
55
- 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
56
40
  end
57
- failing_checker&.message_for(reference)
58
41
  end
59
42
  end
60
43
  end