packwerk 2.2.2 → 3.0.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/RESOLVING_VIOLATIONS.md +7 -7
  8. data/TROUBLESHOOT.md +1 -23
  9. data/USAGE.md +149 -59
  10. data/dev.yml +1 -1
  11. data/exe/packwerk +1 -0
  12. data/gemfiles/Gemfile-rails-6-1 +1 -1
  13. data/lib/packwerk/application_validator.rb +54 -285
  14. data/lib/packwerk/association_inspector.rb +2 -0
  15. data/lib/packwerk/cache.rb +6 -5
  16. data/lib/packwerk/checker.rb +54 -0
  17. data/lib/packwerk/cli/result.rb +11 -0
  18. data/lib/packwerk/cli.rb +56 -31
  19. data/lib/packwerk/configuration.rb +61 -40
  20. data/lib/packwerk/const_node_inspector.rb +2 -0
  21. data/lib/packwerk/constant_context.rb +8 -0
  22. data/lib/packwerk/constant_discovery.rb +5 -6
  23. data/lib/packwerk/constant_name_inspector.rb +2 -0
  24. data/lib/packwerk/disable_sorbet.rb +41 -0
  25. data/lib/packwerk/extension_loader.rb +24 -0
  26. data/lib/packwerk/file_processor.rb +3 -1
  27. data/lib/packwerk/files_for_processing.rb +25 -12
  28. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  29. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  30. data/lib/packwerk/generators/configuration_file.rb +7 -2
  31. data/lib/packwerk/generators/root_package.rb +5 -1
  32. data/lib/packwerk/generators/templates/package.yml +0 -10
  33. data/lib/packwerk/graph.rb +10 -2
  34. data/lib/packwerk/node.rb +1 -1
  35. data/lib/packwerk/node_helpers.rb +14 -7
  36. data/lib/packwerk/node_processor.rb +2 -0
  37. data/lib/packwerk/node_processor_factory.rb +6 -4
  38. data/lib/packwerk/node_visitor.rb +10 -1
  39. data/lib/packwerk/offense_collection.rb +43 -23
  40. data/lib/packwerk/offenses_formatter.rb +59 -2
  41. data/lib/packwerk/package.rb +7 -35
  42. data/lib/packwerk/package_set.rb +1 -1
  43. data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
  44. data/lib/packwerk/parse_run.rb +29 -36
  45. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  46. data/lib/packwerk/parsers/erb.rb +23 -4
  47. data/lib/packwerk/parsers/factory.rb +11 -2
  48. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  49. data/lib/packwerk/parsers/ruby.rb +13 -3
  50. data/lib/packwerk/parsers.rb +6 -2
  51. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  52. data/lib/packwerk/reference.rb +7 -1
  53. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  54. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  55. data/lib/packwerk/reference_extractor.rb +24 -12
  56. data/lib/packwerk/reference_offense.rb +2 -2
  57. data/lib/packwerk/run_context.rb +7 -10
  58. data/lib/packwerk/spring_command.rb +11 -2
  59. data/lib/packwerk/unresolved_reference.rb +9 -1
  60. data/lib/packwerk/validator/result.rb +18 -0
  61. data/lib/packwerk/validator.rb +90 -0
  62. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  63. data/lib/packwerk/version.rb +1 -1
  64. data/lib/packwerk.rb +64 -26
  65. data/packwerk.gemspec +4 -2
  66. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  67. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  68. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  69. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  70. data/sorbet/rbi/shims/parser.rbi +13 -0
  71. metadata +35 -16
  72. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  73. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  74. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  75. data/lib/packwerk/result.rb +0 -9
  76. data/lib/packwerk/sanity_checker.rb +0 -8
  77. data/lib/packwerk/violation_type.rb +0 -11
  78. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  79. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
@@ -6,7 +6,12 @@ require "yaml"
6
6
 
7
7
  module Packwerk
8
8
  class Configuration
9
+ extend T::Sig
10
+
9
11
  class << self
12
+ extend T::Sig
13
+
14
+ sig { params(path: String).returns(Configuration) }
10
15
  def from_path(path = Dir.pwd)
11
16
  raise ArgumentError, "#{File.expand_path(path)} does not exist" unless File.exist?(path)
12
17
 
@@ -21,6 +26,7 @@ module Packwerk
21
26
 
22
27
  private
23
28
 
29
+ sig { params(path: String).returns(Configuration) }
24
30
  def from_packwerk_config(path)
25
31
  new(
26
32
  YAML.load_file(path) || {},
@@ -30,63 +36,78 @@ module Packwerk
30
36
  end
31
37
 
32
38
  DEFAULT_CONFIG_PATH = "packwerk.yml"
33
- DEFAULT_INCLUDE_GLOBS = ["**/*.{rb,rake,erb}"]
34
- DEFAULT_EXCLUDE_GLOBS = ["{bin,node_modules,script,tmp,vendor}/**/*"]
39
+ DEFAULT_INCLUDE_GLOBS = T.let(["**/*.{rb,rake,erb}"], T::Array[String])
40
+ DEFAULT_EXCLUDE_GLOBS = T.let(["{bin,node_modules,script,tmp,vendor}/**/*"], T::Array[String])
35
41
 
36
- attr_reader(
37
- :include, :exclude, :root_path, :package_paths, :custom_associations, :config_path, :cache_directory
38
- )
42
+ sig { returns(T::Array[String]) }
43
+ attr_reader(:include)
39
44
 
40
- def initialize(configs = {}, config_path: nil)
41
- @include = configs["include"] || DEFAULT_INCLUDE_GLOBS
42
- @exclude = configs["exclude"] || DEFAULT_EXCLUDE_GLOBS
43
- root = config_path ? File.dirname(config_path) : "."
44
- @root_path = File.expand_path(root)
45
- @package_paths = configs["package_paths"] || "**/"
46
- @custom_associations = configs["custom_associations"] || []
47
- @parallel = configs.key?("parallel") ? configs["parallel"] : true
48
- @cache_enabled = configs.key?("cache") ? configs["cache"] : false
49
- @cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
50
- @config_path = config_path
45
+ sig { returns(T::Array[String]) }
46
+ attr_reader(:exclude)
51
47
 
52
- if configs["load_paths"]
53
- warning = <<~WARNING
54
- DEPRECATION WARNING: The 'load_paths' key in `packwerk.yml` is deprecated.
55
- This value is no longer cached, and you can remove the key from `packwerk.yml`.
56
- WARNING
48
+ sig { returns(String) }
49
+ attr_reader(:root_path)
57
50
 
58
- warn(warning)
59
- end
51
+ sig { returns(T.any(String, T::Array[String])) }
52
+ attr_reader(:package_paths)
60
53
 
61
- if configs["inflections_file"]
62
- warning = <<~WARNING
63
- DEPRECATION WARNING: The 'inflections_file' key in `packwerk.yml` is deprecated.
64
- This value is no longer cached, and you can remove the key from `packwerk.yml`.
65
- You can also delete #{configs["inflections_file"]}.
66
- WARNING
54
+ sig { returns(T::Array[Symbol]) }
55
+ attr_reader(:custom_associations)
67
56
 
68
- warn(warning)
69
- end
57
+ sig { returns(T.nilable(String)) }
58
+ attr_reader(:config_path)
59
+
60
+ sig { returns(Pathname) }
61
+ attr_reader(:cache_directory)
70
62
 
71
- inflection_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
72
- if Pathname.new(inflection_file).exist?
73
- warning = <<~WARNING
74
- DEPRECATION WARNING: Inflections YMLs in packwerk are now deprecated.
75
- This value is no longer cached, and you can now delete #{inflection_file}
76
- WARNING
63
+ sig do
64
+ params(
65
+ configs: T::Hash[String, T.untyped],
66
+ config_path: T.nilable(String),
67
+ ).void
68
+ end
69
+ def initialize(configs = {}, config_path: nil)
70
+ @include = T.let(configs["include"] || DEFAULT_INCLUDE_GLOBS, T::Array[String])
71
+ @exclude = T.let(configs["exclude"] || DEFAULT_EXCLUDE_GLOBS, T::Array[String])
72
+ root = config_path ? File.dirname(config_path) : "."
73
+ @root_path = T.let(File.expand_path(root), String)
74
+ @package_paths = T.let(configs["package_paths"] || "**/", T.any(String, T::Array[String]))
75
+ @custom_associations = T.let(configs["custom_associations"] || [], T::Array[Symbol])
76
+ @parallel = T.let(configs.key?("parallel") ? configs["parallel"] : true, T::Boolean)
77
+ @cache_enabled = T.let(configs.key?("cache") ? configs["cache"] : false, T::Boolean)
78
+ @cache_directory = T.let(Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk"), Pathname)
79
+ @config_path = config_path
77
80
 
78
- warn(warning)
81
+ @offenses_formatter_identifier = T.let(
82
+ configs["offenses_formatter"] || Formatters::DefaultOffensesFormatter::IDENTIFIER, String
83
+ )
84
+
85
+ if configs.key?("require")
86
+ configs["require"].each do |require_directive|
87
+ ExtensionLoader.load(require_directive, @root_path)
88
+ end
79
89
  end
80
90
  end
81
91
 
92
+ sig { returns(T::Hash[String, Module]) }
82
93
  def load_paths
83
- @load_paths ||= ApplicationLoadPaths.extract_relevant_paths(@root_path, "test")
94
+ @load_paths ||= T.let(
95
+ RailsLoadPaths.for(@root_path, environment: "test"),
96
+ T.nilable(T::Hash[String, Module]),
97
+ )
84
98
  end
85
99
 
100
+ sig { returns(T::Boolean) }
86
101
  def parallel?
87
102
  @parallel
88
103
  end
89
104
 
105
+ sig { returns(OffensesFormatter) }
106
+ def offenses_formatter
107
+ OffensesFormatter.find(@offenses_formatter_identifier)
108
+ end
109
+
110
+ sig { returns(T::Boolean) }
90
111
  def cache_enabled?
91
112
  @cache_enabled
92
113
  end
@@ -52,4 +52,6 @@ module Packwerk
52
52
  "::" + NodeHelpers.parent_module_name(ancestors: ancestors)
53
53
  end
54
54
  end
55
+
56
+ private_constant :ConstNodeInspector
55
57
  end
@@ -0,0 +1,8 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ extend T::Sig
6
+
7
+ ConstantContext = Struct.new(:name, :location, :package)
8
+ end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "constant_resolver"
@@ -17,8 +17,6 @@ module Packwerk
17
17
  class ConstantDiscovery
18
18
  extend T::Sig
19
19
 
20
- ConstantContext = Struct.new(:name, :location, :package, :public?)
21
-
22
20
  # @param constant_resolver [ConstantResolver]
23
21
  # @param packages [Packwerk::PackageSet]
24
22
  sig do
@@ -50,12 +48,12 @@ module Packwerk
50
48
  # @param const_name [String] The unresolved constant's name.
51
49
  # @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
52
50
  # used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
53
- # @return [Packwerk::ConstantDiscovery::ConstantContext]
51
+ # @return [ConstantContext]
54
52
  sig do
55
53
  params(
56
54
  const_name: String,
57
55
  current_namespace_path: T.nilable(T::Array[String]),
58
- ).returns(T.nilable(ConstantDiscovery::ConstantContext))
56
+ ).returns(T.nilable(ConstantContext))
59
57
  end
60
58
  def context_for(const_name, current_namespace_path: [])
61
59
  begin
@@ -71,8 +69,9 @@ module Packwerk
71
69
  constant.name,
72
70
  constant.location,
73
71
  package,
74
- package.public_path?(constant.location),
75
72
  )
76
73
  end
77
74
  end
75
+
76
+ private_constant :ConstantDiscovery
78
77
  end
@@ -18,4 +18,6 @@ module Packwerk
18
18
  end
19
19
  def constant_name_from_node(node, ancestors:); end
20
20
  end
21
+
22
+ private_constant :ConstantNameInspector
21
23
  end
@@ -0,0 +1,41 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+
9
+ T.singleton_class.prepend(
10
+ Module.new do
11
+ def cast(value, type, checked: true)
12
+ value
13
+ end
14
+
15
+ def let(value, type, checked: true)
16
+ value
17
+ end
18
+
19
+ def must(arg)
20
+ arg
21
+ end
22
+
23
+ def absurd(value)
24
+ value
25
+ end
26
+
27
+ def bind(value, type, checked: true)
28
+ value
29
+ end
30
+ end
31
+ )
32
+ rescue RuntimeError => error
33
+ # From https://github.com/sorbet/sorbet/blob/dcf1b069cfb0d6624c027e45e59f4c6ca33de970/gems/sorbet-runtime/lib/types/private/runtime_levels.rb#L54
34
+ # Sorbet has already evaluated a method call somewhere, so we can't disable it.
35
+ # In this case, we want to log a warning so Packwerk can still be used (but will be slower).
36
+ if /Set the default checked level earlier./.match?(error.message)
37
+ warn("Packwerk couldn't disable Sorbet. Please ensure it isn't being used before Packwerk is loaded.")
38
+ else
39
+ raise error
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ # This class handles loading extensions to packwerk using the `require` directive
6
+ # in the `packwerk.yml` configuration.
7
+ module ExtensionLoader
8
+ class << self
9
+ extend T::Sig
10
+ sig { params(require_directive: String, config_dir_path: String).void }
11
+ def load(require_directive, config_dir_path)
12
+ # We want to transform the require directive to behave differently
13
+ # if it's a specific local file being required versus a gem
14
+ if require_directive.start_with?(".")
15
+ require File.join(config_dir_path, require_directive)
16
+ else
17
+ require require_directive
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ private_constant :ExtensionLoader
24
+ end
@@ -64,7 +64,7 @@ module Packwerk
64
64
  references = []
65
65
 
66
66
  node_processor = @node_processor_factory.for(relative_file: relative_file, node: node)
67
- node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
67
+ node_visitor = NodeVisitor.new(node_processor: node_processor)
68
68
  node_visitor.visit(node, ancestors: [], result: references)
69
69
 
70
70
  references
@@ -82,4 +82,6 @@ module Packwerk
82
82
  @parser_factory.for_path(file_path)
83
83
  end
84
84
  end
85
+
86
+ private_constant :FileProcessor
85
87
  end
@@ -15,10 +15,10 @@ module Packwerk
15
15
  relative_file_paths: T::Array[String],
16
16
  configuration: Configuration,
17
17
  ignore_nested_packages: T::Boolean
18
- ).returns(RelativeFileSet)
18
+ ).returns(FilesForProcessing)
19
19
  end
20
20
  def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
21
- new(relative_file_paths, configuration, ignore_nested_packages).files
21
+ new(relative_file_paths, configuration, ignore_nested_packages)
22
22
  end
23
23
  end
24
24
 
@@ -33,37 +33,48 @@ module Packwerk
33
33
  @relative_file_paths = relative_file_paths
34
34
  @configuration = configuration
35
35
  @ignore_nested_packages = ignore_nested_packages
36
- @custom_files = T.let(nil, T.nilable(RelativeFileSet))
36
+ @specified_files = T.let(nil, T.nilable(RelativeFileSet))
37
+ @files = T.let(nil, T.nilable(RelativeFileSet))
37
38
  end
38
39
 
39
40
  sig { returns(RelativeFileSet) }
40
41
  def files
41
- include_files = if custom_files.empty?
42
+ @files ||= files_for_processing
43
+ end
44
+
45
+ sig { returns(T::Boolean) }
46
+ def files_specified?
47
+ specified_files.any?
48
+ end
49
+
50
+ private
51
+
52
+ sig { returns(RelativeFileSet) }
53
+ def files_for_processing
54
+ all_included_files = if specified_files.empty?
42
55
  configured_included_files
43
56
  else
44
- configured_included_files & custom_files
57
+ configured_included_files & specified_files
45
58
  end
46
59
 
47
- include_files - configured_excluded_files
60
+ all_included_files - configured_excluded_files
48
61
  end
49
62
 
50
- private
51
-
52
63
  sig { returns(RelativeFileSet) }
53
- def custom_files
54
- @custom_files ||= Set.new(
64
+ def specified_files
65
+ @specified_files ||= Set.new(
55
66
  @relative_file_paths.map do |relative_file_path|
56
67
  if File.file?(relative_file_path)
57
68
  relative_file_path
58
69
  else
59
- custom_included_files(relative_file_path)
70
+ specified_included_files(relative_file_path)
60
71
  end
61
72
  end
62
73
  ).flatten
63
74
  end
64
75
 
65
76
  sig { params(relative_file_path: String).returns(RelativeFileSet) }
66
- def custom_included_files(relative_file_path)
77
+ def specified_included_files(relative_file_path)
67
78
  # Note, assuming include globs are always relative paths
68
79
  relative_includes = @configuration.include
69
80
  relative_files = Dir.glob([File.join(relative_file_path, "**", "*")]).select do |relative_path|
@@ -100,4 +111,6 @@ module Packwerk
100
111
  Set.new(relative_globs.flat_map { |glob| Dir[glob] })
101
112
  end
102
113
  end
114
+
115
+ private_constant :FilesForProcessing
103
116
  end
@@ -0,0 +1,77 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Formatters
6
+ class DefaultOffensesFormatter
7
+ include OffensesFormatter
8
+
9
+ IDENTIFIER = T.let("default", String)
10
+
11
+ extend T::Sig
12
+
13
+ sig { override.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
14
+ def show_offenses(offenses)
15
+ return "No offenses detected" if offenses.empty?
16
+
17
+ <<~EOS
18
+ #{offenses_list(offenses)}
19
+ #{offenses_summary(offenses)}
20
+ EOS
21
+ end
22
+
23
+ sig { override.params(offense_collection: OffenseCollection, fileset: T::Set[String]).returns(String) }
24
+ def show_stale_violations(offense_collection, fileset)
25
+ if offense_collection.stale_violations?(fileset)
26
+ "There were stale violations found, please run `packwerk update-todo`"
27
+ else
28
+ "No stale violations detected"
29
+ end
30
+ end
31
+
32
+ sig { override.returns(String) }
33
+ def identifier
34
+ IDENTIFIER
35
+ end
36
+
37
+ sig { override.params(strict_mode_violations: T::Array[ReferenceOffense]).returns(String) }
38
+ def show_strict_mode_violations(strict_mode_violations)
39
+ if strict_mode_violations.any?
40
+ strict_mode_violations.compact.map { |offense| format_strict_mode_violation(offense) }.join("\n")
41
+ else
42
+ ""
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ sig { returns(OutputStyle) }
49
+ def style
50
+ @style ||= T.let(Packwerk::OutputStyles::Coloured.new, T.nilable(Packwerk::OutputStyles::Coloured))
51
+ end
52
+
53
+ sig { params(offense: ReferenceOffense).returns(String) }
54
+ def format_strict_mode_violation(offense)
55
+ reference_package = offense.reference.package
56
+ defining_package = offense.reference.constant.package
57
+ "#{reference_package} cannot have #{offense.violation_type} violations on #{defining_package} "\
58
+ "because strict mode is enabled for #{offense.violation_type} violations in "\
59
+ "the enforcing package's package.yml"
60
+ end
61
+
62
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
63
+ def offenses_list(offenses)
64
+ offenses
65
+ .compact
66
+ .map { |offense| offense.to_s(style) }
67
+ .join("\n")
68
+ end
69
+
70
+ sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
71
+ def offenses_summary(offenses)
72
+ offenses_string = "offense".pluralize(offenses.length)
73
+ "#{offenses.length} #{offenses_string} detected"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "benchmark"
@@ -14,37 +14,56 @@ module Packwerk
14
14
  @style = style
15
15
  end
16
16
 
17
- def started(target_files)
18
- files_size = target_files.size
19
- files_string = "file".pluralize(files_size)
20
- @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
21
- end
22
-
17
+ sig { params(block: T.proc.void).void }
23
18
  def started_validation(&block)
24
- @out.puts("📦 Packwerk is running validation...")
19
+ start_validation
25
20
 
26
21
  execution_time = Benchmark.realtime(&block)
27
22
  finished(execution_time)
23
+ end
28
24
 
29
- @out.puts("✅ Packages are valid. Use `packwerk check` to run static checks.")
25
+ sig { params(target_files: FilesForProcessing::RelativeFileSet, block: T.proc.void).void }
26
+ def started_inspection(target_files, &block)
27
+ start_inspection(target_files)
28
+
29
+ execution_time = Benchmark.realtime(&block)
30
+ finished(execution_time)
30
31
  end
31
32
 
33
+ sig { void }
32
34
  def mark_as_inspected
33
35
  @out.print(".")
34
36
  end
35
37
 
38
+ sig { void }
36
39
  def mark_as_failed
37
40
  @out.print("#{@style.error}E#{@style.reset}")
38
41
  end
39
42
 
43
+ sig { void }
44
+ def interrupted
45
+ @out.puts
46
+ @out.puts("Manually interrupted. Violations caught so far are listed below:")
47
+ end
48
+
49
+ private
50
+
51
+ sig { params(execution_time: Float).void }
40
52
  def finished(execution_time)
41
53
  @out.puts
42
54
  @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
43
55
  end
44
56
 
45
- def interrupted
46
- @out.puts
47
- @out.puts("Manually interrupted. Violations caught so far are listed below:")
57
+ sig { void }
58
+ def start_validation
59
+ @out.puts("📦 Packwerk is running validation...")
60
+ end
61
+
62
+ sig { params(target_files: FilesForProcessing::RelativeFileSet).void }
63
+ def start_inspection(target_files)
64
+ files_size = target_files.size
65
+ files_string = "file".pluralize(files_size)
66
+ @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
48
67
  end
49
68
  end
50
69
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "erb"
@@ -11,6 +11,9 @@ module Packwerk
11
11
  CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
12
12
 
13
13
  class << self
14
+ extend T::Sig
15
+
16
+ sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
14
17
  def generate(root:, out:)
15
18
  new(root: root, out: out).generate
16
19
  end
@@ -25,7 +28,7 @@ module Packwerk
25
28
  sig { returns(T::Boolean) }
26
29
  def generate
27
30
  @out.puts("📦 Generating Packwerk configuration file...")
28
- default_config_path = File.join(@root, ::Packwerk::Configuration::DEFAULT_CONFIG_PATH)
31
+ default_config_path = File.join(@root, Configuration::DEFAULT_CONFIG_PATH)
29
32
 
30
33
  if File.exist?(default_config_path)
31
34
  @out.puts("⚠️ Packwerk configuration file already exists.")
@@ -40,10 +43,12 @@ module Packwerk
40
43
 
41
44
  private
42
45
 
46
+ sig { returns(String) }
43
47
  def render
44
48
  ERB.new(template, trim_mode: "-").result(binding)
45
49
  end
46
50
 
51
+ sig { returns(String) }
47
52
  def template
48
53
  template_file_path = File.join(__dir__, CONFIGURATION_TEMPLATE_FILE_PATH)
49
54
  File.read(template_file_path)
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -7,11 +7,15 @@ module Packwerk
7
7
  extend T::Sig
8
8
 
9
9
  class << self
10
+ extend T::Sig
11
+
12
+ sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
10
13
  def generate(root:, out:)
11
14
  new(root: root, out: out).generate
12
15
  end
13
16
  end
14
17
 
18
+ sig { params(root: String, out: T.any(IO, StringIO)).void }
15
19
  def initialize(root:, out: $stdout)
16
20
  @root = root
17
21
  @out = out
@@ -5,16 +5,6 @@
5
5
  # Turn on dependency checks for this package
6
6
  enforce_dependencies: true
7
7
 
8
- # Turn on privacy checks for this package
9
- # enforcing privacy is often not useful for the root package, because it would require defining a public interface
10
- # for something that should only be a thin wrapper in the first place.
11
- # We recommend enabling this for any new packages you create to aid with encapsulation.
12
- enforce_privacy: false
13
-
14
- # By default the public path will be app/public/, however this may not suit all applications' architecture so
15
- # this allows you to modify what your package's public path is.
16
- # public_path: app/public/
17
-
18
8
  # A list of this package's dependencies
19
9
  # Note that packages in this list require their own `package.yml` file
20
10
  # dependencies:
@@ -4,8 +4,14 @@
4
4
  module Packwerk
5
5
  # A general implementation of a graph data structure with the ability to check for - and list - cycles.
6
6
  class Graph
7
- # @param [Array<Array>] edges The edges of the graph; An edge being represented as an Array of two nodes.
8
- def initialize(*edges)
7
+ extend T::Sig
8
+ sig do
9
+ params(
10
+ # The edges of the graph; An edge being represented as an Array of two nodes.
11
+ edges: T::Array[T::Array[T.any(String, Integer, NilClass)]]
12
+ ).void
13
+ end
14
+ def initialize(edges)
9
15
  @edges = edges.uniq
10
16
  @cycles = Set.new
11
17
  process
@@ -73,4 +79,6 @@ module Packwerk
73
79
  @cycles << cycle
74
80
  end
75
81
  end
82
+
83
+ private_constant :Graph
76
84
  end
data/lib/packwerk/node.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- class Node
5
+ module Node
6
6
  Location = Struct.new(:line, :column)
7
7
  end
8
8
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser"
@@ -59,11 +59,16 @@ module Packwerk
59
59
  end
60
60
  end
61
61
 
62
- sig { params(node: AST::Node).returns(T.untyped) }
63
- def each_child(node)
64
- if block_given?
62
+ sig do
63
+ params(
64
+ node: AST::Node,
65
+ block: T.nilable(T.proc.params(arg0: Parser::AST::Node).void),
66
+ ).returns(T::Enumerable[AST::Node])
67
+ end
68
+ def each_child(node, &block)
69
+ if block
65
70
  node.children.each do |child|
66
- yield child if child.is_a?(Parser::AST::Node)
71
+ yield(child) if child.is_a?(Parser::AST::Node)
67
72
  end
68
73
  else
69
74
  enum_for(:each_child, node)
@@ -181,9 +186,9 @@ module Packwerk
181
186
  end
182
187
  end
183
188
 
184
- sig { params(node: Parser::AST::Node).returns(T.nilable(Node::Location)) }
189
+ sig { params(node: AST::Node).returns(T.nilable(Node::Location)) }
185
190
  def name_location(node)
186
- location = node.location
191
+ location = T.cast(node, Parser::AST::Node).location
187
192
 
188
193
  if location.respond_to?(:name)
189
194
  name = location.name
@@ -332,4 +337,6 @@ module Packwerk
332
337
  end
333
338
  end
334
339
  end
340
+
341
+ private_constant :NodeHelpers
335
342
  end