packwerk 2.0.0 → 2.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +26 -22
  3. data/README.md +13 -1
  4. data/USAGE.md +7 -0
  5. data/lib/packwerk/application_load_paths.rb +12 -18
  6. data/lib/packwerk/application_validator.rb +88 -40
  7. data/lib/packwerk/cache.rb +169 -0
  8. data/lib/packwerk/cli.rb +29 -13
  9. data/lib/packwerk/configuration.rb +17 -12
  10. data/lib/packwerk/constant_discovery.rb +20 -4
  11. data/lib/packwerk/constant_name_inspector.rb +1 -1
  12. data/lib/packwerk/deprecated_references.rb +1 -1
  13. data/lib/packwerk/file_processor.rb +43 -22
  14. data/lib/packwerk/files_for_processing.rb +55 -26
  15. data/lib/packwerk/generators/templates/packwerk.yml.erb +6 -0
  16. data/lib/packwerk/node.rb +2 -1
  17. data/lib/packwerk/node_processor.rb +6 -6
  18. data/lib/packwerk/node_processor_factory.rb +3 -4
  19. data/lib/packwerk/node_visitor.rb +3 -0
  20. data/lib/packwerk/offense.rb +10 -2
  21. data/lib/packwerk/package.rb +1 -1
  22. data/lib/packwerk/package_set.rb +4 -3
  23. data/lib/packwerk/parse_run.rb +37 -17
  24. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  25. data/lib/packwerk/parsers/erb.rb +2 -0
  26. data/lib/packwerk/parsers/factory.rb +2 -0
  27. data/lib/packwerk/parsers/parser_interface.rb +19 -0
  28. data/lib/packwerk/parsers/ruby.rb +2 -0
  29. data/lib/packwerk/parsers.rb +1 -0
  30. data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
  31. data/lib/packwerk/reference_checking/reference_checker.rb +3 -4
  32. data/lib/packwerk/reference_extractor.rb +72 -20
  33. data/lib/packwerk/reference_offense.rb +8 -3
  34. data/lib/packwerk/result.rb +2 -2
  35. data/lib/packwerk/run_context.rb +62 -36
  36. data/lib/packwerk/spring_command.rb +1 -1
  37. data/lib/packwerk/unresolved_reference.rb +10 -0
  38. data/lib/packwerk/version.rb +1 -1
  39. data/lib/packwerk.rb +2 -0
  40. data/packwerk.gemspec +4 -2
  41. data/sorbet/config +1 -0
  42. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
  43. data/sorbet/tapioca/require.rb +1 -1
  44. metadata +36 -5
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "constant_resolver"
@@ -8,76 +8,107 @@ module Packwerk
8
8
  class RunContext
9
9
  extend T::Sig
10
10
 
11
- attr_reader(
12
- :root_path,
13
- :load_paths,
14
- :package_paths,
15
- :inflector,
16
- :custom_associations,
17
- :checker_classes,
18
- )
19
-
20
- DEFAULT_CHECKERS = [
21
- ::Packwerk::ReferenceChecking::Checkers::DependencyChecker,
22
- ::Packwerk::ReferenceChecking::Checkers::PrivacyChecker,
23
- ]
11
+ DEFAULT_CHECKERS = T.let([
12
+ ::Packwerk::ReferenceChecking::Checkers::DependencyChecker.new,
13
+ ::Packwerk::ReferenceChecking::Checkers::PrivacyChecker.new,
14
+ ], T::Array[ReferenceChecking::Checkers::Checker])
24
15
 
25
16
  class << self
17
+ extend T::Sig
18
+
19
+ sig do
20
+ params(configuration: Configuration).returns(RunContext)
21
+ end
26
22
  def from_configuration(configuration)
27
23
  inflector = ActiveSupport::Inflector
24
+
28
25
  new(
29
26
  root_path: configuration.root_path,
30
27
  load_paths: configuration.load_paths,
31
28
  package_paths: configuration.package_paths,
32
29
  inflector: inflector,
33
- custom_associations: configuration.custom_associations
30
+ custom_associations: configuration.custom_associations,
31
+ cache_enabled: configuration.cache_enabled?,
32
+ cache_directory: configuration.cache_directory,
33
+ config_path: configuration.config_path,
34
34
  )
35
35
  end
36
36
  end
37
37
 
38
+ sig do
39
+ params(
40
+ root_path: String,
41
+ load_paths: T::Hash[String, Module],
42
+ inflector: T.class_of(ActiveSupport::Inflector),
43
+ cache_directory: Pathname,
44
+ config_path: T.nilable(String),
45
+ package_paths: T.nilable(T.any(T::Array[String], String)),
46
+ custom_associations: AssociationInspector::CustomAssociations,
47
+ checkers: T::Array[ReferenceChecking::Checkers::Checker],
48
+ cache_enabled: T::Boolean,
49
+ ).void
50
+ end
38
51
  def initialize(
39
52
  root_path:,
40
53
  load_paths:,
54
+ inflector:,
55
+ cache_directory:,
56
+ config_path: nil,
41
57
  package_paths: nil,
42
- inflector: nil,
43
58
  custom_associations: [],
44
- checker_classes: DEFAULT_CHECKERS
59
+ checkers: DEFAULT_CHECKERS,
60
+ cache_enabled: false
45
61
  )
46
62
  @root_path = root_path
47
63
  @load_paths = load_paths
48
64
  @package_paths = package_paths
49
65
  @inflector = inflector
50
66
  @custom_associations = custom_associations
51
- @checker_classes = checker_classes
67
+ @checkers = checkers
68
+ @cache_enabled = cache_enabled
69
+ @cache_directory = cache_directory
70
+ @config_path = config_path
71
+
72
+ @file_processor = T.let(nil, T.nilable(FileProcessor))
73
+ @context_provider = T.let(nil, T.nilable(ConstantDiscovery))
74
+ # We need to initialize this before we fork the process, see https://github.com/Shopify/packwerk/issues/182
75
+ @cache = T.let(
76
+ Cache.new(enable_cache: @cache_enabled, cache_directory: @cache_directory, config_path: @config_path), Cache
77
+ )
52
78
  end
53
79
 
54
- sig { params(file: String).returns(T::Array[Packwerk::Offense]) }
55
- def process_file(file:)
56
- references = file_processor.call(file)
80
+ sig { params(absolute_file: String).returns(T::Array[Packwerk::Offense]) }
81
+ def process_file(absolute_file:)
82
+ processed_file = file_processor.call(absolute_file)
83
+
84
+ references = ReferenceExtractor.get_fully_qualified_references_from(
85
+ processed_file.unresolved_references,
86
+ context_provider
87
+ )
88
+ reference_checker = ReferenceChecking::ReferenceChecker.new(@checkers)
57
89
 
58
- reference_checker = ReferenceChecking::ReferenceChecker.new(checkers)
59
- references.flat_map { |reference| reference_checker.call(reference) }
90
+ processed_file.offenses + references.flat_map { |reference| reference_checker.call(reference) }
60
91
  end
61
92
 
62
93
  private
63
94
 
64
95
  sig { returns(FileProcessor) }
65
96
  def file_processor
66
- @file_processor ||= FileProcessor.new(node_processor_factory: node_processor_factory)
97
+ @file_processor ||= FileProcessor.new(node_processor_factory: node_processor_factory, cache: @cache)
67
98
  end
68
99
 
69
100
  sig { returns(NodeProcessorFactory) }
70
101
  def node_processor_factory
71
102
  NodeProcessorFactory.new(
72
103
  context_provider: context_provider,
73
- root_path: root_path,
104
+ root_path: @root_path,
74
105
  constant_name_inspectors: constant_name_inspectors
75
106
  )
76
107
  end
77
108
 
78
109
  sig { returns(ConstantDiscovery) }
79
110
  def context_provider
80
- ::Packwerk::ConstantDiscovery.new(
111
+ @context_provider ||= ::Packwerk::ConstantDiscovery.new(
81
112
  constant_resolver: resolver,
82
113
  packages: package_set
83
114
  )
@@ -86,27 +117,22 @@ module Packwerk
86
117
  sig { returns(ConstantResolver) }
87
118
  def resolver
88
119
  ConstantResolver.new(
89
- root_path: root_path,
90
- load_paths: load_paths,
91
- inflector: inflector,
120
+ root_path: @root_path,
121
+ load_paths: @load_paths,
122
+ inflector: @inflector,
92
123
  )
93
124
  end
94
125
 
95
126
  sig { returns(PackageSet) }
96
127
  def package_set
97
- ::Packwerk::PackageSet.load_all_from(root_path, package_pathspec: package_paths)
98
- end
99
-
100
- sig { returns(T::Array[ReferenceChecking::Checkers::Checker]) }
101
- def checkers
102
- checker_classes.map(&:new)
128
+ ::Packwerk::PackageSet.load_all_from(@root_path, package_pathspec: @package_paths)
103
129
  end
104
130
 
105
131
  sig { returns(T::Array[ConstantNameInspector]) }
106
132
  def constant_name_inspectors
107
133
  [
108
134
  ::Packwerk::ConstNodeInspector.new,
109
- ::Packwerk::AssociationInspector.new(inflector: inflector, custom_associations: custom_associations),
135
+ ::Packwerk::AssociationInspector.new(inflector: @inflector, custom_associations: @custom_associations),
110
136
  ]
111
137
  end
112
138
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: false
2
+ # typed: true
3
3
 
4
4
  require "spring/commands"
5
5
 
@@ -0,0 +1,10 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ # An unresolved reference from a file in one package to a constant that may be defined in a different package.
6
+ # Unresolved means that we know how it's referred to in the file,
7
+ # and we have enough context on that reference to figure out the fully qualified reference such that we
8
+ # can produce a Reference in a separate pass. However, we have not yet resolved it to its fully qualified version.
9
+ UnresolvedReference = Struct.new(:constant_name, :namespace_path, :relative_path, :source_location)
10
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "2.0.0"
5
+ VERSION = "2.2.0"
6
6
  end
data/lib/packwerk.rb CHANGED
@@ -15,6 +15,7 @@ module Packwerk
15
15
  autoload :ApplicationValidator
16
16
  autoload :AssociationInspector
17
17
  autoload :OffenseCollection
18
+ autoload :Cache
18
19
  autoload :Cli
19
20
  autoload :Configuration
20
21
  autoload :ConstantDiscovery
@@ -36,6 +37,7 @@ module Packwerk
36
37
  autoload :ParsedConstantDefinitions
37
38
  autoload :Parsers
38
39
  autoload :ParseRun
40
+ autoload :UnresolvedReference
39
41
  autoload :Reference
40
42
  autoload :ReferenceExtractor
41
43
  autoload :ReferenceOffense
data/packwerk.gemspec CHANGED
@@ -41,16 +41,18 @@ Gem::Specification.new do |spec|
41
41
  spec.required_ruby_version = ">= 2.6"
42
42
 
43
43
  spec.add_dependency("activesupport", ">= 5.2")
44
- spec.add_dependency("constant_resolver")
44
+ spec.add_dependency("constant_resolver", ">=0.2.0")
45
45
  spec.add_dependency("parallel")
46
- spec.add_dependency("sorbet-runtime")
46
+ spec.add_dependency("sorbet-runtime", ">=0.5.9914")
47
47
  spec.add_dependency("bundler")
48
+ spec.add_dependency("digest")
48
49
 
49
50
  spec.add_development_dependency("rake")
50
51
  spec.add_development_dependency("sorbet")
51
52
  spec.add_development_dependency("m")
52
53
  # https://github.com/ruby/psych/pull/487
53
54
  spec.add_development_dependency("psych", "~> 3")
55
+ spec.add_development_dependency("zeitwerk")
54
56
 
55
57
  # For Ruby parsing
56
58
  spec.add_dependency("ast")
data/sorbet/config CHANGED
@@ -1,2 +1,3 @@
1
1
  --dir
2
2
  .
3
+ --enable-experimental-requires-ancestor
@@ -562,7 +562,7 @@ module Tapioca::GenericTypeRegistry
562
562
  def name_of(constant); end
563
563
  sig { params(object: BasicObject).returns(Integer) }
564
564
  def object_id_of(object); end
565
- sig { params(constant: T.untyped, type_variable_type: T.enum([:type_member, :type_template]), type_variable: T::Types::TypeVariable, fixed: T.untyped, lower: T.untyped, upper: T.untyped).void }
565
+ sig { params(constant: T.untyped, type_variable_type: T.deprecated_enum([:type_member, :type_template]), type_variable: T::Types::TypeVariable, fixed: T.untyped, lower: T.untyped, upper: T.untyped).void }
566
566
  def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper); end
567
567
  sig { params(type_variable_type: Symbol, variance: Symbol, fixed: T.untyped, lower: T.untyped, upper: T.untyped).returns(String) }
568
568
  def serialize_type_variable(type_variable_type, variance, fixed, lower, upper); end
@@ -3,7 +3,7 @@
3
3
  # This is an autogenerated file for explicit gem requires.
4
4
  # Please instead update this file by running `tapioca require`.
5
5
 
6
- # typed: false
6
+ # typed: strict
7
7
 
8
8
  require "ast"
9
9
  require "ast/node"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-08 00:00:00.000000000 Z
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: parallel
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -54,6 +54,20 @@ dependencies:
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: sorbet-runtime
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.9914
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.9914
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
@@ -67,7 +81,7 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: bundler
84
+ name: digest
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -136,6 +150,20 @@ dependencies:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '3'
153
+ - !ruby/object:Gem::Dependency
154
+ name: zeitwerk
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  - !ruby/object:Gem::Dependency
140
168
  name: ast
141
169
  requirement: !ruby/object:Gem::Requirement
@@ -222,6 +250,7 @@ files:
222
250
  - lib/packwerk/application_load_paths.rb
223
251
  - lib/packwerk/application_validator.rb
224
252
  - lib/packwerk/association_inspector.rb
253
+ - lib/packwerk/cache.rb
225
254
  - lib/packwerk/cli.rb
226
255
  - lib/packwerk/configuration.rb
227
256
  - lib/packwerk/const_node_inspector.rb
@@ -254,6 +283,7 @@ files:
254
283
  - lib/packwerk/parsers.rb
255
284
  - lib/packwerk/parsers/erb.rb
256
285
  - lib/packwerk/parsers/factory.rb
286
+ - lib/packwerk/parsers/parser_interface.rb
257
287
  - lib/packwerk/parsers/ruby.rb
258
288
  - lib/packwerk/reference.rb
259
289
  - lib/packwerk/reference_checking/checkers/checker.rb
@@ -266,6 +296,7 @@ files:
266
296
  - lib/packwerk/run_context.rb
267
297
  - lib/packwerk/sanity_checker.rb
268
298
  - lib/packwerk/spring_command.rb
299
+ - lib/packwerk/unresolved_reference.rb
269
300
  - lib/packwerk/version.rb
270
301
  - lib/packwerk/violation_type.rb
271
302
  - library.yml