packwerk 1.3.1 → 2.1.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/Gemfile.lock +12 -9
  4. data/README.md +9 -3
  5. data/TROUBLESHOOT.md +3 -3
  6. data/UPGRADING.md +54 -0
  7. data/USAGE.md +32 -51
  8. data/exe/packwerk +7 -1
  9. data/lib/packwerk/application_load_paths.rb +1 -0
  10. data/lib/packwerk/application_validator.rb +17 -59
  11. data/lib/packwerk/association_inspector.rb +1 -1
  12. data/lib/packwerk/cache.rb +168 -0
  13. data/lib/packwerk/cli.rb +37 -20
  14. data/lib/packwerk/configuration.rb +40 -5
  15. data/lib/packwerk/const_node_inspector.rb +3 -2
  16. data/lib/packwerk/constant_discovery.rb +4 -4
  17. data/lib/packwerk/constant_name_inspector.rb +1 -1
  18. data/lib/packwerk/deprecated_references.rb +18 -6
  19. data/lib/packwerk/file_processor.rb +53 -14
  20. data/lib/packwerk/files_for_processing.rb +15 -4
  21. data/lib/packwerk/formatters/offenses_formatter.rb +1 -1
  22. data/lib/packwerk/formatters/progress_formatter.rb +1 -1
  23. data/lib/packwerk/generators/configuration_file.rb +4 -19
  24. data/lib/packwerk/generators/templates/package.yml +1 -1
  25. data/lib/packwerk/generators/templates/packwerk.yml.erb +5 -5
  26. data/lib/packwerk/graph.rb +2 -0
  27. data/lib/packwerk/node.rb +2 -0
  28. data/lib/packwerk/node_processor.rb +10 -22
  29. data/lib/packwerk/node_processor_factory.rb +0 -3
  30. data/lib/packwerk/node_visitor.rb +7 -2
  31. data/lib/packwerk/package.rb +25 -4
  32. data/lib/packwerk/package_set.rb +44 -8
  33. data/lib/packwerk/parsed_constant_definitions.rb +5 -4
  34. data/lib/packwerk/reference.rb +2 -1
  35. data/lib/packwerk/reference_checking/checkers/checker.rb +21 -0
  36. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +31 -0
  37. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +58 -0
  38. data/lib/packwerk/reference_checking/reference_checker.rb +33 -0
  39. data/lib/packwerk/reference_extractor.rb +66 -19
  40. data/lib/packwerk/reference_offense.rb +1 -0
  41. data/lib/packwerk/run_context.rb +32 -11
  42. data/lib/packwerk/sanity_checker.rb +1 -1
  43. data/lib/packwerk/spring_command.rb +28 -0
  44. data/lib/packwerk/unresolved_reference.rb +10 -0
  45. data/lib/packwerk/version.rb +1 -1
  46. data/lib/packwerk/violation_type.rb +1 -1
  47. data/lib/packwerk.rb +19 -12
  48. data/packwerk.gemspec +4 -1
  49. data/service.yml +0 -2
  50. data/sorbet/rbi/gems/psych@3.3.2.rbi +24 -0
  51. metadata +41 -11
  52. data/lib/packwerk/checker.rb +0 -17
  53. data/lib/packwerk/dependency_checker.rb +0 -26
  54. data/lib/packwerk/generators/inflections_file.rb +0 -43
  55. data/lib/packwerk/generators/templates/inflections.yml +0 -6
  56. data/lib/packwerk/inflections/custom.rb +0 -33
  57. data/lib/packwerk/inflections/default.rb +0 -73
  58. data/lib/packwerk/inflector.rb +0 -48
  59. data/lib/packwerk/privacy_checker.rb +0 -53
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require "digest"
5
+
6
+ module Packwerk
7
+ class Cache
8
+ extend T::Sig
9
+
10
+ class CacheContents < T::Struct
11
+ extend T::Sig
12
+
13
+ const :file_contents_digest, String
14
+ const :unresolved_references, T::Array[UnresolvedReference]
15
+
16
+ sig { returns(String) }
17
+ def serialize
18
+ to_json
19
+ end
20
+
21
+ sig { params(serialized_cache_contents: String).returns(CacheContents) }
22
+ def self.deserialize(serialized_cache_contents)
23
+ cache_contents_json = JSON.parse(serialized_cache_contents)
24
+ unresolved_references = cache_contents_json["unresolved_references"].map do |json|
25
+ UnresolvedReference.new(
26
+ json["constant_name"],
27
+ json["namespace_path"],
28
+ json["relative_path"],
29
+ Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
30
+ )
31
+ end
32
+
33
+ CacheContents.new(
34
+ file_contents_digest: cache_contents_json["file_contents_digest"],
35
+ unresolved_references: unresolved_references,
36
+ )
37
+ end
38
+ end
39
+
40
+ CACHE_SHAPE = T.type_alias do
41
+ T::Hash[
42
+ String,
43
+ CacheContents
44
+ ]
45
+ end
46
+
47
+ sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: String).void }
48
+ def initialize(enable_cache:, cache_directory:, config_path:)
49
+ @enable_cache = enable_cache
50
+ @cache = T.let({}, CACHE_SHAPE)
51
+ @files_by_digest = T.let({}, T::Hash[String, String])
52
+ @config_path = config_path
53
+ @cache_directory = cache_directory
54
+
55
+ if @enable_cache
56
+ create_cache_directory!
57
+ bust_cache_if_packwerk_yml_has_changed!
58
+ bust_cache_if_inflections_have_changed!
59
+ end
60
+ end
61
+
62
+ sig { void }
63
+ def bust_cache!
64
+ FileUtils.rm_rf(@cache_directory)
65
+ end
66
+
67
+ sig do
68
+ params(
69
+ file_path: String,
70
+ block: T.proc.returns(T::Array[UnresolvedReference])
71
+ ).returns(T::Array[UnresolvedReference])
72
+ end
73
+ def with_cache(file_path, &block)
74
+ return block.call unless @enable_cache
75
+
76
+ cache_location = @cache_directory.join(digest_for_string(file_path))
77
+
78
+ cache_contents = if cache_location.exist?
79
+ T.let(CacheContents.deserialize(cache_location.read),
80
+ CacheContents)
81
+ end
82
+
83
+ file_contents_digest = digest_for_file(file_path)
84
+
85
+ if !cache_contents.nil? && cache_contents.file_contents_digest == file_contents_digest
86
+ Debug.out("Cache hit for #{file_path}")
87
+
88
+ cache_contents.unresolved_references
89
+ else
90
+ Debug.out("Cache miss for #{file_path}")
91
+
92
+ unresolved_references = block.call
93
+
94
+ cache_contents = CacheContents.new(
95
+ file_contents_digest: file_contents_digest,
96
+ unresolved_references: unresolved_references,
97
+ )
98
+ cache_location.write(cache_contents.serialize)
99
+
100
+ unresolved_references
101
+ end
102
+ end
103
+
104
+ sig { params(file: String).returns(String) }
105
+ def digest_for_file(file)
106
+ digest_for_string(File.read(file))
107
+ end
108
+
109
+ sig { params(str: String).returns(String) }
110
+ def digest_for_string(str)
111
+ # MD5 appears to be the fastest
112
+ # https://gist.github.com/morimori/1330095
113
+ Digest::MD5.hexdigest(str)
114
+ end
115
+
116
+ sig { void }
117
+ def bust_cache_if_packwerk_yml_has_changed!
118
+ bust_cache_if_contents_have_changed(File.read(@config_path), :packwerk_yml)
119
+ end
120
+
121
+ sig { void }
122
+ def bust_cache_if_inflections_have_changed!
123
+ bust_cache_if_contents_have_changed(YAML.dump(ActiveSupport::Inflector.inflections), :inflections)
124
+ end
125
+
126
+ sig { params(contents: String, contents_key: Symbol).void }
127
+ def bust_cache_if_contents_have_changed(contents, contents_key)
128
+ current_digest = digest_for_string(contents)
129
+ cached_digest_path = @cache_directory.join(contents_key.to_s)
130
+
131
+ if !cached_digest_path.exist?
132
+ # In this case, we have nothing cached
133
+ # We save the current digest. This way the next time we compare current digest to cached digest,
134
+ # we can accurately determine if we should bust the cache
135
+ cached_digest_path.write(current_digest)
136
+
137
+ nil
138
+ elsif cached_digest_path.read == current_digest
139
+ Debug.out("#{contents_key} contents have NOT changed, preserving cache")
140
+ else
141
+ Debug.out("#{contents_key} contents have changed, busting cache")
142
+
143
+ bust_cache!
144
+ create_cache_directory!
145
+
146
+ cached_digest_path.write(current_digest)
147
+ end
148
+ end
149
+
150
+ sig { void }
151
+ def create_cache_directory!
152
+ FileUtils.mkdir_p(@cache_directory)
153
+ end
154
+ end
155
+
156
+ class Debug
157
+ extend T::Sig
158
+
159
+ sig { params(out: String).void }
160
+ def self.out(out)
161
+ if ENV["DEBUG_PACKWERK_CACHE"]
162
+ puts(out)
163
+ end
164
+ end
165
+ end
166
+
167
+ private_constant :Debug
168
+ end
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
 
@@ -50,8 +53,6 @@ module Packwerk
50
53
  output_result(parse_run(args).check)
51
54
  when "detect-stale-violations"
52
55
  output_result(parse_run(args).detect_stale_violations)
53
- when "update"
54
- update(args)
55
56
  when "update-deprecations"
56
57
  output_result(parse_run(args).update_deprecations)
57
58
  when "validate"
@@ -85,19 +86,18 @@ module Packwerk
85
86
 
86
87
  def generate_configs
87
88
  configuration_file = Packwerk::Generators::ConfigurationFile.generate(
88
- load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths(@configuration.root_path, @environment),
89
89
  root: @configuration.root_path,
90
90
  out: @out
91
91
  )
92
- inflections_file = Packwerk::Generators::InflectionsFile.generate(root: @configuration.root_path, out: @out)
92
+
93
93
  root_package = Packwerk::Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
94
94
 
95
- success = configuration_file && inflections_file && root_package
95
+ success = configuration_file && root_package
96
96
 
97
97
  result = if success
98
98
  <<~EOS
99
99
 
100
- 🎉 Packwerk is ready to be used. You can start defining packages and run `packwerk check`.
100
+ 🎉 Packwerk is ready to be used. You can start defining packages and run `bin/packwerk check`.
101
101
  For more information on how to use Packwerk, see: https://github.com/Shopify/packwerk/blob/main/USAGE.md
102
102
  EOS
103
103
  else
@@ -112,19 +112,18 @@ module Packwerk
112
112
  success
113
113
  end
114
114
 
115
- def update(paths)
116
- warn("`packwerk update` is deprecated in favor of `packwerk update-deprecations`.")
117
- output_result(parse_run(paths).update_deprecations)
118
- end
119
-
120
115
  def output_result(result)
121
116
  @out.puts
122
117
  @out.puts(result.message)
123
118
  result.status
124
119
  end
125
120
 
126
- def fetch_files_to_process(paths)
127
- files = FilesForProcessing.fetch(paths: paths, configuration: @configuration)
121
+ def fetch_files_to_process(paths, ignore_nested_packages)
122
+ files = FilesForProcessing.fetch(
123
+ paths: paths,
124
+ ignore_nested_packages: ignore_nested_packages,
125
+ configuration: @configuration
126
+ )
128
127
  abort("No files found or given. "\
129
128
  "Specify files or check the include and exclude glob in the config file.") if files.empty?
130
129
  files
@@ -132,11 +131,6 @@ module Packwerk
132
131
 
133
132
  def validate(_paths)
134
133
  @progress_formatter.started_validation do
135
- checker = Packwerk::ApplicationValidator.new(
136
- config_file_path: @configuration.config_path,
137
- configuration: @configuration,
138
- environment: @environment,
139
- )
140
134
  result = checker.check_all
141
135
 
142
136
  list_validation_errors(result)
@@ -145,6 +139,14 @@ module Packwerk
145
139
  end
146
140
  end
147
141
 
142
+ def checker
143
+ Packwerk::ApplicationValidator.new(
144
+ config_file_path: @configuration.config_path,
145
+ configuration: @configuration,
146
+ environment: @environment,
147
+ )
148
+ end
149
+
148
150
  def list_validation_errors(result)
149
151
  @out.puts
150
152
  if result.ok?
@@ -155,9 +157,24 @@ module Packwerk
155
157
  end
156
158
  end
157
159
 
158
- def parse_run(paths)
160
+ def parse_run(params)
161
+ paths = T.let([], T::Array[String])
162
+ ignore_nested_packages = nil
163
+
164
+ if params.any? { |p| p.include?("--packages") }
165
+ OptionParser.new do |parser|
166
+ parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
167
+ paths = p
168
+ end
169
+ end.parse!(params)
170
+ ignore_nested_packages = true
171
+ else
172
+ paths = params
173
+ ignore_nested_packages = false
174
+ end
175
+
159
176
  ParseRun.new(
160
- files: fetch_files_to_process(paths),
177
+ files: fetch_files_to_process(paths, ignore_nested_packages),
161
178
  configuration: @configuration,
162
179
  progress_formatter: @progress_formatter,
163
180
  offenses_formatter: @offenses_formatter
@@ -34,8 +34,7 @@ module Packwerk
34
34
  DEFAULT_EXCLUDE_GLOBS = ["{bin,node_modules,script,tmp,vendor}/**/*"]
35
35
 
36
36
  attr_reader(
37
- :include, :exclude, :root_path, :package_paths, :custom_associations, :load_paths, :inflections_file,
38
- :config_path,
37
+ :include, :exclude, :root_path, :package_paths, :custom_associations, :config_path, :cache_directory
39
38
  )
40
39
 
41
40
  def initialize(configs = {}, config_path: nil)
@@ -45,15 +44,51 @@ module Packwerk
45
44
  @root_path = File.expand_path(root)
46
45
  @package_paths = configs["package_paths"] || "**/"
47
46
  @custom_associations = configs["custom_associations"] || []
48
- @load_paths = configs["load_paths"] || []
49
- @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
50
47
  @parallel = configs.key?("parallel") ? configs["parallel"] : true
51
-
48
+ @cache_enabled = configs.key?("cache") ? configs["cache"] : false
49
+ @cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
52
50
  @config_path = config_path
51
+
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
57
+
58
+ warn(warning)
59
+ end
60
+
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
67
+
68
+ warn(warning)
69
+ end
70
+
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
77
+
78
+ warn(warning)
79
+ end
80
+ end
81
+
82
+ def load_paths
83
+ @load_paths ||= ApplicationLoadPaths.extract_relevant_paths(@root_path, "test")
53
84
  end
54
85
 
55
86
  def parallel?
56
87
  @parallel
57
88
  end
89
+
90
+ def cache_enabled?
91
+ @cache_enabled
92
+ end
58
93
  end
59
94
  end
@@ -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 "constant_resolver"
5
5
 
6
6
  module Packwerk
7
- # Get information about (partially qualified) constants without loading the application code.
7
+ # Get information about unresolved constants without loading the application code.
8
8
  # Information gathered: Fully qualified name, path to file containing the definition, package,
9
9
  # and visibility (public/private to the package).
10
10
  #
@@ -35,9 +35,9 @@ module Packwerk
35
35
  end
36
36
 
37
37
  # Analyze a constant via its name.
38
- # If the name is partially qualified, we need the current namespace path to correctly infer its full name
38
+ # If the constant is unresolved, we need the current namespace path to correctly infer its full name
39
39
  #
40
- # @param const_name [String] The constant's name, fully or partially qualified.
40
+ # @param const_name [String] The unresolved constant's name.
41
41
  # @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
42
42
  # used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
43
43
  # @return [Packwerk::ConstantDiscovery::ConstantContext]
@@ -45,7 +45,7 @@ module Packwerk
45
45
  begin
46
46
  constant = @resolver.resolve(const_name, current_namespace_path: current_namespace_path)
47
47
  rescue ConstantResolver::Error => e
48
- raise(ConstantResolver::Error, e.message + "\n Make sure autoload paths are added to the config file.")
48
+ raise(ConstantResolver::Error, e.message)
49
49
  end
50
50
 
51
51
  return unless constant
@@ -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,16 @@ 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)
19
+ @deprecated_references = T.let(nil, T.nilable(ENTRIES_TYPE))
15
20
  end
16
21
 
17
22
  sig do
@@ -73,7 +78,7 @@ module Packwerk
73
78
  #
74
79
  # You can regenerate this file using the following command:
75
80
  #
76
- # packwerk update-deprecations #{@package.name}
81
+ # bin/packwerk update-deprecations #{@package.name}
77
82
  MESSAGE
78
83
  File.open(@filepath, "w") do |f|
79
84
  f.write(message)
@@ -84,7 +89,7 @@ module Packwerk
84
89
 
85
90
  private
86
91
 
87
- sig { returns(Hash) }
92
+ sig { returns(ENTRIES_TYPE) }
88
93
  def prepare_entries_for_dump
89
94
  @new_entries.each do |package_name, package_violations|
90
95
  package_violations.each do |_, entries_for_file|
@@ -97,13 +102,20 @@ module Packwerk
97
102
  @new_entries = @new_entries.sort.to_h
98
103
  end
99
104
 
100
- sig { returns(Hash) }
105
+ sig { returns(ENTRIES_TYPE) }
101
106
  def deprecated_references
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,39 +1,78 @@
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")
11
13
  end
12
14
  end
13
15
 
14
- def initialize(node_processor_factory:, parser_factory: nil)
16
+ sig do
17
+ params(
18
+ node_processor_factory: NodeProcessorFactory,
19
+ cache: Cache,
20
+ parser_factory: T.nilable(Parsers::Factory)
21
+ ).void
22
+ end
23
+ def initialize(node_processor_factory:, cache:, parser_factory: nil)
15
24
  @node_processor_factory = node_processor_factory
25
+ @cache = cache
16
26
  @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
17
27
  end
18
28
 
29
+ sig do
30
+ params(file_path: String).returns(
31
+ T::Array[
32
+ T.any(
33
+ Packwerk::UnresolvedReference,
34
+ Packwerk::Offense,
35
+ )
36
+ ]
37
+ )
38
+ end
19
39
  def call(file_path)
20
- parser = @parser_factory.for_path(file_path)
21
- return [UnknownFileTypeResult.new(file: file_path)] if parser.nil?
40
+ return [UnknownFileTypeResult.new(file: file_path)] if parser_for(file_path).nil?
41
+
42
+ @cache.with_cache(file_path) do
43
+ node = parse_into_ast(file_path)
22
44
 
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]
45
+ return [] unless node
46
+
47
+ references_from_ast(node, file_path)
27
48
  end
49
+ rescue Parsers::ParseError => e
50
+ [e.result]
51
+ end
28
52
 
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)
53
+ private
33
54
 
34
- node_visitor.visit(node, ancestors: [], result: result)
55
+ sig do
56
+ params(node: Parser::AST::Node, file_path: String).returns(T::Array[UnresolvedReference])
57
+ end
58
+ def references_from_ast(node, file_path)
59
+ references = []
60
+
61
+ node_processor = @node_processor_factory.for(filename: file_path, node: node)
62
+ node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
63
+ node_visitor.visit(node, ancestors: [], result: references)
64
+
65
+ references
66
+ end
67
+
68
+ def parse_into_ast(file_path)
69
+ File.open(file_path, "r", nil, external_encoding: Encoding::UTF_8) do |file|
70
+ parser_for(file_path).call(io: file, file_path: file_path)
35
71
  end
36
- result
72
+ end
73
+
74
+ def parser_for(file_path)
75
+ @parser_factory.for_path(file_path)
37
76
  end
38
77
  end
39
78
  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
@@ -44,7 +44,7 @@ module Packwerk
44
44
 
45
45
  sig { params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
46
46
  def offenses_summary(offenses)
47
- offenses_string = Inflector.default.pluralize("offense", offenses.length)
47
+ offenses_string = "offense".pluralize(offenses.length)
48
48
  "#{offenses.length} #{offenses_string} detected"
49
49
  end
50
50
  end
@@ -16,7 +16,7 @@ module Packwerk
16
16
 
17
17
  def started(target_files)
18
18
  files_size = target_files.size
19
- files_string = Inflector.default.pluralize("file", files_size)
19
+ files_string = "file".pluralize(files_size)
20
20
  @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
21
21
  end
22
22
 
@@ -11,18 +11,15 @@ module Packwerk
11
11
  CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
12
12
 
13
13
  class << self
14
- def generate(load_paths:, root:, out:)
15
- new(load_paths: load_paths, root: root, out: out).generate
14
+ def generate(root:, out:)
15
+ new(root: root, out: out).generate
16
16
  end
17
17
  end
18
18
 
19
- sig { params(load_paths: T::Array[String], root: String, out: T.any(StringIO, IO)).void }
20
- def initialize(load_paths:, root:, out: $stdout)
21
- @load_paths = load_paths
19
+ sig { params(root: String, out: T.any(StringIO, IO)).void }
20
+ def initialize(root:, out: $stdout)
22
21
  @root = root
23
22
  @out = out
24
-
25
- set_template_variables
26
23
  end
27
24
 
28
25
  sig { returns(T::Boolean) }
@@ -43,18 +40,6 @@ module Packwerk
43
40
 
44
41
  private
45
42
 
46
- def set_template_variables
47
- @load_paths_formatted = if @load_paths.empty?
48
- "# load_paths:\n# - 'app/models'\n"
49
- else
50
- @load_paths.map { |path| "- #{path}\n" }.join
51
- end
52
-
53
- @load_paths_comment = unless @load_paths.empty?
54
- "# These load paths were auto generated by Packwerk.\nload_paths:\n"
55
- end
56
- end
57
-
58
43
  def render
59
44
  ERB.new(template, trim_mode: "-").result(binding)
60
45
  end
@@ -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