packwerk 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +9 -7
  3. data/README.md +2 -1
  4. data/TROUBLESHOOT.md +3 -3
  5. data/USAGE.md +27 -11
  6. data/exe/packwerk +7 -1
  7. data/lib/packwerk/application_load_paths.rb +1 -0
  8. data/lib/packwerk/application_validator.rb +3 -1
  9. data/lib/packwerk/cli.rb +27 -5
  10. data/lib/packwerk/const_node_inspector.rb +3 -2
  11. data/lib/packwerk/constant_name_inspector.rb +1 -1
  12. data/lib/packwerk/deprecated_references.rb +11 -6
  13. data/lib/packwerk/file_processor.rb +39 -14
  14. data/lib/packwerk/files_for_processing.rb +15 -4
  15. data/lib/packwerk/generators/templates/package.yml +1 -1
  16. data/lib/packwerk/graph.rb +2 -0
  17. data/lib/packwerk/inflector.rb +1 -0
  18. data/lib/packwerk/node.rb +1 -0
  19. data/lib/packwerk/node_processor.rb +10 -22
  20. data/lib/packwerk/node_processor_factory.rb +0 -2
  21. data/lib/packwerk/node_visitor.rb +4 -2
  22. data/lib/packwerk/package.rb +25 -4
  23. data/lib/packwerk/package_set.rb +43 -8
  24. data/lib/packwerk/parsed_constant_definitions.rb +1 -0
  25. data/lib/packwerk/reference.rb +2 -1
  26. data/lib/packwerk/reference_checking/checkers/checker.rb +21 -0
  27. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +31 -0
  28. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +58 -0
  29. data/lib/packwerk/reference_checking/reference_checker.rb +33 -0
  30. data/lib/packwerk/reference_extractor.rb +2 -2
  31. data/lib/packwerk/reference_offense.rb +1 -0
  32. data/lib/packwerk/run_context.rb +9 -6
  33. data/lib/packwerk/sanity_checker.rb +1 -1
  34. data/lib/packwerk/spring_command.rb +28 -0
  35. data/lib/packwerk/version.rb +1 -1
  36. data/lib/packwerk/violation_type.rb +1 -1
  37. data/lib/packwerk.rb +14 -3
  38. data/packwerk.gemspec +3 -1
  39. data/service.yml +0 -2
  40. data/sorbet/rbi/gems/psych@3.3.2.rbi +24 -0
  41. metadata +23 -6
  42. data/lib/packwerk/checker.rb +0 -17
  43. data/lib/packwerk/dependency_checker.rb +0 -26
  44. data/lib/packwerk/privacy_checker.rb +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bd7c65bd8c7e2c9448f0c0ab3dffc69291c0a379d7da46b7deb7a70e87b805f
4
- data.tar.gz: ddfeb4e7d17a5deba8ab1319897adefe77462d64f088e3ae8ddfbfa0ecefa1d0
3
+ metadata.gz: 5d150d89ede62e601246e2ff0290ecda4aab883a7c8b8be02feb86760ad26b72
4
+ data.tar.gz: 33b088b3387b8fdfe694967f20529fb8795dec8572dc9e036cb4979402f19c5b
5
5
  SHA512:
6
- metadata.gz: 784b0938aae086fc90d362542b3b372fa235790f40983be1172c3563be4c82f13052642638447496043c94e68e72dcdb8adc344ee8336130e2130b92afb9bccc
7
- data.tar.gz: e464eb8f88b8827cace5a7c38a4cf653d686bd19327e1b1123a6828d075be8919f9a73cbca57413a8ed859225525d9b64e57406b9d4b25ebac3f5b3c91703b03
6
+ metadata.gz: 102154447670b85dc4e0825dcdf1ec11cd0fd291ffc1c7230b98da2208b0a39c38d2c8726a33226aeea68082e16a24917db7ad3380c871b9b02b0d76c8ac69fc
7
+ data.tar.gz: 666e3745e9167028e5d3901d74400848c87c1b8a64a7487d3e4bfe13916e2902990633db7c448057b8106fd4b6b5c5de08c697c88151a305c92d34259266448f
data/Gemfile.lock CHANGED
@@ -87,10 +87,11 @@ GIT
87
87
  PATH
88
88
  remote: .
89
89
  specs:
90
- packwerk (1.3.2)
90
+ packwerk (1.4.0)
91
91
  activesupport (>= 5.2)
92
92
  ast
93
93
  better_html
94
+ bundler
94
95
  constant_resolver
95
96
  parallel
96
97
  parser
@@ -135,16 +136,16 @@ GEM
135
136
  marcel (1.0.0)
136
137
  method_source (1.0.0)
137
138
  mini_mime (1.0.3)
138
- mini_portile2 (2.5.1)
139
+ mini_portile2 (2.6.1)
139
140
  minitest (5.14.4)
140
141
  minitest-focus (1.2.1)
141
142
  minitest (>= 4, < 6)
142
143
  mocha (1.12.0)
143
144
  nio4r (2.5.7)
144
- nokogiri (1.11.5)
145
- mini_portile2 (~> 2.5.0)
145
+ nokogiri (1.12.5)
146
+ mini_portile2 (~> 2.6.1)
146
147
  racc (~> 1.4)
147
- nokogiri (1.11.5-x86_64-darwin)
148
+ nokogiri (1.12.5-x86_64-darwin)
148
149
  racc (~> 1.4)
149
150
  parallel (1.20.1)
150
151
  parlour (6.0.0)
@@ -157,6 +158,7 @@ GEM
157
158
  pry (0.14.0)
158
159
  coderay (~> 1.1)
159
160
  method_source (~> 1.0)
161
+ psych (3.3.2)
160
162
  racc (1.5.2)
161
163
  rack (2.2.3)
162
164
  rack-test (1.1.0)
@@ -189,7 +191,7 @@ GEM
189
191
  rubocop-sorbet (0.6.1)
190
192
  rubocop
191
193
  ruby-progressbar (1.11.0)
192
- smart_properties (1.15.0)
194
+ smart_properties (1.16.3)
193
195
  sorbet (0.5.6360)
194
196
  sorbet-static (= 0.5.6360)
195
197
  sorbet-runtime (0.5.6360)
@@ -237,13 +239,13 @@ PLATFORMS
237
239
  x86_64-darwin-20
238
240
 
239
241
  DEPENDENCIES
240
- bundler
241
242
  byebug
242
243
  constant_resolver
243
244
  m
244
245
  minitest-focus
245
246
  mocha
246
247
  packwerk!
248
+ psych (~> 3)
247
249
  rails!
248
250
  rake
249
251
  rubocop-performance
data/README.md CHANGED
@@ -47,7 +47,8 @@ Or install it yourself as:
47
47
 
48
48
  $ gem install packwerk
49
49
 
50
- 3. Run `packwerk init` to generate the configuration files
50
+ 2. Run `bundle binstub packwerk` to generate the binstub
51
+ 3. Run `bin/packwerk init` to generate the configuration files
51
52
 
52
53
  ## Usage
53
54
 
data/TROUBLESHOOT.md CHANGED
@@ -14,11 +14,11 @@ Packwerk can give feedback via continuous integration (CI) if you have it set up
14
14
 
15
15
  You can specify folders or packages in Packwerk commands for a shorter run time:
16
16
 
17
- packwerk check components/your_package
17
+ bin/packwerk check components/your_package
18
18
 
19
- packwerk update-deprecations components/your_package
19
+ bin/packwerk update-deprecations components/your_package
20
20
 
21
- _Note: You cannot specify folders or packages for `packwerk validate` because the command runs for the entire application._
21
+ _Note: You cannot specify folders or packages for `bin/packwerk validate` because the command runs for the entire application._
22
22
 
23
23
  ![](static/packwerk_check_violation.gif)
24
24
 
data/USAGE.md CHANGED
@@ -6,7 +6,9 @@
6
6
  * [What is a package?](#what-is-a-package)
7
7
  * [Package principles](#package-principles)
8
8
  * [Getting started](#getting-started)
9
- * [Setting up the configuration file](#setting-up-the-configuration-file)
9
+ * [Setting up Spring](#setting-up-spring)
10
+ * [Configuring Packwerk](#configuring-packwerk)
11
+ * [Using a custom ERB parser](#using-a-custom-erb-parser)
10
12
  * [Inflections](#inflections)
11
13
  * [Validating the package system](#validating-the-package-system)
12
14
  * [Defining packages](#defining-packages)
@@ -42,9 +44,12 @@ The [package principles](https://en.wikipedia.org/wiki/Package_principles) page
42
44
 
43
45
  ## Getting started
44
46
 
45
- After including Packwerk in the Gemfile, you can generate the necessary files to get Packwerk running by executing:
47
+ After including Packwerk in the Gemfile, you will first want to generate a binstub:
48
+ You can do this by running `bundle binstub packwerk`, which will generate a [binstub](https://bundler.io/man/bundle-binstubs.1.html#DESCRIPTION) at `bin/packwerk`.
46
49
 
47
- packwerk init
50
+ Then, you can generate the necessary files to get Packwerk running by executing:
51
+
52
+ bin/packwerk init
48
53
 
49
54
  Here is a list of files generated:
50
55
 
@@ -56,7 +61,14 @@ Here is a list of files generated:
56
61
 
57
62
  After that, you may begin creating packages for your application. See [Defining packages](#Defining-packages)
58
63
 
59
- ## Setting up the configuration file
64
+ ### Setting up Spring
65
+
66
+ [Spring](https://github.com/rails/spring) is a preloader for Rails. Because `packwerk` loads `Rails`, it can be sped up dramatically by enabling spring. Packwerk supports the usage of Spring.
67
+ Firstly, spring needs to know about the packwerk spring command when spring is loading. To do that, add `require 'packwerk/spring_command'` to `config/spring.rb` in your application.
68
+ Secondly, to enable Spring, first run `bin/spring binstub packwerk` which will "springify" the generated binstub.
69
+
70
+
71
+ ## Configuring Packwerk
60
72
 
61
73
  Packwerk reads from the `packwerk.yml` configuration file in the root directory. Packwerk will run with the default configuration if any of these settings are not specified.
62
74
 
@@ -134,7 +146,7 @@ Any new inflectors should be added to `config/inflections.yml`.
134
146
 
135
147
  There are some criteria that an application must meet in order to have a valid package system. These criteria include having a valid autoload path cache, package definition files, and application folder structure. The dependency graph within the package system also has to be acyclic.
136
148
 
137
- We recommend setting up the package system validation for your Rails application in a CI step (or through a test suite for Ruby projects) separate from `packwerk check`.
149
+ We recommend setting up the package system validation for your Rails application in a CI step (or through a test suite for Ruby projects) separate from `bin/packwerk check`.
138
150
 
139
151
  Use the following command to validate the application:
140
152
 
@@ -231,13 +243,17 @@ After enforcing the boundary checks for a package, you may execute:
231
243
 
232
244
  Packwerk will check the entire codebase for any new or stale violations.
233
245
 
234
- You can also specify folders for a shorter run time:
246
+ You can also specify folders for a shorter run time. When checking against folders all subfolders will be analyzed, irrespective of nested package boundaries.
235
247
 
236
248
  packwerk check components/your_package
237
249
 
250
+ You can also specify packages for a shorter run time. When checking against packages any packages nested underneath the specified packages will not be checked. This can be helpful to test packages like the root package, which can have many nested packages.
251
+
252
+ packwerk check --packages=components/your_package,components/your_other_package
253
+
238
254
  ![](static/packwerk_check.gif)
239
255
 
240
- In order to keep the package system valid at each version of the application, we recommend running `packwerk check` in your CI pipeline.
256
+ In order to keep the package system valid at each version of the application, we recommend running `bin/packwerk check` in your CI pipeline.
241
257
 
242
258
  See: [TROUBLESHOOT.md - Sample violations](TROUBLESHOOT.md#Sample-violations)
243
259
 
@@ -247,17 +263,17 @@ For existing codebases, packages are likely to have existing boundary violations
247
263
 
248
264
  If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [deprecated references list](#Understanding_the_list_of_deprecated_references) by executing:
249
265
 
250
- packwerk update-deprecations
266
+ bin/packwerk update-deprecations
251
267
 
252
- Similar to `packwerk check`, you may also run `packwerk update-deprecations` on folders or packages:
268
+ Similar to `bin/packwerk check`, you may also run `bin/packwerk update-deprecations` on folders or packages:
253
269
 
254
- packwerk update-deprecations components/your_package
270
+ bin/packwerk update-deprecations components/your_package
255
271
 
256
272
  ![](static/packwerk_update.gif)
257
273
 
258
274
  _Note: Changing dependencies or enabling dependencies will not require a full update of the codebase, only the package that changed. On the other hand, changing or enabling privacy will require a full update of the codebase._
259
275
 
260
- `packwerk update-deprecations` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `packwerk update-deprecations` to resolve a violation should be the very last resort.
276
+ `bin/packwerk update-deprecations` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `bin/packwerk update-deprecations` to resolve a violation should be the very last resort.
261
277
 
262
278
  See: [TROUBLESHOOT.md - Troubleshooting violations](TROUBLESHOOT.md#Troubleshooting_violations)
263
279
 
data/exe/packwerk CHANGED
@@ -3,4 +3,10 @@
3
3
 
4
4
  require "packwerk"
5
5
 
6
- Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured.new).run(ARGV.dup)
6
+ # Needs to be run in test environment in order to have test helper paths available in the autoload paths
7
+ ENV["RAILS_ENV"] = "test"
8
+
9
+ # Command line arguments needs to be duplicated because spring modifies it
10
+ packwerk_argv = ARGV.dup
11
+ cli = Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured.new)
12
+ cli.run(packwerk_argv)
@@ -4,6 +4,7 @@
4
4
  require "bundler"
5
5
 
6
6
  module Packwerk
7
+ # Extracts the load paths from the analyzed application so that we can map constant names to paths.
7
8
  module ApplicationLoadPaths
8
9
  class << self
9
10
  extend T::Sig
@@ -7,6 +7,8 @@ require "pathname"
7
7
  require "yaml"
8
8
 
9
9
  module Packwerk
10
+ # Checks the structure of the application and its packwerk configuration to make sure we can run a check and deliver
11
+ # correct results.
10
12
  class ApplicationValidator
11
13
  def initialize(config_file_path:, configuration:, environment:)
12
14
  @config_file_path = config_file_path
@@ -296,7 +298,7 @@ module Packwerk
296
298
  end
297
299
 
298
300
  def package_manifests(glob_pattern = package_glob)
299
- PackageSet.package_paths(@configuration.root_path, glob_pattern)
301
+ PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
300
302
  .map { |f| File.realpath(f) }
301
303
  end
302
304
 
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
 
@@ -97,7 +100,7 @@ module Packwerk
97
100
  result = if success
98
101
  <<~EOS
99
102
 
100
- 🎉 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`.
101
104
  For more information on how to use Packwerk, see: https://github.com/Shopify/packwerk/blob/main/USAGE.md
102
105
  EOS
103
106
  else
@@ -123,8 +126,12 @@ module Packwerk
123
126
  result.status
124
127
  end
125
128
 
126
- def fetch_files_to_process(paths)
127
- 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
+ )
128
135
  abort("No files found or given. "\
129
136
  "Specify files or check the include and exclude glob in the config file.") if files.empty?
130
137
  files
@@ -155,9 +162,24 @@ module Packwerk
155
162
  end
156
163
  end
157
164
 
158
- def parse_run(paths)
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
176
+ else
177
+ paths = params
178
+ ignore_nested_packages = false
179
+ end
180
+
159
181
  ParseRun.new(
160
- files: fetch_files_to_process(paths),
182
+ files: fetch_files_to_process(paths, ignore_nested_packages),
161
183
  configuration: @configuration,
162
184
  progress_formatter: @progress_formatter,
163
185
  offenses_formatter: @offenses_formatter
@@ -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
@@ -73,7 +77,7 @@ module Packwerk
73
77
  #
74
78
  # You can regenerate this file using the following command:
75
79
  #
76
- # 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,8 +101,9 @@ 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
108
  load_yaml(@filepath)
104
109
  else
@@ -106,7 +111,7 @@ module Packwerk
106
111
  end
107
112
  end
108
113
 
109
- sig { params(filepath: String).returns(Hash) }
114
+ sig { params(filepath: String).returns(ENTRIES_TYPE) }
110
115
  def load_yaml(filepath)
111
116
  YAML.load_file(filepath) || {}
112
117
  rescue Psych::Exception
@@ -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
@@ -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|