packwerk 2.3.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) 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 +3 -8
  8. data/TROUBLESHOOT.md +2 -25
  9. data/UPGRADING.md +12 -0
  10. data/USAGE.md +136 -54
  11. data/dev.yml +1 -1
  12. data/exe/packwerk +4 -0
  13. data/gemfiles/Gemfile-rails-6-1 +1 -1
  14. data/lib/packwerk/application_validator.rb +54 -285
  15. data/lib/packwerk/association_inspector.rb +2 -0
  16. data/lib/packwerk/cache.rb +6 -5
  17. data/lib/packwerk/checker.rb +54 -0
  18. data/lib/packwerk/cli/result.rb +11 -0
  19. data/lib/packwerk/cli.rb +55 -40
  20. data/lib/packwerk/configuration.rb +61 -40
  21. data/lib/packwerk/const_node_inspector.rb +2 -0
  22. data/lib/packwerk/constant_context.rb +8 -0
  23. data/lib/packwerk/constant_discovery.rb +5 -6
  24. data/lib/packwerk/constant_name_inspector.rb +2 -0
  25. data/lib/packwerk/disable_sorbet.rb +41 -0
  26. data/lib/packwerk/extension_loader.rb +24 -0
  27. data/lib/packwerk/file_processor.rb +3 -1
  28. data/lib/packwerk/files_for_processing.rb +25 -12
  29. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  30. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  31. data/lib/packwerk/generators/configuration_file.rb +7 -2
  32. data/lib/packwerk/generators/root_package.rb +5 -1
  33. data/lib/packwerk/generators/templates/package.yml +0 -10
  34. data/lib/packwerk/graph.rb +10 -2
  35. data/lib/packwerk/node.rb +1 -1
  36. data/lib/packwerk/node_helpers.rb +14 -7
  37. data/lib/packwerk/node_processor.rb +2 -0
  38. data/lib/packwerk/node_processor_factory.rb +6 -4
  39. data/lib/packwerk/node_visitor.rb +10 -1
  40. data/lib/packwerk/offense_collection.rb +26 -18
  41. data/lib/packwerk/offenses_formatter.rb +59 -2
  42. data/lib/packwerk/package.rb +7 -35
  43. data/lib/packwerk/package_set.rb +1 -1
  44. data/lib/packwerk/package_todo.rb +19 -20
  45. data/lib/packwerk/parse_run.rb +27 -34
  46. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  47. data/lib/packwerk/parsers/erb.rb +23 -4
  48. data/lib/packwerk/parsers/factory.rb +11 -2
  49. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  50. data/lib/packwerk/parsers/ruby.rb +13 -3
  51. data/lib/packwerk/parsers.rb +6 -2
  52. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  53. data/lib/packwerk/reference.rb +7 -1
  54. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  55. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  56. data/lib/packwerk/reference_extractor.rb +24 -12
  57. data/lib/packwerk/reference_offense.rb +2 -2
  58. data/lib/packwerk/run_context.rb +7 -10
  59. data/lib/packwerk/spring_command.rb +9 -2
  60. data/lib/packwerk/unresolved_reference.rb +9 -1
  61. data/lib/packwerk/validator/result.rb +18 -0
  62. data/lib/packwerk/validator.rb +90 -0
  63. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  64. data/lib/packwerk/version.rb +1 -1
  65. data/lib/packwerk.rb +64 -26
  66. data/packwerk.gemspec +4 -2
  67. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  68. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  69. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  70. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  71. data/sorbet/rbi/shims/parser.rbi +13 -0
  72. metadata +34 -15
  73. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  74. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  75. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  76. data/lib/packwerk/result.rb +0 -9
  77. data/lib/packwerk/sanity_checker.rb +0 -8
  78. data/lib/packwerk/violation_type.rb +0 -11
  79. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  80. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
data/lib/packwerk/cli.rb CHANGED
@@ -14,8 +14,8 @@ module Packwerk
14
14
  out: T.any(StringIO, IO),
15
15
  err_out: T.any(StringIO, IO),
16
16
  environment: String,
17
- style: Packwerk::OutputStyle,
18
- offenses_formatter: T.nilable(Packwerk::OffensesFormatter)
17
+ style: OutputStyle,
18
+ offenses_formatter: T.nilable(OffensesFormatter)
19
19
  ).void
20
20
  end
21
21
  def initialize(
@@ -33,7 +33,7 @@ module Packwerk
33
33
  @configuration = T.let(configuration || Configuration.from_path, Configuration)
34
34
  @progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
35
35
  @offenses_formatter = T.let(
36
- offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
36
+ offenses_formatter || @configuration.offenses_formatter,
37
37
  OffensesFormatter
38
38
  )
39
39
  end
@@ -52,22 +52,13 @@ module Packwerk
52
52
  init
53
53
  when "check"
54
54
  output_result(parse_run(args).check)
55
- when "detect-stale-violations"
56
- output_result(parse_run(args).detect_stale_violations)
57
- when "update-deprecations"
58
- warning = <<~WARNING.squish
59
- DEPRECATION WARNING: `update-deprecations` is deprecated in favor of
60
- `update-todo`.
61
- WARNING
62
-
63
- warn(warning)
64
- output_result(parse_run(args).update_todo)
65
- when "update-todo"
66
- output_result(parse_run(args).update_todo)
67
- when "update"
55
+ when "update-todo", "update"
68
56
  output_result(parse_run(args).update_todo)
69
57
  when "validate"
70
58
  validate(args)
59
+ when "version"
60
+ @out.puts(Packwerk::VERSION)
61
+ true
71
62
  when nil, "help"
72
63
  usage
73
64
  else
@@ -89,12 +80,12 @@ module Packwerk
89
80
 
90
81
  sig { returns(T::Boolean) }
91
82
  def generate_configs
92
- configuration_file = Packwerk::Generators::ConfigurationFile.generate(
83
+ configuration_file = Generators::ConfigurationFile.generate(
93
84
  root: @configuration.root_path,
94
85
  out: @out
95
86
  )
96
87
 
97
- root_package = Packwerk::Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
88
+ root_package = Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
98
89
 
99
90
  success = configuration_file && root_package
100
91
 
@@ -124,8 +115,9 @@ module Packwerk
124
115
  Subcommands:
125
116
  init - set up packwerk
126
117
  check - run all checks
127
- update-deprecations - update deprecated_references.yml files
118
+ update-todo - update package_todo.yml files
128
119
  validate - verify integrity of packwerk and package configuration
120
+ version - output packwerk version
129
121
  help - display help information about packwerk
130
122
  USAGE
131
123
  true
@@ -142,72 +134,95 @@ module Packwerk
142
134
  params(
143
135
  relative_file_paths: T::Array[String],
144
136
  ignore_nested_packages: T::Boolean
145
- ).returns(FilesForProcessing::RelativeFileSet)
137
+ ).returns(FilesForProcessing)
146
138
  end
147
139
  def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
148
- relative_file_set = FilesForProcessing.fetch(
140
+ files_for_processing = FilesForProcessing.fetch(
149
141
  relative_file_paths: relative_file_paths,
150
142
  ignore_nested_packages: ignore_nested_packages,
151
143
  configuration: @configuration
152
144
  )
153
- abort("No files found or given. "\
154
- "Specify files or check the include and exclude glob in the config file.") if relative_file_set.empty?
155
- relative_file_set
145
+ @out.puts(<<~MSG.squish) if files_for_processing.files.empty?
146
+ No files found or given.
147
+ Specify files or check the include and exclude glob in the config file.
148
+ MSG
149
+
150
+ files_for_processing
156
151
  end
157
152
 
158
153
  sig { params(_paths: T::Array[String]).returns(T::Boolean) }
159
154
  def validate(_paths)
155
+ result = T.let(nil, T.nilable(Validator::Result))
156
+
160
157
  @progress_formatter.started_validation do
161
- result = checker.check_all
158
+ result = validator.check_all(package_set, @configuration)
162
159
 
163
160
  list_validation_errors(result)
164
-
165
- return result.ok?
166
161
  end
162
+
163
+ T.must(result).ok?
167
164
  end
168
165
 
169
166
  sig { returns(ApplicationValidator) }
170
- def checker
171
- Packwerk::ApplicationValidator.new(
172
- config_file_path: @configuration.config_path,
173
- configuration: @configuration,
174
- environment: @environment,
167
+ def validator
168
+ ApplicationValidator.new
169
+ end
170
+
171
+ sig { returns(PackageSet) }
172
+ def package_set
173
+ PackageSet.load_all_from(
174
+ @configuration.root_path,
175
+ package_pathspec: @configuration.package_paths
175
176
  )
176
177
  end
177
178
 
178
- sig { params(result: ApplicationValidator::Result).void }
179
+ sig { params(result: Validator::Result).void }
179
180
  def list_validation_errors(result)
180
181
  @out.puts
181
182
  if result.ok?
182
183
  @out.puts("Validation successful 🎉")
183
184
  else
184
185
  @out.puts("Validation failed ❗")
186
+ @out.puts
185
187
  @out.puts(result.error_value)
186
188
  end
187
189
  end
188
190
 
189
- sig { params(params: T.untyped).returns(ParseRun) }
190
- def parse_run(params)
191
+ sig { params(args: T::Array[String]).returns(ParseRun) }
192
+ def parse_run(args)
191
193
  relative_file_paths = T.let([], T::Array[String])
192
194
  ignore_nested_packages = nil
195
+ formatter = @offenses_formatter
193
196
 
194
- if params.any? { |p| p.include?("--packages") }
197
+ if args.any? { |arg| arg.include?("--packages") }
195
198
  OptionParser.new do |parser|
196
199
  parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
197
200
  relative_file_paths = p
198
201
  end
199
- end.parse!(params)
202
+ end.parse!(args)
200
203
  ignore_nested_packages = true
201
204
  else
202
- relative_file_paths = params
205
+ relative_file_paths = args
203
206
  ignore_nested_packages = false
204
207
  end
205
208
 
209
+ if args.any? { |arg| arg.include?("--offenses-formatter") }
210
+ OptionParser.new do |parser|
211
+ parser.on("--offenses-formatter=FORMATTER", String,
212
+ "identifier of offenses formatter to use") do |formatter_identifier|
213
+ formatter = OffensesFormatter.find(formatter_identifier)
214
+ end
215
+ end.parse!(args)
216
+ end
217
+
218
+ files_for_processing = fetch_files_to_process(relative_file_paths, ignore_nested_packages)
219
+
206
220
  ParseRun.new(
207
- relative_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
221
+ relative_file_set: files_for_processing.files,
222
+ file_set_specified: files_for_processing.files_specified?,
208
223
  configuration: @configuration,
209
224
  progress_formatter: @progress_formatter,
210
- offenses_formatter: @offenses_formatter
225
+ offenses_formatter: formatter
211
226
  )
212
227
  end
213
228
  end
@@ -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