klenod-build 0.0.15 → 0.0.17
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/graph.rb +38 -7
- data/lib/klenod/build/plugins/class_names_runtime.rb +1 -1
- data/lib/klenod/build/plugins/haml_plugin/helper_source.rb +2 -1
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +17 -6
- data/lib/klenod/build/plugins/haml_plugin.rb +16 -7
- data/lib/klenod/build/plugins/markdown_compiler.rb +1 -1
- data/lib/klenod/build/plugins/markdown_plugin.rb +2 -2
- data/lib/klenod/build/plugins/ruby_plugin.rb +64 -8
- data/lib/klenod/build/ruby_import_rewriter.rb +37 -15
- data/lib/klenod/build/source_error.rb +37 -0
- 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: d24b06569546f0b32ba9aebee8e17b989b9cd67198714952404b818dbae7d425
|
|
4
|
+
data.tar.gz: c61a9de1db41a5258cf2f9ae742ab9256c69fa06df0ef799035db39df89630f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb64e39cf3daedff82d30572bb92c136a3091118a843aa7e9180c40dd83e5e074be8a017867744fb4921a6a092eab366095a510639bacf8aba37a2859c013dd3
|
|
7
|
+
data.tar.gz: 9d9ad93cbfc302d0db9e4bddca0cb333929fe62480791bfadef0848bc09c4459fd6ed5145fa93f444ed077f1f7e63d172900a7065f81a6e0e24d7f0cf71c9eed
|
data/lib/klenod/build/graph.rb
CHANGED
|
@@ -374,6 +374,7 @@ module Klenod
|
|
|
374
374
|
transform = loaded_source.transform || transform_module_source(module_id, source)
|
|
375
375
|
resolved_dependencies = resolve_transform_dependencies(module_id, transform)
|
|
376
376
|
dependency_records = load_eager_dependency_records(resolved_dependencies)
|
|
377
|
+
assert_eager_import_names!(module_id, source, resolved_dependencies, dependency_records)
|
|
377
378
|
transform = finalize_transform_result(module_id, transform, resolved_dependencies, dependency_records)
|
|
378
379
|
assert_supported_transform!(module_id, source, transform)
|
|
379
380
|
assert_generated_ruby_parses!(module_id, transform)
|
|
@@ -430,6 +431,7 @@ module Klenod
|
|
|
430
431
|
transform = loaded_source.transform || transform_module_source(module_id, source)
|
|
431
432
|
resolved_dependencies = resolve_transform_dependencies(module_id, transform)
|
|
432
433
|
dependency_records = collect_eager_dependency_records(resolved_dependencies)
|
|
434
|
+
assert_eager_import_names!(module_id, source, resolved_dependencies, dependency_records)
|
|
433
435
|
transform = finalize_transform_result(module_id, transform, resolved_dependencies, dependency_records)
|
|
434
436
|
assert_supported_transform!(module_id, source, transform)
|
|
435
437
|
assert_generated_ruby_parses!(module_id, transform)
|
|
@@ -462,7 +464,7 @@ module Klenod
|
|
|
462
464
|
Runtime::Mod.new(
|
|
463
465
|
module_id.to_s,
|
|
464
466
|
record.transformed_source,
|
|
465
|
-
imports: imports_for(record.resolved_dependencies, dependency_records),
|
|
467
|
+
imports: imports_for(module_id, record.source, record.resolved_dependencies, dependency_records),
|
|
466
468
|
source_map: record.source_map,
|
|
467
469
|
version: record.version,
|
|
468
470
|
eval_path: eval_path_for(module_id),
|
|
@@ -659,7 +661,7 @@ module Klenod
|
|
|
659
661
|
Runtime::Mod.new(
|
|
660
662
|
module_id.to_s,
|
|
661
663
|
transform.code,
|
|
662
|
-
imports: imports_for(resolved_dependencies, dependency_records),
|
|
664
|
+
imports: imports_for(module_id, source, resolved_dependencies, dependency_records),
|
|
663
665
|
source_map: transform.source_map,
|
|
664
666
|
version: cached ? cached.version + 1 : 0,
|
|
665
667
|
eval_path: eval_path_for(module_id),
|
|
@@ -690,7 +692,7 @@ module Klenod
|
|
|
690
692
|
source_dir.join(module_id.path).to_s
|
|
691
693
|
end
|
|
692
694
|
|
|
693
|
-
def imports_for(resolved_dependencies, dependency_records)
|
|
695
|
+
def imports_for(module_id, source, resolved_dependencies, dependency_records)
|
|
694
696
|
resolved_dependencies.to_h do |resolved_dependency|
|
|
695
697
|
value =
|
|
696
698
|
if resolved_dependency.dependency.eager
|
|
@@ -702,6 +704,7 @@ module Klenod
|
|
|
702
704
|
evaluate_module(resolved_dependency.module_id)
|
|
703
705
|
record = @records.fetch(resolved_dependency.module_id)
|
|
704
706
|
raise_failed_module!(record)
|
|
707
|
+
assert_import_name!(module_id, source, resolved_dependency, record)
|
|
705
708
|
import_value(resolved_dependency, record)
|
|
706
709
|
end
|
|
707
710
|
end
|
|
@@ -898,10 +901,9 @@ module Klenod
|
|
|
898
901
|
@records.to_h do |module_id, record|
|
|
899
902
|
imports =
|
|
900
903
|
record.resolved_dependencies.to_h do |resolved_dependency|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
]
|
|
904
|
+
target = @records.fetch(resolved_dependency.module_id)
|
|
905
|
+
assert_import_name!(module_id, record.source, resolved_dependency, target)
|
|
906
|
+
[resolved_dependency.dependency.id, runtime_import_spec(resolved_dependency, target)]
|
|
905
907
|
end
|
|
906
908
|
|
|
907
909
|
[
|
|
@@ -990,6 +992,35 @@ module Klenod
|
|
|
990
992
|
end
|
|
991
993
|
end
|
|
992
994
|
|
|
995
|
+
# Lazy targets are not collected until bundle or call time, so only the
|
|
996
|
+
# eager imports can be checked while the importer is collected.
|
|
997
|
+
def assert_eager_import_names!(module_id, source, resolved_dependencies, dependency_records)
|
|
998
|
+
resolved_dependencies.each do |resolved_dependency|
|
|
999
|
+
next unless resolved_dependency.dependency.eager
|
|
1000
|
+
|
|
1001
|
+
record = dependency_records.fetch(resolved_dependency.dependency.id)
|
|
1002
|
+
assert_import_name!(module_id, source, resolved_dependency, record)
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
# A named import (`import("./x", :Bar)`) is checked against the constants
|
|
1007
|
+
# the target defines statically, recorded by the Ruby plugin as
|
|
1008
|
+
# metadata[:ruby_constants]. A missing Default is not an error: the
|
|
1009
|
+
# import then returns the Exports module.
|
|
1010
|
+
def assert_import_name!(module_id, source, resolved_dependency, record)
|
|
1011
|
+
name = resolved_dependency.dependency.metadata[:import_name]
|
|
1012
|
+
return unless name
|
|
1013
|
+
|
|
1014
|
+
constants = record.metadata[:ruby_constants]
|
|
1015
|
+
return if constants&.include?(name)
|
|
1016
|
+
|
|
1017
|
+
raise MissingExportError.new(
|
|
1018
|
+
MissingExportError::Report.new(resolved_dependency.dependency, record.id, name, constants),
|
|
1019
|
+
source: source,
|
|
1020
|
+
module_id: module_id
|
|
1021
|
+
)
|
|
1022
|
+
end
|
|
1023
|
+
|
|
993
1024
|
def import_value(resolved_dependency, record)
|
|
994
1025
|
raise_failed_module!(record)
|
|
995
1026
|
|
|
@@ -22,7 +22,7 @@ module Klenod
|
|
|
22
22
|
return nil unless module_id.scheme == :virtual && module_id == CLASS_NAMES_MODULE_ID
|
|
23
23
|
|
|
24
24
|
source = class_names_runtime_source
|
|
25
|
-
LoadResult.new(source, nil, TransformResult.new(source, [], nil, [], [], {}))
|
|
25
|
+
LoadResult.new(source, nil, TransformResult.new(source, [], nil, [], [], {ruby_constants: [:Default]}))
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def class_names_runtime_dependency(module_id)
|
|
@@ -549,6 +549,9 @@ module Klenod
|
|
|
549
549
|
source_column_offset = source.lines.fetch(node.line, "").match(/\A\s*/).to_s.length
|
|
550
550
|
text = import_rewriter.call(text, source_line_offset: node.line, source_column_offset: source_column_offset)
|
|
551
551
|
end
|
|
552
|
+
unless builder.statements(text).node?
|
|
553
|
+
builder.ruby_parse_error(text, line_no: node.line + 1, context: "Could not parse Ruby filter")
|
|
554
|
+
end
|
|
552
555
|
heredoc_body_lines = ruby_heredoc_body_lines(text)
|
|
553
556
|
source = +""
|
|
554
557
|
text.each_line.with_index(node.line + 1) do |line, line_no|
|
|
@@ -625,12 +628,15 @@ module Klenod
|
|
|
625
628
|
|
|
626
629
|
def dynamic_attributes(node, builder:, props:)
|
|
627
630
|
dynamic_attributes = node.value.fetch(:dynamic_attributes)
|
|
628
|
-
sources = [
|
|
631
|
+
sources = [
|
|
632
|
+
[dynamic_attributes.new, true],
|
|
633
|
+
[dynamic_attributes.old, false]
|
|
634
|
+
].reject { |source, _parenthesized| source.nil? }
|
|
629
635
|
return {} if sources.empty?
|
|
630
636
|
|
|
631
637
|
measure_compile(:haml_compile_dynamic_attributes) do
|
|
632
638
|
dynamic = {}
|
|
633
|
-
sources.each do |source|
|
|
639
|
+
sources.each do |source, parenthesized|
|
|
634
640
|
source = builder.line_rewritten_source(source, node.line)
|
|
635
641
|
simple = simple_dynamic_attributes(source, builder: builder) unless @event_handler
|
|
636
642
|
if simple
|
|
@@ -655,7 +661,7 @@ module Klenod
|
|
|
655
661
|
end
|
|
656
662
|
|
|
657
663
|
key = attribute_key(assoc.key, builder: builder)
|
|
658
|
-
value = event_handler_value(key, assoc.value, builder: builder) || attribute_value(assoc, builder: builder)
|
|
664
|
+
value = event_handler_value(key, assoc.value, parenthesized: parenthesized, builder: builder) || attribute_value(assoc, builder: builder)
|
|
659
665
|
dynamic[key] = value
|
|
660
666
|
props[key] = value
|
|
661
667
|
end
|
|
@@ -784,14 +790,19 @@ module Klenod
|
|
|
784
790
|
builder.ruby_parse_error(source, line_no: assoc.key.location&.start_line, context: "Could not parse Haml dynamic attributes")
|
|
785
791
|
end
|
|
786
792
|
|
|
787
|
-
def event_handler_value(key, value, builder:)
|
|
788
|
-
|
|
793
|
+
def event_handler_value(key, value, parenthesized:, builder:)
|
|
794
|
+
normalized_key = key.to_s.tr("-", "_")
|
|
795
|
+
return unless normalized_key.match?(/\Aon(?:[a-z]|_[a-z])/)
|
|
789
796
|
return unless value
|
|
790
797
|
|
|
791
798
|
method_name = builder.fragment(value).source
|
|
792
799
|
return unless method_name.match?(/\A[a-zA-Z_]\w*[!?=]?\z/)
|
|
793
800
|
|
|
794
|
-
|
|
801
|
+
if @event_handler
|
|
802
|
+
builder.expression("#{@event_handler}.callback(self, :#{method_name})")
|
|
803
|
+
elsif parenthesized
|
|
804
|
+
builder.symbol(method_name)
|
|
805
|
+
end
|
|
795
806
|
end
|
|
796
807
|
|
|
797
808
|
def omitted_attribute_value_name(node)
|
|
@@ -97,7 +97,9 @@ module Klenod
|
|
|
97
97
|
|
|
98
98
|
return nil unless module_id.scheme == :virtual && module_id == HAML_HELPER_MODULE_ID
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
# Loaded with a ready transform, so the Ruby plugin never scans it: declare
|
|
101
|
+
# the Default export it defines.
|
|
102
|
+
LoadResult.new(haml_helper_source, nil, TransformResult.new(haml_helper_source, [], nil, [], [], {ruby_constants: [:Default]}))
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
def transform(module_id, code, context)
|
|
@@ -136,7 +138,7 @@ module Klenod
|
|
|
136
138
|
specifier: "/markdown-components",
|
|
137
139
|
importer_id: module_id,
|
|
138
140
|
kind: :markdown_components,
|
|
139
|
-
metadata: {optional: true}
|
|
141
|
+
metadata: {optional: true, import_name: :Default}
|
|
140
142
|
)
|
|
141
143
|
.with(id: "#{module_id}:markdown_components")
|
|
142
144
|
dependencies << markdown_components_dependency
|
|
@@ -205,11 +207,11 @@ module Klenod
|
|
|
205
207
|
styles_source: styles_source,
|
|
206
208
|
translations_source: translations_source,
|
|
207
209
|
i18n_source: i18n_source_for(builder),
|
|
208
|
-
haml_helper_source: haml_helper_dependency && builder.constant_assignment("HamlHelper",
|
|
210
|
+
haml_helper_source: haml_helper_dependency && builder.constant_assignment("HamlHelper", builder.import_call(haml_helper_dependency.id).source),
|
|
209
211
|
styleable: !style_dependencies.empty?,
|
|
210
212
|
profiler: context.profiler,
|
|
211
213
|
import_rewriter: import_rewriter,
|
|
212
|
-
markdown_components_source: markdown_components_dependency ? "__klenod_import__(#{markdown_components_dependency.id.inspect})
|
|
214
|
+
markdown_components_source: markdown_components_dependency ? "__klenod_import__(#{markdown_components_dependency.id.inspect})" : "{}",
|
|
213
215
|
variables: @variables,
|
|
214
216
|
event_handler: @event_handler,
|
|
215
217
|
cache_static_subtrees: @cache_static_subtrees
|
|
@@ -246,19 +248,26 @@ module Klenod
|
|
|
246
248
|
def import_value(resolved_dependency, record, context)
|
|
247
249
|
styles_import = class_names_runtime_import_value(resolved_dependency, record, context)
|
|
248
250
|
return styles_import if styles_import
|
|
249
|
-
return nil unless record
|
|
251
|
+
return nil unless default_export?(record)
|
|
250
252
|
|
|
251
|
-
context.mods.fetch(record.id).const_get(:Exports)
|
|
253
|
+
context.mods.fetch(record.id).const_get(:Exports).const_get(:Default, false)
|
|
252
254
|
end
|
|
253
255
|
|
|
254
256
|
def runtime_import_value(resolved_dependency, record, _context)
|
|
255
257
|
styles_import = class_names_runtime_runtime_import_value(resolved_dependency, record)
|
|
256
258
|
return styles_import if styles_import
|
|
257
|
-
return Runtime::DefaultImport.new(:Default) if record
|
|
259
|
+
return Runtime::DefaultImport.new(:Default) if default_export?(record)
|
|
258
260
|
|
|
259
261
|
super
|
|
260
262
|
end
|
|
261
263
|
|
|
264
|
+
# Components and the helper module both export Default. The helper is
|
|
265
|
+
# unwrapped here as well as by the Ruby plugin, so a context built
|
|
266
|
+
# without the Ruby plugin still renders.
|
|
267
|
+
def default_export?(record)
|
|
268
|
+
record.id.extname == ".haml" || record.id == HAML_HELPER_MODULE_ID
|
|
269
|
+
end
|
|
270
|
+
|
|
262
271
|
def invalidate_module_ids(paths, context)
|
|
263
272
|
paths
|
|
264
273
|
.filter_map { |path| companion_owner_module_id(path, context) }
|
|
@@ -22,7 +22,7 @@ module Klenod
|
|
|
22
22
|
|
|
23
23
|
def compile(source, interpolate: false)
|
|
24
24
|
source = protect_escaped_interpolation(source) if interpolate
|
|
25
|
-
document = Kramdown::Document.new(source, input: "GFM")
|
|
25
|
+
document = Kramdown::Document.new(source, input: "GFM", hard_wrap: false)
|
|
26
26
|
compile_children(document.root.children, interpolate: interpolate)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -168,13 +168,13 @@ module Klenod
|
|
|
168
168
|
specifier: "/markdown-components",
|
|
169
169
|
importer_id: module_id,
|
|
170
170
|
kind: :markdown_components,
|
|
171
|
-
metadata: {optional: true}
|
|
171
|
+
metadata: {optional: true, import_name: :Default}
|
|
172
172
|
)
|
|
173
173
|
.with(id: dependency_id(module_id))
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
def components_source_for(dependency)
|
|
177
|
-
dependency ? "__klenod_import__(#{dependency.id.inspect})
|
|
177
|
+
dependency ? "__klenod_import__(#{dependency.id.inspect})" : "{}"
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def dependency_id(module_id)
|
|
@@ -53,7 +53,7 @@ module Klenod
|
|
|
53
53
|
def transform(module_id, code, context)
|
|
54
54
|
return TransformResult.identity(code) unless module_id.extname == ".rb"
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
program = parse!(module_id, code)
|
|
57
57
|
|
|
58
58
|
result =
|
|
59
59
|
begin
|
|
@@ -68,21 +68,77 @@ module Klenod
|
|
|
68
68
|
rescue SyntaxTree::Parser::ParseError => error
|
|
69
69
|
raise ParseError.new(error, source: code, module_id: module_id)
|
|
70
70
|
end
|
|
71
|
-
TransformResult.new(
|
|
71
|
+
TransformResult.new(
|
|
72
|
+
result.code,
|
|
73
|
+
result.dependencies,
|
|
74
|
+
nil,
|
|
75
|
+
[],
|
|
76
|
+
result.watched_patterns,
|
|
77
|
+
{ruby_constants: top_level_constants(program)}
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The value an importer receives: the requested constant, or Default
|
|
82
|
+
# when the module defines one. Nil keeps the Exports module.
|
|
83
|
+
def import_value(resolved_dependency, record, context)
|
|
84
|
+
name = import_constant_name(resolved_dependency, record)
|
|
85
|
+
return nil unless name
|
|
86
|
+
|
|
87
|
+
context.mods.fetch(record.id).const_get(:Exports).const_get(name, false)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def runtime_import_value(resolved_dependency, record, _context)
|
|
91
|
+
name = import_constant_name(resolved_dependency, record)
|
|
92
|
+
return nil unless name
|
|
93
|
+
|
|
94
|
+
Runtime::DefaultImport.new(name)
|
|
72
95
|
end
|
|
73
96
|
|
|
74
97
|
private
|
|
75
98
|
|
|
76
99
|
# The rewriter only parses a file that contains an import it cannot
|
|
77
|
-
# rewrite literally, so without this
|
|
100
|
+
# rewrite literally, so without this parse most syntax errors would
|
|
78
101
|
# not surface until the module was evaluated -- and a production build
|
|
79
102
|
# never evaluates application modules, so the bundle would ship
|
|
80
|
-
# broken. Prism is the parser CRuby itself uses, and
|
|
81
|
-
# well under a
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
# broken. Prism is the parser CRuby itself uses, and parsing costs
|
|
104
|
+
# well under a millisecond per file. The same tree tells us which
|
|
105
|
+
# constants the module exports.
|
|
106
|
+
def parse!(module_id, code)
|
|
107
|
+
result = Prism.parse(code)
|
|
108
|
+
raise ParseError.new(result, source: code, module_id: module_id) if result.failure?
|
|
109
|
+
|
|
110
|
+
result.value
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The constants a module defines at its top level, meaning directly
|
|
114
|
+
# on its Exports module. Only static definitions count: `const_set`,
|
|
115
|
+
# constants inside `if` or `begin`, `A::B = 1`, and `class ::A` are
|
|
116
|
+
# not exports.
|
|
117
|
+
def top_level_constants(program)
|
|
118
|
+
program.statements.body.flat_map { |node| defined_constant_names(node) }.uniq
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def defined_constant_names(node)
|
|
122
|
+
case node
|
|
123
|
+
when Prism::ConstantWriteNode
|
|
124
|
+
[node.name]
|
|
125
|
+
when Prism::ClassNode, Prism::ModuleNode
|
|
126
|
+
node.constant_path.is_a?(Prism::ConstantReadNode) ? [node.constant_path.name] : []
|
|
127
|
+
when Prism::MultiWriteNode
|
|
128
|
+
[*node.lefts, *node.rights].grep(Prism::ConstantTargetNode).map(&:name)
|
|
129
|
+
else
|
|
130
|
+
[]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Records without a constant list were not transformed by this plugin
|
|
135
|
+
# and keep whatever value other plugins or the graph supply.
|
|
136
|
+
def import_constant_name(resolved_dependency, record)
|
|
137
|
+
constants = record.metadata[:ruby_constants]
|
|
138
|
+
return nil unless constants
|
|
84
139
|
|
|
85
|
-
|
|
140
|
+
name = resolved_dependency.dependency.metadata.fetch(:import_name, :Default)
|
|
141
|
+
constants.include?(name) ? name : nil
|
|
86
142
|
end
|
|
87
143
|
end
|
|
88
144
|
end
|
|
@@ -11,7 +11,7 @@ module Klenod
|
|
|
11
11
|
module Build
|
|
12
12
|
class RubyImportRewriter
|
|
13
13
|
ImportCall =
|
|
14
|
-
Data.define(:specifier, :location, :dynamic, :method_name, :eager_override) do
|
|
14
|
+
Data.define(:specifier, :location, :dynamic, :method_name, :eager_override, :import_name) do
|
|
15
15
|
def eager
|
|
16
16
|
eager_override.nil? ? RubyImportRewriter::IMPORT_METHODS.fetch(method_name).fetch(:eager) : eager_override
|
|
17
17
|
end
|
|
@@ -213,21 +213,33 @@ module Klenod
|
|
|
213
213
|
|
|
214
214
|
first = unwrap_paren(parts.first)
|
|
215
215
|
string_parts = first&.instance_variable_get(:@parts)
|
|
216
|
-
|
|
217
|
-
(parts.length == 1) &&
|
|
216
|
+
literal_specifier =
|
|
218
217
|
first&.class&.name == "SyntaxTree::StringLiteral" &&
|
|
219
218
|
string_parts&.length == 1 &&
|
|
220
219
|
string_parts.first.instance_of?(::SyntaxTree::TStringContent)
|
|
220
|
+
import_name = (parts.length == 2) ? constant_symbol_value(parts.fetch(1)) : nil
|
|
221
|
+
literal = literal_specifier && (parts.length == 1 || !import_name.nil?)
|
|
221
222
|
|
|
222
223
|
ImportCall.new(
|
|
223
224
|
literal ? string_parts.first.value : nil,
|
|
224
225
|
node.instance_variable_get(:@location),
|
|
225
226
|
!literal,
|
|
226
227
|
node.instance_variable_get(:@message).value,
|
|
227
|
-
nil
|
|
228
|
+
nil,
|
|
229
|
+
literal ? import_name : nil
|
|
228
230
|
)
|
|
229
231
|
end
|
|
230
232
|
|
|
233
|
+
# The optional second argument of `import`: a symbol literal spelled like
|
|
234
|
+
# a constant, such as `:Bar`. Anything else (`:bar`, `:"Bar"`, a string,
|
|
235
|
+
# a variable) is reported as a dynamic import.
|
|
236
|
+
def constant_symbol_value(node)
|
|
237
|
+
return unless node.instance_of?(::SyntaxTree::SymbolLiteral)
|
|
238
|
+
|
|
239
|
+
value = node.instance_variable_get(:@value)
|
|
240
|
+
value.instance_of?(::SyntaxTree::Const) ? value.value.to_sym : nil
|
|
241
|
+
end
|
|
242
|
+
|
|
231
243
|
def build_import_glob_from_parts(node, parts)
|
|
232
244
|
first = unwrap_paren(parts.first)
|
|
233
245
|
string_parts = first&.instance_variable_get(:@parts)
|
|
@@ -250,7 +262,8 @@ module Klenod
|
|
|
250
262
|
node.instance_variable_get(:@location),
|
|
251
263
|
!literal || !valid_options,
|
|
252
264
|
node.instance_variable_get(:@message).value,
|
|
253
|
-
eager
|
|
265
|
+
eager,
|
|
266
|
+
nil
|
|
254
267
|
)
|
|
255
268
|
end
|
|
256
269
|
|
|
@@ -304,20 +317,29 @@ module Klenod
|
|
|
304
317
|
end
|
|
305
318
|
|
|
306
319
|
def raise_dynamic_import!(call)
|
|
307
|
-
expected =
|
|
320
|
+
expected =
|
|
321
|
+
if call.method_name == "import_glob"
|
|
322
|
+
"import_glob(\"...\")"
|
|
323
|
+
else
|
|
324
|
+
"#{call.method_name}(\"...\") or #{call.method_name}(\"...\", :Constant)"
|
|
325
|
+
end
|
|
308
326
|
raise DynamicImportError, "Only literal #{expected} calls are supported in #{@module_id}"
|
|
309
327
|
end
|
|
310
328
|
|
|
311
329
|
def build_dependency(call, dependency_index)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
330
|
+
dependency =
|
|
331
|
+
Dependency
|
|
332
|
+
.create(
|
|
333
|
+
specifier: call.specifier,
|
|
334
|
+
importer_id: @module_id,
|
|
335
|
+
kind: @kind,
|
|
336
|
+
loc: source_location(call.location)
|
|
337
|
+
)
|
|
338
|
+
.with(eager: call.eager)
|
|
339
|
+
.with(id: "#{@module_id}:dependency:#{dependency_index}")
|
|
340
|
+
return dependency unless call.import_name
|
|
341
|
+
|
|
342
|
+
dependency.with(metadata: {import_name: call.import_name}.freeze)
|
|
321
343
|
end
|
|
322
344
|
|
|
323
345
|
def expand_glob_import(call, dependency_index)
|
|
@@ -110,6 +110,43 @@ module Klenod
|
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# A named import (`import("./x", :Bar)`) of a constant the target module
|
|
114
|
+
# does not define at its top level.
|
|
115
|
+
#
|
|
116
|
+
# The check uses the constants the Ruby plugin recorded statically, so a
|
|
117
|
+
# typo fails while collecting, with an excerpt of the import, instead of
|
|
118
|
+
# surfacing as a NameError from inside module evaluation -- or, worse, at
|
|
119
|
+
# runtime, since a production build never evaluates application modules.
|
|
120
|
+
class MissingExportError < SourceError
|
|
121
|
+
Report = Data.define(:dependency, :target_id, :name, :constants)
|
|
122
|
+
|
|
123
|
+
def kind
|
|
124
|
+
"Missing export"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def location(report)
|
|
130
|
+
Location.new(
|
|
131
|
+
line: report.dependency.loc&.line,
|
|
132
|
+
column: report.dependency.loc&.column,
|
|
133
|
+
detail: "#{report.target_id} does not define #{report.name}",
|
|
134
|
+
hints: [hint_for(report)]
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def hint_for(report)
|
|
139
|
+
case report.constants
|
|
140
|
+
when nil
|
|
141
|
+
"Named imports need a Ruby module; #{report.target_id} is not one"
|
|
142
|
+
when []
|
|
143
|
+
"#{report.target_id} defines no top-level constants; import it without a name to get its Exports module"
|
|
144
|
+
else
|
|
145
|
+
"It defines #{report.constants.join(", ")}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
113
150
|
# Ruby generated from another format that does not parse.
|
|
114
151
|
#
|
|
115
152
|
# This is a bug in the plugin that generated it rather than in anything the
|
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.17
|
|
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.17
|
|
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.17
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: async
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|