packwerk 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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