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