mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:48:11 +00:00
LibJS: Implement the import assertions proposal
The hard part of parsing them in import statements and calls was already done so this is just removing some check which threw before on assertions. And filtering the assertions based on the result of a new host hook.
This commit is contained in:
parent
e0e4ead2c8
commit
f568939568
11 changed files with 270 additions and 83 deletions
|
@ -9,6 +9,7 @@
|
|||
#include <AK/Demangle.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
|
@ -3227,41 +3228,111 @@ void ImportCall::dump(int indent) const
|
|||
}
|
||||
|
||||
// 13.3.10.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-import-call-runtime-semantics-evaluation
|
||||
// Also includes assertions from proposal: https://tc39.es/proposal-import-assertions/#sec-import-call-runtime-semantics-evaluation
|
||||
Completion ImportCall::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
InterpreterNodeScope node_scope { interpreter, *this };
|
||||
// 1. Let referencingScriptOrModule be ! GetActiveScriptOrModule().
|
||||
|
||||
// 2.1.1.1 EvaluateImportCall ( specifierExpression [ , optionsExpression ] ), https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call
|
||||
// 1. Let referencingScriptOrModule be ! GetActiveScriptOrModule().
|
||||
auto referencing_script_or_module = interpreter.vm().get_active_script_or_module();
|
||||
|
||||
if (m_options)
|
||||
return interpreter.vm().throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "import call with assertions/options");
|
||||
|
||||
// 2. Let argRef be the result of evaluating AssignmentExpression.
|
||||
// 3. Let specifier be ? GetValue(argRef).
|
||||
// 2. Let specifierRef be the result of evaluating specifierExpression.
|
||||
// 3. Let specifier be ? GetValue(specifierRef).
|
||||
auto specifier = TRY(m_specifier->execute(interpreter, global_object));
|
||||
|
||||
// 4. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto options_value = js_undefined();
|
||||
// 4. If optionsExpression is present, then
|
||||
if (m_options) {
|
||||
// a. Let optionsRef be the result of evaluating optionsExpression.
|
||||
// b. Let options be ? GetValue(optionsRef).
|
||||
options_value = TRY(m_options->execute(interpreter, global_object)).release_value();
|
||||
}
|
||||
// 5. Else,
|
||||
// a. Let options be undefined.
|
||||
// Note: options_value is undefined by default.
|
||||
|
||||
// 6. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(global_object, global_object.promise_constructor()));
|
||||
|
||||
VERIFY(!interpreter.exception());
|
||||
// 5. Let specifierString be ToString(specifier).
|
||||
auto specifier_string = specifier->to_string(global_object);
|
||||
// 7. Let specifierString be ToString(specifier).
|
||||
// 8. IfAbruptRejectPromise(specifierString, promiseCapability).
|
||||
auto specifier_string = TRY_OR_REJECT_WITH_VALUE(global_object, promise_capability, specifier->to_string(global_object));
|
||||
|
||||
// 6. IfAbruptRejectPromise(specifierString, promiseCapability).
|
||||
// Note: Since we have to use completions and not ThrowCompletionOr's in AST we have to do this manually.
|
||||
if (specifier_string.is_throw_completion()) {
|
||||
// FIXME: We shouldn't have to clear this exception
|
||||
interpreter.vm().clear_exception();
|
||||
(void)TRY(call(global_object, promise_capability.reject, js_undefined(), *specifier_string.throw_completion().value()));
|
||||
return Value { promise_capability.promise };
|
||||
// 9. Let assertions be a new empty List.
|
||||
Vector<ModuleRequest::Assertion> assertions;
|
||||
|
||||
// 10. If options is not undefined, then
|
||||
if (!options_value.is_undefined()) {
|
||||
// a. If Type(options) is not Object,
|
||||
if (!options_value.is_object()) {
|
||||
auto* error = TypeError::create(global_object, String::formatted(ErrorType::NotAnObject.message(), "ImportOptions"));
|
||||
// i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
MUST(call(global_object, *promise_capability.reject, js_undefined(), error));
|
||||
|
||||
// ii. Return promiseCapability.[[Promise]].
|
||||
return Value { promise_capability.promise };
|
||||
}
|
||||
|
||||
// b. Let assertionsObj be Get(options, "assert").
|
||||
// c. IfAbruptRejectPromise(assertionsObj, promiseCapability).
|
||||
auto assertion_object = TRY_OR_REJECT_WITH_VALUE(global_object, promise_capability, options_value.get(global_object, interpreter.vm().names.assert));
|
||||
|
||||
// d. If assertionsObj is not undefined,
|
||||
if (!assertion_object.is_undefined()) {
|
||||
// i. If Type(assertionsObj) is not Object,
|
||||
if (!assertion_object.is_object()) {
|
||||
auto* error = TypeError::create(global_object, String::formatted(ErrorType::NotAnObject.message(), "ImportOptionsAssertions"));
|
||||
// 1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
MUST(call(global_object, *promise_capability.reject, js_undefined(), error));
|
||||
|
||||
// 2. Return promiseCapability.[[Promise]].
|
||||
return Value { promise_capability.promise };
|
||||
}
|
||||
|
||||
// ii. Let keys be EnumerableOwnPropertyNames(assertionsObj, key).
|
||||
// iii. IfAbruptRejectPromise(keys, promiseCapability).
|
||||
auto keys = TRY_OR_REJECT_WITH_VALUE(global_object, promise_capability, assertion_object.as_object().enumerable_own_property_names(Object::PropertyKind::Key));
|
||||
|
||||
// iv. Let supportedAssertions be ! HostGetSupportedImportAssertions().
|
||||
auto supported_assertions = interpreter.vm().host_get_supported_import_assertions();
|
||||
|
||||
// v. For each String key of keys,
|
||||
for (auto const& key : keys) {
|
||||
auto property_key = MUST(key.to_property_key(global_object));
|
||||
|
||||
// 1. Let value be Get(assertionsObj, key).
|
||||
// 2. IfAbruptRejectPromise(value, promiseCapability).
|
||||
auto value = TRY_OR_REJECT_WITH_VALUE(global_object, promise_capability, assertion_object.get(global_object, property_key));
|
||||
|
||||
// 3. If Type(value) is not String, then
|
||||
if (!value.is_string()) {
|
||||
auto* error = TypeError::create(global_object, String::formatted(ErrorType::NotAString.message(), "Import Assertion option value"));
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
MUST(call(global_object, *promise_capability.reject, js_undefined(), error));
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Value { promise_capability.promise };
|
||||
}
|
||||
|
||||
// 4. If supportedAssertions contains key, then
|
||||
if (supported_assertions.contains_slow(property_key.to_string())) {
|
||||
// a. Append { [[Key]]: key, [[Value]]: value } to assertions.
|
||||
assertions.empend(property_key.to_string(), value.as_string().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// e. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in.
|
||||
// Note: This is done when constructing the ModuleRequest.
|
||||
}
|
||||
|
||||
ModuleRequest request { specifier_string.release_value() };
|
||||
// 11. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Assertions]]: assertions }.
|
||||
ModuleRequest request { specifier_string, assertions };
|
||||
|
||||
// 7. Perform ! HostImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability).
|
||||
interpreter.vm().host_import_module_dynamically(referencing_script_or_module, request, promise_capability);
|
||||
// 12. Perform ! HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability).
|
||||
interpreter.vm().host_import_module_dynamically(referencing_script_or_module, move(request), promise_capability);
|
||||
|
||||
// 8. Return promiseCapability.[[Promise]].
|
||||
// 13. Return promiseCapability.[[Promise]].
|
||||
return Value { promise_capability.promise };
|
||||
}
|
||||
|
||||
|
@ -4110,13 +4181,10 @@ void ScopeNode::add_hoisted_function(NonnullRefPtr<FunctionDeclaration> declarat
|
|||
}
|
||||
|
||||
// 16.2.1.11 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation
|
||||
Completion ImportStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
Completion ImportStatement::execute(Interpreter& interpreter, GlobalObject&) const
|
||||
{
|
||||
InterpreterNodeScope node_scope { interpreter, *this };
|
||||
|
||||
if (!m_module_request.assertions.is_empty())
|
||||
return interpreter.vm().throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "import statement with assertions/options");
|
||||
|
||||
// 1. Return NormalCompletion(empty).
|
||||
return normal_completion({});
|
||||
}
|
||||
|
@ -4460,4 +4528,17 @@ ThrowCompletionOr<void> Program::global_declaration_instantiation(Interpreter& i
|
|||
return {};
|
||||
}
|
||||
|
||||
ModuleRequest::ModuleRequest(FlyString module_specifier_, Vector<Assertion> assertions_)
|
||||
: module_specifier(move(module_specifier_))
|
||||
, assertions(move(assertions_))
|
||||
{
|
||||
// Perform step 10.e. from EvaluateImportCall, https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call
|
||||
// or step 2. from 2.7 Static Semantics: AssertClauseToAssertions, https://tc39.es/proposal-import-assertions/#sec-assert-clause-to-assertions
|
||||
// e. / 2. Sort assertions by the code point order of the [[Key]] of each element.
|
||||
// NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in.
|
||||
quick_sort(assertions, [](Assertion const& lhs, Assertion const& rhs) {
|
||||
return lhs.key < rhs.key;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -257,6 +257,8 @@ struct ModuleRequest {
|
|||
{
|
||||
}
|
||||
|
||||
ModuleRequest(FlyString module_specifier, Vector<Assertion> assertions);
|
||||
|
||||
void add_assertion(String key, String value)
|
||||
{
|
||||
assertions.empend(move(key), move(value));
|
||||
|
@ -309,6 +311,7 @@ public:
|
|||
bool has_bound_name(FlyString const& name) const;
|
||||
Vector<ImportEntry> const& entries() const { return m_entries; }
|
||||
ModuleRequest const& module_request() const { return m_module_request; }
|
||||
ModuleRequest& module_request() { return m_module_request; }
|
||||
|
||||
private:
|
||||
ModuleRequest m_module_request;
|
||||
|
@ -406,6 +409,12 @@ public:
|
|||
return *m_statement;
|
||||
}
|
||||
|
||||
ModuleRequest& module_request()
|
||||
{
|
||||
VERIFY(!m_module_request.module_specifier.is_null());
|
||||
return m_module_request;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<ASTNode> m_statement;
|
||||
Vector<ExportEntry> m_entries;
|
||||
|
@ -448,6 +457,9 @@ public:
|
|||
NonnullRefPtrVector<ImportStatement> const& imports() const { return m_imports; }
|
||||
NonnullRefPtrVector<ExportStatement> const& exports() const { return m_exports; }
|
||||
|
||||
NonnullRefPtrVector<ImportStatement>& imports() { return m_imports; }
|
||||
NonnullRefPtrVector<ExportStatement>& exports() { return m_exports; }
|
||||
|
||||
bool has_top_level_await() const { return m_has_top_level_await; }
|
||||
void set_has_top_level_await() { m_has_top_level_await = true; }
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<FlyString> requested_modules)
|
||||
CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules)
|
||||
: Module(realm, filename)
|
||||
, m_requested_modules(move(requested_modules))
|
||||
, m_has_top_level_await(has_top_level_await)
|
||||
|
@ -93,7 +93,14 @@ ThrowCompletionOr<u32> CyclicModule::inner_module_linking(VM& vm, Vector<Module*
|
|||
// 8. Append module to stack.
|
||||
stack.append(this);
|
||||
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module: {} has requested modules: [{}]", filename(), String::join(",", m_requested_modules));
|
||||
#if JS_MODULE_DEBUG
|
||||
StringBuilder request_module_names;
|
||||
for (auto& module_request : m_requested_modules) {
|
||||
request_module_names.append(module_request.module_specifier);
|
||||
request_module_names.append(", ");
|
||||
}
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module: {} has requested modules: [{}]", filename(), request_module_names.string_view());
|
||||
#endif
|
||||
|
||||
// 9. For each String required of module.[[RequestedModules]], do
|
||||
for (auto& required_string : m_requested_modules) {
|
||||
|
@ -300,8 +307,7 @@ ThrowCompletionOr<u32> CyclicModule::inner_module_evaluation(VM& vm, Vector<Modu
|
|||
stack.append(this);
|
||||
|
||||
// 11. For each String required of module.[[RequestedModules]], do
|
||||
for (auto& required_string : m_requested_modules) {
|
||||
ModuleRequest required { required_string };
|
||||
for (auto& required : m_requested_modules) {
|
||||
|
||||
// a. Let requiredModule be ! HostResolveImportedModule(module, required).
|
||||
auto* required_module = MUST(vm.host_resolve_imported_module(this, required)).ptr();
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override;
|
||||
|
||||
protected:
|
||||
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<FlyString> requested_modules);
|
||||
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules);
|
||||
|
||||
virtual ThrowCompletionOr<u32> inner_module_linking(VM& vm, Vector<Module*>& stack, u32 index) override;
|
||||
virtual ThrowCompletionOr<u32> inner_module_evaluation(VM& vm, Vector<Module*>& stack, u32 index) override;
|
||||
|
@ -49,7 +49,7 @@ protected:
|
|||
ThrowCompletionOr<void> m_evaluation_error; // [[EvaluationError]]
|
||||
Optional<u32> m_dfs_index; // [[DFSIndex]]
|
||||
Optional<u32> m_dfs_ancestor_index; // [[DFSAncestorIndex]]
|
||||
Vector<FlyString> m_requested_modules; // [[RequestedModules]]
|
||||
Vector<ModuleRequest> m_requested_modules; // [[RequestedModules]]
|
||||
CyclicModule* m_cycle_root; // [[CycleRoot]]
|
||||
bool m_has_top_level_await { false }; // [[HasTLA]]
|
||||
bool m_async_evaluation { false }; // [[AsyncEvaluation]]
|
||||
|
|
|
@ -38,6 +38,25 @@ struct PromiseCapability {
|
|||
_temporary_try_or_reject_result.release_value(); \
|
||||
})
|
||||
|
||||
// 27.2.1.1.1 IfAbruptRejectPromise ( value, capability ), https://tc39.es/ecma262/#sec-ifabruptrejectpromise
|
||||
#define TRY_OR_REJECT_WITH_VALUE(global_object, capability, expression) \
|
||||
({ \
|
||||
auto _temporary_try_or_reject_result = (expression); \
|
||||
/* 1. If value is an abrupt completion, then */ \
|
||||
if (_temporary_try_or_reject_result.is_error()) { \
|
||||
global_object.vm().clear_exception(); \
|
||||
\
|
||||
/* a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). */ \
|
||||
TRY(JS::call(global_object, *capability.reject, js_undefined(), *_temporary_try_or_reject_result.release_error().value())); \
|
||||
\
|
||||
/* b. Return capability.[[Promise]]. */ \
|
||||
return Value { capability.promise }; \
|
||||
} \
|
||||
\
|
||||
/* 2. Else if value is a Completion Record, set value to value.[[Value]]. */ \
|
||||
_temporary_try_or_reject_result.release_value(); \
|
||||
})
|
||||
|
||||
// 27.2.1.5 NewPromiseCapability ( C ), https://tc39.es/ecma262/#sec-newpromisecapability
|
||||
ThrowCompletionOr<PromiseCapability> new_promise_capability(GlobalObject& global_object, Value constructor);
|
||||
|
||||
|
|
|
@ -88,6 +88,10 @@ VM::VM(OwnPtr<CustomData> custom_data)
|
|||
host_finalize_import_meta = [&](Object*, SourceTextModule const&) {
|
||||
};
|
||||
|
||||
host_get_supported_import_assertions = [&] {
|
||||
return Vector<String> {};
|
||||
};
|
||||
|
||||
#define __JS_ENUMERATE(SymbolName, snake_name) \
|
||||
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
|
||||
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
|
||||
|
@ -805,15 +809,21 @@ ThrowCompletionOr<void> VM::link_and_eval_module(SourceTextModule& module)
|
|||
}
|
||||
|
||||
// 16.2.1.7 HostResolveImportedModule ( referencingScriptOrModule, specifier ), https://tc39.es/ecma262/#sec-hostresolveimportedmodule
|
||||
ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier)
|
||||
ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrModule referencing_script_or_module, ModuleRequest const& module_request)
|
||||
{
|
||||
if (!specifier.assertions.is_empty())
|
||||
return throw_completion<InternalError>(current_realm()->global_object(), ErrorType::NotImplemented, "HostResolveImportedModule with assertions");
|
||||
|
||||
// An implementation of HostResolveImportedModule must conform to the following requirements:
|
||||
// - If it completes normally, the [[Value]] slot of the completion must contain an instance of a concrete subclass of Module Record.
|
||||
// - If a Module Record corresponding to the pair referencingScriptOrModule, specifier does not exist or cannot be created, an exception must be thrown.
|
||||
// - Each time this operation is called with a specific referencingScriptOrModule, specifier pair as arguments it must return the same Module Record instance if it completes normally.
|
||||
// - If a Module Record corresponding to the pair referencingScriptOrModule, moduleRequest does not exist or cannot be created, an exception must be thrown.
|
||||
// - Each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]], moduleRequest.[[Assertions]] triple
|
||||
// as arguments it must return the same Module Record instance if it completes normally.
|
||||
// * It is recommended but not required that implementations additionally conform to the following stronger constraint:
|
||||
// each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]] pair as arguments it must return the same Module Record instance if it completes normally.
|
||||
// - moduleRequest.[[Assertions]] must not influence the interpretation of the module or the module specifier;
|
||||
// instead, it may be used to determine whether the algorithm completes normally or with an abrupt completion.
|
||||
|
||||
// Multiple different referencingScriptOrModule, moduleRequest.[[Specifier]] pairs may map to the same Module Record instance.
|
||||
// The actual mapping semantic is host-defined but typically a normalization process is applied to specifier as part of the mapping process.
|
||||
// A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers.
|
||||
|
||||
StringView base_filename = referencing_script_or_module.visit(
|
||||
[&](Empty) {
|
||||
|
@ -824,7 +834,7 @@ ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrMod
|
|||
});
|
||||
|
||||
LexicalPath base_path { base_filename };
|
||||
auto filepath = LexicalPath::absolute_path(base_path.dirname(), specifier.module_specifier);
|
||||
auto filepath = LexicalPath::absolute_path(base_path.dirname(), module_request.module_specifier);
|
||||
|
||||
#if JS_MODULE_DEBUG
|
||||
String referencing_module_string = referencing_script_or_module.visit(
|
||||
|
@ -839,7 +849,7 @@ ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrMod
|
|||
});
|
||||
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolve_imported_module({}, {})", referencing_module_string, filepath);
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved {} + {} -> {}", base_path, specifier.module_specifier, filepath);
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved {} + {} -> {}", base_path, module_request.module_specifier, filepath);
|
||||
#endif
|
||||
|
||||
auto* loaded_module_or_end = get_stored_module(referencing_script_or_module, filepath);
|
||||
|
@ -855,7 +865,7 @@ ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrMod
|
|||
auto file_or_error = Core::File::open(filepath, Core::OpenMode::ReadOnly);
|
||||
|
||||
if (file_or_error.is_error()) {
|
||||
return throw_completion<SyntaxError>(global_object, ErrorType::ModuleNotFound, specifier.module_specifier);
|
||||
return throw_completion<SyntaxError>(global_object, ErrorType::ModuleNotFound, module_request.module_specifier);
|
||||
}
|
||||
|
||||
// FIXME: Don't read the file in one go.
|
||||
|
@ -884,27 +894,27 @@ ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrMod
|
|||
}
|
||||
|
||||
// 16.2.1.8 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability ), https://tc39.es/ecma262/#sec-hostimportmoduledynamically
|
||||
void VM::import_module_dynamically(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability)
|
||||
void VM::import_module_dynamically(ScriptOrModule referencing_script_or_module, ModuleRequest module_request, PromiseCapability promise_capability)
|
||||
{
|
||||
auto& global_object = current_realm()->global_object();
|
||||
|
||||
// Success path:
|
||||
// - At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, promise),
|
||||
// - At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
|
||||
// where promise is a Promise resolved with undefined.
|
||||
// - Any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed,
|
||||
// given the arguments referencingScriptOrModule and specifier, must complete normally.
|
||||
// given the arguments referencingScriptOrModule and moduleRequest, must complete normally.
|
||||
// - The completion value of any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed,
|
||||
// given the arguments referencingScriptOrModule and specifier, must be a module which has already been evaluated,
|
||||
// given the arguments referencingScriptOrModule and moduleRequest, must be a module which has already been evaluated,
|
||||
// i.e. whose Evaluate concrete method has already been called and returned a normal completion.
|
||||
// Failure path:
|
||||
// - At some future time, the host environment must perform
|
||||
// FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, promise),
|
||||
// FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
|
||||
// where promise is a Promise rejected with an error representing the cause of failure.
|
||||
|
||||
auto* promise = Promise::create(global_object);
|
||||
|
||||
ScopeGuard finish_dynamic_import = [&] {
|
||||
host_finish_dynamic_import(referencing_script_or_module, specifier, promise_capability, promise);
|
||||
host_finish_dynamic_import(referencing_script_or_module, move(module_request), promise_capability, promise);
|
||||
};
|
||||
|
||||
// Generally within ECMA262 we always get a referencing_script_or_moulde. However, ShadowRealm gives an explicit null.
|
||||
|
@ -915,15 +925,15 @@ void VM::import_module_dynamically(ScriptOrModule referencing_script_or_module,
|
|||
// If there is no ScriptOrModule in any of the execution contexts
|
||||
if (referencing_script_or_module.has<Empty>()) {
|
||||
// Throw an error for now
|
||||
promise->reject(InternalError::create(global_object, String::formatted(ErrorType::ModuleNotFoundNoReferencingScript.message(), specifier.module_specifier)));
|
||||
promise->reject(InternalError::create(global_object, String::formatted(ErrorType::ModuleNotFoundNoReferencingScript.message(), module_request.module_specifier)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY(!exception());
|
||||
// Note: If host_resolve_imported_module returns a module it has been loaded successfully and the next call in finish_dynamic_import will retrieve it again.
|
||||
auto module_or_error = host_resolve_imported_module(referencing_script_or_module, specifier);
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] HostImportModuleDynamically(..., {}) -> {}", specifier.module_specifier, module_or_error.is_error() ? "failed" : "passed");
|
||||
auto module_or_error = host_resolve_imported_module(referencing_script_or_module, module_request);
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] HostImportModuleDynamically(..., {}) -> {}", module_request.module_specifier, module_or_error.is_error() ? "failed" : "passed");
|
||||
if (module_or_error.is_throw_completion()) {
|
||||
// Note: We should not leak the exception thrown in host_resolve_imported_module.
|
||||
clear_exception();
|
||||
|
@ -952,17 +962,17 @@ void VM::import_module_dynamically(ScriptOrModule referencing_script_or_module,
|
|||
}
|
||||
|
||||
// 16.2.1.9 FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, innerPromise ), https://tc39.es/ecma262/#sec-finishdynamicimport
|
||||
void VM::finish_dynamic_import(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability, Promise* inner_promise)
|
||||
void VM::finish_dynamic_import(ScriptOrModule referencing_script_or_module, ModuleRequest module_request, PromiseCapability promise_capability, Promise* inner_promise)
|
||||
{
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] finish_dynamic_import on {}", specifier.module_specifier);
|
||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] finish_dynamic_import on {}", module_request.module_specifier);
|
||||
|
||||
// 1. Let fulfilledClosure be a new Abstract Closure with parameters (result) that captures referencingScriptOrModule, specifier, and promiseCapability and performs the following steps when called:
|
||||
auto fulfilled_closure = [referencing_script_or_module, specifier, promise_capability](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr<Value> {
|
||||
auto fulfilled_closure = [referencing_script_or_module, module_request = move(module_request), promise_capability](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr<Value> {
|
||||
auto result = vm.argument(0);
|
||||
// a. Assert: result is undefined.
|
||||
VERIFY(result.is_undefined());
|
||||
// b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
|
||||
auto module_record = MUST(vm.host_resolve_imported_module(referencing_script_or_module, specifier));
|
||||
auto module_record = MUST(vm.host_resolve_imported_module(referencing_script_or_module, module_request));
|
||||
|
||||
// c. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
|
||||
// Note: If HostResolveImportedModule returns a module evaluate will have been called on it.
|
||||
|
|
|
@ -233,12 +233,14 @@ public:
|
|||
ScriptOrModule get_active_script_or_module() const;
|
||||
|
||||
Function<ThrowCompletionOr<NonnullRefPtr<Module>>(ScriptOrModule, ModuleRequest const&)> host_resolve_imported_module;
|
||||
Function<void(ScriptOrModule, ModuleRequest const&, PromiseCapability)> host_import_module_dynamically;
|
||||
Function<void(ScriptOrModule, ModuleRequest, PromiseCapability)> host_import_module_dynamically;
|
||||
Function<void(ScriptOrModule, ModuleRequest const&, PromiseCapability, Promise*)> host_finish_dynamic_import;
|
||||
|
||||
Function<HashMap<PropertyKey, Value>(SourceTextModule const&)> host_get_import_meta_properties;
|
||||
Function<void(Object*, SourceTextModule const&)> host_finalize_import_meta;
|
||||
|
||||
Function<Vector<String>()> host_get_supported_import_assertions;
|
||||
|
||||
void enable_default_host_import_module_dynamically_hook();
|
||||
|
||||
private:
|
||||
|
@ -247,11 +249,11 @@ private:
|
|||
ThrowCompletionOr<void> property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment, GlobalObject& global_object);
|
||||
ThrowCompletionOr<void> iterator_binding_initialization(BindingPattern const& binding, Iterator& iterator_record, Environment* environment, GlobalObject& global_object);
|
||||
|
||||
ThrowCompletionOr<NonnullRefPtr<Module>> resolve_imported_module(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier);
|
||||
ThrowCompletionOr<NonnullRefPtr<Module>> resolve_imported_module(ScriptOrModule referencing_script_or_module, ModuleRequest const& module_request);
|
||||
ThrowCompletionOr<void> link_and_eval_module(SourceTextModule& module);
|
||||
|
||||
void import_module_dynamically(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability);
|
||||
void finish_dynamic_import(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability, Promise* inner_promise);
|
||||
void import_module_dynamically(ScriptOrModule referencing_script_or_module, ModuleRequest module_request, PromiseCapability promise_capability);
|
||||
void finish_dynamic_import(ScriptOrModule referencing_script_or_module, ModuleRequest module_request, PromiseCapability promise_capability, Promise* inner_promise);
|
||||
|
||||
Exception* m_exception { nullptr };
|
||||
|
||||
|
|
|
@ -13,49 +13,92 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
// 2.7 Static Semantics: AssertClauseToAssertions, https://tc39.es/proposal-import-assertions/#sec-assert-clause-to-assertions
|
||||
static Vector<ModuleRequest::Assertion> assert_clause_to_assertions(Vector<ModuleRequest::Assertion> const& source_assertions, Vector<String> const& supported_import_assertions)
|
||||
{
|
||||
// AssertClause : assert { AssertEntries ,opt }
|
||||
// 1. Let assertions be AssertClauseToAssertions of AssertEntries.
|
||||
Vector<ModuleRequest::Assertion> assertions;
|
||||
|
||||
// AssertEntries : AssertionKey : StringLiteral
|
||||
// AssertEntries : AssertionKey : StringLiteral , AssertEntries
|
||||
// 1. Let supportedAssertions be !HostGetSupportedImportAssertions().
|
||||
|
||||
for (auto& assertion : source_assertions) {
|
||||
// 2. Let key be StringValue of AssertionKey.
|
||||
// 3. If supportedAssertions contains key,
|
||||
if (supported_import_assertions.contains_slow(assertion.key)) {
|
||||
// a. Let entry be a Record { [[Key]]: key, [[Value]]: StringValue of StringLiteral }.
|
||||
assertions.empend(assertion);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in.
|
||||
// Note: The sorting is done in construction of the ModuleRequest object.
|
||||
|
||||
// 3. Return assertions.
|
||||
return assertions;
|
||||
}
|
||||
|
||||
// 16.2.1.3 Static Semantics: ModuleRequests, https://tc39.es/ecma262/#sec-static-semantics-modulerequests
|
||||
static Vector<FlyString> module_requests(Program const& program)
|
||||
static Vector<ModuleRequest> module_requests(Program& program, Vector<String> const& supported_import_assertions)
|
||||
{
|
||||
// A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module.
|
||||
// Note: The List is source text occurrence ordered!
|
||||
struct RequestedModuleAndSourceIndex {
|
||||
FlyString requested_module;
|
||||
u64 source_index;
|
||||
|
||||
bool operator<(RequestedModuleAndSourceIndex const& rhs) const
|
||||
{
|
||||
return source_index < rhs.source_index;
|
||||
}
|
||||
u64 source_index { 0 };
|
||||
ModuleRequest* module_request { nullptr };
|
||||
};
|
||||
|
||||
Vector<RequestedModuleAndSourceIndex> requested_modules_with_indices;
|
||||
|
||||
for (auto const& import_statement : program.imports()) {
|
||||
requested_modules_with_indices.append({ import_statement.module_request().module_specifier.view(),
|
||||
import_statement.source_range().start.offset });
|
||||
for (auto& import_statement : program.imports()) {
|
||||
requested_modules_with_indices.empend(import_statement.source_range().start.offset, &import_statement.module_request());
|
||||
}
|
||||
|
||||
for (auto const& export_statement : program.exports()) {
|
||||
for (auto const& export_entry : export_statement.entries()) {
|
||||
for (auto& export_statement : program.exports()) {
|
||||
for (auto& export_entry : export_statement.entries()) {
|
||||
if (!export_entry.is_module_request())
|
||||
continue;
|
||||
requested_modules_with_indices.append({ export_entry.module_request().module_specifier.view(),
|
||||
export_statement.source_range().start.offset });
|
||||
requested_modules_with_indices.empend(export_statement.source_range().start.offset, &export_statement.module_request());
|
||||
}
|
||||
}
|
||||
|
||||
quick_sort(requested_modules_with_indices);
|
||||
// Note: The List is source code occurrence ordered. https://tc39.es/proposal-import-assertions/#table-cyclic-module-fields
|
||||
quick_sort(requested_modules_with_indices, [&](RequestedModuleAndSourceIndex const& lhs, RequestedModuleAndSourceIndex const& rhs) {
|
||||
return lhs.source_index < rhs.source_index;
|
||||
});
|
||||
|
||||
Vector<FlyString> requested_modules_in_source_order;
|
||||
Vector<ModuleRequest> requested_modules_in_source_order;
|
||||
requested_modules_in_source_order.ensure_capacity(requested_modules_with_indices.size());
|
||||
for (auto& module : requested_modules_with_indices) {
|
||||
requested_modules_in_source_order.append(module.requested_module);
|
||||
// 2.10 Static Semantics: ModuleRequests https://tc39.es/proposal-import-assertions/#sec-static-semantics-modulerequests
|
||||
if (module.module_request->assertions.is_empty()) {
|
||||
// ExportDeclaration : export ExportFromClause FromClause ;
|
||||
// ImportDeclaration : import ImportClause FromClause ;
|
||||
|
||||
// 1. Let specifier be StringValue of the StringLiteral contained in FromClause.
|
||||
// 2. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: an empty List }.
|
||||
requested_modules_in_source_order.empend(module.module_request->module_specifier);
|
||||
} else {
|
||||
// ExportDeclaration : export ExportFromClause FromClause AssertClause ;
|
||||
// ImportDeclaration : import ImportClause FromClause AssertClause ;
|
||||
|
||||
// 1. Let specifier be StringValue of the StringLiteral contained in FromClause.
|
||||
// 2. Let assertions be AssertClauseToAssertions of AssertClause.
|
||||
auto assertions = assert_clause_to_assertions(module.module_request->assertions, supported_import_assertions);
|
||||
// Note: We have to modify the assertions in place because else it might keep non supported ones
|
||||
module.module_request->assertions = move(assertions);
|
||||
|
||||
// 3. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: assertions }.
|
||||
requested_modules_in_source_order.empend(module.module_request->module_specifier, module.module_request->assertions);
|
||||
}
|
||||
}
|
||||
|
||||
return requested_modules_in_source_order;
|
||||
}
|
||||
|
||||
SourceTextModule::SourceTextModule(Realm& realm, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<FlyString> requested_modules,
|
||||
SourceTextModule::SourceTextModule(Realm& realm, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
|
||||
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
|
||||
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
|
||||
RefPtr<ExportStatement> default_export)
|
||||
|
@ -81,8 +124,12 @@ Result<NonnullRefPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule:
|
|||
if (parser.has_errors())
|
||||
return parser.errors();
|
||||
|
||||
// Needed for 2.7 Static Semantics: AssertClauseToAssertions, https://tc39.es/proposal-import-assertions/#sec-assert-clause-to-assertions
|
||||
// 1. Let supportedAssertions be !HostGetSupportedImportAssertions().
|
||||
auto supported_assertions = realm.vm().host_get_supported_import_assertions();
|
||||
|
||||
// 3. Let requestedModules be the ModuleRequests of body.
|
||||
auto requested_modules = module_requests(*body);
|
||||
auto requested_modules = module_requests(*body, supported_assertions);
|
||||
|
||||
// 4. Let importEntries be ImportEntries of body.
|
||||
Vector<ImportEntry> import_entries;
|
||||
|
@ -288,9 +335,6 @@ Completion SourceTextModule::initialize_environment(VM& vm)
|
|||
|
||||
// 7. For each ImportEntry Record in of module.[[ImportEntries]], do
|
||||
for (auto& import_entry : m_import_entries) {
|
||||
if (!import_entry.module_request().assertions.is_empty())
|
||||
return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "import statements with assertions");
|
||||
|
||||
// a. Let importedModule be ! HostResolveImportedModule(module, in.[[ModuleRequest]]).
|
||||
auto imported_module = MUST(vm.host_resolve_imported_module(this, import_entry.module_request()));
|
||||
// b. NOTE: The above call cannot fail because imported module requests are a subset of module.[[RequestedModules]], and these have been resolved earlier in this algorithm.
|
||||
|
|
|
@ -44,7 +44,7 @@ protected:
|
|||
virtual Completion execute_module(VM& vm, Optional<PromiseCapability> capability) override;
|
||||
|
||||
private:
|
||||
SourceTextModule(Realm&, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<FlyString> requested_modules,
|
||||
SourceTextModule(Realm&, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
|
||||
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
|
||||
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
|
||||
RefPtr<ExportStatement> default_export);
|
||||
|
|
|
@ -9,14 +9,14 @@ function validTestModule(filename) {
|
|||
}
|
||||
}
|
||||
|
||||
function expectModulePassed(filename) {
|
||||
function expectModulePassed(filename, options = undefined) {
|
||||
validTestModule(filename);
|
||||
|
||||
let moduleLoaded = false;
|
||||
let moduleResult = null;
|
||||
let thrownError = null;
|
||||
|
||||
import(filename)
|
||||
import(filename, options)
|
||||
.then(result => {
|
||||
moduleLoaded = true;
|
||||
moduleResult = result;
|
||||
|
@ -130,6 +130,11 @@ describe("testing behavior", () => {
|
|||
test("expectModulePassed works", () => {
|
||||
expectModulePassed("./single-const-export.mjs");
|
||||
});
|
||||
|
||||
test("can call expectModulePassed with options", () => {
|
||||
expectModulePassed("./single-const-export.mjs", { key: "value" });
|
||||
expectModulePassed("./single-const-export.mjs", { key1: "value1", key2: "value2" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("in- and exports", () => {
|
||||
|
@ -173,6 +178,10 @@ describe("in- and exports", () => {
|
|||
"Invalid or ambiguous export entry 'default'"
|
||||
);
|
||||
});
|
||||
|
||||
test("can import with (useless) assertions", () => {
|
||||
expectModulePassed("./import-with-assertions.mjs");
|
||||
});
|
||||
});
|
||||
|
||||
describe("loops", () => {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import * as self from "./import-with-assertions.mjs" assert { "key": "value", key2: "value2", default: "shouldwork" };
|
||||
import "./import-with-assertions.mjs" assert { "key": "value", key2: "value2", default: "shouldwork" };
|
||||
|
||||
export { passed } from "./module-with-default.mjs" assert { "key": "value", key2: "value2", default: "shouldwork" };
|
Loading…
Add table
Add a link
Reference in a new issue