packwerk 2.3.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/.ruby-version +1 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -95
- data/README.md +2 -7
- data/TROUBLESHOOT.md +0 -22
- data/USAGE.md +141 -51
- data/dev.yml +1 -1
- data/exe/packwerk +1 -0
- data/gemfiles/Gemfile-rails-6-1 +1 -1
- data/lib/packwerk/application_validator.rb +54 -285
- data/lib/packwerk/association_inspector.rb +2 -0
- data/lib/packwerk/cache.rb +6 -5
- data/lib/packwerk/checker.rb +54 -0
- data/lib/packwerk/cli/result.rb +11 -0
- data/lib/packwerk/cli.rb +55 -40
- data/lib/packwerk/configuration.rb +61 -40
- data/lib/packwerk/const_node_inspector.rb +2 -0
- data/lib/packwerk/constant_context.rb +8 -0
- data/lib/packwerk/constant_discovery.rb +5 -6
- data/lib/packwerk/constant_name_inspector.rb +2 -0
- data/lib/packwerk/disable_sorbet.rb +41 -0
- data/lib/packwerk/extension_loader.rb +24 -0
- data/lib/packwerk/file_processor.rb +3 -1
- data/lib/packwerk/files_for_processing.rb +25 -12
- data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
- data/lib/packwerk/formatters/progress_formatter.rb +31 -12
- data/lib/packwerk/generators/configuration_file.rb +7 -2
- data/lib/packwerk/generators/root_package.rb +5 -1
- data/lib/packwerk/generators/templates/package.yml +0 -10
- data/lib/packwerk/graph.rb +10 -2
- data/lib/packwerk/node.rb +1 -1
- data/lib/packwerk/node_helpers.rb +14 -7
- data/lib/packwerk/node_processor.rb +2 -0
- data/lib/packwerk/node_processor_factory.rb +6 -4
- data/lib/packwerk/node_visitor.rb +10 -1
- data/lib/packwerk/offense_collection.rb +26 -18
- data/lib/packwerk/offenses_formatter.rb +59 -2
- data/lib/packwerk/package.rb +7 -35
- data/lib/packwerk/package_set.rb +1 -1
- data/lib/packwerk/package_todo.rb +15 -9
- data/lib/packwerk/parse_run.rb +27 -34
- data/lib/packwerk/parsed_constant_definitions.rb +28 -5
- data/lib/packwerk/parsers/erb.rb +23 -4
- data/lib/packwerk/parsers/factory.rb +11 -2
- data/lib/packwerk/parsers/parser_interface.rb +1 -1
- data/lib/packwerk/parsers/ruby.rb +13 -3
- data/lib/packwerk/parsers.rb +6 -2
- data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
- data/lib/packwerk/reference.rb +7 -1
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
- data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
- data/lib/packwerk/reference_extractor.rb +24 -12
- data/lib/packwerk/reference_offense.rb +2 -2
- data/lib/packwerk/run_context.rb +7 -10
- data/lib/packwerk/spring_command.rb +11 -2
- data/lib/packwerk/unresolved_reference.rb +9 -1
- data/lib/packwerk/validator/result.rb +18 -0
- data/lib/packwerk/validator.rb +90 -0
- data/lib/packwerk/validators/dependency_validator.rb +154 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +64 -26
- data/packwerk.gemspec +4 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
- data/sorbet/rbi/shims/minitest/test.rb +8 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
- data/sorbet/rbi/shims/parser.rbi +13 -0
- metadata +34 -15
- data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
- data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
- data/lib/packwerk/result.rb +0 -9
- data/lib/packwerk/sanity_checker.rb +0 -8
- data/lib/packwerk/violation_type.rb +0 -11
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "benchmark"
|
@@ -14,37 +14,56 @@ module Packwerk
|
|
14
14
|
@style = style
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
files_size = target_files.size
|
19
|
-
files_string = "file".pluralize(files_size)
|
20
|
-
@out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
|
21
|
-
end
|
22
|
-
|
17
|
+
sig { params(block: T.proc.void).void }
|
23
18
|
def started_validation(&block)
|
24
|
-
|
19
|
+
start_validation
|
25
20
|
|
26
21
|
execution_time = Benchmark.realtime(&block)
|
27
22
|
finished(execution_time)
|
23
|
+
end
|
28
24
|
|
29
|
-
|
25
|
+
sig { params(target_files: FilesForProcessing::RelativeFileSet, block: T.proc.void).void }
|
26
|
+
def started_inspection(target_files, &block)
|
27
|
+
start_inspection(target_files)
|
28
|
+
|
29
|
+
execution_time = Benchmark.realtime(&block)
|
30
|
+
finished(execution_time)
|
30
31
|
end
|
31
32
|
|
33
|
+
sig { void }
|
32
34
|
def mark_as_inspected
|
33
35
|
@out.print(".")
|
34
36
|
end
|
35
37
|
|
38
|
+
sig { void }
|
36
39
|
def mark_as_failed
|
37
40
|
@out.print("#{@style.error}E#{@style.reset}")
|
38
41
|
end
|
39
42
|
|
43
|
+
sig { void }
|
44
|
+
def interrupted
|
45
|
+
@out.puts
|
46
|
+
@out.puts("Manually interrupted. Violations caught so far are listed below:")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { params(execution_time: Float).void }
|
40
52
|
def finished(execution_time)
|
41
53
|
@out.puts
|
42
54
|
@out.puts("📦 Finished in #{execution_time.round(2)} seconds")
|
43
55
|
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
@out.puts("
|
57
|
+
sig { void }
|
58
|
+
def start_validation
|
59
|
+
@out.puts("📦 Packwerk is running validation...")
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(target_files: FilesForProcessing::RelativeFileSet).void }
|
63
|
+
def start_inspection(target_files)
|
64
|
+
files_size = target_files.size
|
65
|
+
files_string = "file".pluralize(files_size)
|
66
|
+
@out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
|
48
67
|
end
|
49
68
|
end
|
50
69
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "erb"
|
@@ -11,6 +11,9 @@ module Packwerk
|
|
11
11
|
CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
|
12
12
|
|
13
13
|
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
|
14
17
|
def generate(root:, out:)
|
15
18
|
new(root: root, out: out).generate
|
16
19
|
end
|
@@ -25,7 +28,7 @@ module Packwerk
|
|
25
28
|
sig { returns(T::Boolean) }
|
26
29
|
def generate
|
27
30
|
@out.puts("📦 Generating Packwerk configuration file...")
|
28
|
-
default_config_path = File.join(@root,
|
31
|
+
default_config_path = File.join(@root, Configuration::DEFAULT_CONFIG_PATH)
|
29
32
|
|
30
33
|
if File.exist?(default_config_path)
|
31
34
|
@out.puts("⚠️ Packwerk configuration file already exists.")
|
@@ -40,10 +43,12 @@ module Packwerk
|
|
40
43
|
|
41
44
|
private
|
42
45
|
|
46
|
+
sig { returns(String) }
|
43
47
|
def render
|
44
48
|
ERB.new(template, trim_mode: "-").result(binding)
|
45
49
|
end
|
46
50
|
|
51
|
+
sig { returns(String) }
|
47
52
|
def template
|
48
53
|
template_file_path = File.join(__dir__, CONFIGURATION_TEMPLATE_FILE_PATH)
|
49
54
|
File.read(template_file_path)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -7,11 +7,15 @@ module Packwerk
|
|
7
7
|
extend T::Sig
|
8
8
|
|
9
9
|
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
|
10
13
|
def generate(root:, out:)
|
11
14
|
new(root: root, out: out).generate
|
12
15
|
end
|
13
16
|
end
|
14
17
|
|
18
|
+
sig { params(root: String, out: T.any(IO, StringIO)).void }
|
15
19
|
def initialize(root:, out: $stdout)
|
16
20
|
@root = root
|
17
21
|
@out = out
|
@@ -5,16 +5,6 @@
|
|
5
5
|
# Turn on dependency checks for this package
|
6
6
|
enforce_dependencies: true
|
7
7
|
|
8
|
-
# Turn on privacy checks for this package
|
9
|
-
# enforcing privacy is often not useful for the root package, because it would require defining a public interface
|
10
|
-
# for something that should only be a thin wrapper in the first place.
|
11
|
-
# We recommend enabling this for any new packages you create to aid with encapsulation.
|
12
|
-
enforce_privacy: false
|
13
|
-
|
14
|
-
# By default the public path will be app/public/, however this may not suit all applications' architecture so
|
15
|
-
# this allows you to modify what your package's public path is.
|
16
|
-
# public_path: app/public/
|
17
|
-
|
18
8
|
# A list of this package's dependencies
|
19
9
|
# Note that packages in this list require their own `package.yml` file
|
20
10
|
# dependencies:
|
data/lib/packwerk/graph.rb
CHANGED
@@ -4,8 +4,14 @@
|
|
4
4
|
module Packwerk
|
5
5
|
# A general implementation of a graph data structure with the ability to check for - and list - cycles.
|
6
6
|
class Graph
|
7
|
-
|
8
|
-
|
7
|
+
extend T::Sig
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
# The edges of the graph; An edge being represented as an Array of two nodes.
|
11
|
+
edges: T::Array[T::Array[T.any(String, Integer, NilClass)]]
|
12
|
+
).void
|
13
|
+
end
|
14
|
+
def initialize(edges)
|
9
15
|
@edges = edges.uniq
|
10
16
|
@cycles = Set.new
|
11
17
|
process
|
@@ -73,4 +79,6 @@ module Packwerk
|
|
73
79
|
@cycles << cycle
|
74
80
|
end
|
75
81
|
end
|
82
|
+
|
83
|
+
private_constant :Graph
|
76
84
|
end
|
data/lib/packwerk/node.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "parser"
|
@@ -59,11 +59,16 @@ module Packwerk
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
sig
|
63
|
-
|
64
|
-
|
62
|
+
sig do
|
63
|
+
params(
|
64
|
+
node: AST::Node,
|
65
|
+
block: T.nilable(T.proc.params(arg0: Parser::AST::Node).void),
|
66
|
+
).returns(T::Enumerable[AST::Node])
|
67
|
+
end
|
68
|
+
def each_child(node, &block)
|
69
|
+
if block
|
65
70
|
node.children.each do |child|
|
66
|
-
yield
|
71
|
+
yield(child) if child.is_a?(Parser::AST::Node)
|
67
72
|
end
|
68
73
|
else
|
69
74
|
enum_for(:each_child, node)
|
@@ -181,9 +186,9 @@ module Packwerk
|
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
184
|
-
sig { params(node:
|
189
|
+
sig { params(node: AST::Node).returns(T.nilable(Node::Location)) }
|
185
190
|
def name_location(node)
|
186
|
-
location = node.location
|
191
|
+
location = T.cast(node, Parser::AST::Node).location
|
187
192
|
|
188
193
|
if location.respond_to?(:name)
|
189
194
|
name = location.name
|
@@ -332,4 +337,6 @@ module Packwerk
|
|
332
337
|
end
|
333
338
|
end
|
334
339
|
end
|
340
|
+
|
341
|
+
private_constant :NodeHelpers
|
335
342
|
end
|
@@ -6,12 +6,12 @@ module Packwerk
|
|
6
6
|
extend T::Sig
|
7
7
|
|
8
8
|
const :root_path, String
|
9
|
-
const :context_provider,
|
9
|
+
const :context_provider, ConstantDiscovery
|
10
10
|
const :constant_name_inspectors, T::Array[ConstantNameInspector]
|
11
11
|
|
12
12
|
sig { params(relative_file: String, node: AST::Node).returns(NodeProcessor) }
|
13
13
|
def for(relative_file:, node:)
|
14
|
-
|
14
|
+
NodeProcessor.new(
|
15
15
|
reference_extractor: reference_extractor(node: node),
|
16
16
|
relative_file: relative_file,
|
17
17
|
)
|
@@ -19,13 +19,15 @@ module Packwerk
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
sig { params(node: AST::Node).returns(
|
22
|
+
sig { params(node: AST::Node).returns(ReferenceExtractor) }
|
23
23
|
def reference_extractor(node:)
|
24
|
-
|
24
|
+
ReferenceExtractor.new(
|
25
25
|
constant_name_inspectors: constant_name_inspectors,
|
26
26
|
root_node: node,
|
27
27
|
root_path: root_path,
|
28
28
|
)
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
private_constant :NodeProcessorFactory
|
31
33
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -11,6 +11,13 @@ module Packwerk
|
|
11
11
|
@node_processor = node_processor
|
12
12
|
end
|
13
13
|
|
14
|
+
sig do
|
15
|
+
params(
|
16
|
+
node: Parser::AST::Node,
|
17
|
+
ancestors: T::Array[Parser::AST::Node],
|
18
|
+
result: T::Array[UnresolvedReference],
|
19
|
+
).void
|
20
|
+
end
|
14
21
|
def visit(node, ancestors:, result:)
|
15
22
|
reference = @node_processor.call(node, ancestors)
|
16
23
|
result << reference if reference
|
@@ -21,4 +28,6 @@ module Packwerk
|
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
31
|
+
|
32
|
+
private_constant :NodeVisitor
|
24
33
|
end
|
@@ -18,6 +18,7 @@ module Packwerk
|
|
18
18
|
@root_path = root_path
|
19
19
|
@package_todo = T.let(package_todo, T::Hash[Packwerk::Package, Packwerk::PackageTodo])
|
20
20
|
@new_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
|
21
|
+
@strict_mode_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
|
21
22
|
@errors = T.let([], T::Array[Packwerk::Offense])
|
22
23
|
end
|
23
24
|
|
@@ -27,6 +28,9 @@ module Packwerk
|
|
27
28
|
sig { returns(T::Array[Packwerk::Offense]) }
|
28
29
|
attr_reader :errors
|
29
30
|
|
31
|
+
sig { returns(T::Array[Packwerk::ReferenceOffense]) }
|
32
|
+
attr_reader :strict_mode_violations
|
33
|
+
|
30
34
|
sig do
|
31
35
|
params(offense: Packwerk::Offense)
|
32
36
|
.returns(T::Boolean)
|
@@ -35,7 +39,7 @@ module Packwerk
|
|
35
39
|
return false unless offense.is_a?(ReferenceOffense)
|
36
40
|
|
37
41
|
reference = offense.reference
|
38
|
-
package_todo_for(reference.
|
42
|
+
package_todo_for(reference.package).listed?(reference, violation_type: offense.violation_type)
|
39
43
|
end
|
40
44
|
|
41
45
|
sig do
|
@@ -46,9 +50,11 @@ module Packwerk
|
|
46
50
|
@errors << offense
|
47
51
|
return
|
48
52
|
end
|
49
|
-
|
50
|
-
|
53
|
+
|
54
|
+
if !already_listed?(offense)
|
51
55
|
new_violations << offense
|
56
|
+
elsif strict_mode_violation?(offense)
|
57
|
+
strict_mode_violations << offense
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
@@ -70,12 +76,19 @@ module Packwerk
|
|
70
76
|
errors + new_violations
|
71
77
|
end
|
72
78
|
|
73
|
-
|
74
|
-
|
75
|
-
|
79
|
+
private
|
80
|
+
|
81
|
+
sig { params(offense: ReferenceOffense).returns(T::Boolean) }
|
82
|
+
def already_listed?(offense)
|
83
|
+
package_todo_for(offense.reference.package).add_entries(offense.reference,
|
84
|
+
offense.violation_type)
|
76
85
|
end
|
77
86
|
|
78
|
-
|
87
|
+
sig { params(offense: ReferenceOffense).returns(T::Boolean) }
|
88
|
+
def strict_mode_violation?(offense)
|
89
|
+
checker = Checker.find(offense.violation_type)
|
90
|
+
checker.strict_mode_violation?(offense)
|
91
|
+
end
|
79
92
|
|
80
93
|
sig { params(package_set: Packwerk::PackageSet).void }
|
81
94
|
def cleanup_extra_package_todo_files(package_set)
|
@@ -89,6 +102,11 @@ module Packwerk
|
|
89
102
|
end
|
90
103
|
end
|
91
104
|
|
105
|
+
sig { void }
|
106
|
+
def dump_package_todo_files
|
107
|
+
@package_todo.each_value(&:dump)
|
108
|
+
end
|
109
|
+
|
92
110
|
sig { params(package: Packwerk::Package).returns(Packwerk::PackageTodo) }
|
93
111
|
def package_todo_for(package)
|
94
112
|
@package_todo[package] ||= Packwerk::PackageTodo.new(
|
@@ -99,17 +117,7 @@ module Packwerk
|
|
99
117
|
|
100
118
|
sig { params(package: Packwerk::Package).returns(String) }
|
101
119
|
def package_todo_file_for(package)
|
102
|
-
|
103
|
-
warning = <<~WARNING.squish
|
104
|
-
DEPRECATION WARNING: `deprecated_references.yml` files have been renamed to `package_todo.yml`.
|
105
|
-
Run `packwerk update-todo` to rename files automatically.
|
106
|
-
WARNING
|
107
|
-
|
108
|
-
warn(warning)
|
109
|
-
File.join(@root_path, package.name, "deprecated_references.yml")
|
110
|
-
else
|
111
|
-
File.join(@root_path, package.name, "package_todo.yml")
|
112
|
-
end
|
120
|
+
File.join(@root_path, package.name, "package_todo.yml")
|
113
121
|
end
|
114
122
|
end
|
115
123
|
end
|
@@ -6,14 +6,71 @@ module Packwerk
|
|
6
6
|
extend T::Sig
|
7
7
|
extend T::Helpers
|
8
8
|
|
9
|
-
|
9
|
+
abstract!
|
10
|
+
|
11
|
+
class DuplicateFormatterError < StandardError
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(identifier: String).void }
|
15
|
+
def initialize(identifier)
|
16
|
+
super("Cannot have multiple identifiers with the same key (`#{identifier}`)")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { params(base: Class).void }
|
24
|
+
def included(base)
|
25
|
+
@offenses_formatters ||= T.let(@offenses_formatters, T.nilable(T::Array[Class]))
|
26
|
+
@offenses_formatters ||= []
|
27
|
+
@offenses_formatters << base
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { returns(T::Array[OffensesFormatter]) }
|
31
|
+
def all
|
32
|
+
T.unsafe(@offenses_formatters).map(&:new)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { params(identifier: String).returns(OffensesFormatter) }
|
36
|
+
def find(identifier)
|
37
|
+
formatter_by_identifier(identifier)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
sig { params(name: String).returns(OffensesFormatter) }
|
43
|
+
def formatter_by_identifier(name)
|
44
|
+
@formatter_by_identifier ||= T.let(nil, T.nilable(T::Hash[String, T.nilable(OffensesFormatter)]))
|
45
|
+
@formatter_by_identifier ||= begin
|
46
|
+
index = T.let({}, T::Hash[String, T.nilable(OffensesFormatter)])
|
47
|
+
OffensesFormatter.all.each do |formatter|
|
48
|
+
identifier = formatter.identifier
|
49
|
+
raise DuplicateFormatterError, identifier if index.key?(identifier)
|
50
|
+
|
51
|
+
index[identifier] = formatter
|
52
|
+
end
|
53
|
+
T.let(index, T.nilable(T::Hash[String, T.nilable(OffensesFormatter)]))
|
54
|
+
end
|
55
|
+
|
56
|
+
T.must(T.must(@formatter_by_identifier)[name])
|
57
|
+
end
|
58
|
+
end
|
10
59
|
|
11
60
|
sig { abstract.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
|
12
61
|
def show_offenses(offenses)
|
13
62
|
end
|
14
63
|
|
15
|
-
sig { abstract.params(offense_collection:
|
64
|
+
sig { abstract.params(offense_collection: OffenseCollection, for_files: T::Set[String]).returns(String) }
|
16
65
|
def show_stale_violations(offense_collection, for_files)
|
17
66
|
end
|
67
|
+
|
68
|
+
sig { abstract.returns(String) }
|
69
|
+
def identifier
|
70
|
+
end
|
71
|
+
|
72
|
+
sig { abstract.params(strict_mode_violations: T::Array[ReferenceOffense]).returns(String) }
|
73
|
+
def show_strict_mode_violations(strict_mode_violations)
|
74
|
+
end
|
18
75
|
end
|
19
76
|
end
|
data/lib/packwerk/package.rb
CHANGED
@@ -17,22 +17,20 @@ module Packwerk
|
|
17
17
|
sig { returns(T::Array[String]) }
|
18
18
|
attr_reader :dependencies
|
19
19
|
|
20
|
-
sig {
|
21
|
-
|
20
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
21
|
+
attr_reader :config
|
22
|
+
|
23
|
+
sig { params(name: String, config: T.nilable(T::Hash[String, T.untyped])).void }
|
24
|
+
def initialize(name:, config: nil)
|
22
25
|
@name = name
|
23
|
-
@config = T.let(config || {}, T::Hash[
|
26
|
+
@config = T.let(config || {}, T::Hash[String, T.untyped])
|
24
27
|
@dependencies = T.let(Array(@config["dependencies"]).freeze, T::Array[String])
|
25
28
|
@public_path = T.let(nil, T.nilable(String))
|
26
29
|
end
|
27
30
|
|
28
|
-
sig { returns(T.nilable(T.any(T::Boolean, T::Array[String]))) }
|
29
|
-
def enforce_privacy
|
30
|
-
@config["enforce_privacy"]
|
31
|
-
end
|
32
|
-
|
33
31
|
sig { returns(T::Boolean) }
|
34
32
|
def enforce_dependencies?
|
35
|
-
@config["enforce_dependencies"]
|
33
|
+
[true, "strict"].include?(@config["enforce_dependencies"])
|
36
34
|
end
|
37
35
|
|
38
36
|
sig { params(package: Package).returns(T::Boolean) }
|
@@ -47,32 +45,6 @@ module Packwerk
|
|
47
45
|
path.start_with?(@name)
|
48
46
|
end
|
49
47
|
|
50
|
-
sig { returns(String) }
|
51
|
-
def public_path
|
52
|
-
@public_path ||= begin
|
53
|
-
unprefixed_public_path = user_defined_public_path || "app/public/"
|
54
|
-
|
55
|
-
if root?
|
56
|
-
unprefixed_public_path
|
57
|
-
else
|
58
|
-
File.join(@name, unprefixed_public_path)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
sig { params(path: String).returns(T::Boolean) }
|
64
|
-
def public_path?(path)
|
65
|
-
path.start_with?(public_path)
|
66
|
-
end
|
67
|
-
|
68
|
-
sig { returns(T.nilable(String)) }
|
69
|
-
def user_defined_public_path
|
70
|
-
return unless @config["public_path"]
|
71
|
-
return @config["public_path"] if @config["public_path"].end_with?("/")
|
72
|
-
|
73
|
-
@config["public_path"] + "/"
|
74
|
-
end
|
75
|
-
|
76
48
|
sig { params(other: T.untyped).returns(T.nilable(Integer)) }
|
77
49
|
def <=>(other)
|
78
50
|
return nil unless other.is_a?(self.class)
|
data/lib/packwerk/package_set.rb
CHANGED
@@ -26,7 +26,7 @@ module Packwerk
|
|
26
26
|
|
27
27
|
packages = package_paths.map do |path|
|
28
28
|
root_relative = path.dirname.relative_path_from(root_path)
|
29
|
-
Package.new(name: root_relative.to_s, config: YAML.load_file(path))
|
29
|
+
Package.new(name: root_relative.to_s, config: YAML.load_file(path, fallback: nil))
|
30
30
|
end
|
31
31
|
|
32
32
|
create_root_package_if_none_in(packages)
|
@@ -20,7 +20,7 @@ module Packwerk
|
|
20
20
|
end
|
21
21
|
|
22
22
|
sig do
|
23
|
-
params(reference: Packwerk::Reference, violation_type:
|
23
|
+
params(reference: Packwerk::Reference, violation_type: String)
|
24
24
|
.returns(T::Boolean)
|
25
25
|
end
|
26
26
|
def listed?(reference, violation_type:)
|
@@ -30,16 +30,18 @@ module Packwerk
|
|
30
30
|
violated_constant_in_file = violated_constants_found.fetch("files", []).include?(reference.relative_path)
|
31
31
|
return false unless violated_constant_in_file
|
32
32
|
|
33
|
-
violated_constants_found.fetch("violations", []).include?(violation_type
|
33
|
+
violated_constants_found.fetch("violations", []).include?(violation_type)
|
34
34
|
end
|
35
35
|
|
36
|
-
sig
|
36
|
+
sig do
|
37
|
+
params(reference: Packwerk::Reference, violation_type: String).returns(T::Boolean)
|
38
|
+
end
|
37
39
|
def add_entries(reference, violation_type)
|
38
40
|
package_violations = @new_entries.fetch(reference.constant.package.name, {})
|
39
41
|
entries_for_constant = package_violations[reference.constant.name] ||= {}
|
40
42
|
|
41
43
|
entries_for_constant["violations"] ||= []
|
42
|
-
entries_for_constant["violations"] << violation_type
|
44
|
+
entries_for_constant["violations"] << violation_type
|
43
45
|
|
44
46
|
entries_for_constant["files"] ||= []
|
45
47
|
entries_for_constant["files"] << reference.relative_path.to_s
|
@@ -64,11 +66,17 @@ module Packwerk
|
|
64
66
|
}
|
65
67
|
end
|
66
68
|
|
67
|
-
|
69
|
+
# We `next false` because if we cannot find existing violations for `for_files` within
|
70
|
+
# the `package_todo.yml` file, then there are no violations that
|
71
|
+
# can be considered stale.
|
72
|
+
next false if package_violations_for_files.empty?
|
68
73
|
|
69
74
|
package_violations_for_files.any? do |constant_name, entries_for_constant|
|
70
75
|
new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
|
71
|
-
|
76
|
+
# If there are no NEW entries that match the old entries `for_files`,
|
77
|
+
# @new_entries is from the list of violations we get when we check this file.
|
78
|
+
# If this list is empty, we also must have stale violations.
|
79
|
+
next true if new_entries_violation_types.nil?
|
72
80
|
|
73
81
|
if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
|
74
82
|
stale_violations =
|
@@ -97,9 +105,7 @@ module Packwerk
|
|
97
105
|
#
|
98
106
|
# bin/packwerk update-todo #{@package.name}
|
99
107
|
MESSAGE
|
100
|
-
|
101
|
-
|
102
|
-
File.open(package_todo_filepath, "w") do |f|
|
108
|
+
File.open(@filepath, "w") do |f|
|
103
109
|
f.write(message)
|
104
110
|
f.write(@new_entries.to_yaml)
|
105
111
|
end
|