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
@@ -0,0 +1,104 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "benchmark"
5
+ require "parallel"
6
+
7
+ module Packwerk
8
+ class ParseRun
9
+ extend T::Sig
10
+
11
+ def initialize(
12
+ files:,
13
+ configuration:,
14
+ progress_formatter: Formatters::ProgressFormatter.new(StringIO.new),
15
+ offenses_formatter: Formatters::OffensesFormatter.new
16
+ )
17
+ @configuration = configuration
18
+ @progress_formatter = progress_formatter
19
+ @offenses_formatter = offenses_formatter
20
+ @files = files
21
+ end
22
+
23
+ def detect_stale_violations
24
+ offense_collection = find_offenses
25
+
26
+ result_status = !offense_collection.stale_violations?
27
+ message = if result_status
28
+ "No stale violations detected"
29
+ else
30
+ "There were stale violations found, please run `packwerk update-deprecations`"
31
+ end
32
+
33
+ Result.new(message: message, status: result_status)
34
+ end
35
+
36
+ def update_deprecations
37
+ offense_collection = find_offenses
38
+ offense_collection.dump_deprecated_references_files
39
+
40
+ message = <<~EOS
41
+ #{@offenses_formatter.show_offenses(offense_collection.errors)}
42
+ ✅ `deprecated_references.yml` has been updated.
43
+ EOS
44
+
45
+ Result.new(message: message, status: offense_collection.errors.empty?)
46
+ end
47
+
48
+ def check
49
+ offense_collection = find_offenses(show_errors: true)
50
+ message = @offenses_formatter.show_offenses(offense_collection.outstanding_offenses)
51
+ Result.new(message: message, status: offense_collection.outstanding_offenses.empty?)
52
+ end
53
+
54
+ private
55
+
56
+ def find_offenses(show_errors: false)
57
+ offense_collection = OffenseCollection.new(@configuration.root_path)
58
+ @progress_formatter.started(@files)
59
+
60
+ run_context = Packwerk::RunContext.from_configuration(@configuration)
61
+ all_offenses = T.let([], T.untyped)
62
+
63
+ process_file = -> (path) do
64
+ run_context.process_file(file: path).tap do |offenses|
65
+ failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
66
+ update_progress(failed: failed)
67
+ end
68
+ end
69
+
70
+ execution_time = Benchmark.realtime do
71
+ all_offenses = if @configuration.parallel?
72
+ Parallel.flat_map(@files, &process_file)
73
+ else
74
+ serial_find_offenses(&process_file)
75
+ end
76
+ end
77
+
78
+ @progress_formatter.finished(execution_time)
79
+
80
+ all_offenses.each { |offense| offense_collection.add_offense(offense) }
81
+ offense_collection
82
+ end
83
+
84
+ def serial_find_offenses
85
+ all_offenses = T.let([], T.untyped)
86
+ @files.each do |path|
87
+ offenses = yield path
88
+ all_offenses.concat(offenses)
89
+ end
90
+ all_offenses
91
+ rescue Interrupt
92
+ @progress_formatter.interrupted
93
+ all_offenses
94
+ end
95
+
96
+ def update_progress(failed: false)
97
+ if failed
98
+ @progress_formatter.mark_as_failed
99
+ else
100
+ @progress_formatter.mark_as_inspected
101
+ end
102
+ end
103
+ end
104
+ end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require "ast/node"
5
5
 
6
- require "packwerk/node"
7
-
8
6
  module Packwerk
9
7
  class ParsedConstantDefinitions
10
8
  def initialize(root_node:)
@@ -1,8 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/offense"
5
-
6
4
  module Packwerk
7
5
  module Parsers
8
6
  autoload :Erb, "packwerk/parsers/erb"
@@ -6,8 +6,6 @@ require "better_html"
6
6
  require "better_html/parser"
7
7
  require "parser/source/buffer"
8
8
 
9
- require "packwerk/parsers"
10
-
11
9
  module Packwerk
12
10
  module Parsers
13
11
  class Erb
@@ -3,8 +3,6 @@
3
3
 
4
4
  require "singleton"
5
5
 
6
- require "packwerk/parsers"
7
-
8
6
  module Packwerk
9
7
  module Parsers
10
8
  class Factory
@@ -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 PrivacyChecker
9
6
  extend T::Sig
@@ -16,10 +13,10 @@ 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 if reference.constant.public?
24
21
 
25
22
  privacy_option = reference.constant.package.enforce_privacy
@@ -28,19 +25,9 @@ module Packwerk
28
25
  return false unless privacy_option == true ||
29
26
  explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_option)
30
27
 
31
- return false if reference_lister.listed?(reference, violation_type: violation_type)
32
-
33
28
  true
34
29
  end
35
30
 
36
- sig { override.params(reference: Packwerk::Reference).returns(String) }
37
- def message_for(reference)
38
- source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
39
- "Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
40
- "referenced from #{source_desc}.\n" \
41
- "Is there a public entrypoint in '#{reference.constant.package.public_path}' that you can use instead?"
42
- end
43
-
44
31
  private
45
32
 
46
33
  sig do
@@ -1,14 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
-
6
- require "packwerk/constant_discovery"
7
- require "packwerk/constant_name_inspector"
8
- require "packwerk/node"
9
- require "packwerk/parsed_constant_definitions"
10
- require "packwerk/reference"
11
-
12
4
  module Packwerk
13
5
  # extracts a possible constant reference from a given AST node
14
6
  class ReferenceExtractor
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ class ReferenceOffense < Offense
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ attr_reader :reference, :violation_type
10
+
11
+ sig do
12
+ params(
13
+ reference: Packwerk::Reference,
14
+ violation_type: Packwerk::ViolationType,
15
+ location: T.nilable(Node::Location)
16
+ )
17
+ .void
18
+ end
19
+ def initialize(reference:, violation_type:, location: nil)
20
+ super(file: reference.relative_path, message: build_message(reference, violation_type), location: location)
21
+ @reference = reference
22
+ @violation_type = violation_type
23
+ end
24
+
25
+ private
26
+
27
+ def build_message(reference, violation_type)
28
+ violation_message = case violation_type
29
+ when ViolationType::Privacy
30
+ source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
31
+ "Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
32
+ "referenced from #{source_desc}.\n" \
33
+ "Is there a public entrypoint in '#{reference.constant.package.public_path}' that you can use instead?"
34
+ when ViolationType::Dependency
35
+ "Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
36
+ "'#{reference.source_package}' does not specify a dependency on " \
37
+ "'#{reference.constant.package}'.\n" \
38
+ "Are we missing an abstraction?\n" \
39
+ "Is the code making the reference, and the referenced constant, in the right packages?\n"
40
+ end
41
+
42
+ <<~EOS
43
+ #{violation_message}
44
+ Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
45
+ To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
46
+ EOS
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ class Result < T::Struct
6
+ prop :message, String
7
+ prop :status, T::Boolean
8
+ end
9
+ end
@@ -3,17 +3,6 @@
3
3
 
4
4
  require "constant_resolver"
5
5
 
6
- require "packwerk/association_inspector"
7
- require "packwerk/constant_discovery"
8
- require "packwerk/const_node_inspector"
9
- require "packwerk/dependency_checker"
10
- require "packwerk/file_processor"
11
- require "packwerk/inflector"
12
- require "packwerk/package_set"
13
- require "packwerk/privacy_checker"
14
- require "packwerk/reference_extractor"
15
- require "packwerk/node_processor_factory"
16
-
17
6
  module Packwerk
18
7
  class RunContext
19
8
  extend T::Sig
@@ -27,23 +16,20 @@ module Packwerk
27
16
  :checker_classes,
28
17
  )
29
18
 
30
- attr_accessor :reference_lister
31
-
32
19
  DEFAULT_CHECKERS = [
33
20
  ::Packwerk::DependencyChecker,
34
21
  ::Packwerk::PrivacyChecker,
35
22
  ]
36
23
 
37
24
  class << self
38
- def from_configuration(configuration, reference_lister:)
25
+ def from_configuration(configuration)
39
26
  inflector = ::Packwerk::Inflector.from_file(configuration.inflections_file)
40
27
  new(
41
28
  root_path: configuration.root_path,
42
29
  load_paths: configuration.load_paths,
43
30
  package_paths: configuration.package_paths,
44
31
  inflector: inflector,
45
- custom_associations: configuration.custom_associations,
46
- reference_lister: reference_lister,
32
+ custom_associations: configuration.custom_associations
47
33
  )
48
34
  end
49
35
  end
@@ -54,8 +40,7 @@ module Packwerk
54
40
  package_paths: nil,
55
41
  inflector: nil,
56
42
  custom_associations: [],
57
- checker_classes: DEFAULT_CHECKERS,
58
- reference_lister:
43
+ checker_classes: DEFAULT_CHECKERS
59
44
  )
60
45
  @root_path = root_path
61
46
  @load_paths = load_paths
@@ -63,7 +48,6 @@ module Packwerk
63
48
  @inflector = inflector
64
49
  @custom_associations = custom_associations
65
50
  @checker_classes = checker_classes
66
- @reference_lister = reference_lister
67
51
  end
68
52
 
69
53
  sig { params(file: String).returns(T::Array[T.nilable(::Packwerk::Offense)]) }
@@ -84,8 +68,7 @@ module Packwerk
84
68
  context_provider: context_provider,
85
69
  checkers: checkers,
86
70
  root_path: root_path,
87
- constant_name_inspectors: constant_name_inspectors,
88
- reference_lister: reference_lister
71
+ constant_name_inspectors: constant_name_inspectors
89
72
  )
90
73
  end
91
74
 
@@ -1,8 +1,6 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/application_validator"
5
-
6
4
  module Packwerk
7
5
  # To do: This alias and file should be removed as it is deprecated
8
6
  warn("DEPRECATION WARNING: Packwerk::SanityChecker is deprecated, use Packwerk::ApplicationValidator instead.")
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "1.1.3"
5
+ VERSION = "1.2.0"
6
6
  end
@@ -1,8 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
-
6
4
  module Packwerk
7
5
  class ViolationType < T::Enum
8
6
  enums do
data/packwerk.gemspec CHANGED
@@ -42,6 +42,7 @@ Gem::Specification.new do |spec|
42
42
 
43
43
  spec.add_dependency("activesupport", ">= 5.2")
44
44
  spec.add_dependency("constant_resolver")
45
+ spec.add_dependency("parallel")
45
46
  spec.add_dependency("sorbet-runtime")
46
47
 
47
48
  spec.add_development_dependency("bundler")
data/service.yml CHANGED
@@ -1,6 +1,3 @@
1
- # https://services.shopify.io/services/packwerk/production
2
- org_line: Kernel
3
- owners: Shopify/core-stewardship
4
1
  classification: library
5
2
  slack_channels:
6
- - core-stewardship
3
+ - core-stewardship
@@ -4,5 +4,114 @@
4
4
 
5
5
  # typed: true
6
6
 
7
- # THIS IS AN EMPTY RBI FILE.
8
- # see https://github.com/Shopify/tapioca/blob/master/README.md#manual-gem-requires
7
+ module Parallel
8
+ extend(::Parallel::ProcessorCount)
9
+
10
+ class << self
11
+ def all?(*args, &block); end
12
+ def any?(*args, &block); end
13
+ def each(array, options = T.unsafe(nil), &block); end
14
+ def each_with_index(array, options = T.unsafe(nil), &block); end
15
+ def flat_map(*args, &block); end
16
+ def in_processes(options = T.unsafe(nil), &block); end
17
+ def in_threads(options = T.unsafe(nil)); end
18
+ def map(source, options = T.unsafe(nil), &block); end
19
+ def map_with_index(array, options = T.unsafe(nil), &block); end
20
+ def worker_number; end
21
+ def worker_number=(worker_num); end
22
+
23
+ private
24
+
25
+ def add_progress_bar!(job_factory, options); end
26
+ def call_with_index(item, index, options, &block); end
27
+ def create_workers(job_factory, options, &block); end
28
+ def extract_count_from_options(options); end
29
+ def process_incoming_jobs(read, write, job_factory, options, &block); end
30
+ def replace_worker(job_factory, workers, i, options, blk); end
31
+ def with_instrumentation(item, index, options); end
32
+ def work_direct(job_factory, options, &block); end
33
+ def work_in_processes(job_factory, options, &blk); end
34
+ def work_in_threads(job_factory, options, &block); end
35
+ def worker(job_factory, options, &block); end
36
+ end
37
+ end
38
+
39
+ class Parallel::Break < ::StandardError
40
+ def initialize(value = T.unsafe(nil)); end
41
+
42
+ def value; end
43
+ end
44
+
45
+ class Parallel::DeadWorker < ::StandardError
46
+ end
47
+
48
+ class Parallel::ExceptionWrapper
49
+ def initialize(exception); end
50
+
51
+ def exception; end
52
+ end
53
+
54
+ class Parallel::JobFactory
55
+ def initialize(source, mutex); end
56
+
57
+ def next; end
58
+ def pack(item, index); end
59
+ def size; end
60
+ def unpack(data); end
61
+
62
+ private
63
+
64
+ def producer?; end
65
+ def queue_wrapper(array); end
66
+ end
67
+
68
+ class Parallel::Kill < ::Parallel::Break
69
+ end
70
+
71
+ module Parallel::ProcessorCount
72
+ def physical_processor_count; end
73
+ def processor_count; end
74
+ end
75
+
76
+ Parallel::Stop = T.let(T.unsafe(nil), Object)
77
+
78
+ class Parallel::UndumpableException < ::StandardError
79
+ def initialize(original); end
80
+
81
+ def backtrace; end
82
+ end
83
+
84
+ class Parallel::UserInterruptHandler
85
+ class << self
86
+ def kill(thing); end
87
+ def kill_on_ctrl_c(pids, options); end
88
+
89
+ private
90
+
91
+ def restore_interrupt(old, signal); end
92
+ def trap_interrupt(signal); end
93
+ end
94
+ end
95
+
96
+ Parallel::UserInterruptHandler::INTERRUPT_SIGNAL = T.let(T.unsafe(nil), Symbol)
97
+
98
+ Parallel::VERSION = T.let(T.unsafe(nil), String)
99
+
100
+ Parallel::Version = T.let(T.unsafe(nil), String)
101
+
102
+ class Parallel::Worker
103
+ def initialize(read, write, pid); end
104
+
105
+ def close_pipes; end
106
+ def pid; end
107
+ def read; end
108
+ def stop; end
109
+ def thread; end
110
+ def thread=(_arg0); end
111
+ def work(data); end
112
+ def write; end
113
+
114
+ private
115
+
116
+ def wait; end
117
+ end