packwerk 1.3.1 → 2.1.0

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