packwerk 1.1.1 → 1.1.2

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