packwerk 2.0.0 → 2.2.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/Gemfile.lock +26 -22
- data/README.md +13 -1
- data/USAGE.md +7 -0
- data/lib/packwerk/application_load_paths.rb +12 -18
- data/lib/packwerk/application_validator.rb +88 -40
- data/lib/packwerk/cache.rb +169 -0
- data/lib/packwerk/cli.rb +29 -13
- data/lib/packwerk/configuration.rb +17 -12
- data/lib/packwerk/constant_discovery.rb +20 -4
- data/lib/packwerk/constant_name_inspector.rb +1 -1
- data/lib/packwerk/deprecated_references.rb +1 -1
- data/lib/packwerk/file_processor.rb +43 -22
- data/lib/packwerk/files_for_processing.rb +55 -26
- data/lib/packwerk/generators/templates/packwerk.yml.erb +6 -0
- 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 +4 -3
- 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 +19 -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 +3 -4
- data/lib/packwerk/reference_extractor.rb +72 -20
- data/lib/packwerk/reference_offense.rb +8 -3
- data/lib/packwerk/result.rb +2 -2
- data/lib/packwerk/run_context.rb +62 -36
- 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 +2 -0
- data/packwerk.gemspec +4 -2
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
- data/sorbet/tapioca/require.rb +1 -1
- metadata +36 -5
@@ -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,12 @@ 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 =
|
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(
|
36
|
+
offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
|
37
|
+
OffensesFormatter
|
38
|
+
)
|
36
39
|
end
|
37
40
|
|
38
41
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
@@ -78,12 +81,14 @@ module Packwerk
|
|
78
81
|
|
79
82
|
private
|
80
83
|
|
84
|
+
sig { returns(T::Boolean) }
|
81
85
|
def init
|
82
86
|
@out.puts("📦 Initializing Packwerk...")
|
83
87
|
|
84
88
|
generate_configs
|
85
89
|
end
|
86
90
|
|
91
|
+
sig { returns(T::Boolean) }
|
87
92
|
def generate_configs
|
88
93
|
configuration_file = Packwerk::Generators::ConfigurationFile.generate(
|
89
94
|
root: @configuration.root_path,
|
@@ -112,23 +117,31 @@ module Packwerk
|
|
112
117
|
success
|
113
118
|
end
|
114
119
|
|
120
|
+
sig { params(result: Result).returns(T::Boolean) }
|
115
121
|
def output_result(result)
|
116
122
|
@out.puts
|
117
123
|
@out.puts(result.message)
|
118
124
|
result.status
|
119
125
|
end
|
120
126
|
|
121
|
-
|
122
|
-
|
123
|
-
|
127
|
+
sig do
|
128
|
+
params(
|
129
|
+
relative_file_paths: T::Array[String],
|
130
|
+
ignore_nested_packages: T::Boolean
|
131
|
+
).returns(FilesForProcessing::AbsoluteFileSet)
|
132
|
+
end
|
133
|
+
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
134
|
+
absolute_file_set = FilesForProcessing.fetch(
|
135
|
+
relative_file_paths: relative_file_paths,
|
124
136
|
ignore_nested_packages: ignore_nested_packages,
|
125
137
|
configuration: @configuration
|
126
138
|
)
|
127
139
|
abort("No files found or given. "\
|
128
|
-
"Specify files or check the include and exclude glob in the config file.") if
|
129
|
-
|
140
|
+
"Specify files or check the include and exclude glob in the config file.") if absolute_file_set.empty?
|
141
|
+
absolute_file_set
|
130
142
|
end
|
131
143
|
|
144
|
+
sig { params(_paths: T::Array[String]).returns(T::Boolean) }
|
132
145
|
def validate(_paths)
|
133
146
|
@progress_formatter.started_validation do
|
134
147
|
result = checker.check_all
|
@@ -139,6 +152,7 @@ module Packwerk
|
|
139
152
|
end
|
140
153
|
end
|
141
154
|
|
155
|
+
sig { returns(ApplicationValidator) }
|
142
156
|
def checker
|
143
157
|
Packwerk::ApplicationValidator.new(
|
144
158
|
config_file_path: @configuration.config_path,
|
@@ -147,6 +161,7 @@ module Packwerk
|
|
147
161
|
)
|
148
162
|
end
|
149
163
|
|
164
|
+
sig { params(result: ApplicationValidator::Result).void }
|
150
165
|
def list_validation_errors(result)
|
151
166
|
@out.puts
|
152
167
|
if result.ok?
|
@@ -157,24 +172,25 @@ module Packwerk
|
|
157
172
|
end
|
158
173
|
end
|
159
174
|
|
175
|
+
sig { params(params: T.untyped).returns(ParseRun) }
|
160
176
|
def parse_run(params)
|
161
|
-
|
177
|
+
relative_file_paths = T.let([], T::Array[String])
|
162
178
|
ignore_nested_packages = nil
|
163
179
|
|
164
180
|
if params.any? { |p| p.include?("--packages") }
|
165
181
|
OptionParser.new do |parser|
|
166
182
|
parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
|
167
|
-
|
183
|
+
relative_file_paths = p
|
168
184
|
end
|
169
185
|
end.parse!(params)
|
170
186
|
ignore_nested_packages = true
|
171
187
|
else
|
172
|
-
|
188
|
+
relative_file_paths = params
|
173
189
|
ignore_nested_packages = false
|
174
190
|
end
|
175
191
|
|
176
192
|
ParseRun.new(
|
177
|
-
|
193
|
+
absolute_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
|
178
194
|
configuration: @configuration,
|
179
195
|
progress_formatter: @progress_formatter,
|
180
196
|
offenses_formatter: @offenses_formatter
|
@@ -34,10 +34,21 @@ 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, :config_path
|
37
|
+
:include, :exclude, :root_path, :package_paths, :custom_associations, :config_path, :cache_directory
|
38
38
|
)
|
39
39
|
|
40
40
|
def initialize(configs = {}, config_path: nil)
|
41
|
+
@include = configs["include"] || DEFAULT_INCLUDE_GLOBS
|
42
|
+
@exclude = configs["exclude"] || DEFAULT_EXCLUDE_GLOBS
|
43
|
+
root = config_path ? File.dirname(config_path) : "."
|
44
|
+
@root_path = File.expand_path(root)
|
45
|
+
@package_paths = configs["package_paths"] || "**/"
|
46
|
+
@custom_associations = configs["custom_associations"] || []
|
47
|
+
@parallel = configs.key?("parallel") ? configs["parallel"] : true
|
48
|
+
@cache_enabled = configs.key?("cache") ? configs["cache"] : false
|
49
|
+
@cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
|
50
|
+
@config_path = config_path
|
51
|
+
|
41
52
|
if configs["load_paths"]
|
42
53
|
warning = <<~WARNING
|
43
54
|
DEPRECATION WARNING: The 'load_paths' key in `packwerk.yml` is deprecated.
|
@@ -47,7 +58,6 @@ module Packwerk
|
|
47
58
|
warn(warning)
|
48
59
|
end
|
49
60
|
|
50
|
-
inflection_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
|
51
61
|
if configs["inflections_file"]
|
52
62
|
warning = <<~WARNING
|
53
63
|
DEPRECATION WARNING: The 'inflections_file' key in `packwerk.yml` is deprecated.
|
@@ -58,6 +68,7 @@ module Packwerk
|
|
58
68
|
warn(warning)
|
59
69
|
end
|
60
70
|
|
71
|
+
inflection_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
|
61
72
|
if Pathname.new(inflection_file).exist?
|
62
73
|
warning = <<~WARNING
|
63
74
|
DEPRECATION WARNING: Inflections YMLs in packwerk are now deprecated.
|
@@ -66,16 +77,6 @@ module Packwerk
|
|
66
77
|
|
67
78
|
warn(warning)
|
68
79
|
end
|
69
|
-
|
70
|
-
@include = configs["include"] || DEFAULT_INCLUDE_GLOBS
|
71
|
-
@exclude = configs["exclude"] || DEFAULT_EXCLUDE_GLOBS
|
72
|
-
root = config_path ? File.dirname(config_path) : "."
|
73
|
-
@root_path = File.expand_path(root)
|
74
|
-
@package_paths = configs["package_paths"] || "**/"
|
75
|
-
@custom_associations = configs["custom_associations"] || []
|
76
|
-
@parallel = configs.key?("parallel") ? configs["parallel"] : true
|
77
|
-
|
78
|
-
@config_path = config_path
|
79
80
|
end
|
80
81
|
|
81
82
|
def load_paths
|
@@ -85,5 +86,9 @@ module Packwerk
|
|
85
86
|
def parallel?
|
86
87
|
@parallel
|
87
88
|
end
|
89
|
+
|
90
|
+
def cache_enabled?
|
91
|
+
@cache_enabled
|
92
|
+
end
|
88
93
|
end
|
89
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,17 +35,28 @@ 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)
|
@@ -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,76 @@ 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)
|
30
|
+
end
|
31
|
+
|
32
|
+
class ProcessedFile < T::Struct
|
33
|
+
const :unresolved_references, T::Array[UnresolvedReference], default: []
|
34
|
+
const :offenses, T::Array[Offense], default: []
|
19
35
|
end
|
20
36
|
|
21
37
|
sig do
|
22
|
-
params(
|
23
|
-
T::Array[
|
24
|
-
T.any(
|
25
|
-
Packwerk::Reference,
|
26
|
-
Packwerk::Offense,
|
27
|
-
)
|
28
|
-
]
|
29
|
-
)
|
38
|
+
params(absolute_file: String).returns(ProcessedFile)
|
30
39
|
end
|
31
|
-
def call(
|
32
|
-
|
40
|
+
def call(absolute_file)
|
41
|
+
parser = parser_for(absolute_file)
|
42
|
+
if parser.nil?
|
43
|
+
return ProcessedFile.new(offenses: [UnknownFileTypeResult.new(file: absolute_file)])
|
44
|
+
end
|
45
|
+
|
46
|
+
unresolved_references = @cache.with_cache(absolute_file) do
|
47
|
+
node = parse_into_ast(absolute_file, parser)
|
48
|
+
return ProcessedFile.new unless node
|
33
49
|
|
34
|
-
|
35
|
-
|
50
|
+
references_from_ast(node, absolute_file)
|
51
|
+
end
|
36
52
|
|
37
|
-
|
53
|
+
ProcessedFile.new(unresolved_references: unresolved_references)
|
38
54
|
rescue Parsers::ParseError => e
|
39
|
-
[e.result]
|
55
|
+
ProcessedFile.new(offenses: [e.result])
|
40
56
|
end
|
41
57
|
|
42
58
|
private
|
43
59
|
|
44
|
-
|
60
|
+
sig do
|
61
|
+
params(node: Parser::AST::Node, absolute_file: String).returns(T::Array[UnresolvedReference])
|
62
|
+
end
|
63
|
+
def references_from_ast(node, absolute_file)
|
45
64
|
references = []
|
46
65
|
|
47
|
-
node_processor = @node_processor_factory.for(
|
66
|
+
node_processor = @node_processor_factory.for(absolute_file: absolute_file, node: node)
|
48
67
|
node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
|
49
68
|
node_visitor.visit(node, ancestors: [], result: references)
|
50
69
|
|
51
70
|
references
|
52
71
|
end
|
53
72
|
|
54
|
-
|
55
|
-
|
56
|
-
|
73
|
+
sig { params(absolute_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
|
74
|
+
def parse_into_ast(absolute_file, parser)
|
75
|
+
File.open(absolute_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
|
76
|
+
parser.call(io: file, file_path: absolute_file)
|
57
77
|
end
|
58
78
|
end
|
59
79
|
|
80
|
+
sig { params(file_path: String).returns(T.nilable(Parsers::ParserInterface)) }
|
60
81
|
def parser_for(file_path)
|
61
82
|
@parser_factory.for_path(file_path)
|
62
83
|
end
|
@@ -1,20 +1,42 @@
|
|
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
|
+
|
8
|
+
AbsoluteFileSet = T.type_alias { T::Set[String] }
|
9
|
+
|
6
10
|
class << self
|
7
|
-
|
8
|
-
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig do
|
14
|
+
params(
|
15
|
+
relative_file_paths: T::Array[String],
|
16
|
+
configuration: Configuration,
|
17
|
+
ignore_nested_packages: T::Boolean
|
18
|
+
).returns(AbsoluteFileSet)
|
19
|
+
end
|
20
|
+
def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
|
21
|
+
new(relative_file_paths, configuration, ignore_nested_packages).files
|
9
22
|
end
|
10
23
|
end
|
11
24
|
|
12
|
-
|
13
|
-
|
25
|
+
sig do
|
26
|
+
params(
|
27
|
+
relative_file_paths: T::Array[String],
|
28
|
+
configuration: Configuration,
|
29
|
+
ignore_nested_packages: T::Boolean
|
30
|
+
).void
|
31
|
+
end
|
32
|
+
def initialize(relative_file_paths, configuration, ignore_nested_packages)
|
33
|
+
@relative_file_paths = relative_file_paths
|
14
34
|
@configuration = configuration
|
15
35
|
@ignore_nested_packages = ignore_nested_packages
|
36
|
+
@custom_files = T.let(nil, T.nilable(AbsoluteFileSet))
|
16
37
|
end
|
17
38
|
|
39
|
+
sig { returns(AbsoluteFileSet) }
|
18
40
|
def files
|
19
41
|
include_files = if custom_files.empty?
|
20
42
|
configured_included_files
|
@@ -27,52 +49,59 @@ module Packwerk
|
|
27
49
|
|
28
50
|
private
|
29
51
|
|
52
|
+
sig { returns(AbsoluteFileSet) }
|
30
53
|
def custom_files
|
31
|
-
@custom_files ||=
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
54
|
+
@custom_files ||= Set.new(
|
55
|
+
@relative_file_paths.map do |relative_file_path|
|
56
|
+
absolute_file_path = File.expand_path(relative_file_path, @configuration.root_path)
|
57
|
+
if File.file?(absolute_file_path)
|
58
|
+
absolute_file_path
|
59
|
+
else
|
60
|
+
custom_included_files(absolute_file_path)
|
61
|
+
end
|
37
62
|
end
|
38
|
-
|
63
|
+
).flatten
|
39
64
|
end
|
40
65
|
|
41
|
-
|
66
|
+
sig { params(absolute_file_path: String).returns(AbsoluteFileSet) }
|
67
|
+
def custom_included_files(absolute_file_path)
|
42
68
|
# Note, assuming include globs are always relative paths
|
43
69
|
absolute_includes = @configuration.include.map do |glob|
|
44
70
|
File.expand_path(glob, @configuration.root_path)
|
45
71
|
end
|
46
72
|
|
47
|
-
|
73
|
+
absolute_files = Dir.glob([File.join(absolute_file_path, "**", "*")]).select do |absolute_path|
|
48
74
|
absolute_includes.any? do |pattern|
|
49
|
-
File.fnmatch?(pattern,
|
75
|
+
File.fnmatch?(pattern, absolute_path, File::FNM_EXTGLOB)
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
53
79
|
if @ignore_nested_packages
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
80
|
+
nested_packages_absolute_file_paths = Dir.glob(File.join(absolute_file_path, "*", "**", "package.yml"))
|
81
|
+
nested_packages_absolute_globs = nested_packages_absolute_file_paths.map do |npp|
|
82
|
+
npp.gsub("package.yml", "**/*")
|
83
|
+
end
|
84
|
+
nested_packages_absolute_globs.each do |absolute_glob|
|
85
|
+
absolute_files -= Dir.glob(absolute_glob)
|
58
86
|
end
|
59
87
|
end
|
60
88
|
|
61
|
-
|
89
|
+
Set.new(absolute_files)
|
62
90
|
end
|
63
91
|
|
92
|
+
sig { returns(AbsoluteFileSet) }
|
64
93
|
def configured_included_files
|
65
|
-
|
94
|
+
absolute_files_for_globs(@configuration.include)
|
66
95
|
end
|
67
96
|
|
97
|
+
sig { returns(AbsoluteFileSet) }
|
68
98
|
def configured_excluded_files
|
69
|
-
|
99
|
+
absolute_files_for_globs(@configuration.exclude)
|
70
100
|
end
|
71
101
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
.uniq
|
102
|
+
sig { params(relative_globs: T::Array[String]).returns(AbsoluteFileSet) }
|
103
|
+
def absolute_files_for_globs(relative_globs)
|
104
|
+
Set.new(relative_globs.flat_map { |glob| Dir[File.expand_path(glob, @configuration.root_path)] })
|
76
105
|
end
|
77
106
|
end
|
78
107
|
end
|
@@ -15,3 +15,9 @@
|
|
15
15
|
# List of custom associations, if any
|
16
16
|
# custom_associations:
|
17
17
|
# - "cache_belongs_to"
|
18
|
+
|
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'
|