packwerk 2.2.2 → 3.0.0

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