packwerk 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -3
  3. data/Gemfile.lock +11 -8
  4. data/README.md +5 -4
  5. data/TROUBLESHOOT.md +3 -3
  6. data/USAGE.md +37 -22
  7. data/bin/m +29 -0
  8. data/bin/rake +29 -0
  9. data/bin/rubocop +29 -0
  10. data/bin/srb +29 -0
  11. data/bin/tapioca +29 -0
  12. data/dev.yml +6 -6
  13. data/exe/packwerk +7 -1
  14. data/lib/packwerk/application_load_paths.rb +19 -8
  15. data/lib/packwerk/application_validator.rb +28 -22
  16. data/lib/packwerk/cli.rb +31 -34
  17. data/lib/packwerk/configuration.rb +1 -1
  18. data/lib/packwerk/const_node_inspector.rb +3 -2
  19. data/lib/packwerk/constant_name_inspector.rb +1 -1
  20. data/lib/packwerk/deprecated_references.rb +19 -7
  21. data/lib/packwerk/file_processor.rb +39 -14
  22. data/lib/packwerk/files_for_processing.rb +15 -4
  23. data/lib/packwerk/formatters/offenses_formatter.rb +10 -1
  24. data/lib/packwerk/generators/templates/package.yml +1 -1
  25. data/lib/packwerk/graph.rb +2 -0
  26. data/lib/packwerk/inflector.rb +1 -0
  27. data/lib/packwerk/node.rb +1 -0
  28. data/lib/packwerk/node_processor.rb +10 -22
  29. data/lib/packwerk/node_processor_factory.rb +0 -2
  30. data/lib/packwerk/node_visitor.rb +4 -2
  31. data/lib/packwerk/offenses_formatter.rb +4 -0
  32. data/lib/packwerk/package.rb +34 -5
  33. data/lib/packwerk/package_set.rb +43 -8
  34. data/lib/packwerk/parse_run.rb +9 -7
  35. data/lib/packwerk/parsed_constant_definitions.rb +1 -0
  36. data/lib/packwerk/reference.rb +2 -1
  37. data/lib/packwerk/reference_checking/checkers/checker.rb +21 -0
  38. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +31 -0
  39. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +58 -0
  40. data/lib/packwerk/reference_checking/reference_checker.rb +33 -0
  41. data/lib/packwerk/reference_extractor.rb +2 -2
  42. data/lib/packwerk/reference_offense.rb +1 -0
  43. data/lib/packwerk/run_context.rb +9 -6
  44. data/lib/packwerk/sanity_checker.rb +1 -1
  45. data/lib/packwerk/version.rb +1 -1
  46. data/lib/packwerk/violation_type.rb +1 -1
  47. data/lib/packwerk.rb +14 -4
  48. data/packwerk.gemspec +3 -1
  49. data/service.yml +0 -2
  50. data/sorbet/rbi/gems/psych@3.3.2.rbi +24 -0
  51. metadata +28 -10
  52. data/lib/packwerk/checker.rb +0 -17
  53. data/lib/packwerk/dependency_checker.rb +0 -26
  54. data/lib/packwerk/generators/application_validation.rb +0 -62
  55. data/lib/packwerk/generators/templates/packwerk +0 -23
  56. data/lib/packwerk/generators/templates/packwerk_validator_test.rb +0 -11
  57. data/lib/packwerk/privacy_checker.rb +0 -53
data/lib/packwerk/cli.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "optparse"
5
+
4
6
  module Packwerk
7
+ # A command-line interface to Packwerk.
5
8
  class Cli
6
9
  extend T::Sig
7
10
 
@@ -10,6 +13,7 @@ module Packwerk
10
13
  configuration: T.nilable(Configuration),
11
14
  out: T.any(StringIO, IO),
12
15
  err_out: T.any(StringIO, IO),
16
+ environment: String,
13
17
  style: Packwerk::OutputStyle,
14
18
  offenses_formatter: T.nilable(Packwerk::OffensesFormatter)
15
19
  ).void
@@ -18,11 +22,13 @@ module Packwerk
18
22
  configuration: nil,
19
23
  out: $stdout,
20
24
  err_out: $stderr,
25
+ environment: "test",
21
26
  style: OutputStyles::Plain.new,
22
27
  offenses_formatter: nil
23
28
  )
24
29
  @out = out
25
30
  @err_out = err_out
31
+ @environment = environment
26
32
  @style = style
27
33
  @configuration = configuration || Configuration.from_path
28
34
  @progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
@@ -77,28 +83,12 @@ module Packwerk
77
83
  def init
78
84
  @out.puts("📦 Initializing Packwerk...")
79
85
 
80
- application_validation = Packwerk::Generators::ApplicationValidation.generate(
81
- for_rails_app: rails_app?,
82
- root: @configuration.root_path,
83
- out: @out
84
- )
85
-
86
- if application_validation
87
- if rails_app?
88
- # To run in the same space as the Rails process,
89
- # in order to fetch load paths for the configuration generator
90
- exec("bin/packwerk", "generate_configs")
91
- else
92
- generate_configurations = generate_configs
93
- end
94
- end
95
-
96
- application_validation && generate_configurations
86
+ generate_configs
97
87
  end
98
88
 
99
89
  def generate_configs
100
90
  configuration_file = Packwerk::Generators::ConfigurationFile.generate(
101
- load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths,
91
+ load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths(@configuration.root_path, @environment),
102
92
  root: @configuration.root_path,
103
93
  out: @out
104
94
  )
@@ -110,7 +100,7 @@ module Packwerk
110
100
  result = if success
111
101
  <<~EOS
112
102
 
113
- 🎉 Packwerk is ready to be used. You can start defining packages and run `packwerk check`.
103
+ 🎉 Packwerk is ready to be used. You can start defining packages and run `bin/packwerk check`.
114
104
  For more information on how to use Packwerk, see: https://github.com/Shopify/packwerk/blob/main/USAGE.md
115
105
  EOS
116
106
  else
@@ -136,22 +126,23 @@ module Packwerk
136
126
  result.status
137
127
  end
138
128
 
139
- def fetch_files_to_process(paths)
140
- files = FilesForProcessing.fetch(paths: paths, configuration: @configuration)
129
+ def fetch_files_to_process(paths, ignore_nested_packages)
130
+ files = FilesForProcessing.fetch(
131
+ paths: paths,
132
+ ignore_nested_packages: ignore_nested_packages,
133
+ configuration: @configuration
134
+ )
141
135
  abort("No files found or given. "\
142
136
  "Specify files or check the include and exclude glob in the config file.") if files.empty?
143
137
  files
144
138
  end
145
139
 
146
140
  def validate(_paths)
147
- warn("`packwerk validate` should be run within the application. "\
148
- "Generate the bin script using `packwerk init` and"\
149
- " use `bin/packwerk validate` instead.") unless defined?(::Rails)
150
-
151
141
  @progress_formatter.started_validation do
152
142
  checker = Packwerk::ApplicationValidator.new(
153
143
  config_file_path: @configuration.config_path,
154
- configuration: @configuration
144
+ configuration: @configuration,
145
+ environment: @environment,
155
146
  )
156
147
  result = checker.check_all
157
148
 
@@ -171,18 +162,24 @@ module Packwerk
171
162
  end
172
163
  end
173
164
 
174
- sig { returns(T::Boolean) }
175
- def rails_app?
176
- if File.exist?("config/application.rb") && File.exist?("bin/rails")
177
- File.foreach("Gemfile").any? { |line| line.match?(/['"]rails['"]/) }
165
+ def parse_run(params)
166
+ paths = T.let([], T::Array[String])
167
+ ignore_nested_packages = nil
168
+
169
+ if params.any? { |p| p.include?("--packages") }
170
+ OptionParser.new do |parser|
171
+ parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
172
+ paths = p
173
+ end
174
+ end.parse!(params)
175
+ ignore_nested_packages = true
178
176
  else
179
- false
177
+ paths = params
178
+ ignore_nested_packages = false
180
179
  end
181
- end
182
180
 
183
- def parse_run(paths)
184
181
  ParseRun.new(
185
- files: fetch_files_to_process(paths),
182
+ files: fetch_files_to_process(paths, ignore_nested_packages),
186
183
  configuration: @configuration,
187
184
  progress_formatter: @progress_formatter,
188
185
  offenses_formatter: @offenses_formatter
@@ -45,7 +45,7 @@ module Packwerk
45
45
  @root_path = File.expand_path(root)
46
46
  @package_paths = configs["package_paths"] || "**/"
47
47
  @custom_associations = configs["custom_associations"] || []
48
- @load_paths = configs["load_paths"] || []
48
+ @load_paths = (configs["load_paths"] || []).uniq
49
49
  @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
50
50
  @parallel = configs.key?("parallel") ? configs["parallel"] : true
51
51
 
@@ -15,6 +15,9 @@ module Packwerk
15
15
  def constant_name_from_node(node, ancestors:)
16
16
  return nil unless Node.constant?(node)
17
17
  parent = ancestors.first
18
+
19
+ # Only process the root `const` node for namespaced constant references. For example, in the
20
+ # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
18
21
  return nil unless root_constant?(parent)
19
22
 
20
23
  if parent && constant_in_module_or_class_definition?(node, parent: parent)
@@ -30,8 +33,6 @@ module Packwerk
30
33
 
31
34
  private
32
35
 
33
- # Only process the root `const` node for namespaced constant references. For example, in the
34
- # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
35
36
  sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
36
37
  def root_constant?(parent)
37
38
  !(parent && Node.constant?(parent))
@@ -4,7 +4,7 @@
4
4
  require "ast"
5
5
 
6
6
  module Packwerk
7
- # An interface describing some object that can extract a constant name from an AST node
7
+ # An interface describing an object that can extract a constant name from an AST node.
8
8
  module ConstantNameInspector
9
9
  extend T::Sig
10
10
  extend T::Helpers
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "yaml"
@@ -7,11 +7,15 @@ module Packwerk
7
7
  class DeprecatedReferences
8
8
  extend T::Sig
9
9
 
10
+ ENTRIES_TYPE = T.type_alias do
11
+ T::Hash[String, T.untyped]
12
+ end
13
+
10
14
  sig { params(package: Packwerk::Package, filepath: String).void }
11
15
  def initialize(package, filepath)
12
16
  @package = package
13
17
  @filepath = filepath
14
- @new_entries = {}
18
+ @new_entries = T.let({}, ENTRIES_TYPE)
15
19
  end
16
20
 
17
21
  sig do
@@ -53,7 +57,7 @@ module Packwerk
53
57
  if entries_for_file["violations"].all? { |type| new_entries_violation_types.include?(type) }
54
58
  stale_violations =
55
59
  entries_for_file["files"] - Array(@new_entries.dig(package, constant_name, "files"))
56
- stale_violations.present?
60
+ stale_violations.any?
57
61
  else
58
62
  return true
59
63
  end
@@ -73,7 +77,7 @@ module Packwerk
73
77
  #
74
78
  # You can regenerate this file using the following command:
75
79
  #
76
- # bundle exec packwerk update-deprecations #{@package.name}
80
+ # bin/packwerk update-deprecations #{@package.name}
77
81
  MESSAGE
78
82
  File.open(@filepath, "w") do |f|
79
83
  f.write(message)
@@ -84,7 +88,7 @@ module Packwerk
84
88
 
85
89
  private
86
90
 
87
- sig { returns(Hash) }
91
+ sig { returns(ENTRIES_TYPE) }
88
92
  def prepare_entries_for_dump
89
93
  @new_entries.each do |package_name, package_violations|
90
94
  package_violations.each do |_, entries_for_file|
@@ -97,13 +101,21 @@ module Packwerk
97
101
  @new_entries = @new_entries.sort.to_h
98
102
  end
99
103
 
100
- sig { returns(Hash) }
104
+ sig { returns(ENTRIES_TYPE) }
101
105
  def deprecated_references
106
+ @deprecated_references ||= T.let(@deprecated_references, T.nilable(ENTRIES_TYPE))
102
107
  @deprecated_references ||= if File.exist?(@filepath)
103
- YAML.load_file(@filepath) || {}
108
+ load_yaml(@filepath)
104
109
  else
105
110
  {}
106
111
  end
107
112
  end
113
+
114
+ sig { params(filepath: String).returns(ENTRIES_TYPE) }
115
+ def load_yaml(filepath)
116
+ YAML.load_file(filepath) || {}
117
+ rescue Psych::Exception
118
+ {}
119
+ end
108
120
  end
109
121
  end
@@ -1,10 +1,12 @@
1
- # typed: false
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast/node"
5
5
 
6
6
  module Packwerk
7
7
  class FileProcessor
8
+ extend T::Sig
9
+
8
10
  class UnknownFileTypeResult < Offense
9
11
  def initialize(file:)
10
12
  super(file: file, message: "unknown file type")
@@ -16,24 +18,47 @@ module Packwerk
16
18
  @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
17
19
  end
18
20
 
21
+ sig do
22
+ params(file_path: String).returns(
23
+ T::Array[
24
+ T.any(
25
+ Packwerk::Reference,
26
+ Packwerk::Offense,
27
+ )
28
+ ]
29
+ )
30
+ end
19
31
  def call(file_path)
20
- parser = @parser_factory.for_path(file_path)
21
- return [UnknownFileTypeResult.new(file: file_path)] if parser.nil?
32
+ return [UnknownFileTypeResult.new(file: file_path)] if parser_for(file_path).nil?
22
33
 
23
- node = File.open(file_path, "r", external_encoding: Encoding::UTF_8) do |file|
24
- parser.call(io: file, file_path: file_path)
25
- rescue Parsers::ParseError => e
26
- return [e.result]
27
- end
34
+ node = parse_into_ast(file_path)
35
+ return [] unless node
36
+
37
+ references_from_ast(node, file_path)
38
+ rescue Parsers::ParseError => e
39
+ [e.result]
40
+ end
41
+
42
+ private
28
43
 
29
- result = []
30
- if node
31
- node_processor = @node_processor_factory.for(filename: file_path, node: node)
32
- node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
44
+ def references_from_ast(node, file_path)
45
+ references = []
33
46
 
34
- node_visitor.visit(node, ancestors: [], result: result)
47
+ node_processor = @node_processor_factory.for(filename: file_path, node: node)
48
+ node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
49
+ node_visitor.visit(node, ancestors: [], result: references)
50
+
51
+ references
52
+ end
53
+
54
+ def parse_into_ast(file_path)
55
+ File.open(file_path, "r", nil, external_encoding: Encoding::UTF_8) do |file|
56
+ parser_for(file_path).call(io: file, file_path: file_path)
35
57
  end
36
- result
58
+ end
59
+
60
+ def parser_for(file_path)
61
+ @parser_factory.for_path(file_path)
37
62
  end
38
63
  end
39
64
  end
@@ -4,14 +4,15 @@
4
4
  module Packwerk
5
5
  class FilesForProcessing
6
6
  class << self
7
- def fetch(paths:, configuration:)
8
- new(paths, configuration).files
7
+ def fetch(paths:, configuration:, ignore_nested_packages: false)
8
+ new(paths, configuration, ignore_nested_packages).files
9
9
  end
10
10
  end
11
11
 
12
- def initialize(paths, configuration)
12
+ def initialize(paths, configuration, ignore_nested_packages)
13
13
  @paths = paths
14
14
  @configuration = configuration
15
+ @ignore_nested_packages = ignore_nested_packages
15
16
  end
16
17
 
17
18
  def files
@@ -43,11 +44,21 @@ module Packwerk
43
44
  File.expand_path(glob, @configuration.root_path)
44
45
  end
45
46
 
46
- Dir.glob([File.join(path, "**", "*")]).select do |file_path|
47
+ files = Dir.glob([File.join(path, "**", "*")]).select do |file_path|
47
48
  absolute_includes.any? do |pattern|
48
49
  File.fnmatch?(pattern, file_path, File::FNM_EXTGLOB)
49
50
  end
50
51
  end
52
+
53
+ if @ignore_nested_packages
54
+ nested_packages_paths = Dir.glob(File.join(path, "*", "**", "package.yml"))
55
+ nested_packages_globs = nested_packages_paths.map { |npp| npp.gsub("package.yml", "**/*") }
56
+ nested_packages_globs.each do |glob|
57
+ files -= Dir.glob(glob)
58
+ end
59
+ end
60
+
61
+ files
51
62
  end
52
63
 
53
64
  def configured_included_files
@@ -15,7 +15,7 @@ module Packwerk
15
15
 
16
16
  sig { override.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
17
17
  def show_offenses(offenses)
18
- return "No offenses detected 🎉" if offenses.empty?
18
+ return "No offenses detected" if offenses.empty?
19
19
 
20
20
  <<~EOS
21
21
  #{offenses_list(offenses)}
@@ -23,6 +23,15 @@ module Packwerk
23
23
  EOS
24
24
  end
25
25
 
26
+ sig { override.params(offense_collection: Packwerk::OffenseCollection).returns(String) }
27
+ def show_stale_violations(offense_collection)
28
+ if offense_collection.stale_violations?
29
+ "There were stale violations found, please run `packwerk update-deprecations`"
30
+ else
31
+ "No stale violations detected"
32
+ end
33
+ end
34
+
26
35
  private
27
36
 
28
37
  sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
@@ -1,5 +1,5 @@
1
1
  # This file represents the root package of the application
2
- # Please validate the configuration using `bin/packwerk validate` (for Rails applications) or running the auto generated
2
+ # Please validate the configuration using `packwerk validate` (for Rails applications) or running the auto generated
3
3
  # test case (for non-Rails projects). You can then use `packwerk check` to check your code.
4
4
 
5
5
  # Turn on dependency checks for this package
@@ -2,7 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
+ # A general implementation of a graph data structure with the ability to check for - and list - cycles.
5
6
  class Graph
7
+ # @param [Array<Array>] edges The edges of the graph; An edge being represented as an Array of two nodes.
6
8
  def initialize(*edges)
7
9
  @edges = edges.uniq
8
10
  @cycles = Set.new
@@ -4,6 +4,7 @@
4
4
  require "active_support/inflector"
5
5
 
6
6
  module Packwerk
7
+ # A custom inflector used, among other things, to map between constant names and file names.
7
8
  class Inflector
8
9
  class << self
9
10
  extend T::Sig
data/lib/packwerk/node.rb CHANGED
@@ -5,6 +5,7 @@ require "parser"
5
5
  require "parser/ast/node"
6
6
 
7
7
  module Packwerk
8
+ # Convenience methods for working with AST nodes.
8
9
  module Node
9
10
  class TypeError < ArgumentError; end
10
11
  Location = Struct.new(:line, :column)
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
+ # Processes a single node in an abstract syntax tree (AST) using the provided checkers.
5
6
  class NodeProcessor
6
7
  extend T::Sig
7
8
 
@@ -9,35 +10,22 @@ module Packwerk
9
10
  params(
10
11
  reference_extractor: ReferenceExtractor,
11
12
  filename: String,
12
- checkers: T::Array[Checker]
13
13
  ).void
14
14
  end
15
- def initialize(reference_extractor:, filename:, checkers:)
15
+ def initialize(reference_extractor:, filename:)
16
16
  @reference_extractor = reference_extractor
17
17
  @filename = filename
18
- @checkers = checkers
19
18
  end
20
19
 
21
- sig { params(node: Parser::AST::Node, ancestors: T::Array[Parser::AST::Node]).returns(T::Array[Offense]) }
22
- def call(node, ancestors)
23
- return [] unless Node.method_call?(node) || Node.constant?(node)
24
- reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
25
- check_reference(reference, node)
20
+ sig do
21
+ params(
22
+ node: Parser::AST::Node,
23
+ ancestors: T::Array[Parser::AST::Node]
24
+ ).returns(T.nilable(Packwerk::Reference))
26
25
  end
27
-
28
- private
29
-
30
- def check_reference(reference, node)
31
- return [] unless reference
32
- @checkers.each_with_object([]) do |checker, violations|
33
- next unless checker.invalid_reference?(reference)
34
- offense = Packwerk::ReferenceOffense.new(
35
- location: Node.location(node),
36
- reference: reference,
37
- violation_type: checker.violation_type
38
- )
39
- violations << offense
40
- end
26
+ def call(node, ancestors)
27
+ return unless Node.method_call?(node) || Node.constant?(node)
28
+ @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
41
29
  end
42
30
  end
43
31
  end
@@ -8,14 +8,12 @@ module Packwerk
8
8
  const :root_path, String
9
9
  const :context_provider, Packwerk::ConstantDiscovery
10
10
  const :constant_name_inspectors, T::Array[ConstantNameInspector]
11
- const :checkers, T::Array[Checker]
12
11
 
13
12
  sig { params(filename: String, node: AST::Node).returns(NodeProcessor) }
14
13
  def for(filename:, node:)
15
14
  ::Packwerk::NodeProcessor.new(
16
15
  reference_extractor: reference_extractor(node: node),
17
16
  filename: filename,
18
- checkers: checkers,
19
17
  )
20
18
  end
21
19
 
@@ -1,14 +1,16 @@
1
- # typed: false
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
+ # Visits all nodes of an AST, processing them using a given node processor.
5
6
  class NodeVisitor
6
7
  def initialize(node_processor:)
7
8
  @node_processor = node_processor
8
9
  end
9
10
 
10
11
  def visit(node, ancestors:, result:)
11
- result.concat(@node_processor.call(node, ancestors))
12
+ reference = @node_processor.call(node, ancestors)
13
+ result << reference if reference
12
14
 
13
15
  child_ancestors = [node] + ancestors
14
16
  Node.each_child(node) do |child|
@@ -11,5 +11,9 @@ module Packwerk
11
11
  sig { abstract.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
12
12
  def show_offenses(offenses)
13
13
  end
14
+
15
+ sig { abstract.params(offense_collection: Packwerk::OffenseCollection).returns(String) }
16
+ def show_stale_violations(offense_collection)
17
+ end
14
18
  end
15
19
  end
@@ -1,45 +1,69 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
+ # The basic unit of modularity for packwerk; a folder that has been declared to define a package.
6
+ # The package contains all constants defined in files in this folder and all subfolders that are not packages
7
+ # themselves.
5
8
  class Package
9
+ extend T::Sig
6
10
  include Comparable
7
11
 
8
12
  ROOT_PACKAGE_NAME = "."
9
13
 
10
- attr_reader :name, :dependencies
14
+ sig { returns(String) }
15
+ attr_reader :name
16
+ sig { returns(T::Array[String]) }
17
+ attr_reader :dependencies
11
18
 
19
+ sig { params(name: String, config: T.nilable(T.any(T::Hash[T.untyped, T.untyped], FalseClass))).void }
12
20
  def initialize(name:, config:)
13
21
  @name = name
14
- @config = config || {}
15
- @dependencies = Array(@config["dependencies"]).freeze
22
+ @config = T.let(config || {}, T::Hash[T.untyped, T.untyped])
23
+ @dependencies = T.let(Array(@config["dependencies"]).freeze, T::Array[String])
16
24
  end
17
25
 
26
+ sig { returns(T.nilable(T.any(T::Boolean, T::Array[String]))) }
18
27
  def enforce_privacy
19
28
  @config["enforce_privacy"]
20
29
  end
21
30
 
31
+ sig { returns(T::Boolean) }
22
32
  def enforce_dependencies?
23
33
  @config["enforce_dependencies"] == true
24
34
  end
25
35
 
36
+ sig { params(package: Package).returns(T::Boolean) }
26
37
  def dependency?(package)
27
38
  @dependencies.include?(package.name)
28
39
  end
29
40
 
41
+ sig { params(path: String).returns(T::Boolean) }
30
42
  def package_path?(path)
31
43
  return true if root?
32
44
  path.start_with?(@name)
33
45
  end
34
46
 
47
+ sig { returns(String) }
35
48
  def public_path
36
- @public_path ||= File.join(@name, user_defined_public_path || "app/public/")
49
+ @public_path = T.let(@public_path, T.nilable(String))
50
+ @public_path ||= begin
51
+ unprefixed_public_path = user_defined_public_path || "app/public/"
52
+
53
+ if root?
54
+ unprefixed_public_path
55
+ else
56
+ File.join(@name, unprefixed_public_path)
57
+ end
58
+ end
37
59
  end
38
60
 
61
+ sig { params(path: String).returns(T::Boolean) }
39
62
  def public_path?(path)
40
63
  path.start_with?(public_path)
41
64
  end
42
65
 
66
+ sig { returns(T.nilable(String)) }
43
67
  def user_defined_public_path
44
68
  return unless @config["public_path"]
45
69
  return @config["public_path"] if @config["public_path"].end_with?("/")
@@ -47,23 +71,28 @@ module Packwerk
47
71
  @config["public_path"] + "/"
48
72
  end
49
73
 
74
+ sig { params(other: T.untyped).returns(T.nilable(Integer)) }
50
75
  def <=>(other)
51
76
  return nil unless other.is_a?(self.class)
52
77
  name <=> other.name
53
78
  end
54
79
 
80
+ sig { params(other: T.untyped).returns(T::Boolean) }
55
81
  def eql?(other)
56
82
  self == other
57
83
  end
58
84
 
85
+ sig { returns(Integer) }
59
86
  def hash
60
87
  name.hash
61
88
  end
62
89
 
90
+ sig { returns(String) }
63
91
  def to_s
64
92
  name
65
93
  end
66
94
 
95
+ sig { returns(T::Boolean) }
67
96
  def root?
68
97
  @name == ROOT_PACKAGE_NAME
69
98
  end