klenod-build 0.0.14 → 0.0.15
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/lib/klenod/build/context.rb +8 -2
- data/lib/klenod/build/filesystem_resolver.rb +21 -6
- data/lib/klenod/build/graph/invalidator.rb +5 -5
- data/lib/klenod/build/graph.rb +100 -4
- data/lib/klenod/build/hashing.rb +7 -0
- data/lib/klenod/build/plugins/gem_import_plugin.rb +2 -2
- data/lib/klenod/build/plugins/google_fonts_plugin.rb +19 -1
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +2 -0
- data/lib/klenod/build/plugins/haml_plugin.rb +4 -0
- data/lib/klenod/build/plugins/image_plugin.rb +34 -0
- data/lib/klenod/build/plugins/intl_plugin.rb +31 -7
- data/lib/klenod/build/plugins/static_asset_plugin.rb +8 -0
- data/lib/klenod/build/resolver.rb +1 -0
- data/lib/klenod/build/ruby_import_rewriter.rb +18 -1
- data/lib/klenod/build/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abf589452fadde26fe16d0bbc326e0161e8ed8a719ea516e12e286ef8e1609aa
|
|
4
|
+
data.tar.gz: 9f54e971b5dc88cd07348be0d96e9885928db10c64b4228bfb64e350b202f669
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af84591cca45a914ffbf087f308eea54d264cb14a0e03d6e60732a806d5e4cf44b00d291679b1f3dfe1f6d39f58bda043ff6b1de625b9d904667d2db5f8a6c07
|
|
7
|
+
data.tar.gz: 8f5fbdc8411f73fee7b7f6b86f803d7e04e588db97dc3d4d5019b7bf08cc63a1d73b05d82a97e74135fc2370be072b00524b7df3ac301c0ed2f4eb025c4352d7
|
data/lib/klenod/build/context.rb
CHANGED
|
@@ -113,7 +113,8 @@ module Klenod
|
|
|
113
113
|
asset_generation_concurrency: AssetGenerationQueue::DEFAULT_CONCURRENCY,
|
|
114
114
|
asset_download_concurrency: AssetGenerationQueue::DEFAULT_DOWNLOAD_CONCURRENCY,
|
|
115
115
|
profiler: nil,
|
|
116
|
-
namespace: Module.new
|
|
116
|
+
namespace: Module.new,
|
|
117
|
+
analysis: false
|
|
117
118
|
)
|
|
118
119
|
@source_dir = source_dir
|
|
119
120
|
plugins = plugins.to_a if plugins.equal?(DEFAULT_PLUGINS)
|
|
@@ -131,12 +132,17 @@ module Klenod
|
|
|
131
132
|
asset_generation_concurrency: asset_generation_concurrency,
|
|
132
133
|
asset_download_concurrency: asset_download_concurrency,
|
|
133
134
|
profiler: profiler,
|
|
134
|
-
namespace: namespace
|
|
135
|
+
namespace: namespace,
|
|
136
|
+
analysis: analysis
|
|
135
137
|
)
|
|
136
138
|
end
|
|
137
139
|
|
|
138
140
|
attr_reader :graph, :mode, :base, :asset_origin
|
|
139
141
|
|
|
142
|
+
def analysis?
|
|
143
|
+
@graph.analysis?
|
|
144
|
+
end
|
|
145
|
+
|
|
140
146
|
def evaluate(specifier)
|
|
141
147
|
@graph.load(specifier)
|
|
142
148
|
end
|
|
@@ -15,6 +15,14 @@ module Klenod
|
|
|
15
15
|
@root = Pathname.new(root).expand_path
|
|
16
16
|
@extensions = extensions.freeze
|
|
17
17
|
@path_prefix = path_prefix.to_s
|
|
18
|
+
@spelling_index = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Forget the cached file listing behind did-you-mean suggestions. The
|
|
22
|
+
# owning resolver clears it whenever files change, so a suggestion never
|
|
23
|
+
# walks the source tree more than once between file events.
|
|
24
|
+
def clear_cache
|
|
25
|
+
@spelling_index = nil
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def resolve(relative_path)
|
|
@@ -101,14 +109,21 @@ module Klenod
|
|
|
101
109
|
[]
|
|
102
110
|
end
|
|
103
111
|
|
|
112
|
+
SpellingIndex = Data.define(:aliases, :checker)
|
|
113
|
+
|
|
104
114
|
def spelling_corrections(relative_path)
|
|
105
|
-
|
|
106
|
-
matches =
|
|
107
|
-
DidYouMean::TreeSpellChecker
|
|
108
|
-
.new(dictionary: aliases.keys.sort, separator: "/")
|
|
109
|
-
.correct(relative_path)
|
|
115
|
+
index = spelling_index
|
|
116
|
+
matches = index.checker.correct(relative_path)
|
|
110
117
|
|
|
111
|
-
matches.flat_map { aliases.fetch(it) }.uniq.first(MAX_CORRECTIONS)
|
|
118
|
+
matches.flat_map { index.aliases.fetch(it) }.uniq.first(MAX_CORRECTIONS)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def spelling_index
|
|
122
|
+
@spelling_index ||=
|
|
123
|
+
begin
|
|
124
|
+
aliases = spelling_aliases
|
|
125
|
+
SpellingIndex.new(aliases, DidYouMean::TreeSpellChecker.new(dictionary: aliases.keys.sort, separator: "/"))
|
|
126
|
+
end
|
|
112
127
|
end
|
|
113
128
|
|
|
114
129
|
def spelling_aliases
|
|
@@ -24,7 +24,7 @@ module Klenod
|
|
|
24
24
|
errors = []
|
|
25
25
|
|
|
26
26
|
removed_module_ids.each do |module_id|
|
|
27
|
-
|
|
27
|
+
graph.remove_record(module_id)
|
|
28
28
|
mods.delete(module_id)
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -189,9 +189,7 @@ module Klenod
|
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def direct_dependents(module_id)
|
|
192
|
-
|
|
193
|
-
candidate_id if record.resolved_dependencies.any? { |dependency| dependency.module_id == module_id }
|
|
194
|
-
end
|
|
192
|
+
graph.dependents(module_id)
|
|
195
193
|
end
|
|
196
194
|
|
|
197
195
|
def mark_module_failed(module_id, error)
|
|
@@ -206,7 +204,8 @@ module Klenod
|
|
|
206
204
|
source_hash = loaded_source.source_hash || Hashing.hexdigest(source)
|
|
207
205
|
version = cached ? cached.version + 1 : 0
|
|
208
206
|
|
|
209
|
-
|
|
207
|
+
graph.store_record(
|
|
208
|
+
module_id,
|
|
210
209
|
ModuleRecord.new(
|
|
211
210
|
module_id,
|
|
212
211
|
source_hash,
|
|
@@ -222,6 +221,7 @@ module Klenod
|
|
|
222
221
|
version,
|
|
223
222
|
:failed
|
|
224
223
|
)
|
|
224
|
+
)
|
|
225
225
|
mods[module_id] = FailedModule.new(error)
|
|
226
226
|
end
|
|
227
227
|
|
data/lib/klenod/build/graph.rb
CHANGED
|
@@ -71,9 +71,11 @@ module Klenod
|
|
|
71
71
|
asset_generation_concurrency: AssetGenerationQueue::DEFAULT_CONCURRENCY,
|
|
72
72
|
asset_download_concurrency: AssetGenerationQueue::DEFAULT_DOWNLOAD_CONCURRENCY,
|
|
73
73
|
profiler: nil,
|
|
74
|
-
namespace: Module.new
|
|
74
|
+
namespace: Module.new,
|
|
75
|
+
analysis: false
|
|
75
76
|
)
|
|
76
77
|
@profiler = profiler || Profiler.new
|
|
78
|
+
@analysis = analysis
|
|
77
79
|
@resolver = Resolver.new(source_dir: source_dir, profiler: @profiler)
|
|
78
80
|
@plugins = plugins
|
|
79
81
|
@mode = mode
|
|
@@ -90,6 +92,16 @@ module Klenod
|
|
|
90
92
|
@virtual_metadata = {}
|
|
91
93
|
@virtual_owners = {}
|
|
92
94
|
@loading_tasks = {}
|
|
95
|
+
@source_overrides = {}
|
|
96
|
+
@dependents = {}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Analysis mode collects the graph for tooling such as the language
|
|
100
|
+
# server. Plugins must keep the record shape but skip work whose result
|
|
101
|
+
# is never used there: network fetches, hashing whole files, compiling
|
|
102
|
+
# JavaScript, and building asset bytes.
|
|
103
|
+
def analysis?
|
|
104
|
+
@analysis
|
|
93
105
|
end
|
|
94
106
|
|
|
95
107
|
def load(specifier)
|
|
@@ -211,10 +223,89 @@ module Klenod
|
|
|
211
223
|
end
|
|
212
224
|
end
|
|
213
225
|
|
|
226
|
+
# Editor buffers take precedence over files on disk for the given
|
|
227
|
+
# module until cleared. The next collection re-reads and, when the text
|
|
228
|
+
# changed, re-transforms the module. A caller that already transformed
|
|
229
|
+
# the same text can pass that `transform` so collection reuses it.
|
|
230
|
+
def override_source(module_id, source, transform: nil)
|
|
231
|
+
@source_overrides[module_id] = LoadResult.new(source, nil, transform)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def clear_source_override(module_id)
|
|
235
|
+
@source_overrides.delete(module_id)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def source_override?(module_id)
|
|
239
|
+
@source_overrides.key?(module_id)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Module ids whose collected record imports the given module, eagerly
|
|
243
|
+
# or lazily.
|
|
244
|
+
def dependents(module_id)
|
|
245
|
+
@dependents.fetch(module_id) { [] }.to_a
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def store_record(module_id, record)
|
|
249
|
+
previous = @records[module_id]
|
|
250
|
+
previous&.resolved_dependencies&.each { |dependency| @dependents[dependency.module_id]&.delete(module_id) }
|
|
251
|
+
record.resolved_dependencies.each { |dependency| (@dependents[dependency.module_id] ||= Set.new) << module_id }
|
|
252
|
+
@records[module_id] = record
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def remove_record(module_id)
|
|
256
|
+
record = @records.delete(module_id)
|
|
257
|
+
record&.resolved_dependencies&.each { |dependency| @dependents[dependency.module_id]&.delete(module_id) }
|
|
258
|
+
record
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Collect everything reachable from a collected module through eager
|
|
262
|
+
# and lazy dependencies. Each module the walk collects itself is
|
|
263
|
+
# yielded with `nil`; a module that fails to collect is yielded with
|
|
264
|
+
# its error and the walk continues past it.
|
|
265
|
+
def collect_reachable(module_id)
|
|
266
|
+
queue = @records.fetch(module_id).resolved_dependencies.dup
|
|
267
|
+
seen = Set.new([module_id])
|
|
268
|
+
index = 0
|
|
269
|
+
|
|
270
|
+
while index < queue.length
|
|
271
|
+
dependency_id = queue[index].module_id
|
|
272
|
+
index += 1
|
|
273
|
+
next unless seen.add?(dependency_id)
|
|
274
|
+
|
|
275
|
+
record = @records[dependency_id]
|
|
276
|
+
unless record
|
|
277
|
+
begin
|
|
278
|
+
record = collect_module(dependency_id)
|
|
279
|
+
rescue StandardError, ScriptError => error
|
|
280
|
+
yield dependency_id, error if block_given?
|
|
281
|
+
next
|
|
282
|
+
end
|
|
283
|
+
yield dependency_id, nil if block_given?
|
|
284
|
+
end
|
|
285
|
+
queue.concat(record.resolved_dependencies)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Transform source for a module without collecting it. Editor tooling
|
|
290
|
+
# such as the language server uses this to analyze unsaved text with the
|
|
291
|
+
# same plugins as a build, without adding a record or evaluating anything.
|
|
292
|
+
def transform_source(module_id, source)
|
|
293
|
+
transform = transform_module_source(module_id, source)
|
|
294
|
+
assert_supported_transform!(module_id, source, transform)
|
|
295
|
+
transform
|
|
296
|
+
end
|
|
297
|
+
|
|
214
298
|
def absolute_path(module_id)
|
|
215
299
|
@resolver.absolute_path(module_id)
|
|
216
300
|
end
|
|
217
301
|
|
|
302
|
+
# Forget resolved paths so files created, renamed, or deleted outside
|
|
303
|
+
# a build invalidation are looked up again. Invalidation does this
|
|
304
|
+
# itself; tooling that only transforms must ask for it.
|
|
305
|
+
def clear_resolver_cache
|
|
306
|
+
@resolver.clear_cache
|
|
307
|
+
end
|
|
308
|
+
|
|
218
309
|
def source_dir
|
|
219
310
|
@resolver.source_dir
|
|
220
311
|
end
|
|
@@ -235,7 +326,7 @@ module Klenod
|
|
|
235
326
|
@virtual_sources.delete(module_id)
|
|
236
327
|
@virtual_metadata.delete(module_id)
|
|
237
328
|
@virtual_owners.delete(module_id)
|
|
238
|
-
|
|
329
|
+
remove_record(module_id)
|
|
239
330
|
@mods.delete(module_id)
|
|
240
331
|
end
|
|
241
332
|
end
|
|
@@ -290,7 +381,7 @@ module Klenod
|
|
|
290
381
|
mod = instantiate_module(module_id, transform, resolved_dependencies, dependency_records, cached, source)
|
|
291
382
|
record = build_module_record(module_id, source, source_hash, transformed_hash, transform, resolved_dependencies, mod)
|
|
292
383
|
|
|
293
|
-
|
|
384
|
+
store_record(module_id, record)
|
|
294
385
|
@mods[module_id] = mod
|
|
295
386
|
record
|
|
296
387
|
end
|
|
@@ -345,7 +436,7 @@ module Klenod
|
|
|
345
436
|
transformed_hash = Hashing.hexdigest(transform.code)
|
|
346
437
|
record = build_module_record(module_id, source, source_hash, transformed_hash, transform, resolved_dependencies, cached)
|
|
347
438
|
|
|
348
|
-
|
|
439
|
+
store_record(module_id, record)
|
|
349
440
|
@mods.delete(module_id)
|
|
350
441
|
@profiler.progress(:collect_module, module_id: record.id.to_s, total_records: @records.length)
|
|
351
442
|
record
|
|
@@ -851,6 +942,11 @@ module Klenod
|
|
|
851
942
|
end
|
|
852
943
|
|
|
853
944
|
def load_source(module_id)
|
|
945
|
+
if (override = @source_overrides[module_id])
|
|
946
|
+
@profiler.count(:source_override_hit)
|
|
947
|
+
return override
|
|
948
|
+
end
|
|
949
|
+
|
|
854
950
|
@plugins.each do |plugin|
|
|
855
951
|
@profiler.count(:plugin_load_check)
|
|
856
952
|
loaded =
|
data/lib/klenod/build/hashing.rb
CHANGED
|
@@ -13,6 +13,13 @@ module Klenod
|
|
|
13
13
|
Digest::SHA256.file(path.to_s).hexdigest
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# A cheap change-detection key for a file when its bytes are not
|
|
17
|
+
# needed, e.g. graph analysis that skips asset work.
|
|
18
|
+
def self.file_key(path)
|
|
19
|
+
stat = File.stat(path.to_s)
|
|
20
|
+
short("#{path}:#{stat.mtime.to_f}:#{stat.size}")
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
def self.short(value, length: 16)
|
|
17
24
|
hexdigest(value)[0, length]
|
|
18
25
|
end
|
|
@@ -39,11 +39,11 @@ module Klenod
|
|
|
39
39
|
raise error.with_resolution_context(dependency: dependency, importer_id: dependency.importer_id)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def load(module_id,
|
|
42
|
+
def load(module_id, context)
|
|
43
43
|
return nil unless module_id.scheme == :gem
|
|
44
44
|
|
|
45
45
|
path = path_for(module_id)
|
|
46
|
-
LoadResult.new(path.binread, Hashing.file_hexdigest(path), nil)
|
|
46
|
+
LoadResult.new(path.binread, context.analysis? ? Hashing.file_key(path) : Hashing.file_hexdigest(path), nil)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
@@ -163,7 +163,9 @@ module Klenod
|
|
|
163
163
|
return nil unless google_fonts_module?(module_id)
|
|
164
164
|
|
|
165
165
|
url = google_fonts_url_for(module_id)
|
|
166
|
-
css = fetch_css(url)
|
|
166
|
+
css = context.analysis? ? cached_css(url) : fetch_css(url)
|
|
167
|
+
return analysis_load(module_id, url, context) unless css
|
|
168
|
+
|
|
167
169
|
font_faces = FontFaceParser.new(css).font_faces_by_url
|
|
168
170
|
font_assets = {}
|
|
169
171
|
rewritten_css =
|
|
@@ -220,6 +222,22 @@ module Klenod
|
|
|
220
222
|
raise Error, "Could not download Google Fonts asset #{url.inspect}: #{error.message}"
|
|
221
223
|
end
|
|
222
224
|
|
|
225
|
+
# Analysis never touches the network: a warm cache is used as is,
|
|
226
|
+
# and a cold one yields an empty stylesheet that keeps importers
|
|
227
|
+
# transforming.
|
|
228
|
+
def cached_css(url)
|
|
229
|
+
return nil unless @css_cache && !@refresh_cache
|
|
230
|
+
|
|
231
|
+
@css_cache.read(url)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def analysis_load(module_id, url, context)
|
|
235
|
+
css_asset = css_asset(module_id, url, "", [])
|
|
236
|
+
css_asset.url = context.asset_url(css_asset.output_path)
|
|
237
|
+
@assets_by_module_id[module_id] = [css_asset]
|
|
238
|
+
ruby_module_source(css_asset.url)
|
|
239
|
+
end
|
|
240
|
+
|
|
223
241
|
def fetch_css(url)
|
|
224
242
|
if @css_cache && !@refresh_cache
|
|
225
243
|
css = @css_cache.read(url)
|
|
@@ -136,6 +136,8 @@ module Klenod
|
|
|
136
136
|
end
|
|
137
137
|
rescue RubyParseError => error
|
|
138
138
|
raise ParseError.new(error, source: source, module_id: module_id)
|
|
139
|
+
rescue RubyImportRewriter::ParseError => error
|
|
140
|
+
raise ParseError.new(RubyParseError.new(error.message, line: error.line), source: source, module_id: module_id)
|
|
139
141
|
ensure
|
|
140
142
|
@profiler = previous_profiler
|
|
141
143
|
@module_id = previous_module_id
|
|
@@ -78,6 +78,10 @@ module Klenod
|
|
|
78
78
|
@transformer = Transformer.new
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
# The validated `variables` mapping, so tooling such as the language
|
|
82
|
+
# server can tell whether `$name` reads a framework prop.
|
|
83
|
+
attr_reader :variables
|
|
84
|
+
|
|
81
85
|
def resolve(dependency, _context)
|
|
82
86
|
styles_dependency = resolve_class_names_runtime(dependency)
|
|
83
87
|
return styles_dependency if styles_dependency
|
|
@@ -91,6 +91,8 @@ module Klenod
|
|
|
91
91
|
return nil unless EXTENSIONS.include?(module_id.extname)
|
|
92
92
|
|
|
93
93
|
source_path = context.absolute_path(module_id)
|
|
94
|
+
return analysis_load(module_id, source_path, context) if context.analysis?
|
|
95
|
+
|
|
94
96
|
source_hash = Hashing.file_hexdigest(source_path)
|
|
95
97
|
dimensions = image_dimensions(source_path, module_id)
|
|
96
98
|
image_options = image_options_for(module_id)
|
|
@@ -158,6 +160,38 @@ module Klenod
|
|
|
158
160
|
raise DecodeError.new(error, module_id: module_id)
|
|
159
161
|
end
|
|
160
162
|
|
|
163
|
+
# Analysis keeps the module shape (dimensions, content type, a
|
|
164
|
+
# single asset reference) without hashing the file, decoding it, or
|
|
165
|
+
# describing variants nobody will generate.
|
|
166
|
+
def analysis_load(module_id, source_path, context)
|
|
167
|
+
source_hash = Hashing.file_key(source_path)
|
|
168
|
+
dimensions = image_dimensions(source_path, module_id)
|
|
169
|
+
asset = static_image_asset(module_id, source_path, source_hash, dimensions)
|
|
170
|
+
asset.url = context.asset_url(asset.output_path)
|
|
171
|
+
image_runtime_dependency = image_runtime_dependency_for(module_id)
|
|
172
|
+
transform =
|
|
173
|
+
TransformResult.new(
|
|
174
|
+
image_module_source(module_id, [asset], image_runtime_dependency, context, placeholder: nil),
|
|
175
|
+
[image_runtime_dependency],
|
|
176
|
+
nil,
|
|
177
|
+
[asset],
|
|
178
|
+
[],
|
|
179
|
+
{}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
LoadResult.new(image_source(module_id), source_hash, transform)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def image_runtime_dependency_for(module_id)
|
|
186
|
+
Dependency
|
|
187
|
+
.create(
|
|
188
|
+
specifier: IMAGE_RUNTIME_SPECIFIER,
|
|
189
|
+
importer_id: module_id,
|
|
190
|
+
kind: :image_runtime
|
|
191
|
+
)
|
|
192
|
+
.with(id: "#{module_id}:image_runtime")
|
|
193
|
+
end
|
|
194
|
+
|
|
161
195
|
def default_image_asset(module_id, source_path, source_hash, dimensions, image_options, queue)
|
|
162
196
|
return static_image_asset(module_id, source_path, source_hash, dimensions) unless generated_default_image?(image_options)
|
|
163
197
|
|
|
@@ -26,6 +26,12 @@ module Klenod
|
|
|
26
26
|
class Plugin < Klenod::Build::Plugin
|
|
27
27
|
INTL_FILE_RE = /\.intl\.(?<locale>[^\/]+)\.toml\z/
|
|
28
28
|
|
|
29
|
+
CachedTranslations = Data.define(:mtime, :size, :translations)
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@cache = {}
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
def translations_for(context, module_id)
|
|
30
36
|
base = module_id.path.delete_suffix(module_id.extname)
|
|
31
37
|
pattern = context.absolute_path(ModuleId.new("#{base}.intl.*.toml", nil)).to_s
|
|
@@ -39,18 +45,36 @@ module Klenod
|
|
|
39
45
|
end
|
|
40
46
|
end
|
|
41
47
|
|
|
48
|
+
# Changed or removed companions leave the cache immediately, so it
|
|
49
|
+
# only ever holds live files. Nothing else needs invalidating: the
|
|
50
|
+
# owning Haml module is re-transformed by HamlPlugin's own hook.
|
|
51
|
+
def invalidate_module_ids(paths, _context)
|
|
52
|
+
paths.each { |path| @cache.delete(File.expand_path(path)) }
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
55
|
+
|
|
42
56
|
private
|
|
43
57
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
58
|
+
# Parsed translations are kept per file and validated by stat, so a
|
|
59
|
+
# module that transforms often, as in the language server, does not
|
|
60
|
+
# re-read and re-parse companions that did not change. Read the file
|
|
61
|
+
# here rather than using TomlRB.load_file, so a parse failure can
|
|
62
|
+
# carry the source for the excerpt.
|
|
46
63
|
def translations_from(path, module_id)
|
|
64
|
+
stat = File.stat(path)
|
|
65
|
+
cached = @cache[path]
|
|
66
|
+
return cached.translations if cached && cached.mtime == stat.mtime && cached.size == stat.size
|
|
67
|
+
|
|
47
68
|
source = File.read(path)
|
|
69
|
+
translations =
|
|
70
|
+
begin
|
|
71
|
+
TomlRB.parse(source)
|
|
72
|
+
rescue TomlRB::Error => error
|
|
73
|
+
raise ParseError.new(error, source: source, module_id: companion_id(path, module_id))
|
|
74
|
+
end
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rescue TomlRB::Error => error
|
|
52
|
-
raise ParseError.new(error, source: source, module_id: companion_id(path, module_id))
|
|
53
|
-
end
|
|
76
|
+
@cache[path] = CachedTranslations.new(stat.mtime, stat.size, translations)
|
|
77
|
+
translations
|
|
54
78
|
end
|
|
55
79
|
|
|
56
80
|
# The companion is not a module in the graph, but its id resolves to a
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../asset"
|
|
4
4
|
require_relative "../hashing"
|
|
5
|
+
require_relative "../load_result"
|
|
5
6
|
require_relative "../plugin"
|
|
6
7
|
require_relative "../transform_result"
|
|
7
8
|
|
|
@@ -21,6 +22,13 @@ module Klenod
|
|
|
21
22
|
".woff2" => "font/woff2"
|
|
22
23
|
}.freeze
|
|
23
24
|
|
|
25
|
+
# Analysis never needs the bytes of a font, so the file is not read.
|
|
26
|
+
def load(module_id, context)
|
|
27
|
+
return nil unless context.analysis? && CONTENT_TYPES.key?(module_id.extname)
|
|
28
|
+
|
|
29
|
+
LoadResult.new("", Hashing.short(module_id.to_s), TransformResult.new("Default = nil\n", [], nil, [], [], {}))
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
def transform(module_id, code, context)
|
|
25
33
|
return super unless CONTENT_TYPES.key?(module_id.extname)
|
|
26
34
|
|
|
@@ -39,6 +39,18 @@ module Klenod
|
|
|
39
39
|
IMPORT_SOURCE_PATTERN = /(?<![A-Za-z0-9_])(?:import|lazy_import|import_glob)\s*(?:\(|["'])/
|
|
40
40
|
FAST_LITERAL_IMPORT_PATTERN = /(?<![A-Za-z0-9_])(import|lazy_import)\s*\(\s*("(?:\\.|[^"\\#])*")\s*\)/
|
|
41
41
|
|
|
42
|
+
# Ruby that does not parse, reported at its line in the enclosing
|
|
43
|
+
# source. Haml wraps it into its own parse error with a source excerpt.
|
|
44
|
+
class ParseError < Klenod::Build::Error
|
|
45
|
+
attr_reader :line, :column
|
|
46
|
+
|
|
47
|
+
def initialize(message, line:, column:)
|
|
48
|
+
@line = line
|
|
49
|
+
@column = column
|
|
50
|
+
super(message)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
42
54
|
def initialize(module_id:, kind:, source_dir: nil, profiler: nil, dependency_id_offset: 0, source_line_offset: 0, source_column_offset: 0)
|
|
43
55
|
@module_id = module_id
|
|
44
56
|
@kind = kind
|
|
@@ -54,7 +66,12 @@ module Klenod
|
|
|
54
66
|
fast_result = rewrite_literal_import_calls(code)
|
|
55
67
|
return fast_result if fast_result
|
|
56
68
|
|
|
57
|
-
ast =
|
|
69
|
+
ast =
|
|
70
|
+
begin
|
|
71
|
+
measure(:ruby_import_parse) { SyntaxTree.parse(code) }
|
|
72
|
+
rescue SyntaxTree::Parser::ParseError => error
|
|
73
|
+
raise ParseError.new(error.message, line: error.lineno + @source_line_offset, column: error.column + 1 + @source_column_offset)
|
|
74
|
+
end
|
|
58
75
|
calls = measure(:ruby_import_scan) { import_calls(ast) }
|
|
59
76
|
expanded = expand_import_calls(calls)
|
|
60
77
|
|
data/lib/klenod/build/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: klenod-build
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrés Alin
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.0.
|
|
18
|
+
version: 0.0.15
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.0.
|
|
25
|
+
version: 0.0.15
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: async
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|