packwerk 1.4.0 → 2.1.1
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/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
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "digest"
|
5
|
+
|
6
|
+
module Packwerk
|
7
|
+
class Cache
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
class CacheContents < T::Struct
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
const :file_contents_digest, String
|
14
|
+
const :unresolved_references, T::Array[UnresolvedReference]
|
15
|
+
|
16
|
+
sig { returns(String) }
|
17
|
+
def serialize
|
18
|
+
to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(serialized_cache_contents: String).returns(CacheContents) }
|
22
|
+
def self.deserialize(serialized_cache_contents)
|
23
|
+
cache_contents_json = JSON.parse(serialized_cache_contents)
|
24
|
+
unresolved_references = cache_contents_json["unresolved_references"].map do |json|
|
25
|
+
UnresolvedReference.new(
|
26
|
+
json["constant_name"],
|
27
|
+
json["namespace_path"],
|
28
|
+
json["relative_path"],
|
29
|
+
Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
CacheContents.new(
|
34
|
+
file_contents_digest: cache_contents_json["file_contents_digest"],
|
35
|
+
unresolved_references: unresolved_references,
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
CACHE_SHAPE = T.type_alias do
|
41
|
+
T::Hash[
|
42
|
+
String,
|
43
|
+
CacheContents
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: T.nilable(String)).void }
|
48
|
+
def initialize(enable_cache:, cache_directory:, config_path:)
|
49
|
+
@enable_cache = enable_cache
|
50
|
+
@cache = T.let({}, CACHE_SHAPE)
|
51
|
+
@files_by_digest = T.let({}, T::Hash[String, String])
|
52
|
+
@config_path = config_path
|
53
|
+
@cache_directory = cache_directory
|
54
|
+
|
55
|
+
if @enable_cache
|
56
|
+
create_cache_directory!
|
57
|
+
bust_cache_if_packwerk_yml_has_changed!
|
58
|
+
bust_cache_if_inflections_have_changed!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { void }
|
63
|
+
def bust_cache!
|
64
|
+
FileUtils.rm_rf(@cache_directory)
|
65
|
+
end
|
66
|
+
|
67
|
+
sig do
|
68
|
+
params(
|
69
|
+
file_path: String,
|
70
|
+
block: T.proc.returns(T::Array[UnresolvedReference])
|
71
|
+
).returns(T::Array[UnresolvedReference])
|
72
|
+
end
|
73
|
+
def with_cache(file_path, &block)
|
74
|
+
return block.call unless @enable_cache
|
75
|
+
|
76
|
+
cache_location = @cache_directory.join(digest_for_string(file_path))
|
77
|
+
|
78
|
+
cache_contents = if cache_location.exist?
|
79
|
+
T.let(CacheContents.deserialize(cache_location.read),
|
80
|
+
CacheContents)
|
81
|
+
end
|
82
|
+
|
83
|
+
file_contents_digest = digest_for_file(file_path)
|
84
|
+
|
85
|
+
if !cache_contents.nil? && cache_contents.file_contents_digest == file_contents_digest
|
86
|
+
Debug.out("Cache hit for #{file_path}")
|
87
|
+
|
88
|
+
cache_contents.unresolved_references
|
89
|
+
else
|
90
|
+
Debug.out("Cache miss for #{file_path}")
|
91
|
+
|
92
|
+
unresolved_references = block.call
|
93
|
+
|
94
|
+
cache_contents = CacheContents.new(
|
95
|
+
file_contents_digest: file_contents_digest,
|
96
|
+
unresolved_references: unresolved_references,
|
97
|
+
)
|
98
|
+
cache_location.write(cache_contents.serialize)
|
99
|
+
|
100
|
+
unresolved_references
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { params(file: String).returns(String) }
|
105
|
+
def digest_for_file(file)
|
106
|
+
digest_for_string(File.read(file))
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { params(str: String).returns(String) }
|
110
|
+
def digest_for_string(str)
|
111
|
+
# MD5 appears to be the fastest
|
112
|
+
# https://gist.github.com/morimori/1330095
|
113
|
+
Digest::MD5.hexdigest(str)
|
114
|
+
end
|
115
|
+
|
116
|
+
sig { void }
|
117
|
+
def bust_cache_if_packwerk_yml_has_changed!
|
118
|
+
return nil if @config_path.nil?
|
119
|
+
bust_cache_if_contents_have_changed(File.read(@config_path), :packwerk_yml)
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { void }
|
123
|
+
def bust_cache_if_inflections_have_changed!
|
124
|
+
bust_cache_if_contents_have_changed(YAML.dump(ActiveSupport::Inflector.inflections), :inflections)
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { params(contents: String, contents_key: Symbol).void }
|
128
|
+
def bust_cache_if_contents_have_changed(contents, contents_key)
|
129
|
+
current_digest = digest_for_string(contents)
|
130
|
+
cached_digest_path = @cache_directory.join(contents_key.to_s)
|
131
|
+
|
132
|
+
if !cached_digest_path.exist?
|
133
|
+
# In this case, we have nothing cached
|
134
|
+
# We save the current digest. This way the next time we compare current digest to cached digest,
|
135
|
+
# we can accurately determine if we should bust the cache
|
136
|
+
cached_digest_path.write(current_digest)
|
137
|
+
|
138
|
+
nil
|
139
|
+
elsif cached_digest_path.read == current_digest
|
140
|
+
Debug.out("#{contents_key} contents have NOT changed, preserving cache")
|
141
|
+
else
|
142
|
+
Debug.out("#{contents_key} contents have changed, busting cache")
|
143
|
+
|
144
|
+
bust_cache!
|
145
|
+
create_cache_directory!
|
146
|
+
|
147
|
+
cached_digest_path.write(current_digest)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
sig { void }
|
152
|
+
def create_cache_directory!
|
153
|
+
FileUtils.mkdir_p(@cache_directory)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Debug
|
158
|
+
extend T::Sig
|
159
|
+
|
160
|
+
sig { params(out: String).void }
|
161
|
+
def self.out(out)
|
162
|
+
if ENV["DEBUG_PACKWERK_CACHE"]
|
163
|
+
puts(out)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
private_constant :Debug
|
169
|
+
end
|
data/lib/packwerk/cli.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "optparse"
|
@@ -30,9 +30,10 @@ module Packwerk
|
|
30
30
|
@err_out = err_out
|
31
31
|
@environment = environment
|
32
32
|
@style = style
|
33
|
-
@configuration = configuration || Configuration.from_path
|
34
|
-
@progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
|
35
|
-
@offenses_formatter = offenses_formatter || Formatters::OffensesFormatter.new(style: @style)
|
33
|
+
@configuration = T.let(configuration || Configuration.from_path, Configuration)
|
34
|
+
@progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
|
35
|
+
@offenses_formatter = T.let(offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
|
36
|
+
OffensesFormatter)
|
36
37
|
end
|
37
38
|
|
38
39
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
@@ -53,8 +54,6 @@ module Packwerk
|
|
53
54
|
output_result(parse_run(args).check)
|
54
55
|
when "detect-stale-violations"
|
55
56
|
output_result(parse_run(args).detect_stale_violations)
|
56
|
-
when "update"
|
57
|
-
update(args)
|
58
57
|
when "update-deprecations"
|
59
58
|
output_result(parse_run(args).update_deprecations)
|
60
59
|
when "validate"
|
@@ -80,22 +79,23 @@ module Packwerk
|
|
80
79
|
|
81
80
|
private
|
82
81
|
|
82
|
+
sig { returns(T::Boolean) }
|
83
83
|
def init
|
84
84
|
@out.puts("📦 Initializing Packwerk...")
|
85
85
|
|
86
86
|
generate_configs
|
87
87
|
end
|
88
88
|
|
89
|
+
sig { returns(T::Boolean) }
|
89
90
|
def generate_configs
|
90
91
|
configuration_file = Packwerk::Generators::ConfigurationFile.generate(
|
91
|
-
load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths(@configuration.root_path, @environment),
|
92
92
|
root: @configuration.root_path,
|
93
93
|
out: @out
|
94
94
|
)
|
95
|
-
|
95
|
+
|
96
96
|
root_package = Packwerk::Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
|
97
97
|
|
98
|
-
success = configuration_file &&
|
98
|
+
success = configuration_file && root_package
|
99
99
|
|
100
100
|
result = if success
|
101
101
|
<<~EOS
|
@@ -115,35 +115,28 @@ module Packwerk
|
|
115
115
|
success
|
116
116
|
end
|
117
117
|
|
118
|
-
|
119
|
-
warn("`packwerk update` is deprecated in favor of `packwerk update-deprecations`.")
|
120
|
-
output_result(parse_run(paths).update_deprecations)
|
121
|
-
end
|
122
|
-
|
118
|
+
sig { params(result: Result).returns(T::Boolean) }
|
123
119
|
def output_result(result)
|
124
120
|
@out.puts
|
125
121
|
@out.puts(result.message)
|
126
122
|
result.status
|
127
123
|
end
|
128
124
|
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
sig { params(relative_file_paths: T::Array[String], ignore_nested_packages: T::Boolean).returns(T::Array[String]) }
|
126
|
+
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
127
|
+
absolute_files = FilesForProcessing.fetch(
|
128
|
+
relative_file_paths: relative_file_paths,
|
132
129
|
ignore_nested_packages: ignore_nested_packages,
|
133
130
|
configuration: @configuration
|
134
131
|
)
|
135
132
|
abort("No files found or given. "\
|
136
|
-
"Specify files or check the include and exclude glob in the config file.") if
|
137
|
-
|
133
|
+
"Specify files or check the include and exclude glob in the config file.") if absolute_files.empty?
|
134
|
+
absolute_files
|
138
135
|
end
|
139
136
|
|
137
|
+
sig { params(_paths: T::Array[String]).returns(T::Boolean) }
|
140
138
|
def validate(_paths)
|
141
139
|
@progress_formatter.started_validation do
|
142
|
-
checker = Packwerk::ApplicationValidator.new(
|
143
|
-
config_file_path: @configuration.config_path,
|
144
|
-
configuration: @configuration,
|
145
|
-
environment: @environment,
|
146
|
-
)
|
147
140
|
result = checker.check_all
|
148
141
|
|
149
142
|
list_validation_errors(result)
|
@@ -152,6 +145,16 @@ module Packwerk
|
|
152
145
|
end
|
153
146
|
end
|
154
147
|
|
148
|
+
sig { returns(ApplicationValidator) }
|
149
|
+
def checker
|
150
|
+
Packwerk::ApplicationValidator.new(
|
151
|
+
config_file_path: @configuration.config_path,
|
152
|
+
configuration: @configuration,
|
153
|
+
environment: @environment,
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
sig { params(result: ApplicationValidator::Result).void }
|
155
158
|
def list_validation_errors(result)
|
156
159
|
@out.puts
|
157
160
|
if result.ok?
|
@@ -162,24 +165,25 @@ module Packwerk
|
|
162
165
|
end
|
163
166
|
end
|
164
167
|
|
168
|
+
sig { params(params: T.untyped).returns(ParseRun) }
|
165
169
|
def parse_run(params)
|
166
|
-
|
170
|
+
relative_file_paths = T.let([], T::Array[String])
|
167
171
|
ignore_nested_packages = nil
|
168
172
|
|
169
173
|
if params.any? { |p| p.include?("--packages") }
|
170
174
|
OptionParser.new do |parser|
|
171
175
|
parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
|
172
|
-
|
176
|
+
relative_file_paths = p
|
173
177
|
end
|
174
178
|
end.parse!(params)
|
175
179
|
ignore_nested_packages = true
|
176
180
|
else
|
177
|
-
|
181
|
+
relative_file_paths = params
|
178
182
|
ignore_nested_packages = false
|
179
183
|
end
|
180
184
|
|
181
185
|
ParseRun.new(
|
182
|
-
|
186
|
+
absolute_files: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
|
183
187
|
configuration: @configuration,
|
184
188
|
progress_formatter: @progress_formatter,
|
185
189
|
offenses_formatter: @offenses_formatter
|
@@ -34,8 +34,7 @@ module Packwerk
|
|
34
34
|
DEFAULT_EXCLUDE_GLOBS = ["{bin,node_modules,script,tmp,vendor}/**/*"]
|
35
35
|
|
36
36
|
attr_reader(
|
37
|
-
:include, :exclude, :root_path, :package_paths, :custom_associations, :
|
38
|
-
:config_path,
|
37
|
+
:include, :exclude, :root_path, :package_paths, :custom_associations, :config_path, :cache_directory
|
39
38
|
)
|
40
39
|
|
41
40
|
def initialize(configs = {}, config_path: nil)
|
@@ -45,15 +44,51 @@ module Packwerk
|
|
45
44
|
@root_path = File.expand_path(root)
|
46
45
|
@package_paths = configs["package_paths"] || "**/"
|
47
46
|
@custom_associations = configs["custom_associations"] || []
|
48
|
-
@load_paths = (configs["load_paths"] || []).uniq
|
49
|
-
@inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
|
50
47
|
@parallel = configs.key?("parallel") ? configs["parallel"] : true
|
51
|
-
|
48
|
+
@cache_enabled = configs.key?("cache") ? configs["cache"] : false
|
49
|
+
@cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
|
52
50
|
@config_path = config_path
|
51
|
+
|
52
|
+
if configs["load_paths"]
|
53
|
+
warning = <<~WARNING
|
54
|
+
DEPRECATION WARNING: The 'load_paths' key in `packwerk.yml` is deprecated.
|
55
|
+
This value is no longer cached, and you can remove the key from `packwerk.yml`.
|
56
|
+
WARNING
|
57
|
+
|
58
|
+
warn(warning)
|
59
|
+
end
|
60
|
+
|
61
|
+
if configs["inflections_file"]
|
62
|
+
warning = <<~WARNING
|
63
|
+
DEPRECATION WARNING: The 'inflections_file' key in `packwerk.yml` is deprecated.
|
64
|
+
This value is no longer cached, and you can remove the key from `packwerk.yml`.
|
65
|
+
You can also delete #{configs["inflections_file"]}.
|
66
|
+
WARNING
|
67
|
+
|
68
|
+
warn(warning)
|
69
|
+
end
|
70
|
+
|
71
|
+
inflection_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
|
72
|
+
if Pathname.new(inflection_file).exist?
|
73
|
+
warning = <<~WARNING
|
74
|
+
DEPRECATION WARNING: Inflections YMLs in packwerk are now deprecated.
|
75
|
+
This value is no longer cached, and you can now delete #{inflection_file}
|
76
|
+
WARNING
|
77
|
+
|
78
|
+
warn(warning)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def load_paths
|
83
|
+
@load_paths ||= ApplicationLoadPaths.extract_relevant_paths(@root_path, "test")
|
53
84
|
end
|
54
85
|
|
55
86
|
def parallel?
|
56
87
|
@parallel
|
57
88
|
end
|
89
|
+
|
90
|
+
def cache_enabled?
|
91
|
+
@cache_enabled
|
92
|
+
end
|
58
93
|
end
|
59
94
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
require "constant_resolver"
|
5
5
|
|
6
6
|
module Packwerk
|
7
|
-
# Get information about
|
7
|
+
# Get information about unresolved constants without loading the application code.
|
8
8
|
# Information gathered: Fully qualified name, path to file containing the definition, package,
|
9
9
|
# and visibility (public/private to the package).
|
10
10
|
#
|
@@ -15,10 +15,15 @@ module Packwerk
|
|
15
15
|
# have no way of inferring the file it is defined in. You could argue though that inheritance means that another
|
16
16
|
# constant with the same name exists in the inheriting class, and this view is sufficient for all our use cases.
|
17
17
|
class ConstantDiscovery
|
18
|
+
extend T::Sig
|
19
|
+
|
18
20
|
ConstantContext = Struct.new(:name, :location, :package, :public?)
|
19
21
|
|
20
22
|
# @param constant_resolver [ConstantResolver]
|
21
23
|
# @param packages [Packwerk::PackageSet]
|
24
|
+
sig do
|
25
|
+
params(constant_resolver: ConstantResolver, packages: Packwerk::PackageSet).void
|
26
|
+
end
|
22
27
|
def initialize(constant_resolver:, packages:)
|
23
28
|
@packages = packages
|
24
29
|
@resolver = constant_resolver
|
@@ -30,22 +35,33 @@ module Packwerk
|
|
30
35
|
#
|
31
36
|
# @return [Packwerk::Package] the package that contains the given file,
|
32
37
|
# or nil if the path is not owned by any component
|
38
|
+
sig do
|
39
|
+
params(
|
40
|
+
path: String,
|
41
|
+
).returns(Packwerk::Package)
|
42
|
+
end
|
33
43
|
def package_from_path(path)
|
34
44
|
@packages.package_from_path(path)
|
35
45
|
end
|
36
46
|
|
37
47
|
# Analyze a constant via its name.
|
38
|
-
# If the
|
48
|
+
# If the constant is unresolved, we need the current namespace path to correctly infer its full name
|
39
49
|
#
|
40
|
-
# @param const_name [String] The constant's name
|
50
|
+
# @param const_name [String] The unresolved constant's name.
|
41
51
|
# @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
|
42
52
|
# used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
|
43
53
|
# @return [Packwerk::ConstantDiscovery::ConstantContext]
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
const_name: String,
|
57
|
+
current_namespace_path: T.nilable(T::Array[String]),
|
58
|
+
).returns(T.nilable(ConstantDiscovery::ConstantContext))
|
59
|
+
end
|
44
60
|
def context_for(const_name, current_namespace_path: [])
|
45
61
|
begin
|
46
62
|
constant = @resolver.resolve(const_name, current_namespace_path: current_namespace_path)
|
47
63
|
rescue ConstantResolver::Error => e
|
48
|
-
raise(ConstantResolver::Error, e.message
|
64
|
+
raise(ConstantResolver::Error, e.message)
|
49
65
|
end
|
50
66
|
|
51
67
|
return unless constant
|
@@ -55,7 +71,7 @@ module Packwerk
|
|
55
71
|
constant.name,
|
56
72
|
constant.location,
|
57
73
|
package,
|
58
|
-
package
|
74
|
+
package.public_path?(constant.location),
|
59
75
|
)
|
60
76
|
end
|
61
77
|
end
|
@@ -16,6 +16,7 @@ module Packwerk
|
|
16
16
|
@package = package
|
17
17
|
@filepath = filepath
|
18
18
|
@new_entries = T.let({}, ENTRIES_TYPE)
|
19
|
+
@deprecated_references = T.let(nil, T.nilable(ENTRIES_TYPE))
|
19
20
|
end
|
20
21
|
|
21
22
|
sig do
|
@@ -103,7 +104,6 @@ module Packwerk
|
|
103
104
|
|
104
105
|
sig { returns(ENTRIES_TYPE) }
|
105
106
|
def deprecated_references
|
106
|
-
@deprecated_references ||= T.let(@deprecated_references, T.nilable(ENTRIES_TYPE))
|
107
107
|
@deprecated_references ||= if File.exist?(@filepath)
|
108
108
|
load_yaml(@filepath)
|
109
109
|
else
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "ast/node"
|
@@ -8,55 +8,74 @@ module Packwerk
|
|
8
8
|
extend T::Sig
|
9
9
|
|
10
10
|
class UnknownFileTypeResult < Offense
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { params(file: String).void }
|
11
14
|
def initialize(file:)
|
12
15
|
super(file: file, message: "unknown file type")
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
16
|
-
|
19
|
+
sig do
|
20
|
+
params(
|
21
|
+
node_processor_factory: NodeProcessorFactory,
|
22
|
+
cache: Cache,
|
23
|
+
parser_factory: T.nilable(Parsers::Factory)
|
24
|
+
).void
|
25
|
+
end
|
26
|
+
def initialize(node_processor_factory:, cache:, parser_factory: nil)
|
17
27
|
@node_processor_factory = node_processor_factory
|
18
|
-
@
|
28
|
+
@cache = cache
|
29
|
+
@parser_factory = T.let(parser_factory || Packwerk::Parsers::Factory.instance, Parsers::Factory)
|
19
30
|
end
|
20
31
|
|
21
32
|
sig do
|
22
|
-
params(
|
33
|
+
params(absolute_file: String).returns(
|
23
34
|
T::Array[
|
24
35
|
T.any(
|
25
|
-
Packwerk::
|
36
|
+
Packwerk::UnresolvedReference,
|
26
37
|
Packwerk::Offense,
|
27
38
|
)
|
28
39
|
]
|
29
40
|
)
|
30
41
|
end
|
31
|
-
def call(
|
32
|
-
|
42
|
+
def call(absolute_file)
|
43
|
+
parser = parser_for(absolute_file)
|
44
|
+
return [UnknownFileTypeResult.new(file: absolute_file)] if T.unsafe(parser).nil?
|
33
45
|
|
34
|
-
|
35
|
-
|
46
|
+
@cache.with_cache(absolute_file) do
|
47
|
+
node = parse_into_ast(absolute_file, T.must(parser))
|
48
|
+
return [] unless node
|
36
49
|
|
37
|
-
|
50
|
+
references_from_ast(node, absolute_file)
|
51
|
+
end
|
38
52
|
rescue Parsers::ParseError => e
|
39
53
|
[e.result]
|
40
54
|
end
|
41
55
|
|
42
56
|
private
|
43
57
|
|
44
|
-
|
58
|
+
sig do
|
59
|
+
params(node: Parser::AST::Node, absolute_file: String).returns(T::Array[UnresolvedReference])
|
60
|
+
end
|
61
|
+
def references_from_ast(node, absolute_file)
|
45
62
|
references = []
|
46
63
|
|
47
|
-
node_processor = @node_processor_factory.for(
|
64
|
+
node_processor = @node_processor_factory.for(absolute_file: absolute_file, node: node)
|
48
65
|
node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
|
49
66
|
node_visitor.visit(node, ancestors: [], result: references)
|
50
67
|
|
51
68
|
references
|
52
69
|
end
|
53
70
|
|
54
|
-
|
55
|
-
|
56
|
-
|
71
|
+
sig { params(absolute_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
|
72
|
+
def parse_into_ast(absolute_file, parser)
|
73
|
+
File.open(absolute_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
|
74
|
+
parser.call(io: file, file_path: absolute_file)
|
57
75
|
end
|
58
76
|
end
|
59
77
|
|
78
|
+
sig { params(file_path: String).returns(T.nilable(Parsers::ParserInterface)) }
|
60
79
|
def parser_for(file_path)
|
61
80
|
@parser_factory.for_path(file_path)
|
62
81
|
end
|
@@ -1,20 +1,40 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Packwerk
|
5
5
|
class FilesForProcessing
|
6
|
+
extend T::Sig
|
7
|
+
|
6
8
|
class << self
|
7
|
-
|
8
|
-
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig do
|
12
|
+
params(
|
13
|
+
relative_file_paths: T::Array[String],
|
14
|
+
configuration: Configuration,
|
15
|
+
ignore_nested_packages: T::Boolean
|
16
|
+
).returns(T::Array[String])
|
17
|
+
end
|
18
|
+
def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
|
19
|
+
new(relative_file_paths, configuration, ignore_nested_packages).files
|
9
20
|
end
|
10
21
|
end
|
11
22
|
|
12
|
-
|
13
|
-
|
23
|
+
sig do
|
24
|
+
params(
|
25
|
+
relative_file_paths: T::Array[String],
|
26
|
+
configuration: Configuration,
|
27
|
+
ignore_nested_packages: T::Boolean
|
28
|
+
).void
|
29
|
+
end
|
30
|
+
def initialize(relative_file_paths, configuration, ignore_nested_packages)
|
31
|
+
@relative_file_paths = relative_file_paths
|
14
32
|
@configuration = configuration
|
15
33
|
@ignore_nested_packages = ignore_nested_packages
|
34
|
+
@custom_files = T.let(nil, T.nilable(T::Array[String]))
|
16
35
|
end
|
17
36
|
|
37
|
+
sig { returns(T::Array[String]) }
|
18
38
|
def files
|
19
39
|
include_files = if custom_files.empty?
|
20
40
|
configured_included_files
|
@@ -27,50 +47,57 @@ module Packwerk
|
|
27
47
|
|
28
48
|
private
|
29
49
|
|
50
|
+
sig { returns(T::Array[String]) }
|
30
51
|
def custom_files
|
31
|
-
@custom_files ||= @
|
32
|
-
|
33
|
-
if File.file?(
|
34
|
-
|
52
|
+
@custom_files ||= @relative_file_paths.flat_map do |relative_file_path|
|
53
|
+
absolute_file_path = File.expand_path(relative_file_path, @configuration.root_path)
|
54
|
+
if File.file?(absolute_file_path)
|
55
|
+
absolute_file_path
|
35
56
|
else
|
36
|
-
custom_included_files(
|
57
|
+
custom_included_files(absolute_file_path)
|
37
58
|
end
|
38
59
|
end
|
39
60
|
end
|
40
61
|
|
41
|
-
|
62
|
+
sig { params(absolute_file_path: String).returns(T::Array[String]) }
|
63
|
+
def custom_included_files(absolute_file_path)
|
42
64
|
# Note, assuming include globs are always relative paths
|
43
65
|
absolute_includes = @configuration.include.map do |glob|
|
44
66
|
File.expand_path(glob, @configuration.root_path)
|
45
67
|
end
|
46
68
|
|
47
|
-
|
69
|
+
absolute_files = Dir.glob([File.join(absolute_file_path, "**", "*")]).select do |absolute_path|
|
48
70
|
absolute_includes.any? do |pattern|
|
49
|
-
File.fnmatch?(pattern,
|
71
|
+
File.fnmatch?(pattern, absolute_path, File::FNM_EXTGLOB)
|
50
72
|
end
|
51
73
|
end
|
52
74
|
|
53
75
|
if @ignore_nested_packages
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
76
|
+
nested_packages_absolute_file_paths = Dir.glob(File.join(absolute_file_path, "*", "**", "package.yml"))
|
77
|
+
nested_packages_absolute_globs = nested_packages_absolute_file_paths.map do |npp|
|
78
|
+
npp.gsub("package.yml", "**/*")
|
79
|
+
end
|
80
|
+
nested_packages_absolute_globs.each do |absolute_glob|
|
81
|
+
absolute_files -= Dir.glob(absolute_glob)
|
58
82
|
end
|
59
83
|
end
|
60
84
|
|
61
|
-
|
85
|
+
absolute_files
|
62
86
|
end
|
63
87
|
|
88
|
+
sig { returns(T::Array[String]) }
|
64
89
|
def configured_included_files
|
65
|
-
|
90
|
+
absolute_files_for_globs(@configuration.include)
|
66
91
|
end
|
67
92
|
|
93
|
+
sig { returns(T::Array[String]) }
|
68
94
|
def configured_excluded_files
|
69
|
-
|
95
|
+
absolute_files_for_globs(@configuration.exclude)
|
70
96
|
end
|
71
97
|
|
72
|
-
|
73
|
-
|
98
|
+
sig { params(relative_globs: T::Array[String]).returns(T::Array[String]) }
|
99
|
+
def absolute_files_for_globs(relative_globs)
|
100
|
+
relative_globs
|
74
101
|
.flat_map { |glob| Dir[File.expand_path(glob, @configuration.root_path)] }
|
75
102
|
.uniq
|
76
103
|
end
|