packwerk 1.4.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +22 -19
- data/README.md +7 -2
- data/UPGRADING.md +54 -0
- data/USAGE.md +5 -40
- data/lib/packwerk/application_validator.rb +88 -93
- data/lib/packwerk/association_inspector.rb +1 -1
- data/lib/packwerk/cache.rb +169 -0
- data/lib/packwerk/cli.rb +32 -28
- data/lib/packwerk/configuration.rb +40 -5
- data/lib/packwerk/constant_discovery.rb +21 -5
- data/lib/packwerk/constant_name_inspector.rb +1 -1
- data/lib/packwerk/deprecated_references.rb +1 -1
- data/lib/packwerk/file_processor.rb +34 -15
- data/lib/packwerk/files_for_processing.rb +49 -22
- data/lib/packwerk/formatters/offenses_formatter.rb +1 -1
- data/lib/packwerk/formatters/progress_formatter.rb +1 -1
- data/lib/packwerk/generators/configuration_file.rb +4 -19
- data/lib/packwerk/generators/templates/packwerk.yml.erb +5 -5
- data/lib/packwerk/node.rb +2 -1
- data/lib/packwerk/node_processor.rb +6 -6
- data/lib/packwerk/node_processor_factory.rb +3 -4
- data/lib/packwerk/node_visitor.rb +3 -0
- data/lib/packwerk/offense.rb +10 -2
- data/lib/packwerk/package.rb +1 -1
- data/lib/packwerk/package_set.rb +3 -2
- data/lib/packwerk/parse_run.rb +37 -17
- data/lib/packwerk/parsed_constant_definitions.rb +4 -4
- data/lib/packwerk/parsers/erb.rb +2 -0
- data/lib/packwerk/parsers/factory.rb +2 -0
- data/lib/packwerk/parsers/parser_interface.rb +17 -0
- data/lib/packwerk/parsers/ruby.rb +2 -0
- data/lib/packwerk/parsers.rb +1 -0
- data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
- data/lib/packwerk/reference_checking/reference_checker.rb +2 -1
- data/lib/packwerk/reference_extractor.rb +78 -20
- data/lib/packwerk/reference_offense.rb +8 -3
- data/lib/packwerk/result.rb +2 -2
- data/lib/packwerk/run_context.rb +62 -38
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/unresolved_reference.rb +10 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +5 -9
- data/packwerk.gemspec +1 -0
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
- data/sorbet/tapioca/require.rb +1 -1
- metadata +21 -7
- data/lib/packwerk/generators/inflections_file.rb +0 -43
- data/lib/packwerk/generators/templates/inflections.yml +0 -6
- data/lib/packwerk/inflections/custom.rb +0 -33
- data/lib/packwerk/inflections/default.rb +0 -73
- data/lib/packwerk/inflector.rb +0 -49
@@ -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 =
|
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 =
|
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(
|
15
|
-
new(
|
14
|
+
def generate(root:, out:)
|
15
|
+
new(root: root, out: out).generate
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
sig { params(
|
20
|
-
def initialize(
|
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
|
@@ -12,12 +12,12 @@
|
|
12
12
|
# Patterns to find package configuration files
|
13
13
|
# package_paths: "**/"
|
14
14
|
|
15
|
-
# List of application load paths
|
16
|
-
<%= @load_paths_comment -%>
|
17
|
-
<%= @load_paths_formatted %>
|
18
15
|
# List of custom associations, if any
|
19
16
|
# custom_associations:
|
20
17
|
# - "cache_belongs_to"
|
21
18
|
|
22
|
-
#
|
23
|
-
#
|
19
|
+
# Whether or not you want the cache enabled (disabled by default)
|
20
|
+
# cache: true
|
21
|
+
|
22
|
+
# Where you want the cache to be stored (default below)
|
23
|
+
# cache_directory: 'tmp/cache/packwerk'
|
data/lib/packwerk/node.rb
CHANGED
@@ -5,7 +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
|
+
# Convenience methods for working with Parser::AST::Node nodes.
|
9
9
|
module Node
|
10
10
|
class TypeError < ArgumentError; end
|
11
11
|
Location = Struct.new(:line, :column)
|
@@ -264,6 +264,7 @@ module Packwerk
|
|
264
264
|
# "Class.new"
|
265
265
|
# "Module.new"
|
266
266
|
method_call?(node) &&
|
267
|
+
receiver(node) &&
|
267
268
|
constant?(receiver(node)) &&
|
268
269
|
["Class", "Module"].include?(constant_name(receiver(node))) &&
|
269
270
|
method_name(node) == :new
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -9,23 +9,23 @@ module Packwerk
|
|
9
9
|
sig do
|
10
10
|
params(
|
11
11
|
reference_extractor: ReferenceExtractor,
|
12
|
-
|
12
|
+
absolute_file: String,
|
13
13
|
).void
|
14
14
|
end
|
15
|
-
def initialize(reference_extractor:,
|
15
|
+
def initialize(reference_extractor:, absolute_file:)
|
16
16
|
@reference_extractor = reference_extractor
|
17
|
-
@
|
17
|
+
@absolute_file = absolute_file
|
18
18
|
end
|
19
19
|
|
20
20
|
sig do
|
21
21
|
params(
|
22
22
|
node: Parser::AST::Node,
|
23
23
|
ancestors: T::Array[Parser::AST::Node]
|
24
|
-
).returns(T.nilable(
|
24
|
+
).returns(T.nilable(UnresolvedReference))
|
25
25
|
end
|
26
26
|
def call(node, ancestors)
|
27
27
|
return unless Node.method_call?(node) || Node.constant?(node)
|
28
|
-
@reference_extractor.reference_from_node(node, ancestors: ancestors,
|
28
|
+
@reference_extractor.reference_from_node(node, ancestors: ancestors, absolute_file: @absolute_file)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -9,11 +9,11 @@ module Packwerk
|
|
9
9
|
const :context_provider, Packwerk::ConstantDiscovery
|
10
10
|
const :constant_name_inspectors, T::Array[ConstantNameInspector]
|
11
11
|
|
12
|
-
sig { params(
|
13
|
-
def for(
|
12
|
+
sig { params(absolute_file: String, node: AST::Node).returns(NodeProcessor) }
|
13
|
+
def for(absolute_file:, node:)
|
14
14
|
::Packwerk::NodeProcessor.new(
|
15
15
|
reference_extractor: reference_extractor(node: node),
|
16
|
-
|
16
|
+
absolute_file: absolute_file,
|
17
17
|
)
|
18
18
|
end
|
19
19
|
|
@@ -22,7 +22,6 @@ module Packwerk
|
|
22
22
|
sig { params(node: AST::Node).returns(::Packwerk::ReferenceExtractor) }
|
23
23
|
def reference_extractor(node:)
|
24
24
|
::Packwerk::ReferenceExtractor.new(
|
25
|
-
context_provider: context_provider,
|
26
25
|
constant_name_inspectors: constant_name_inspectors,
|
27
26
|
root_node: node,
|
28
27
|
root_path: root_path,
|
data/lib/packwerk/offense.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "parser/source/map"
|
@@ -8,7 +8,14 @@ module Packwerk
|
|
8
8
|
extend T::Sig
|
9
9
|
extend T::Helpers
|
10
10
|
|
11
|
-
|
11
|
+
sig { returns(T.nilable(Node::Location)) }
|
12
|
+
attr_reader :location
|
13
|
+
|
14
|
+
sig { returns(String) }
|
15
|
+
attr_reader :file
|
16
|
+
|
17
|
+
sig { returns(String) }
|
18
|
+
attr_reader :message
|
12
19
|
|
13
20
|
sig do
|
14
21
|
params(file: String, message: String, location: T.nilable(Node::Location))
|
@@ -22,6 +29,7 @@ module Packwerk
|
|
22
29
|
|
23
30
|
sig { params(style: OutputStyle).returns(String) }
|
24
31
|
def to_s(style = OutputStyles::Plain.new)
|
32
|
+
location = self.location
|
25
33
|
if location
|
26
34
|
<<~EOS
|
27
35
|
#{style.filename}#{file}#{style.reset}:#{location.line}:#{location.column}
|
data/lib/packwerk/package.rb
CHANGED
@@ -21,6 +21,7 @@ module Packwerk
|
|
21
21
|
@name = name
|
22
22
|
@config = T.let(config || {}, T::Hash[T.untyped, T.untyped])
|
23
23
|
@dependencies = T.let(Array(@config["dependencies"]).freeze, T::Array[String])
|
24
|
+
@public_path = T.let(nil, T.nilable(String))
|
24
25
|
end
|
25
26
|
|
26
27
|
sig { returns(T.nilable(T.any(T::Boolean, T::Array[String]))) }
|
@@ -46,7 +47,6 @@ module Packwerk
|
|
46
47
|
|
47
48
|
sig { returns(String) }
|
48
49
|
def public_path
|
49
|
-
@public_path = T.let(@public_path, T.nilable(String))
|
50
50
|
@public_path ||= begin
|
51
51
|
unprefixed_public_path = user_defined_public_path || "app/public/"
|
52
52
|
|
data/lib/packwerk/package_set.rb
CHANGED
@@ -79,6 +79,7 @@ module Packwerk
|
|
79
79
|
sorted_packages = packages.sort_by { |package| -package.name.length }
|
80
80
|
packages = sorted_packages.each_with_object({}) { |package, hash| hash[package.name] = package }
|
81
81
|
@packages = T.let(packages, T::Hash[String, Package])
|
82
|
+
@package_from_path = T.let({}, T::Hash[String, T.nilable(Package)])
|
82
83
|
end
|
83
84
|
|
84
85
|
sig { override.params(blk: T.proc.params(arg0: Package).returns(T.untyped)).returns(T.untyped) }
|
@@ -91,10 +92,10 @@ module Packwerk
|
|
91
92
|
packages[name]
|
92
93
|
end
|
93
94
|
|
94
|
-
sig { params(file_path: T.any(Pathname, String)).returns(
|
95
|
+
sig { params(file_path: T.any(Pathname, String)).returns(Package) }
|
95
96
|
def package_from_path(file_path)
|
96
97
|
path_string = file_path.to_s
|
97
|
-
packages.values.find { |package| package.package_path?(path_string) }
|
98
|
+
@package_from_path[path_string] ||= T.must(packages.values.find { |package| package.package_path?(path_string) })
|
98
99
|
end
|
99
100
|
end
|
100
101
|
end
|
data/lib/packwerk/parse_run.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "benchmark"
|
@@ -8,8 +8,20 @@ module Packwerk
|
|
8
8
|
class ParseRun
|
9
9
|
extend T::Sig
|
10
10
|
|
11
|
+
ProcessFileProc = T.type_alias do
|
12
|
+
T.proc.params(path: String).returns(T::Array[Offense])
|
13
|
+
end
|
14
|
+
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
absolute_files: T::Array[String],
|
18
|
+
configuration: Configuration,
|
19
|
+
progress_formatter: Formatters::ProgressFormatter,
|
20
|
+
offenses_formatter: OffensesFormatter,
|
21
|
+
).void
|
22
|
+
end
|
11
23
|
def initialize(
|
12
|
-
|
24
|
+
absolute_files:,
|
13
25
|
configuration:,
|
14
26
|
progress_formatter: Formatters::ProgressFormatter.new(StringIO.new),
|
15
27
|
offenses_formatter: Formatters::OffensesFormatter.new
|
@@ -17,9 +29,10 @@ module Packwerk
|
|
17
29
|
@configuration = configuration
|
18
30
|
@progress_formatter = progress_formatter
|
19
31
|
@offenses_formatter = offenses_formatter
|
20
|
-
@
|
32
|
+
@absolute_files = absolute_files
|
21
33
|
end
|
22
34
|
|
35
|
+
sig { returns(Result) }
|
23
36
|
def detect_stale_violations
|
24
37
|
offense_collection = find_offenses
|
25
38
|
|
@@ -29,6 +42,7 @@ module Packwerk
|
|
29
42
|
Result.new(message: message, status: result_status)
|
30
43
|
end
|
31
44
|
|
45
|
+
sig { returns(Result) }
|
32
46
|
def update_deprecations
|
33
47
|
offense_collection = find_offenses
|
34
48
|
offense_collection.dump_deprecated_references_files
|
@@ -41,6 +55,7 @@ module Packwerk
|
|
41
55
|
Result.new(message: message, status: offense_collection.errors.empty?)
|
42
56
|
end
|
43
57
|
|
58
|
+
sig { returns(Result) }
|
44
59
|
def check
|
45
60
|
offense_collection = find_offenses(show_errors: true)
|
46
61
|
|
@@ -55,23 +70,24 @@ module Packwerk
|
|
55
70
|
|
56
71
|
private
|
57
72
|
|
73
|
+
sig { params(show_errors: T::Boolean).returns(OffenseCollection) }
|
58
74
|
def find_offenses(show_errors: false)
|
59
75
|
offense_collection = OffenseCollection.new(@configuration.root_path)
|
60
|
-
@progress_formatter.started(@
|
76
|
+
@progress_formatter.started(@absolute_files)
|
61
77
|
|
62
78
|
run_context = Packwerk::RunContext.from_configuration(@configuration)
|
63
|
-
all_offenses = T.let([], T
|
79
|
+
all_offenses = T.let([], T::Array[Offense])
|
64
80
|
|
65
|
-
process_file = -> (
|
66
|
-
run_context.process_file(
|
81
|
+
process_file = T.let(-> (absolute_file) do
|
82
|
+
run_context.process_file(absolute_file: absolute_file).tap do |offenses|
|
67
83
|
failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
|
68
84
|
update_progress(failed: failed)
|
69
85
|
end
|
70
|
-
end
|
86
|
+
end, ProcessFileProc)
|
71
87
|
|
72
88
|
execution_time = Benchmark.realtime do
|
73
89
|
all_offenses = if @configuration.parallel?
|
74
|
-
Parallel.flat_map(@
|
90
|
+
Parallel.flat_map(@absolute_files, &process_file)
|
75
91
|
else
|
76
92
|
serial_find_offenses(&process_file)
|
77
93
|
end
|
@@ -83,18 +99,22 @@ module Packwerk
|
|
83
99
|
offense_collection
|
84
100
|
end
|
85
101
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
sig { params(block: ProcessFileProc).returns(T::Array[Offense]) }
|
103
|
+
def serial_find_offenses(&block)
|
104
|
+
all_offenses = T.let([], T::Array[Offense])
|
105
|
+
begin
|
106
|
+
@absolute_files.each do |absolute_file|
|
107
|
+
offenses = block.call(absolute_file)
|
108
|
+
all_offenses.concat(offenses)
|
109
|
+
end
|
110
|
+
rescue Interrupt
|
111
|
+
@progress_formatter.interrupted
|
112
|
+
all_offenses
|
91
113
|
end
|
92
114
|
all_offenses
|
93
|
-
rescue Interrupt
|
94
|
-
@progress_formatter.interrupted
|
95
|
-
all_offenses
|
96
115
|
end
|
97
116
|
|
117
|
+
sig { params(failed: T::Boolean).void }
|
98
118
|
def update_progress(failed: false)
|
99
119
|
if failed
|
100
120
|
@progress_formatter.mark_as_failed
|
@@ -25,13 +25,13 @@ module Packwerk
|
|
25
25
|
def self.reference_qualifications(constant_name, namespace_path:)
|
26
26
|
return [constant_name] if constant_name.start_with?("::")
|
27
27
|
|
28
|
-
|
28
|
+
resolved_constant_name = "::#{constant_name}"
|
29
29
|
|
30
30
|
possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
|
31
31
|
acc << "#{acc.last}::#{current}" if acc.last && current
|
32
32
|
end
|
33
33
|
|
34
|
-
possible_namespaces.map { |namespace| namespace +
|
34
|
+
possible_namespaces.map { |namespace| namespace + resolved_constant_name }
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
@@ -53,9 +53,9 @@ module Packwerk
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def add_definition(constant_name, current_namespace_path, location)
|
56
|
-
|
56
|
+
resolved_constant = [""].concat(current_namespace_path).push(constant_name).join("::")
|
57
57
|
|
58
|
-
@local_definitions[
|
58
|
+
@local_definitions[resolved_constant] = location
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
data/lib/packwerk/parsers/erb.rb
CHANGED
@@ -6,6 +6,7 @@ require "singleton"
|
|
6
6
|
module Packwerk
|
7
7
|
module Parsers
|
8
8
|
class Factory
|
9
|
+
extend T::Sig
|
9
10
|
include Singleton
|
10
11
|
|
11
12
|
RUBY_REGEX = %r{
|
@@ -19,6 +20,7 @@ module Packwerk
|
|
19
20
|
ERB_REGEX = /\.erb\Z/
|
20
21
|
private_constant :ERB_REGEX
|
21
22
|
|
23
|
+
sig { params(path: String).returns(T.nilable(ParserInterface)) }
|
22
24
|
def for_path(path)
|
23
25
|
case path
|
24
26
|
when RUBY_REGEX
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module Parsers
|
6
|
+
module ParserInterface
|
7
|
+
extend T::Helpers
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
interface!
|
11
|
+
|
12
|
+
sig { abstract.params(io: File, file_path: String).returns(T.untyped) }
|
13
|
+
def call(io:, file_path:)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/packwerk/parsers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -6,6 +6,7 @@ module Packwerk
|
|
6
6
|
class ReferenceChecker
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
+
sig { params(checkers: T::Array[Checkers::Checker]).void }
|
9
10
|
def initialize(checkers)
|
10
11
|
@checkers = checkers
|
11
12
|
end
|
@@ -8,58 +8,116 @@ module Packwerk
|
|
8
8
|
|
9
9
|
sig do
|
10
10
|
params(
|
11
|
-
context_provider: Packwerk::ConstantDiscovery,
|
12
11
|
constant_name_inspectors: T::Array[Packwerk::ConstantNameInspector],
|
13
12
|
root_node: ::AST::Node,
|
14
13
|
root_path: String,
|
15
14
|
).void
|
16
15
|
end
|
17
16
|
def initialize(
|
18
|
-
context_provider:,
|
19
17
|
constant_name_inspectors:,
|
20
18
|
root_node:,
|
21
19
|
root_path:
|
22
20
|
)
|
23
|
-
@context_provider = context_provider
|
24
21
|
@constant_name_inspectors = constant_name_inspectors
|
25
22
|
@root_path = root_path
|
26
23
|
@local_constant_definitions = ParsedConstantDefinitions.new(root_node: root_node)
|
27
24
|
end
|
28
25
|
|
29
|
-
|
26
|
+
sig do
|
27
|
+
params(
|
28
|
+
node: Parser::AST::Node,
|
29
|
+
ancestors: T::Array[Parser::AST::Node],
|
30
|
+
absolute_file: String
|
31
|
+
).returns(T.nilable(UnresolvedReference))
|
32
|
+
end
|
33
|
+
def reference_from_node(node, ancestors:, absolute_file:)
|
30
34
|
constant_name = T.let(nil, T.nilable(String))
|
31
35
|
|
32
36
|
@constant_name_inspectors.each do |inspector|
|
33
37
|
constant_name = inspector.constant_name_from_node(node, ancestors: ancestors)
|
38
|
+
|
34
39
|
break if constant_name
|
35
40
|
end
|
36
41
|
|
37
|
-
|
42
|
+
if constant_name
|
43
|
+
reference_from_constant(
|
44
|
+
constant_name,
|
45
|
+
node: node,
|
46
|
+
ancestors: ancestors,
|
47
|
+
absolute_file: absolute_file
|
48
|
+
)
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
|
-
|
52
|
+
sig do
|
53
|
+
params(
|
54
|
+
unresolved_references_and_offenses: T::Array[T.any(UnresolvedReference, Offense)],
|
55
|
+
context_provider: ConstantDiscovery
|
56
|
+
).returns(T::Array[T.any(Reference, Offense)])
|
57
|
+
end
|
58
|
+
def self.get_fully_qualified_references_and_offenses_from(unresolved_references_and_offenses, context_provider)
|
59
|
+
fully_qualified_references_and_offenses = T.let([], T::Array[T.any(Reference, Offense)])
|
41
60
|
|
42
|
-
|
43
|
-
|
44
|
-
|
61
|
+
unresolved_references_and_offenses.each do |unresolved_references_or_offense|
|
62
|
+
if unresolved_references_or_offense.is_a?(Offense)
|
63
|
+
fully_qualified_references_and_offenses << unresolved_references_or_offense
|
45
64
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
65
|
+
next
|
66
|
+
end
|
67
|
+
|
68
|
+
unresolved_reference = unresolved_references_or_offense
|
69
|
+
|
70
|
+
constant =
|
71
|
+
context_provider.context_for(
|
72
|
+
unresolved_reference.constant_name,
|
73
|
+
current_namespace_path: unresolved_reference.namespace_path
|
74
|
+
)
|
75
|
+
|
76
|
+
next if constant.nil?
|
77
|
+
|
78
|
+
package_for_constant = constant.package
|
79
|
+
|
80
|
+
next if package_for_constant.nil?
|
81
|
+
|
82
|
+
source_package = context_provider.package_from_path(unresolved_reference.relative_path)
|
83
|
+
|
84
|
+
next if source_package == package_for_constant
|
85
|
+
|
86
|
+
fully_qualified_references_and_offenses << Reference.new(
|
87
|
+
source_package,
|
88
|
+
unresolved_reference.relative_path,
|
89
|
+
constant,
|
90
|
+
unresolved_reference.source_location
|
50
91
|
)
|
92
|
+
end
|
93
|
+
|
94
|
+
fully_qualified_references_and_offenses
|
95
|
+
end
|
51
96
|
|
52
|
-
|
97
|
+
private
|
53
98
|
|
54
|
-
|
55
|
-
|
56
|
-
|
99
|
+
sig do
|
100
|
+
params(
|
101
|
+
constant_name: String,
|
102
|
+
node: Parser::AST::Node,
|
103
|
+
ancestors: T::Array[Parser::AST::Node],
|
104
|
+
absolute_file: String
|
105
|
+
).returns(T.nilable(UnresolvedReference))
|
106
|
+
end
|
107
|
+
def reference_from_constant(constant_name, node:, ancestors:, absolute_file:)
|
108
|
+
namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
|
57
109
|
|
58
|
-
|
110
|
+
return if local_reference?(constant_name, Node.name_location(node), namespace_path)
|
59
111
|
|
60
|
-
|
112
|
+
relative_file = Pathname.new(absolute_file).relative_path_from(@root_path).to_s
|
113
|
+
location = Node.location(node)
|
61
114
|
|
62
|
-
|
115
|
+
UnresolvedReference.new(
|
116
|
+
constant_name,
|
117
|
+
namespace_path,
|
118
|
+
relative_file,
|
119
|
+
location
|
120
|
+
)
|
63
121
|
end
|
64
122
|
|
65
123
|
def local_reference?(constant_name, name_location, namespace_path)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
@@ -7,7 +7,11 @@ module Packwerk
|
|
7
7
|
extend T::Sig
|
8
8
|
extend T::Helpers
|
9
9
|
|
10
|
-
|
10
|
+
sig { returns(Reference) }
|
11
|
+
attr_reader :reference
|
12
|
+
|
13
|
+
sig { returns(ViolationType) }
|
14
|
+
attr_reader :violation_type
|
11
15
|
|
12
16
|
sig do
|
13
17
|
params(
|
@@ -25,10 +29,11 @@ module Packwerk
|
|
25
29
|
|
26
30
|
private
|
27
31
|
|
32
|
+
sig { params(reference: Reference, violation_type: ViolationType).returns(String) }
|
28
33
|
def build_message(reference, violation_type)
|
29
34
|
violation_message = case violation_type
|
30
35
|
when ViolationType::Privacy
|
31
|
-
source_desc =
|
36
|
+
source_desc = "'#{reference.source_package}'"
|
32
37
|
"Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
|
33
38
|
"referenced from #{source_desc}.\n" \
|
34
39
|
"Is there a public entrypoint in '#{reference.constant.package.public_path}' that you can use instead?"
|