packwerk 1.1.1 → 1.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da27cf2b0d19f98831fc32b685ab58ab5a1abeacad532eff698787ef0dd8892d
4
- data.tar.gz: 2b7d574417307d4b2d4aa0ae8e85665e5a701bc304307abd0302d02ebb4b6060
3
+ metadata.gz: cd008f86d84fd0224aac9d8f92bc104123f23869e9381c947b3e51864523c947
4
+ data.tar.gz: debf7f89ce8f8419a02f2af84c3f1900c7bc2f2ac79cce535284e3e697133d74
5
5
  SHA512:
6
- metadata.gz: 730635a8851f68e9b43b25454f56bfe7296a42e76da335ab4b14b5fb2562a09c758107b9b25ab8d8455bd722e28079023568d84a6621d30b77b5d709c4dd5bd9
7
- data.tar.gz: 97e9941ee9756c33dc952dca17ca6fe255ad8e2605584f3715616ff8e4a2a1caf380a079c0954435fff54ff83a7a92000176d9d6aa30858ef67f263958d2d105
6
+ metadata.gz: 2bf12776a5e5cd4f3f9d06f3bbabb3d9b0c6d873325dba9a7bc43dc49ffedbc119678b77a2087cface6428bf564e037b75386abb8fca2d06a313e617461db53a
7
+ data.tar.gz: d6ca7a42dea1c5338c40e89b6fe6b14baa43372cf44e22af3f19763ee1048c03629e34f4466b632e7f5b6d82932b8e05b2d02d5e26ebf91852334b9f2ca6d5b2
@@ -85,7 +85,7 @@ GIT
85
85
  PATH
86
86
  remote: .
87
87
  specs:
88
- packwerk (1.1.1)
88
+ packwerk (1.1.2)
89
89
  activesupport (>= 5.2)
90
90
  ast
91
91
  better_html
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applications.
4
4
 
5
5
  Packwerk can be used to:
6
- * Combine group of files into packages
6
+ * Combine groups of files into packages
7
7
  * Define package-level constant visibility (i.e. have publicly accessible constants)
8
8
  * Enforce privacy (inbound) and dependency (outbound) boundaries between packages
9
9
  * Help existing codebases to become more modular without obstructing development
@@ -3,4 +3,4 @@
3
3
 
4
4
  require "packwerk"
5
5
 
6
- Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured).run(ARGV.dup)
6
+ Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured.new).run(ARGV.dup)
@@ -27,7 +27,9 @@ require "packwerk/graph"
27
27
  require "packwerk/inflector"
28
28
  require "packwerk/node_processor"
29
29
  require "packwerk/node_visitor"
30
- require "packwerk/output_styles"
30
+ require "packwerk/output_style"
31
+ require "packwerk/output_styles/plain"
32
+ require "packwerk/output_styles/coloured"
31
33
  require "packwerk/package"
32
34
  require "packwerk/package_set"
33
35
  require "packwerk/parsers"
@@ -28,8 +28,9 @@ module Packwerk
28
28
  .select { |railtie| railtie.is_a?(Rails::Engine) }
29
29
  .push(Rails.application)
30
30
  .flat_map do |engine|
31
- (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths).uniq
32
- end
31
+ paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
32
+ paths.map(&:to_s).uniq
33
+ end
33
34
  end
34
35
 
35
36
  sig do
@@ -35,16 +35,7 @@ module Packwerk
35
35
  check_root_package_exists,
36
36
  ]
37
37
 
38
- results.reject!(&:ok?)
39
-
40
- if results.empty?
41
- Result.new(true)
42
- else
43
- Result.new(
44
- false,
45
- results.map(&:error_value).join("\n===\n")
46
- )
47
- end
38
+ merge_results(results)
48
39
  end
49
40
 
50
41
  def check_autoload_path_cache
@@ -65,51 +56,37 @@ module Packwerk
65
56
  def check_package_manifests_for_privacy
66
57
  privacy_settings = package_manifests_settings_for("enforce_privacy")
67
58
 
68
- autoload_paths = @configuration.load_paths
69
-
70
59
  resolver = ConstantResolver.new(
71
60
  root_path: @configuration.root_path,
72
- load_paths: autoload_paths
61
+ load_paths: @configuration.load_paths
73
62
  )
74
63
 
75
- errors = []
64
+ results = []
76
65
 
77
- privacy_settings.each do |filepath, setting|
66
+ privacy_settings.each do |config_file_path, setting|
78
67
  next unless setting.is_a?(Array)
68
+ constants = setting
79
69
 
80
- setting.each do |constant|
81
- # make sure the constant can be loaded
82
- constant.constantize # rubocop:disable Sorbet/ConstantsFromStrings
83
- context = resolver.resolve(constant)
70
+ assert_constants_can_be_loaded(constants)
84
71
 
85
- unless context
86
- errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
87
- next
88
- end
89
-
90
- expected_filename = constant.underscore + ".rb"
72
+ constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
91
73
 
92
- # We don't support all custom inflections yet, so we may accidentally resolve constants to the
93
- # file that defines their parent namespace. This restriction makes sure that we don't.
94
- next if context.location.end_with?(expected_filename)
95
-
96
- errors << "Explicitly private constants need to have their own files.\n"\
97
- "#{constant}, listed in #{filepath.inspect}, was resolved to #{context.location.inspect}.\n"\
98
- "It should be in something like #{expected_filename.inspect}"
74
+ constant_locations.each do |name, location|
75
+ results << if location
76
+ check_private_constant_location(name, location, config_file_path)
77
+ else
78
+ private_constant_unresolvable(name, config_file_path)
79
+ end
99
80
  end
100
81
  end
101
82
 
102
- if errors.empty?
103
- Result.new(true)
104
- else
105
- Result.new(false, errors.join("\n---\n"))
106
- end
83
+ merge_results(results, separator: "\n---\n")
107
84
  end
108
85
 
109
86
  def check_package_manifest_syntax
110
87
  errors = []
111
88
 
112
- package_manifests(package_glob).each do |f|
89
+ package_manifests.each do |f|
113
90
  hash = YAML.load_file(f)
114
91
  next unless hash
115
92
 
@@ -193,24 +170,16 @@ module Packwerk
193
170
  end
194
171
  end
195
172
 
196
- errors = results.reject(&:ok?)
197
-
198
- if errors.empty?
199
- Result.new(true)
200
- else
201
- Result.new(
202
- false,
203
- "Inflections specified in #{inflections_file} don't line up with application!\n" +
204
- errors.map(&:error_value).join("\n")
205
- )
206
- end
173
+ merge_results(
174
+ results,
175
+ separator: "\n",
176
+ errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
177
+ )
207
178
  end
208
179
 
209
180
  def check_acyclic_graph
210
- packages = Packwerk::PackageSet.load_all_from(@configuration.root_path)
211
-
212
- edges = packages.flat_map do |package|
213
- package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
181
+ edges = package_set.flat_map do |package|
182
+ package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
214
183
  end
215
184
  dependency_graph = Packwerk::Graph.new(*edges)
216
185
 
@@ -316,8 +285,7 @@ module Packwerk
316
285
  private
317
286
 
318
287
  def package_manifests_settings_for(setting)
319
- package_manifests(package_glob)
320
- .map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
288
+ package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
321
289
  end
322
290
 
323
291
  def format_yaml_strings(list)
@@ -328,13 +296,17 @@ module Packwerk
328
296
  @configuration.package_paths || "**"
329
297
  end
330
298
 
331
- def package_manifests(glob_pattern)
299
+ def package_manifests(glob_pattern = package_glob)
332
300
  PackageSet.package_paths(@configuration.root_path, glob_pattern)
333
301
  .map { |f| File.realpath(f) }
334
302
  end
335
303
 
336
304
  def relative_paths(paths)
337
- paths.map { |path| Pathname.new(path).relative_path_from(@configuration.root_path) }
305
+ paths.map { |path| relative_path(path) }
306
+ end
307
+
308
+ def relative_path(path)
309
+ Pathname.new(path).relative_path_from(@configuration.root_path)
338
310
  end
339
311
 
340
312
  def invalid_package_path?(path)
@@ -344,5 +316,54 @@ module Packwerk
344
316
  package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
345
317
  !File.file?(package_path)
346
318
  end
319
+
320
+ def assert_constants_can_be_loaded(constants)
321
+ constants.each(&:constantize)
322
+ nil
323
+ end
324
+
325
+ def private_constant_unresolvable(name, config_file_path)
326
+ explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
327
+
328
+ Result.new(
329
+ false,
330
+ "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
331
+ "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
332
+ "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
333
+ "private. Add a #{explicit_filepath} file to explicitly define the constant."
334
+ )
335
+ end
336
+
337
+ def check_private_constant_location(name, location, config_file_path)
338
+ declared_package = package_set.package_from_path(relative_path(config_file_path))
339
+ constant_package = package_set.package_from_path(location)
340
+
341
+ if constant_package == declared_package
342
+ Result.new(true)
343
+ else
344
+ Result.new(
345
+ false,
346
+ "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
347
+ "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
348
+ )
349
+ end
350
+ end
351
+
352
+ def package_set
353
+ @package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
354
+ end
355
+
356
+ def merge_results(results, separator: "\n===\n", errors_headline: "")
357
+ results.reject!(&:ok?)
358
+
359
+ if results.empty?
360
+ Result.new(true)
361
+ else
362
+ Result.new(
363
+ false,
364
+ errors_headline + results.map(&:error_value).join(separator)
365
+ )
366
+ end
367
+ end
347
368
  end
348
369
  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"
@@ -7,20 +7,32 @@ require "packwerk/node"
7
7
  module Packwerk
8
8
  # Extracts the implicit constant reference from an active record association
9
9
  class AssociationInspector
10
+ extend T::Sig
10
11
  include ConstantNameInspector
11
12
 
12
- RAILS_ASSOCIATIONS = %i(
13
- belongs_to
14
- has_many
15
- has_one
16
- has_and_belongs_to_many
17
- ).to_set
13
+ CustomAssociations = T.type_alias { T.any(T::Array[Symbol], T::Set[Symbol]) }
18
14
 
15
+ RAILS_ASSOCIATIONS = T.let(
16
+ %i(
17
+ belongs_to
18
+ has_many
19
+ has_one
20
+ has_and_belongs_to_many
21
+ ).to_set,
22
+ CustomAssociations
23
+ )
24
+
25
+ sig { params(inflector: Inflector, custom_associations: CustomAssociations).void }
19
26
  def initialize(inflector:, custom_associations: Set.new)
20
27
  @inflector = inflector
21
- @associations = RAILS_ASSOCIATIONS + custom_associations
28
+ @associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
22
29
  end
23
30
 
31
+ sig do
32
+ override
33
+ .params(node: AST::Node, ancestors: T::Array[AST::Node])
34
+ .returns(T.nilable(String))
35
+ end
24
36
  def constant_name_from_node(node, ancestors:)
25
37
  return unless Node.method_call?(node)
26
38
  return unless association?(node)
@@ -38,11 +50,13 @@ module Packwerk
38
50
 
39
51
  private
40
52
 
53
+ sig { params(node: AST::Node).returns(T::Boolean) }
41
54
  def association?(node)
42
55
  method_name = Node.method_name(node)
43
56
  @associations.include?(method_name)
44
57
  end
45
58
 
59
+ sig { params(arguments: T::Array[AST::Node]).returns(T.nilable(AST::Node)) }
46
60
  def custom_class_name(arguments)
47
61
  association_options = arguments.detect { |n| Node.hash?(n) }
48
62
  return unless association_options
@@ -50,6 +64,7 @@ module Packwerk
50
64
  Node.value_from_hash(association_options, :class_name)
51
65
  end
52
66
 
67
+ sig { params(arguments: T::Array[AST::Node]).returns(T.any(T.nilable(Symbol), T.nilable(String))) }
53
68
  def association_name(arguments)
54
69
  return unless Node.symbol?(arguments[0])
55
70
 
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -15,9 +15,15 @@ module Packwerk
15
15
  include ReferenceLister
16
16
  abstract!
17
17
 
18
+ sig do
19
+ params(
20
+ root_path: String,
21
+ deprecated_references: T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences]
22
+ ).void
23
+ end
18
24
  def initialize(root_path, deprecated_references = {})
19
25
  @root_path = root_path
20
- @deprecated_references = T.let(deprecated_references, T::Hash[String, Packwerk::DeprecatedReferences])
26
+ @deprecated_references = T.let(deprecated_references, T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences])
21
27
  end
22
28
 
23
29
  sig do
@@ -33,6 +39,7 @@ module Packwerk
33
39
 
34
40
  private
35
41
 
42
+ sig { params(package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
36
43
  def deprecated_references_for(package)
37
44
  @deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
38
45
  package,
@@ -40,6 +47,7 @@ module Packwerk
40
47
  )
41
48
  end
42
49
 
50
+ sig { params(package: Packwerk::Package).returns(String) }
43
51
  def deprecated_references_file_for(package)
44
52
  File.join(@root_path, package.name, "deprecated_references.yml")
45
53
  end
@@ -11,6 +11,9 @@ module Packwerk
11
11
 
12
12
  interface!
13
13
 
14
+ sig { returns(ViolationType).abstract }
15
+ def violation_type; end
16
+
14
17
  sig { params(reference: Reference, reference_lister: ReferenceLister).returns(T::Boolean).abstract }
15
18
  def invalid_reference?(reference, reference_lister); end
16
19
 
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -10,9 +10,10 @@ module Packwerk
10
10
  extend T::Sig
11
11
  include ReferenceLister
12
12
 
13
+ sig { params(root_path: String).void }
13
14
  def initialize(root_path)
14
15
  @root_path = root_path
15
- @deprecated_references = {}
16
+ @deprecated_references = T.let({}, T::Hash[Packwerk::Package, Packwerk::DeprecatedReferences])
16
17
  end
17
18
 
18
19
  sig do
@@ -26,6 +27,7 @@ module Packwerk
26
27
 
27
28
  private
28
29
 
30
+ sig { params(source_package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
29
31
  def deprecated_references_for(source_package)
30
32
  @deprecated_references[source_package] ||= Packwerk::DeprecatedReferences.new(
31
33
  source_package,
@@ -33,6 +35,7 @@ module Packwerk
33
35
  )
34
36
  end
35
37
 
38
+ sig { params(package: Packwerk::Package).returns(String) }
36
39
  def deprecated_references_file_for(package)
37
40
  File.join(@root_path, package.name, "deprecated_references.yml")
38
41
  end
@@ -10,7 +10,8 @@ require "packwerk/files_for_processing"
10
10
  require "packwerk/formatters/offenses_formatter"
11
11
  require "packwerk/formatters/progress_formatter"
12
12
  require "packwerk/inflector"
13
- require "packwerk/output_styles"
13
+ require "packwerk/output_style"
14
+ require "packwerk/output_styles/plain"
14
15
  require "packwerk/run_context"
15
16
  require "packwerk/updating_deprecated_references"
16
17
  require "packwerk/checking_deprecated_references"
@@ -23,7 +24,16 @@ module Packwerk
23
24
  extend T::Sig
24
25
  include OffenseProgressMarker
25
26
 
26
- def initialize(run_context: nil, configuration: nil, out: $stdout, err_out: $stderr, style: OutputStyles::Plain)
27
+ sig do
28
+ params(
29
+ run_context: T.nilable(Packwerk::RunContext),
30
+ configuration: T.nilable(Configuration),
31
+ out: T.any(StringIO, IO),
32
+ err_out: T.any(StringIO, IO),
33
+ style: Packwerk::OutputStyle
34
+ ).void
35
+ end
36
+ def initialize(run_context: nil, configuration: nil, out: $stdout, err_out: $stderr, style: OutputStyles::Plain.new)
27
37
  @out = out
28
38
  @err_out = err_out
29
39
  @style = style
@@ -49,7 +49,7 @@ module Packwerk
49
49
  sig { returns(Result) }
50
50
  def calculate_result
51
51
  result_status = !reference_lister.stale_violations?
52
- message = "There were stale violations found, please run `packwerk update`"
52
+ message = "There were stale violations found, please run `packwerk update-deprecations`"
53
53
  if result_status
54
54
  message = "No stale violations detected"
55
55
  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,15 +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
18
  return nil unless Node.constant?(node)
13
19
  parent = ancestors.first
14
20
  return nil unless root_constant?(parent)
15
21
 
16
22
  if parent && constant_in_module_or_class_definition?(node, parent: parent)
17
- fully_qualify_constant(node, ancestors: ancestors)
23
+ fully_qualify_constant(ancestors)
18
24
  else
19
25
  begin
20
26
  Node.constant_name(node)
@@ -28,27 +34,22 @@ module Packwerk
28
34
 
29
35
  # Only process the root `const` node for namespaced constant references. For example, in the
30
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) }
31
38
  def root_constant?(parent)
32
39
  !(parent && Node.constant?(parent))
33
40
  end
34
41
 
42
+ sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
35
43
  def constant_in_module_or_class_definition?(node, parent:)
36
44
  parent_name = Node.module_name_from_definition(parent)
37
45
  parent_name && parent_name == Node.constant_name(node)
38
46
  end
39
47
 
40
- def fully_qualify_constant(node, ancestors:)
48
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
49
+ def fully_qualify_constant(ancestors)
41
50
  # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
42
51
  # 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("::")
52
+ "::" + Node.parent_module_name(ancestors: ancestors)
52
53
  end
53
54
  end
54
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 " \
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "yaml"
6
- require "sorbet-runtime"
7
6
 
8
7
  require "packwerk/reference"
9
8
  require "packwerk/reference_lister"
@@ -14,6 +13,7 @@ module Packwerk
14
13
  extend T::Sig
15
14
  include ReferenceLister
16
15
 
16
+ sig { params(package: Packwerk::Package, filepath: String).void }
17
17
  def initialize(package, filepath)
18
18
  @package = package
19
19
  @filepath = filepath
@@ -35,6 +35,7 @@ module Packwerk
35
35
  violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
36
36
  end
37
37
 
38
+ sig { params(reference: Packwerk::Reference, violation_type: String).void }
38
39
  def add_entries(reference, violation_type)
39
40
  package_violations = @new_entries.fetch(reference.constant.package.name, {})
40
41
  entries_for_file = package_violations[reference.constant.name] ||= {}
@@ -66,6 +67,7 @@ module Packwerk
66
67
  end
67
68
  end
68
69
 
70
+ sig { void }
69
71
  def dump
70
72
  if @new_entries.empty?
71
73
  File.delete(@filepath) if File.exist?(@filepath)
@@ -88,6 +90,7 @@ module Packwerk
88
90
 
89
91
  private
90
92
 
93
+ sig { returns(Hash) }
91
94
  def prepare_entries_for_dump
92
95
  @new_entries.each do |package_name, package_violations|
93
96
  package_violations.each do |_, entries_for_file|
@@ -100,6 +103,7 @@ module Packwerk
100
103
  @new_entries = @new_entries.sort.to_h
101
104
  end
102
105
 
106
+ sig { returns(Hash) }
103
107
  def deprecated_references
104
108
  @deprecated_references ||= if File.exist?(@filepath)
105
109
  YAML.load_file(@filepath) || {}
@@ -5,17 +5,16 @@ require "benchmark"
5
5
  require "sorbet-runtime"
6
6
 
7
7
  require "packwerk/inflector"
8
- require "packwerk/output_styles"
8
+ require "packwerk/output_style"
9
+ require "packwerk/output_styles/plain"
9
10
 
10
11
  module Packwerk
11
12
  module Formatters
12
13
  class OffensesFormatter
13
14
  extend T::Sig
14
15
 
15
- sig do
16
- params(style: T.any(T.class_of(OutputStyles::Plain), T.class_of(OutputStyles::Coloured))).void
17
- end
18
- def initialize(style: OutputStyles::Plain)
16
+ sig { params(style: OutputStyle).void }
17
+ def initialize(style: OutputStyles::Plain.new)
19
18
  @style = style
20
19
  end
21
20
 
@@ -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
@@ -9,6 +9,8 @@ module Packwerk
9
9
  Location = Struct.new(:line, :column)
10
10
 
11
11
  class << self
12
+ extend T::Sig
13
+
12
14
  def class_or_module_name(class_or_module_node)
13
15
  case type_of(class_or_module_node)
14
16
  when CLASS, MODULE
@@ -178,6 +180,7 @@ module Packwerk
178
180
  class_node.children[1]
179
181
  end
180
182
 
183
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
181
184
  def parent_module_name(ancestors:)
182
185
  definitions = ancestors
183
186
  .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
@@ -4,7 +4,8 @@
4
4
  require "parser/source/map"
5
5
  require "sorbet-runtime"
6
6
 
7
- require "packwerk/output_styles"
7
+ require "packwerk/output_style"
8
+ require "packwerk/output_styles/plain"
8
9
 
9
10
  module Packwerk
10
11
  class Offense
@@ -23,11 +24,8 @@ module Packwerk
23
24
  @message = message
24
25
  end
25
26
 
26
- sig do
27
- params(style: T.any(T.class_of(OutputStyles::Plain), T.class_of(OutputStyles::Coloured)))
28
- .returns(String)
29
- end
30
- def to_s(style = OutputStyles::Plain)
27
+ sig { params(style: OutputStyle).returns(String) }
28
+ def to_s(style = OutputStyles::Plain.new)
31
29
  if location
32
30
  <<~EOS
33
31
  #{style.filename}#{file}#{style.reset}:#{location.line}:#{location.column}
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyle
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ sig { abstract.returns(String) }
12
+ def reset; end
13
+
14
+ sig { abstract.returns(String) }
15
+ def filename; end
16
+
17
+ sig { abstract.returns(String) }
18
+ def error; end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyles
6
+ # See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
7
+ class Coloured
8
+ extend T::Sig
9
+ include OutputStyle
10
+
11
+ sig { override.returns(String) }
12
+ def reset
13
+ "\033[m"
14
+ end
15
+
16
+ sig { override.returns(String) }
17
+ def filename
18
+ # 36 is foreground cyan
19
+ "\033[36m"
20
+ end
21
+
22
+ sig { override.returns(String) }
23
+ def error
24
+ # 31 is foreground red
25
+ "\033[31m"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyles
6
+ class Plain
7
+ extend T::Sig
8
+ include OutputStyle
9
+
10
+ sig { override.returns(String) }
11
+ def reset
12
+ ""
13
+ end
14
+
15
+ sig { override.returns(String) }
16
+ def filename
17
+ ""
18
+ end
19
+
20
+ sig { override.returns(String) }
21
+ def error
22
+ ""
23
+ end
24
+ end
25
+ end
26
+ end
@@ -60,7 +60,8 @@ module Packwerk
60
60
  end
61
61
 
62
62
  def package_from_path(file_path)
63
- @packages.values.find { |package| package.package_path?(file_path) }
63
+ path_string = file_path.to_s
64
+ @packages.values.find { |package| package.package_path?(path_string) }
64
65
  end
65
66
  end
66
67
  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,26 +6,34 @@ require "packwerk/checker"
6
6
 
7
7
  module Packwerk
8
8
  class PrivacyChecker
9
+ extend T::Sig
9
10
  include Checker
10
11
 
12
+ sig { override.returns(Packwerk::ViolationType) }
11
13
  def violation_type
12
14
  ViolationType::Privacy
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 if reference.constant.public?
23
+ return false if reference.constant.public?
17
24
 
18
25
  privacy_option = reference.constant.package.enforce_privacy
19
- return if enforcement_disabled?(privacy_option)
26
+ return false if enforcement_disabled?(privacy_option)
20
27
 
21
- return unless privacy_option == true ||
28
+ return false unless privacy_option == true ||
22
29
  explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_option)
23
30
 
24
- return if reference_lister.listed?(reference, violation_type: violation_type)
31
+ return false if reference_lister.listed?(reference, violation_type: violation_type)
25
32
 
26
33
  true
27
34
  end
28
35
 
36
+ sig { override.params(reference: Packwerk::Reference).returns(String) }
29
37
  def message_for(reference)
30
38
  source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
31
39
  "Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
@@ -35,12 +43,22 @@ module Packwerk
35
43
 
36
44
  private
37
45
 
46
+ sig do
47
+ params(
48
+ constant: ConstantDiscovery::ConstantContext,
49
+ explicitly_private_constants: T::Array[String]
50
+ ).returns(T::Boolean)
51
+ end
38
52
  def explicitly_private_constant?(constant, explicitly_private_constants:)
39
53
  explicitly_private_constants.include?(constant.name) ||
40
54
  # nested constants
41
55
  explicitly_private_constants.any? { |epc| constant.name.start_with?(epc + "::") }
42
56
  end
43
57
 
58
+ sig do
59
+ params(privacy_option: T.nilable(T.any(T::Boolean, T::Array[String])))
60
+ .returns(T::Boolean)
61
+ end
44
62
  def enforcement_disabled?(privacy_option)
45
63
  [false, nil].include?(privacy_option)
46
64
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "1.1.1"
5
+ VERSION = "1.1.2"
6
6
  end
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: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-09 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -223,7 +223,9 @@ files:
223
223
  - lib/packwerk/node_processor_factory.rb
224
224
  - lib/packwerk/node_visitor.rb
225
225
  - lib/packwerk/offense.rb
226
- - lib/packwerk/output_styles.rb
226
+ - lib/packwerk/output_style.rb
227
+ - lib/packwerk/output_styles/coloured.rb
228
+ - lib/packwerk/output_styles/plain.rb
227
229
  - lib/packwerk/package.rb
228
230
  - lib/packwerk/package_set.rb
229
231
  - lib/packwerk/parsed_constant_definitions.rb
@@ -1,41 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module Packwerk
5
- module OutputStyles
6
- class Plain
7
- class << self
8
- def reset
9
- ""
10
- end
11
-
12
- def filename
13
- ""
14
- end
15
-
16
- def error
17
- ""
18
- end
19
- end
20
- end
21
-
22
- # See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
23
- class Coloured
24
- class << self
25
- def reset
26
- "\033[m"
27
- end
28
-
29
- def filename
30
- # 36 is foreground cyan
31
- "\033[36m"
32
- end
33
-
34
- def error
35
- # 31 is foreground red
36
- "\033[31m"
37
- end
38
- end
39
- end
40
- end
41
- end