diff --git a/bin/ChakraCore/ChakraCore.def b/bin/ChakraCore/ChakraCore.def index d74a88bfcde..46cbb361c51 100644 --- a/bin/ChakraCore/ChakraCore.def +++ b/bin/ChakraCore/ChakraCore.def @@ -38,3 +38,9 @@ JsTTDNotifyYield JsTTDPrepContextsForTopLevelEventMove JsTTDMoveToTopLevelEvent JsTTDReplayExecution + +JsInitializeModuleRecord +JsParseModuleSource +JsModuleEvaluation +JsSetModuleHostInfo +JsGetModuleHostInfo diff --git a/bin/ch/ChakraRtInterface.cpp b/bin/ch/ChakraRtInterface.cpp index c96269cf782..2d8feacef01 100644 --- a/bin/ch/ChakraRtInterface.cpp +++ b/bin/ch/ChakraRtInterface.cpp @@ -115,6 +115,11 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary) m_jsApiHooks.pfJsrtSetPromiseContinuationCallback = (JsAPIHooks::JsrtSetPromiseContinuationCallbackPtr)GetChakraCoreSymbol(library, "JsSetPromiseContinuationCallback"); m_jsApiHooks.pfJsrtGetContextOfObject = (JsAPIHooks::JsrtGetContextOfObject)GetChakraCoreSymbol(library, "JsGetContextOfObject"); m_jsApiHooks.pfJsrtParseScriptWithAttributesUtf8 = (JsAPIHooks::JsrtParseScriptWithAttributesUtf8)GetChakraCoreSymbol(library, "JsParseScriptWithAttributesUtf8"); + m_jsApiHooks.pfJsrtInitializeModuleRecord = (JsAPIHooks::JsInitializeModuleRecordPtr)GetChakraCoreSymbol(library, "JsInitializeModuleRecord"); + m_jsApiHooks.pfJsrtParseModuleSource = (JsAPIHooks::JsParseModuleSourcePtr)GetChakraCoreSymbol(library, "JsParseModuleSource"); + m_jsApiHooks.pfJsrtSetModuleHostInfo = (JsAPIHooks::JsSetModuleHostInfoPtr)GetChakraCoreSymbol(library, "JsSetModuleHostInfo"); + m_jsApiHooks.pfJsrtGetModuleHostInfo = (JsAPIHooks::JsGetModuleHostInfoPtr)GetChakraCoreSymbol(library, "JsGetModuleHostInfo"); + m_jsApiHooks.pfJsrtModuleEvaluation = (JsAPIHooks::JsModuleEvaluationPtr)GetChakraCoreSymbol(library, "JsModuleEvaluation"); m_jsApiHooks.pfJsrtDiagStartDebugging = (JsAPIHooks::JsrtDiagStartDebugging)GetChakraCoreSymbol(library, "JsDiagStartDebugging"); m_jsApiHooks.pfJsrtDiagStopDebugging = (JsAPIHooks::JsrtDiagStopDebugging)GetChakraCoreSymbol(library, "JsDiagStopDebugging"); m_jsApiHooks.pfJsrtDiagGetSource = (JsAPIHooks::JsrtDiagGetSource)GetChakraCoreSymbol(library, "JsDiagGetSource"); @@ -135,7 +140,6 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary) m_jsApiHooks.pfJsrtRunScriptUtf8 = (JsAPIHooks::JsrtRunScriptUtf8)GetChakraCoreSymbol(library, "JsRunScriptUtf8"); m_jsApiHooks.pfJsrtSerializeScriptUtf8 = (JsAPIHooks::JsrtSerializeScriptUtf8)GetChakraCoreSymbol(library, "JsSerializeScriptUtf8"); m_jsApiHooks.pfJsrtRunSerializedScriptUtf8 = (JsAPIHooks::JsrtRunSerializedScriptUtf8)GetChakraCoreSymbol(library, "JsRunSerializedScriptUtf8"); - m_jsApiHooks.pfJsrtExperimentalApiRunModuleUtf8 = (JsAPIHooks::JsrtRunModuleUtf8Ptr)GetChakraCoreSymbol(library, "JsExperimentalApiRunModuleUtf8"); m_jsApiHooks.pfJsrtGetPropertyIdFromNameUtf8 = (JsAPIHooks::JsrtGetPropertyIdFromNameUtf8Ptr)GetChakraCoreSymbol(library, "JsGetPropertyIdFromNameUtf8"); m_jsApiHooks.pfJsrtStringFree = (JsAPIHooks::JsrtStringFreePtr)GetChakraCoreSymbol(library, "JsStringFree"); diff --git a/bin/ch/ChakraRtInterface.h b/bin/ch/ChakraRtInterface.h index a23aa8740be..30ce885c89d 100644 --- a/bin/ch/ChakraRtInterface.h +++ b/bin/ch/ChakraRtInterface.h @@ -28,6 +28,11 @@ struct JsAPIHooks typedef JsErrorCode (WINAPI *JsrtGetPropertyIdFromNameUtf8Ptr)(const char *name, JsPropertyIdRef *propertyId); typedef JsErrorCode (WINAPI *JsrtGetPropertyPtr)(JsValueRef object, JsPropertyIdRef property, JsValueRef* value); typedef JsErrorCode (WINAPI *JsrtHasPropertyPtr)(JsValueRef object, JsPropertyIdRef property, bool *hasProperty); + typedef JsErrorCode (WINAPI *JsInitializeModuleRecordPtr)(JsModuleRecord referencingModule, JsValueRef normalizedSpecifier, JsModuleRecord* moduleRecord); + typedef JsErrorCode (WINAPI *JsParseModuleSourcePtr)(JsModuleRecord requestModule, JsSourceContext sourceContext, byte* sourceText, unsigned int sourceLength, JsParseModuleSourceFlags sourceFlag, JsValueRef* exceptionValueRef); + typedef JsErrorCode (WINAPI *JsModuleEvaluationPtr)(JsModuleRecord requestModule, JsValueRef* result); + typedef JsErrorCode (WINAPI *JsSetModuleHostInfoPtr)(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void* hostInfo); + typedef JsErrorCode (WINAPI *JsGetModuleHostInfoPtr)(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void** hostInfo); typedef JsErrorCode (WINAPI *JsrtCallFunctionPtr)(JsValueRef function, JsValueRef* arguments, unsigned short argumentCount, JsValueRef *result); typedef JsErrorCode (WINAPI *JsrtNumberToDoublePtr)(JsValueRef value, double *doubleValue); typedef JsErrorCode (WINAPI *JsrtNumberToIntPtr)(JsValueRef value, int *intValue); @@ -35,7 +40,7 @@ struct JsAPIHooks typedef JsErrorCode (WINAPI *JsrtGetExternalDataPtr)(JsValueRef object, void **data); typedef JsErrorCode (WINAPI *JsrtCreateArrayPtr)(unsigned int length, JsValueRef *result); typedef JsErrorCode (WINAPI *JsrtCreateErrorPtr)(JsValueRef message, JsValueRef *error); - typedef JsErrorCode(WINAPI *JsrtHasExceptionPtr)(bool *hasException); + typedef JsErrorCode (WINAPI *JsrtHasExceptionPtr)(bool *hasException); typedef JsErrorCode (WINAPI *JsrtSetExceptionPtr)(JsValueRef exception); typedef JsErrorCode (WINAPI *JsrtGetAndClearExceptiopnPtr)(JsValueRef* exception); typedef JsErrorCode (WINAPI *JsrtGetRuntimePtr)(JsContextRef context, JsRuntimeHandle *runtime); @@ -68,7 +73,6 @@ struct JsAPIHooks typedef JsErrorCode(WINAPI *JsrtRunScriptUtf8)(const char *script, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef *result); typedef JsErrorCode(WINAPI *JsrtSerializeScriptUtf8)(const char *script, ChakraBytePtr buffer, unsigned int *bufferSize); typedef JsErrorCode(WINAPI *JsrtRunSerializedScriptUtf8)(JsSerializedScriptLoadUtf8SourceCallback scriptLoadCallback, JsSerializedScriptUnloadCallback scriptUnloadCallback, ChakraBytePtr buffer, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef * result); - typedef JsErrorCode(WINAPI *JsrtRunModuleUtf8Ptr)(const char *script, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef *result); typedef JsErrorCode(WINAPI *JsrtStringFreePtr)(const char *stringValue); typedef JsErrorCode(WINAPI *JsrtTTDCreateRecordRuntimePtr)(JsRuntimeAttributes attributes, char* infoUri, size_t infoUriCount, UINT32 snapInterval, UINT32 snapHistoryLength, JsThreadServiceCallback threadService, JsRuntimeHandle *runtime); @@ -117,6 +121,11 @@ struct JsAPIHooks JsrtGetPropertyIdFromNameUtf8Ptr pfJsrtGetPropertyIdFromNameUtf8; JsrtGetPropertyPtr pfJsrtGetProperty; JsrtHasPropertyPtr pfJsrtHasProperty; + JsParseModuleSourcePtr pfJsrtParseModuleSource; + JsInitializeModuleRecordPtr pfJsrtInitializeModuleRecord; + JsModuleEvaluationPtr pfJsrtModuleEvaluation; + JsSetModuleHostInfoPtr pfJsrtSetModuleHostInfo; + JsGetModuleHostInfoPtr pfJsrtGetModuleHostInfo; JsrtCallFunctionPtr pfJsrtCallFunction; JsrtNumberToDoublePtr pfJsrtNumberToDouble; JsrtNumberToIntPtr pfJsrtNumberToInt; @@ -156,7 +165,6 @@ struct JsAPIHooks JsrtRunScriptUtf8 pfJsrtRunScriptUtf8; JsrtSerializeScriptUtf8 pfJsrtSerializeScriptUtf8; JsrtRunSerializedScriptUtf8 pfJsrtRunSerializedScriptUtf8; - JsrtRunModuleUtf8Ptr pfJsrtExperimentalApiRunModuleUtf8; JsrtStringFreePtr pfJsrtStringFree; JsrtTTDCreateRecordRuntimePtr pfJsrtTTDCreateRecordRuntime; @@ -317,6 +325,15 @@ class ChakraRTInterface static JsErrorCode WINAPI JsDiagGetProperties(unsigned int objectHandle, unsigned int fromCount, unsigned int totalCount, JsValueRef * propertiesObject) { return HOOK_JS_API(DiagGetProperties(objectHandle, fromCount, totalCount, propertiesObject)); } static JsErrorCode WINAPI JsDiagGetObjectFromHandle(unsigned int handle, JsValueRef * handleObject) { return HOOK_JS_API(DiagGetObjectFromHandle(handle, handleObject)); } static JsErrorCode WINAPI JsDiagEvaluateUtf8(const char * expression, unsigned int stackFrameIndex, JsValueRef * evalResult) { return HOOK_JS_API(DiagEvaluateUtf8(expression, stackFrameIndex, evalResult)); } + static JsErrorCode WINAPI JsParseModuleSource(JsModuleRecord requestModule, JsSourceContext sourceContext, byte* sourceText, unsigned int sourceLength, JsParseModuleSourceFlags sourceFlag, JsValueRef* exceptionValueRef) { + return m_jsApiHooks.pfJsrtParseModuleSource(requestModule, sourceContext, sourceText, sourceLength, sourceFlag, exceptionValueRef); + } + static JsErrorCode WINAPI JsModuleEvaluation(JsModuleRecord requestModule, JsValueRef* result) { return m_jsApiHooks.pfJsrtModuleEvaluation(requestModule, result); } + static JsErrorCode WINAPI JsInitializeModuleRecord(JsModuleRecord referencingModule, JsValueRef normalizedSpecifier, JsModuleRecord* moduleRecord) { + return m_jsApiHooks.pfJsrtInitializeModuleRecord(referencingModule, normalizedSpecifier, moduleRecord); + } + static JsErrorCode WINAPI JsSetModuleHostInfo(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void* hostInfo) { return m_jsApiHooks.pfJsrtSetModuleHostInfo(requestModule, moduleHostInfo, hostInfo); } + static JsErrorCode WINAPI JsGetModuleHostInfo(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void** hostInfo) { return m_jsApiHooks.pfJsrtGetModuleHostInfo(requestModule, moduleHostInfo, hostInfo); } static JsErrorCode WINAPI JsValueToCharCopy(JsValueRef value, char **stringValue, size_t *length) { @@ -329,7 +346,6 @@ class ChakraRTInterface static JsErrorCode WINAPI JsRunScriptUtf8(const char *script, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef *result) { return HOOK_JS_API(RunScriptUtf8(script, sourceContext, sourceUrl, result)); } static JsErrorCode WINAPI JsSerializeScriptUtf8(const char *script, ChakraBytePtr buffer, unsigned int *bufferSize) { return HOOK_JS_API(SerializeScriptUtf8(script, buffer, bufferSize)); } static JsErrorCode WINAPI JsRunSerializedScriptUtf8(JsSerializedScriptLoadUtf8SourceCallback scriptLoadCallback, JsSerializedScriptUnloadCallback scriptUnloadCallback, ChakraBytePtr buffer, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef * result) { return HOOK_JS_API(RunSerializedScriptUtf8(scriptLoadCallback, scriptUnloadCallback, buffer, sourceContext, sourceUrl, result)); } - static JsErrorCode WINAPI JsRunModuleUtf8(const char *script, JsSourceContext sourceContext, const char *sourceUrl, JsValueRef *result) { return HOOK_JS_API(ExperimentalApiRunModuleUtf8(script, sourceContext, sourceUrl, result)); } static JsErrorCode WINAPI JsPointerToStringUtf8(const char *stringValue, size_t length, JsValueRef *value) { return HOOK_JS_API(PointerToStringUtf8(stringValue, length, value)); } static JsErrorCode WINAPI JsStringFree(char *stringValue) { return HOOK_JS_API(StringFree(stringValue)); } static JsErrorCode WINAPI JsTTDCreateRecordRuntime(JsRuntimeAttributes attributes, char* infoUri, size_t infoUriCount, UINT32 snapInterval, UINT32 snapHistoryLength, JsThreadServiceCallback threadService, JsRuntimeHandle *runtime) { return HOOK_JS_API(TTDCreateRecordRuntime(attributes, infoUri, infoUriCount, snapInterval, snapHistoryLength, threadService, runtime)); } diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index ff425ff9e4c..b6debc66c86 100644 --- a/bin/ch/WScriptJsrt.cpp +++ b/bin/ch/WScriptJsrt.cpp @@ -3,8 +3,10 @@ // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "stdafx.h" +#include MessageQueue* WScriptJsrt::messageQueue = nullptr; +std::map WScriptJsrt::moduleRecordMap; DWORD_PTR WScriptJsrt::sourceContext = 0; #define ERROR_MESSAGE_TO_STRING(errorCode, errorMessage, errorMessageString) \ @@ -125,11 +127,6 @@ JsValueRef __stdcall WScriptJsrt::QuitCallback(JsValueRef callee, bool isConstru ExitProcess(exitCode); } -JsValueRef __stdcall WScriptJsrt::LoadModuleFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) -{ - return LoadScriptFileHelper(callee, arguments, argumentCount, true); -} - JsValueRef __stdcall WScriptJsrt::LoadScriptFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) { return LoadScriptFileHelper(callee, arguments, argumentCount, false); @@ -220,10 +217,12 @@ JsValueRef WScriptJsrt::LoadScriptHelper(JsValueRef callee, bool isConstructCall else { AutoString fileContent; - AutoString fileName; + char* fileNameNarrow = nullptr; + bool freeFileName = false; AutoString scriptInjectType; size_t fileContentLength; size_t scriptInjectTypeLength; + char fileNameBuffer[MAX_PATH]; IfJsrtErrorSetGo(ChakraRTInterface::JsStringToPointerUtf8Copy(arguments[1], &fileContent, &fileContentLength)); @@ -234,15 +233,26 @@ JsValueRef WScriptJsrt::LoadScriptHelper(JsValueRef callee, bool isConstructCall if (argumentCount > 3) { size_t unused; - IfJsrtErrorSetGo(ChakraRTInterface::JsStringToPointerUtf8Copy(arguments[3], &fileName, &unused)); + IfJsrtErrorSetGo(ChakraRTInterface::JsStringToPointerUtf8Copy(arguments[3], &fileNameNarrow, &unused)); + freeFileName = true; } } + if (!freeFileName && isSourceModule) + { + sprintf_s(fileNameBuffer, MAX_PATH, "moduleScript%i.js", (int)sourceContext); + fileNameNarrow = fileNameBuffer; + } + if (*fileContent) { // TODO: This is CESU-8. How to tell the engine? // TODO: How to handle this source (script) life time? - returnValue = LoadScript(callee, *fileName, *fileContent, *scriptInjectType ? *scriptInjectType : "self", isSourceModule); + returnValue = LoadScript(callee, fileNameNarrow, *fileContent, *scriptInjectType ? *scriptInjectType : "self", isSourceModule); + if (freeFileName) + { + ChakraRTInterface::JsStringFree(fileNameNarrow); + } } } @@ -265,6 +275,64 @@ JsValueRef WScriptJsrt::LoadScriptHelper(JsValueRef callee, bool isConstructCall return returnValue; } +JsErrorCode WScriptJsrt::InitializeModuleInfo(JsValueRef specifier, JsModuleRecord moduleRecord) +{ + JsErrorCode errorCode = JsNoError; + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule); + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback); + } + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier); + } + IfJsrtErrorFailLogAndRetErrorCode(errorCode); + return JsNoError; +} + +JsErrorCode WScriptJsrt::LoadModuleFromString(LPCSTR fileName, LPCSTR fileContent) +{ + DWORD_PTR dwSourceCookie = WScriptJsrt::GetNextSourceContext(); + JsModuleRecord requestModule = JS_INVALID_REFERENCE; + auto moduleRecordEntry = moduleRecordMap.find(std::string(fileName)); + JsErrorCode errorCode = JsNoError; + // we need to create a new moduleRecord if the specifier (fileName) is not found; otherwise we'll use the old one. + if (moduleRecordEntry == moduleRecordMap.end()) + { + JsValueRef specifier; + errorCode = ChakraRTInterface::JsPointerToStringUtf8(fileName, strlen(fileName), &specifier); + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, specifier, &requestModule); + } + if (errorCode == JsNoError) + { + errorCode = InitializeModuleInfo(specifier, requestModule); + } + if (errorCode == JsNoError) + { + moduleRecordMap[std::string(fileName)] = requestModule; + } + } + else + { + requestModule = moduleRecordEntry->second; + } + IfJsrtErrorFailLogAndRetErrorCode(errorCode); + JsValueRef errorObject = JS_INVALID_REFERENCE; + + // ParseModuleSource is sync, while additional fetch & evaluation are async. + errorCode = ChakraRTInterface::JsParseModuleSource(requestModule, dwSourceCookie, (LPBYTE)fileContent, + (unsigned int)strlen(fileContent), JsParseModuleSourceFlags_DataIsUTF8, &errorObject); + if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE) + { + ChakraRTInterface::JsSetException(errorObject); + return errorCode; + } + return JsNoError; +} + JsValueRef WScriptJsrt::LoadScript(JsValueRef callee, LPCSTR fileName, LPCSTR fileContent, LPCSTR scriptInjectType, bool isSourceModule) { HRESULT hr = E_FAIL; @@ -301,25 +369,24 @@ JsValueRef WScriptJsrt::LoadScript(JsValueRef callee, LPCSTR fileName, LPCSTR fi strcpy_s(fullPathNarrow, "script.js"); } - if (strcmp(scriptInjectType, "self") == 0) + // this is called with LoadModuleCallback method as well where caller pass in a string that should be + // treated as a module source text instead of opening a new file. + if (isSourceModule || (strcmp(scriptInjectType, "module") == 0)) + { + errorCode = LoadModuleFromString(fileName, fileContent); + } + else if (strcmp(scriptInjectType, "self") == 0) { JsContextRef calleeContext; IfJsrtErrorSetGo(ChakraRTInterface::JsGetContextOfObject(callee, &calleeContext)); IfJsrtErrorSetGo(ChakraRTInterface::JsSetCurrentContext(calleeContext)); - if (isSourceModule) - { - errorCode = ChakraRTInterface::JsRunModuleUtf8(fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); - } - else - { #if ENABLE_TTD - errorCode = ChakraRTInterface::JsTTDRunScript(-1, fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); + errorCode = ChakraRTInterface::JsTTDRunScript(-1, fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); #else - errorCode = ChakraRTInterface::JsRunScriptUtf8(fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); + errorCode = ChakraRTInterface::JsRunScriptUtf8(fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); #endif - } if (errorCode == JsNoError) { @@ -339,19 +406,11 @@ JsValueRef WScriptJsrt::LoadScript(JsValueRef callee, LPCSTR fileName, LPCSTR fi // Initialize the host objects Initialize(); - - if (isSourceModule) - { - errorCode = ChakraRTInterface::JsRunModuleUtf8(fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); - } - else - { #if ENABLE_TTD errorCode = ChakraRTInterface::JsTTDRunScript(-1, fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); #else errorCode = ChakraRTInterface::JsRunScriptUtf8(fileContent, GetNextSourceContext(), fullPathNarrow, &returnValue); #endif - } if (errorCode == JsNoError) { @@ -654,7 +713,6 @@ bool WScriptJsrt::Initialize() IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Echo", EchoCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Quit", QuitCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadScriptFile", LoadScriptFileCallback)); - IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadModuleFile", LoadModuleFileCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadScript", LoadScriptCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadModule", LoadModuleCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "SetTimeout", SetTimeoutCallback)); @@ -864,3 +922,128 @@ HRESULT WScriptJsrt::CallbackMessage::CallFunction(LPCSTR fileName) Error: return hr; } + +WScriptJsrt::ModuleMessage::ModuleMessage(JsModuleRecord module, JsValueRef specifier) + : MessageBase(0), moduleRecord(module), specifier(specifier) +{ + ChakraRTInterface::JsAddRef(module, nullptr); + if (specifier != nullptr) + { + // nullptr specifier means a Promise to execute; non-nullptr means a "fetch" operation. + ChakraRTInterface::JsAddRef(specifier, nullptr); + } +} + +WScriptJsrt::ModuleMessage::~ModuleMessage() +{ + ChakraRTInterface::JsRelease(moduleRecord, nullptr); + if (specifier != nullptr) + { + ChakraRTInterface::JsRelease(specifier, nullptr); + } +} + +HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName) +{ + JsErrorCode errorCode; + JsValueRef result = JS_INVALID_REFERENCE; + HRESULT hr; + if (specifier == nullptr) + { + errorCode = ChakraRTInterface::JsModuleEvaluation(moduleRecord, &result); + if (errorCode != JsNoError) + { + PrintException(fileName, errorCode); + } + } + else + { + LPCSTR fileContent = nullptr; + char* specifierStr = nullptr; + size_t length; + errorCode = ChakraRTInterface::JsStringToPointerUtf8Copy(specifier, &specifierStr, &length); + if (errorCode == JsNoError) + { + hr = Helpers::LoadScriptFromFile(specifierStr, fileContent); + if (FAILED(hr)) + { + fprintf(stderr, "Couldn't load file.\n"); + } + else + { + LoadScript(nullptr, specifierStr, fileContent, "module", true); + } + } + } + return errorCode; +} + +// Callback from chakracore to fetch dependent module. In the test harness, we are not doing any translation, just treat the specifier as fileName. +// While this call will come back directly from ParseModuleSource, the additional task are treated as Promise that will be executed later. +JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord) +{ + JsModuleRecord moduleRecord = JS_INVALID_REFERENCE; + AutoString specifierStr; + size_t length; + + JsErrorCode errorCode = ChakraRTInterface::JsStringToPointerUtf8Copy(specifier, &specifierStr, &length); + if (errorCode != JsNoError) + { + return errorCode; + } + auto moduleEntry = moduleRecordMap.find(std::string(*specifierStr)); + if (moduleEntry != moduleRecordMap.end()) + { + *dependentModuleRecord = moduleEntry->second; + return JsNoError; + } + + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsInitializeModuleRecord(referencingModule, specifier, &moduleRecord); + } + if (errorCode == JsNoError) + { + InitializeModuleInfo(specifier, moduleRecord); + moduleRecordMap[std::string(*specifierStr)] = moduleRecord; + ModuleMessage* moduleMessage = + WScriptJsrt::ModuleMessage::Create(referencingModule, specifier); + if (moduleMessage == nullptr) + { + return JsErrorOutOfMemory; + } + WScriptJsrt::PushMessage(moduleMessage); + *dependentModuleRecord = moduleRecord; + } + return errorCode; +} + +// Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully. +JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar) +{ + if (exceptionVar != nullptr) + { + ChakraRTInterface::JsSetException(exceptionVar); + JsValueRef specifier = JS_INVALID_REFERENCE; + ChakraRTInterface::JsGetModuleHostInfo(referencingModule, JsModuleHostInfo_HostDefined, &specifier); + AutoString fileName; + size_t length; + if (specifier != JS_INVALID_REFERENCE) + { + ChakraRTInterface::JsStringToPointerUtf8Copy(specifier, &fileName, &length); + } + PrintException(*fileName, JsErrorScriptException); + } + else + { + WScriptJsrt::ModuleMessage* moduleMessage = + WScriptJsrt::ModuleMessage::Create(referencingModule, nullptr); + if (moduleMessage == nullptr) + { + return JsErrorOutOfMemory; + } + WScriptJsrt::PushMessage(moduleMessage); + } + return JsNoError; +} + diff --git a/bin/ch/WScriptJsrt.h b/bin/ch/WScriptJsrt.h index 0cc851b8598..4044fb50bc5 100644 --- a/bin/ch/WScriptJsrt.h +++ b/bin/ch/WScriptJsrt.h @@ -28,9 +28,32 @@ class WScriptJsrt } }; + class ModuleMessage : public MessageBase + { + private: + JsModuleRecord moduleRecord; + JsValueRef specifier; + + ModuleMessage(JsModuleRecord module, JsValueRef specifier); + + public: + ~ModuleMessage(); + + virtual HRESULT Call(LPCSTR fileName) override; + + static ModuleMessage* Create(JsModuleRecord module, JsValueRef specifier) + { + return new ModuleMessage(module, specifier); + } + + }; + static void AddMessageQueue(MessageQueue *messageQueue); static void PushMessage(MessageBase *message) { messageQueue->InsertSorted(message); } + static JsErrorCode FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); + static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); + static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode) { switch (errorCode) @@ -68,7 +91,6 @@ class WScriptJsrt static bool CreateNamedFunction(const char*, JsNativeFunction callback, JsValueRef* functionVar); static JsValueRef __stdcall EchoCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef __stdcall QuitCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); - static JsValueRef __stdcall LoadModuleFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef __stdcall LoadScriptFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef __stdcall LoadScriptCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef __stdcall LoadModuleCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); @@ -80,7 +102,10 @@ class WScriptJsrt static JsValueRef __stdcall RequestAsyncBreakCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef __stdcall EmptyCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); + static JsErrorCode __stdcall LoadModuleFromString(LPCSTR fileName, LPCSTR fileContent); + static JsErrorCode __stdcall InitializeModuleInfo(JsValueRef specifier, JsModuleRecord moduleRecord); static MessageQueue *messageQueue; static DWORD_PTR sourceContext; + static std::map moduleRecordMap; }; diff --git a/bin/ch/ch.cpp b/bin/ch/ch.cpp index 6f157416c36..bbeecc78128 100644 --- a/bin/ch/ch.cpp +++ b/bin/ch/ch.cpp @@ -344,6 +344,13 @@ HRESULT RunScript(const char* fileName, LPCSTR fileContents, BYTE *bcBuffer, cha if (messageQueue != nullptr) { messageQueue->RemoveAll(); + // clean up possible pinned exception object on exit to avoid potential leak + bool hasException; + if (ChakraRTInterface::JsHasException(&hasException) == JsNoError && hasException) + { + JsValueRef exception = JS_INVALID_REFERENCE; + ChakraRTInterface::JsGetAndClearException(&exception); + } delete messageQueue; } return hr; diff --git a/bin/ch/stdafx.h b/bin/ch/stdafx.h index e206bbceadc..a1c5c6f8091 100644 --- a/bin/ch/stdafx.h +++ b/bin/ch/stdafx.h @@ -25,6 +25,7 @@ #define ENABLE_TEST_HOOKS 1 #include "CommonDefines.h" +#include #ifdef _WIN32 #include @@ -63,7 +64,9 @@ #if defined(DBG) #define _STRINGIZE_(x) #x +#if !defined(_STRINGIZE) #define _STRINGIZE(x) _STRINGIZE_(x) +#endif #define AssertMsg(exp, comment) \ do { \ @@ -80,10 +83,9 @@ if (!(exp)) \ #define Assert(exp) AssertMsg(exp, #exp) #define _JSRT_ -#include "ChakraCommon.h" +#include "ChakraCore.h" #include "Core/CommonTypedefs.h" #include "TestHooksRt.h" -#include "ChakraDebug.h" typedef void * Var; diff --git a/lib/Jsrt/ChakraCommon.h b/lib/Jsrt/ChakraCommon.h index 3798140d73f..a182065fba8 100644 --- a/lib/Jsrt/ChakraCommon.h +++ b/lib/Jsrt/ChakraCommon.h @@ -186,7 +186,22 @@ typedef unsigned char* ChakraBytePtr; /// The error code is returned by existing JsGetPropertyNamefromId if the function is called with non-string property id. /// JsErrorPropertyNotString, - + /// + /// Module evaulation is called in wrong context. + /// + JsErrorInvalidContext, + /// + /// Module evaulation is called in wrong context. + /// + JsInvalidModuleHostInfoKind, + /// + /// Module was parsed already when JsParseModuleSource is called. + /// + JsErrorModuleParsed, + /// + /// Module was evaluated already when JsModuleEvaluation is called. + /// + JsErrorModuleEvaluated, /// /// Category of errors that relates to errors occurring within the engine itself. /// @@ -195,6 +210,10 @@ typedef unsigned char* ChakraBytePtr; /// The Chakra engine has run out of memory. /// JsErrorOutOfMemory, + /// + /// The Chakra engine failed to set the Floating Point Unit state. + /// + JsErrorBadFPUState, /// /// Category of errors that relates to errors in a script. @@ -1073,28 +1092,6 @@ typedef unsigned char* ChakraBytePtr; _In_z_ const char *sourceUrl, _Out_ JsValueRef *result); - /// - /// Executes a module. - /// - /// - /// Requires an active script context. - /// - /// The module script to parse and execute, encoded as utf8. - /// - /// A cookie identifying the script that can be used by debuggable script contexts. - /// - /// The location the module script came from, encoded as utf8. - /// The result of executing the module script, if any. This parameter can be null. - /// - /// The code JsNoError if the operation succeeded, a failure code otherwise. - /// - CHAKRA_API - JsExperimentalApiRunModuleUtf8( - _In_z_ const char *script, - _In_ JsSourceContext sourceContext, - _In_z_ const char *sourceUrl, - _Out_ JsValueRef *result); - /// /// Serializes a parsed script to a buffer than can be reused. /// diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index d8cdb75832e..40bb18517ad 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -25,4 +25,142 @@ #include "ChakraDebug.h" +typedef void* JsModuleRecord; + +typedef enum JsParseModuleSourceFlags +{ + JsParseModuleSourceFlags_DataIsUTF16LE = 0x00000000, + JsParseModuleSourceFlags_DataIsUTF8 = 0x00000001 +} JsParseModuleSourceFlags; + +typedef enum JsModuleHostInfoKind +{ + JsModuleHostInfo_Exception = 0x01, + JsModuleHostInfo_HostDefined = 0x02, + JsModuleHostInfo_NotifyModuleReadyCallback = 0x3, + JsModuleHostInfo_FetchImportedModuleCallback = 0x4 +} JsModuleHostInfoKind; + +/// +/// User implemented callback to fetch additional imported modules. +/// +/// +/// Notify the host to fetch the dependent module. This is the "import" part before HostResolveImportedModule in ES6 spec. +/// This notifies the host that the referencing module has the specified module dependency, and the host need to retrieve the module back. +/// +/// The referencing module that is requesting the dependency modules. +/// The specifier coming from the module source code. +/// The ModuleRecord of the dependent module. If the module was requested before from other source, return the +/// existing ModuleRecord, otherwise return a newly created ModuleRecord. +/// +/// true if the operation succeeded, false otherwise. +/// +typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); + +/// +/// User implemented callback to get notification when the module is ready. +/// +/// +/// Notify the host after ModuleDeclarationInstantiation step (15.2.1.1.6.4) is finished. If there was error in the process, exceptionVar +/// holds the exception. Otherwise the referencingModule is ready and the host should schedule execution afterwards. +/// +/// If nullptr, the module is successfully initialized and host should queue the execution job +/// otherwise it's the exception object. +/// +/// true if the operation succeeded, false otherwise. +/// +typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); + +/// +/// Initialize a ModuleRecord from host +/// +/// +/// Bootstrap the module loading process by creating a new module record. +/// +/// The referencingModule as in HostResolveImportedModule (15.2.1.17). nullptr if this is the top level module. +/// The host normalized specifier. This is the key to a unique ModuleRecord. +/// The new ModuleRecord created. The host should not try to call this API twice with the same normalizedSpecifier. +/// chakra will return an existing ModuleRecord if the specifier was passed in before. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API +JsInitializeModuleRecord( + _In_opt_ JsModuleRecord referencingModule, + _In_ JsValueRef normalizedSpecifier, + _Outptr_result_maybenull_ JsModuleRecord* moduleRecord); + +/// +/// Parse the module source +/// +/// +/// This is basically ParseModule operation in ES6 spec. It is slightly different in that the ModuleRecord was initialized earlier, and passed in as an argument. +/// +/// The ModuleRecord that holds the parse tree of the source code. +/// A cookie identifying the script that can be used by debuggable script contexts. +/// The source script to be parsed, but not executed in this code. +/// The source length of sourceText. The input might contain embedded null. +/// The type of the source code passed in. It could be UNICODE or utf8 at this time. +/// The error object if there is parse error. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API +JsParseModuleSource( + _In_ JsModuleRecord requestModule, + _In_ JsSourceContext sourceContext, + _In_ byte* script, + _In_ unsigned int scriptLength, + _In_ JsParseModuleSourceFlags sourceFlag, + _Outptr_result_maybenull_ JsValueRef* exceptionValueRef); + +/// +/// Execute module code. +/// +/// +/// This method implements 15.2.1.1.6.5, "ModuleEvaluation" concrete method. +/// When this methid is called, the chakra engine should have notified the host that the module and all its dependent are ready to be executed. +/// One moduleRecord will be executed only once. Additional execution call on the same moduleRecord will fail. +/// +/// The module to be executed. +/// The return value of the module. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API +JsModuleEvaluation( + _In_ JsModuleRecord requestModule, + _Outptr_result_maybenull_ JsValueRef* result); + +/// +/// Set the host info for the specified module. +/// +/// The request module. +/// The type of host info to be set. +/// The host info to be set. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API +JsSetModuleHostInfo( + _In_ JsModuleRecord requestModule, + _In_ JsModuleHostInfoKind moduleHostInfo, + _In_ void* hostInfo); + +/// +/// Retrieve the host info for the specified module. +/// +/// The request module. +/// The type of host info to get. +/// The host info to be retrieved. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API +JsGetModuleHostInfo( + _In_ JsModuleRecord requestModule, + _In_ JsModuleHostInfoKind moduleHostInfo, + _Outptr_result_maybenull_ void** hostInfo); + #endif // _CHAKRACORE_H_ diff --git a/lib/Jsrt/Core/Chakra.Jsrt.Core.vcxproj b/lib/Jsrt/Core/Chakra.Jsrt.Core.vcxproj index a2e09c72352..46c77ee38eb 100644 --- a/lib/Jsrt/Core/Chakra.Jsrt.Core.vcxproj +++ b/lib/Jsrt/Core/Chakra.Jsrt.Core.vcxproj @@ -1,6 +1,6 @@  - + Chakra.Jsrt.Core @@ -34,10 +34,11 @@ + - + - + \ No newline at end of file diff --git a/lib/Jsrt/Core/JsrtContextCore.cpp b/lib/Jsrt/Core/JsrtContextCore.cpp index 0726607f0f0..c65ba57ddf7 100644 --- a/lib/Jsrt/Core/JsrtContextCore.cpp +++ b/lib/Jsrt/Core/JsrtContextCore.cpp @@ -94,3 +94,41 @@ void JsrtContextCore::OnScriptLoad(Js::JavascriptFunction * scriptFunction, Js:: jsrtDebugManager->ReportScriptCompile(scriptFunction, utf8SourceInfo, compileException); } } + +HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) +{ + if (fetchImportedModuleCallback == nullptr) + { + return E_INVALIDARG; + } + Js::JavascriptString* specifierVar = Js::JavascriptString::NewCopySz(specifier, GetScriptContext()); + JsModuleRecord dependentRecord = JS_INVALID_REFERENCE; + { + AUTO_NO_EXCEPTION_REGION; + JsErrorCode errorCode = fetchImportedModuleCallback(referencingModule, specifierVar, &dependentRecord); + if (errorCode == JsNoError) + { + *dependentModuleRecord = static_cast(dependentRecord); + return NOERROR; + } + } + return E_INVALIDARG; +} + +HRESULT ChakraCoreHostScriptContext::NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) +{ + if (notifyModuleReadyCallback == nullptr) + { + return E_INVALIDARG; + } + { + AUTO_NO_EXCEPTION_REGION; + JsErrorCode errorCode = notifyModuleReadyCallback(referencingModule, exceptionVar); + if (errorCode == JsNoError) + { + return NOERROR; + } + } + return E_INVALIDARG; +} + diff --git a/lib/Jsrt/Core/JsrtContextCore.h b/lib/Jsrt/Core/JsrtContextCore.h index 64b0f5804b9..7283287857c 100644 --- a/lib/Jsrt/Core/JsrtContextCore.h +++ b/lib/Jsrt/Core/JsrtContextCore.h @@ -12,6 +12,7 @@ class JsrtContextCore sealed : public JsrtContext public: static JsrtContextCore *New(JsrtRuntime * runtime); virtual void Dispose(bool isShutdown) override; + ChakraCoreHostScriptContext* GetHostScriptContext() const { return hostContext; } void OnScriptLoad(Js::JavascriptFunction * scriptFunction, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* compileException); private: @@ -25,7 +26,9 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext { public: ChakraCoreHostScriptContext(Js::ScriptContext* scriptContext) - : HostScriptContext(scriptContext) + : HostScriptContext(scriptContext), + notifyModuleReadyCallback(nullptr), + fetchImportedModuleCallback(nullptr) { } ~ChakraCoreHostScriptContext() @@ -162,18 +165,15 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext return E_NOTIMPL; } - HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override - { - AssertMsg(false, "not implemented"); - return S_FALSE; - } + HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override; - HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override - { - AssertMsg(false, "not implemented"); - return S_FALSE; - } + HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override; + void SetNotifyModuleReadyCallback(NotifyModuleReadyCallback notifyCallback) { this->notifyModuleReadyCallback = notifyCallback; } + NotifyModuleReadyCallback GetNotifyModuleReadyCallback() const { return this->notifyModuleReadyCallback; } + + void SetFetchImportedModuleCallback(FetchImportedModuleCallBack fetchCallback) { this->fetchImportedModuleCallback = fetchCallback ; } + FetchImportedModuleCallBack GetFetchImportedModuleCallback() const { return this->fetchImportedModuleCallback; } #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM) void EnsureParentInfo(Js::ScriptContext* scriptContext = NULL) override @@ -183,4 +183,7 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext } #endif +private: + FetchImportedModuleCallBack fetchImportedModuleCallback; + NotifyModuleReadyCallback notifyModuleReadyCallback; }; diff --git a/lib/Jsrt/Core/JsrtCore.cpp b/lib/Jsrt/Core/JsrtCore.cpp new file mode 100644 index 00000000000..0381acf164b --- /dev/null +++ b/lib/Jsrt/Core/JsrtCore.cpp @@ -0,0 +1,211 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +#include "JsrtPch.h" +#include "JsrtInternal.h" +#include "jsrtHelper.h" +#include "JsrtContextCore.h" +#include "chakracore.h" + +CHAKRA_API +JsInitializeModuleRecord( + _In_opt_ JsModuleRecord referencingModule, + _In_ JsValueRef normalizedSpecifier, + _Outptr_result_maybenull_ JsModuleRecord* moduleRecord) +{ + PARAM_NOT_NULL(moduleRecord); + + Js::SourceTextModuleRecord* childModuleRecord = nullptr; + + JsErrorCode errorCode = ContextAPIWrapper([&](Js::ScriptContext *scriptContext) -> JsErrorCode { + childModuleRecord = Js::SourceTextModuleRecord::Create(scriptContext); + if (referencingModule == nullptr) + { + childModuleRecord->SetIsRootModule(); + } + if (normalizedSpecifier != JS_INVALID_REFERENCE) + { + childModuleRecord->SetSpecifier(normalizedSpecifier); + } + return JsNoError; + }); + if (errorCode == JsNoError) + { + *moduleRecord = childModuleRecord; + } + else + { + *moduleRecord = JS_INVALID_REFERENCE; + } + return errorCode; +} + +CHAKRA_API +JsParseModuleSource( + _In_ JsModuleRecord requestModule, + _In_ JsSourceContext sourceContext, + _In_ byte* sourceText, + _In_ unsigned int sourceLength, + _In_ JsParseModuleSourceFlags sourceFlag, + _Outptr_result_maybenull_ JsValueRef* exceptionValueRef) +{ + PARAM_NOT_NULL(requestModule); + PARAM_NOT_NULL(exceptionValueRef); + if (sourceFlag > JsParseModuleSourceFlags_DataIsUTF8) + { + return JsErrorInvalidArgument; + } + + *exceptionValueRef = JS_INVALID_REFERENCE; + HRESULT hr; + if (!Js::SourceTextModuleRecord::Is(requestModule)) + { + return JsErrorInvalidArgument; + } + Js::SourceTextModuleRecord* moduleRecord = Js::SourceTextModuleRecord::FromHost(requestModule); + if (moduleRecord->WasParsed()) + { + return JsErrorModuleParsed; + } + Js::ScriptContext* scriptContext = moduleRecord->GetScriptContext(); + JsErrorCode errorCode = GlobalAPIWrapper([&]() -> JsErrorCode { + SourceContextInfo* sourceContextInfo = scriptContext->GetSourceContextInfo(sourceContext, nullptr); + if (sourceContextInfo == nullptr) + { + sourceContextInfo = scriptContext->CreateSourceContextInfo(sourceContext, nullptr, 0, nullptr, nullptr, 0); + } + SRCINFO si = { + /* sourceContextInfo */ sourceContextInfo, + /* dlnHost */ 0, + /* ulColumnHost */ 0, + /* lnMinHost */ 0, + /* ichMinHost */ 0, + /* ichLimHost */ static_cast(sourceLength), + /* ulCharOffset */ 0, + /* mod */ 0, + /* grfsi */ 0 + }; + hr = moduleRecord->ParseSource(sourceText, sourceLength, &si, exceptionValueRef, sourceFlag == JsParseModuleSourceFlags_DataIsUTF8 ? true : false); + if (FAILED(hr)) + { + return JsErrorScriptCompile; + } + return JsNoError; + }); + return errorCode; +} + +CHAKRA_API +JsModuleEvaluation( + _In_ JsModuleRecord requestModule, + _Outptr_result_maybenull_ JsValueRef* result) +{ + if (!Js::SourceTextModuleRecord::Is(requestModule)) + { + return JsErrorInvalidArgument; + } + Js::SourceTextModuleRecord* moduleRecord = Js::SourceTextModuleRecord::FromHost(requestModule); + if (moduleRecord->WasEvaluated()) + { + return JsErrorModuleEvaluated; + } + if (result != nullptr) + { + *result = JS_INVALID_REFERENCE; + } + Js::ScriptContext* scriptContext = moduleRecord->GetScriptContext(); + JsrtContext* jsrtContext = (JsrtContext*)scriptContext->GetLibrary()->GetPinnedJsrtContextObject(); + JsErrorCode errorCode = SetContextAPIWrapper(jsrtContext, [&](Js::ScriptContext *scriptContext) -> JsErrorCode { + SmartFPUControl smartFpuControl; + if (smartFpuControl.HasErr()) + { + return JsErrorBadFPUState; + } + JsValueRef returnRef = moduleRecord->ModuleEvaluation(); + if (result != nullptr) + { + *result = returnRef; + } + return JsNoError; + }); + return errorCode; +} + +CHAKRA_API +JsSetModuleHostInfo( + _In_ JsModuleRecord requestModule, + _In_ JsModuleHostInfoKind moduleHostInfo, + _In_ void* hostInfo) +{ + if (!Js::SourceTextModuleRecord::Is(requestModule)) + { + return JsErrorInvalidArgument; + } + Js::SourceTextModuleRecord* moduleRecord = Js::SourceTextModuleRecord::FromHost(requestModule); + Js::ScriptContext* scriptContext = moduleRecord->GetScriptContext(); + JsrtContext* jsrtContext = (JsrtContext*)scriptContext->GetLibrary()->GetPinnedJsrtContextObject(); + JsErrorCode errorCode = SetContextAPIWrapper(jsrtContext, [&](Js::ScriptContext *scriptContext) -> JsErrorCode { + JsrtContextCore* currentContext = static_cast(JsrtContextCore::GetCurrent()); + switch (moduleHostInfo) + { + case JsModuleHostInfo_Exception: + moduleRecord->OnHostException(hostInfo); + break; + case JsModuleHostInfo_HostDefined: + moduleRecord->SetHostDefined(hostInfo); + break; + case JsModuleHostInfo_FetchImportedModuleCallback: + currentContext->GetHostScriptContext()->SetFetchImportedModuleCallback(static_cast(hostInfo)); + break; + case JsModuleHostInfo_NotifyModuleReadyCallback: + currentContext->GetHostScriptContext()->SetNotifyModuleReadyCallback(static_cast(hostInfo)); + break; + default: + return JsInvalidModuleHostInfoKind; + }; + return JsNoError; + }); + return errorCode; +} + +CHAKRA_API +JsGetModuleHostInfo( + _In_ JsModuleRecord requestModule, + _In_ JsModuleHostInfoKind moduleHostInfo, + _Outptr_result_maybenull_ void** hostInfo) +{ + if (!Js::SourceTextModuleRecord::Is(requestModule) || (hostInfo == nullptr)) + { + return JsErrorInvalidArgument; + } + *hostInfo = nullptr; + Js::SourceTextModuleRecord* moduleRecord = Js::SourceTextModuleRecord::FromHost(requestModule); + Js::ScriptContext* scriptContext = moduleRecord->GetScriptContext(); + JsrtContext* jsrtContext = (JsrtContext*)scriptContext->GetLibrary()->GetPinnedJsrtContextObject(); + JsErrorCode errorCode = SetContextAPIWrapper(jsrtContext, [&](Js::ScriptContext *scriptContext) -> JsErrorCode { + JsrtContextCore* currentContext = static_cast(JsrtContextCore::GetCurrent()); + switch (moduleHostInfo) + { + case JsModuleHostInfo_Exception: + if (moduleRecord->GetErrorObject() != nullptr) + { + *hostInfo = moduleRecord->GetErrorObject(); + } + break; + case JsModuleHostInfo_HostDefined: + *hostInfo = moduleRecord->GetHostDefined(); + break; + case JsModuleHostInfo_FetchImportedModuleCallback: + *hostInfo = currentContext->GetHostScriptContext()->GetFetchImportedModuleCallback(); + break; + case JsModuleHostInfo_NotifyModuleReadyCallback: + *hostInfo = currentContext->GetHostScriptContext()->GetNotifyModuleReadyCallback(); + break; + default: + return JsInvalidModuleHostInfoKind; + }; + return JsNoError; + }); + return errorCode; +} diff --git a/lib/Jsrt/Jsrt.cpp b/lib/Jsrt/Jsrt.cpp index 004a5ecff6e..39b8009d491 100644 --- a/lib/Jsrt/Jsrt.cpp +++ b/lib/Jsrt/Jsrt.cpp @@ -3420,15 +3420,6 @@ CHAKRA_API JsRunSerializedScriptUtf8( buffer, sourceContext, url, false, result); } -CHAKRA_API JsExperimentalApiRunModuleUtf8( - _In_z_ const char *script, - _In_ JsSourceContext sourceContext, - _In_z_ const char *sourceUrl, - _Out_ JsValueRef *result) -{ - return RunScriptCore(-1, script, sourceContext, sourceUrl, false, JsParseScriptAttributeNone, true, result); -} - CHAKRA_API JsStringFree(_In_ char* stringValue) { if (stringValue == nullptr) diff --git a/lib/Jsrt/JsrtCommonExports.inc b/lib/Jsrt/JsrtCommonExports.inc index f7fd31959c5..9bc6d7530aa 100644 --- a/lib/Jsrt/JsrtCommonExports.inc +++ b/lib/Jsrt/JsrtCommonExports.inc @@ -11,7 +11,6 @@ JsGetContextData JsSetContextData JsRunScript - JsExperimentalApiRunModule JsGetUndefinedValue JsGetNullValue JsGetTrueValue @@ -107,7 +106,6 @@ JsRunScriptUtf8 JsSerializeScriptUtf8 JsRunSerializedScriptUtf8 - JsExperimentalApiRunModuleUtf8 JsGetPropertyIdFromNameUtf8 JsStringFree JsDiagEvaluateUtf8 diff --git a/lib/Jsrt/JsrtInternal.h b/lib/Jsrt/JsrtInternal.h index 21ec805c700..df5b2f1aa45 100644 --- a/lib/Jsrt/JsrtInternal.h +++ b/lib/Jsrt/JsrtInternal.h @@ -228,6 +228,70 @@ JsErrorCode ContextAPINoScriptWrapper(Fn fn, bool allowInObjectBeforeCollectCall return errCode; } +template +JsErrorCode SetContextAPIWrapper(JsrtContext* newContext, Fn fn) +{ + JsrtContext* oldContext = JsrtContext::GetCurrent(); + Js::ScriptContext* scriptContext = newContext->GetScriptContext(); + + JsErrorCode errorCode = JsNoError; + try + { + // For now, treat this like an out of memory; consider if we should do something else here. + + AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_StackOverflow | ExceptionType_JavascriptException)); + if (JsrtContext::TrySetCurrent(newContext)) + { + // Enter script + BEGIN_ENTER_SCRIPT(scriptContext, true, true, true) + { + errorCode = fn(scriptContext); + } + END_ENTER_SCRIPT + } + else + { + errorCode = JsErrorWrongThread; + } + + // These are error codes that should only be produced by the wrappers and should never + // be produced by the internal calls. + Assert(errorCode != JsErrorFatal && + errorCode != JsErrorNoCurrentContext && + errorCode != JsErrorInExceptionState && + errorCode != JsErrorInDisabledState && + errorCode != JsErrorOutOfMemory && + errorCode != JsErrorScriptException && + errorCode != JsErrorScriptTerminated); + } + catch (Js::OutOfMemoryException) + { + errorCode = JsErrorOutOfMemory; + } + catch (Js::JavascriptExceptionObject * exceptionObject) + { + scriptContext->GetThreadContext()->SetRecordedException(exceptionObject); + errorCode = JsErrorScriptException; + } + catch (Js::ScriptAbortException) + { + Assert(scriptContext->GetThreadContext()->GetRecordedException() == nullptr); + scriptContext->GetThreadContext()->SetRecordedException(scriptContext->GetThreadContext()->GetPendingTerminatedErrorObject()); + errorCode = JsErrorScriptTerminated; + } + catch (Js::EvalDisabledException) + { + errorCode = JsErrorScriptEvalDisabled; + } + catch (Js::StackOverflowException) + { + return JsErrorOutOfMemory; + } + CATCH_OTHER_EXCEPTIONS + JsrtContext::TrySetCurrent(oldContext); + return errorCode; +} + void HandleScriptCompileError(Js::ScriptContext * scriptContext, CompileScriptException * se); #if DBG diff --git a/lib/Runtime/Language/SourceTextModuleRecord.cpp b/lib/Runtime/Language/SourceTextModuleRecord.cpp index 75a0c598ac5..9be95b8b4ad 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.cpp +++ b/lib/Runtime/Language/SourceTextModuleRecord.cpp @@ -23,6 +23,7 @@ namespace Js localExportMapByExportName(nullptr), localExportMapByLocalName(nullptr), localExportIndexList(nullptr), + normalizedSpecifier(nullptr), errorObject(nullptr), hostDefined(nullptr), exportedNames(nullptr), @@ -30,6 +31,7 @@ namespace Js wasParsed(false), wasDeclarationInitialized(false), isRootModule(false), + hadNotifyHostReady(false), localExportSlots(nullptr), numUnParsedChildrenModule(0), moduleId(InvalidModuleIndex), @@ -199,7 +201,11 @@ namespace Js Assert(this->errorObject == nullptr); ModuleDeclarationInstantiation(); - hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + if (!hadNotifyHostReady) + { + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + hadNotifyHostReady = true; + } } } return hr; @@ -208,11 +214,6 @@ namespace Js HRESULT SourceTextModuleRecord::OnChildModuleReady(SourceTextModuleRecord* childModule, Var childException) { HRESULT hr = NOERROR; - if (numUnParsedChildrenModule == 0) - { - return NOERROR; // this is only in case of recursive module reference. Let the higher stack frame handle this module. - } - numUnParsedChildrenModule--; if (childException != nullptr) { // propagate the error up as needed. @@ -221,9 +222,20 @@ namespace Js this->errorObject = childException; } NotifyParentsAsNeeded(); + if (isRootModule && !hadNotifyHostReady) + { + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + hadNotifyHostReady = true; + } } else { + if (numUnParsedChildrenModule == 0) + { + return NOERROR; // this is only in case of recursive module reference. Let the higher stack frame handle this module. + } + numUnParsedChildrenModule--; + hr = PrepareForModuleDeclarationInitialization(); } return hr; @@ -642,8 +654,13 @@ namespace Js { return nullptr; } - SetWasEvaluated(); Assert(this->errorObject == nullptr); + if (this->errorObject != nullptr) + { + JavascriptExceptionOperators::Throw(errorObject, scriptContext); + } + Assert(!WasEvaluated()); + SetWasEvaluated(); // we shouldn't evaluate if there are existing failure. This is defense in depth as the host shouldn't // call into evaluation if there was previous fialure on the module. if (this->errorObject) diff --git a/lib/Runtime/Language/SourceTextModuleRecord.h b/lib/Runtime/Language/SourceTextModuleRecord.h index bb316599de2..9a1de017f1c 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.h +++ b/lib/Runtime/Language/SourceTextModuleRecord.h @@ -43,6 +43,11 @@ namespace Js void* GetHostDefined() const { return hostDefined; } void SetHostDefined(void* hostObj) { hostDefined = hostObj; } + void SetSpecifier(Var specifier) { this->normalizedSpecifier = specifier; } + Var GetSpecifier() const { return normalizedSpecifier; } + + Var GetErrorObject() const { return errorObject; } + bool WasParsed() const { return wasParsed; } void SetWasParsed() { wasParsed = true; } bool WasDeclarationInitialized() const { return wasDeclarationInitialized; } @@ -65,6 +70,17 @@ namespace Js Assert((moduleRecord == nullptr) || (moduleRecord->magicNumber == moduleRecord->ModuleMagicNumber)); return moduleRecord; } + + static bool Is(void* hostModuleRecord) + { + SourceTextModuleRecord* moduleRecord = static_cast(hostModuleRecord); + if (moduleRecord != nullptr && (moduleRecord->magicNumber == moduleRecord->ModuleMagicNumber)) + { + return true; + } + return false; + } + static SourceTextModuleRecord* Create(ScriptContext* scriptContext); uint GetLocalExportSlotIndexByExportName(PropertyId exportNameId); @@ -89,6 +105,7 @@ namespace Js bool wasParsed; bool wasDeclarationInitialized; bool isRootModule; + bool hadNotifyHostReady; ParseNodePtr parseTree; Utf8SourceInfo* pSourceInfo; uint sourceIndex; @@ -110,6 +127,7 @@ namespace Js Js::JavascriptFunction* rootFunction; void* hostDefined; + Var normalizedSpecifier; Var errorObject; Var* localExportSlots; diff --git a/test/es6/ModuleCircularBar.js b/test/es6/ModuleCircularBar.js new file mode 100644 index 00000000000..9a32f61267e --- /dev/null +++ b/test/es6/ModuleCircularBar.js @@ -0,0 +1,14 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +import { circular_foo } from "ModuleCircularFoo.js" +export function circular_bar() { + increment(); + return circular_foo(); +} +export function increment() { + counter++; +} +export var counter = 0; diff --git a/test/es6/ModuleCircularFoo.js b/test/es6/ModuleCircularFoo.js new file mode 100644 index 00000000000..eedc7f377a0 --- /dev/null +++ b/test/es6/ModuleCircularFoo.js @@ -0,0 +1,15 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +import { circular_bar, increment, counter } from "ModuleCircularBar.js" +export function circular_foo() { + if (counter == 0) { + return circular_bar(); + } else { + increment(); + return counter; + } +} +export { circular_bar as rexportbar } from "ModuleCircularBar.js" diff --git a/test/es6/ModuleComplexExports.js b/test/es6/ModuleComplexExports.js new file mode 100644 index 00000000000..5148a0f0bba --- /dev/null +++ b/test/es6/ModuleComplexExports.js @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export function foo() { return 'foo'; }; +export { foo as foo2 }; + +function bar() { return 'bar'; }; +export { bar, bar as bar2 }; + +export let let2 = 'let2'; +export const const2 = 'const2'; +export var var2 = 'var2'; +export { let2 as let4, const2 as const4, var2 as var4 }; + +let let3 = 'let3'; +const const3 = 'const3'; +var var3 = 'var3'; +export { let3, let3 as let5, const3, const3 as const5, var3, var3 as var5 }; + +export class class2 { + member() { return 'class2'; } + static static_member() { return 'class2'; } +}; +export { class2 as class3 }; + +class class4 { + member() { return 'class4'; } + static static_member() { return 'class4'; } +}; +export { class4, class4 as class5 }; + +export async function asyncfoo() { }; +async function asyncbar() { }; +export { asyncfoo as asyncfoo2, asyncbar, asyncbar as asyncbar2 }; + +export function* genfoo() { }; +function* genbar() { }; +export { genfoo as genfoo2, genbar, genbar as genbar2 }; + +export default function () { return 'default'; }; + +var mutatingExportTarget = function() { return 'before'; }; +function changeMutatingExportTarget() { + mutatingExportTarget = function() { return 'after'; }; + return 'ok'; +} + +export { mutatingExportTarget as target, changeMutatingExportTarget as changeTarget }; + +var exportedAsKeyword = 'ModuleComplexExports'; +export { exportedAsKeyword as export }; +export { exportedAsKeyword as function }; + +var as = function() { return 'as'; }; +export { as as as }; diff --git a/test/es6/ModuleComplexReexports.js b/test/es6/ModuleComplexReexports.js new file mode 100644 index 00000000000..eeef76c7111 --- /dev/null +++ b/test/es6/ModuleComplexReexports.js @@ -0,0 +1,11 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export { bar2 as ModuleComplexReexports_foo } from 'ModuleComplexExports.js'; + +export { function as switch } from 'ModuleComplexExports.js'; + +import { foo, foo2, foo as localfoo, foo2 as localfoo2 } from "ModuleComplexExports.js"; +export { foo, foo2 as baz, localfoo, localfoo as bar, localfoo2, localfoo2 as bar2 }; diff --git a/test/es6/ModuleDefaultExport1.js b/test/es6/ModuleDefaultExport1.js new file mode 100644 index 00000000000..4c061e6fc99 --- /dev/null +++ b/test/es6/ModuleDefaultExport1.js @@ -0,0 +1,7 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +function ModuleDefaultExport1_foo() { return 'ModuleDefaultExport1'; } +export { ModuleDefaultExport1_foo as default }; diff --git a/test/es6/ModuleDefaultExport2.js b/test/es6/ModuleDefaultExport2.js new file mode 100644 index 00000000000..5c53d04a219 --- /dev/null +++ b/test/es6/ModuleDefaultExport2.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export default function() { return 'ModuleDefaultExport2'; } diff --git a/test/es6/ModuleDefaultExport3.js b/test/es6/ModuleDefaultExport3.js new file mode 100644 index 00000000000..4ebdd5ec258 --- /dev/null +++ b/test/es6/ModuleDefaultExport3.js @@ -0,0 +1,8 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +let export3 = 1; +export { export3 as default }; +export3 = 2; diff --git a/test/es6/ModuleDefaultExport4.js b/test/es6/ModuleDefaultExport4.js new file mode 100644 index 00000000000..9a869bd8f57 --- /dev/null +++ b/test/es6/ModuleDefaultExport4.js @@ -0,0 +1,8 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +let export4 = 1; +export default export4; +export4 = 2; diff --git a/test/es6/ModuleDefaultReexport.js b/test/es6/ModuleDefaultReexport.js new file mode 100644 index 00000000000..970e0633697 --- /dev/null +++ b/test/es6/ModuleDefaultReexport.js @@ -0,0 +1,7 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export { default } from 'ModuleDefaultExport1.js' +export { default as not_default } from 'ModuleDefaultExport2.js' diff --git a/test/es6/ModuleReexportDefault.js b/test/es6/ModuleReexportDefault.js new file mode 100644 index 00000000000..3f927416914 --- /dev/null +++ b/test/es6/ModuleReexportDefault.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export { default } from 'ModuleDefaultExport1.js' diff --git a/test/es6/ModuleSimpleExport.js b/test/es6/ModuleSimpleExport.js new file mode 100644 index 00000000000..7491f7dc3b6 --- /dev/null +++ b/test/es6/ModuleSimpleExport.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export function ModuleSimpleExport_foo() { return 'ModuleSimpleExport'; }; diff --git a/test/es6/ModuleSimpleReexport.js b/test/es6/ModuleSimpleReexport.js new file mode 100644 index 00000000000..529ebdb7a41 --- /dev/null +++ b/test/es6/ModuleSimpleReexport.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +export { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js'; diff --git a/test/es6/ValidExportDefaultStatement1.js b/test/es6/ValidExportDefaultStatement1.js new file mode 100644 index 00000000000..cd11a4479bf --- /dev/null +++ b/test/es6/ValidExportDefaultStatement1.js @@ -0,0 +1,7 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +var x; +export default x; + \ No newline at end of file diff --git a/test/es6/ValidExportDefaultStatement2.js b/test/es6/ValidExportDefaultStatement2.js new file mode 100644 index 00000000000..d1aa194184d --- /dev/null +++ b/test/es6/ValidExportDefaultStatement2.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +export default function() {return "hello world";} + \ No newline at end of file diff --git a/test/es6/ValidExportStatements.js b/test/es6/ValidExportStatements.js new file mode 100644 index 00000000000..10cdbd05bf8 --- /dev/null +++ b/test/es6/ValidExportStatements.js @@ -0,0 +1,50 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +function foo() { } +class bar { } +function* baz() { } +function foobar() { } + +// Export function expressions +export function fn1 () { }; +export function fn2 () { } + +// Export generator expressions +export function* gn1 () { }; +export function* gn2 () { } + +// Export class expressions +export class cl1 { }; +export class cl2 { } + +// Export let decls +export let let1; +export let let2 = 2; +export let let3, let4, let5; +export let let6 = { } +export let let7 = [ ] + +// Export const decls +export const const2 = 'str'; +export const const3 = 3, const4 = 4; +export const const5 = { } +export const const6 = [ ] + +// Export with export clauses +export {}; +export { foo }; +export { bar, }; +export { foo as foo2, baz } +export { foo as foo3, baz as baz2, } +export { foo as foo4, bar as bar2, foobar } + +// Export var decls +export var var1 = 'string'; +export var var2; +export var var3 = 5, var4 +export var var5, var6, var7 + +export default 'default'; diff --git a/test/es6/ValidImportStatements.js b/test/es6/ValidImportStatements.js new file mode 100644 index 00000000000..5bab3c47b47 --- /dev/null +++ b/test/es6/ValidImportStatements.js @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// Empty import statement is legal - does nothing but execute the module +import "ValidExportStatements.js"; +import "ValidExportStatements.js" + +// Import default binding +import ns1 from "ValidExportStatements.js"; +import ns2 from "ValidExportStatements.js" + +// Named import list +import { foo } from "ValidExportStatements.js"; +import { foo as foo22, bar, } from "ValidExportStatements.js" + +// Namespace import statement +//import * as ns3 from "ValidExportStatements.js"; +//import * as ns8 from "ValidExportStatements.js" + +// Import statement with default binding and a second clause +//import ns4, * as ns5 from "ValidExportStatements.js" +import ns6, { baz } from "ValidExportStatements.js"; +import ns7, { foo as foo23, foobar, } from "ValidExportStatements.js" diff --git a/test/es6/ValidReExportStatements.js b/test/es6/ValidReExportStatements.js new file mode 100644 index 00000000000..6a9a8046369 --- /dev/null +++ b/test/es6/ValidReExportStatements.js @@ -0,0 +1,12 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// Re-export statements +//export * from 'ValidExportDefaultStatement1.js'; +export {} from 'ValidExportDefaultStatement2.js'; +export { foo } from 'ValidExportStatements.js'; +export { bar, } from 'ValidExportStatements.js'; +export { foo as foo2, baz } from 'ValidExportStatements.js' +export { foo as foo3, bar as bar2, } from 'ValidExportStatements.js' diff --git a/test/es6/exportmodule.js b/test/es6/exportmodule.js new file mode 100644 index 00000000000..707f1e68ae7 --- /dev/null +++ b/test/es6/exportmodule.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +export var x; +x = 'Pass'; diff --git a/test/es6/module-functionality.js b/test/es6/module-functionality.js new file mode 100644 index 00000000000..bf91b582083 --- /dev/null +++ b/test/es6/module-functionality.js @@ -0,0 +1,296 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// ES6 Module functionality tests -- verifies functionality of import and export statements + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +function testModuleScript(source, message, shouldFail) { + let testfunc = () => WScript.LoadModule(source, 'samethread'); + + if (shouldFail) { + let caught = false; + + // We can't use assert.throws here because the SyntaxError used to construct the thrown error + // is from a different context so it won't be strictly equal to our SyntaxError. + try { + testfunc(); + } catch(e) { + caught = true; + + // Compare toString output of SyntaxError and other context SyntaxError constructor. + assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message); + } + + assert.isTrue(caught, `Expected error not thrown: ${message}`); + } else { + assert.doesNotThrow(testfunc, message); + } +} + +var tests = [ + { + name: "Validate a simple module export", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js'; + assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');`; + testModuleScript(functionBody, "Test importing a simple exported function", false); + } + }, + { + name: "Validate importing from multiple modules", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js'; + assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); + import { foo2 } from 'ModuleComplexExports.js'; + assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js');`; + testModuleScript(functionBody, "Test importing from multiple modules", false); + } + }, + { + name: "Validate a variety of more complex exports", + body: function () { + let functionBody = + `import { foo, foo2 } from 'ModuleComplexExports.js'; + assert.areEqual('foo', foo(), 'Failed to import foo from ModuleComplexExports.js'); + assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js'); + import { bar, bar2 } from 'ModuleComplexExports.js'; + assert.areEqual('bar', bar(), 'Failed to import bar from ModuleComplexExports.js'); + assert.areEqual('bar', bar2(), 'Failed to import bar2 from ModuleComplexExports.js'); + import { let2, let3, let4, let5 } from 'ModuleComplexExports.js'; + assert.areEqual('let2', let2, 'Failed to import let2 from ModuleComplexExports.js'); + assert.areEqual('let3', let3, 'Failed to import let3 from ModuleComplexExports.js'); + assert.areEqual('let2', let4, 'Failed to import let4 from ModuleComplexExports.js'); + assert.areEqual('let3', let5, 'Failed to import let5 from ModuleComplexExports.js'); + import { const2, const3, const4, const5 } from 'ModuleComplexExports.js'; + assert.areEqual('const2', const2, 'Failed to import const2 from ModuleComplexExports.js'); + assert.areEqual('const3', const3, 'Failed to import const3 from ModuleComplexExports.js'); + assert.areEqual('const2', const4, 'Failed to import const4 from ModuleComplexExports.js'); + assert.areEqual('const3', const5, 'Failed to import const5 from ModuleComplexExports.js'); + import { var2, var3, var4, var5 } from 'ModuleComplexExports.js'; + assert.areEqual('var2', var2, 'Failed to import var2 from ModuleComplexExports.js'); + assert.areEqual('var3', var3, 'Failed to import var3 from ModuleComplexExports.js'); + assert.areEqual('var2', var4, 'Failed to import var4 from ModuleComplexExports.js'); + assert.areEqual('var3', var5, 'Failed to import var5 from ModuleComplexExports.js'); + import { class2, class3, class4, class5 } from 'ModuleComplexExports.js'; + assert.areEqual('class2', class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js'); + assert.areEqual('class2', new class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js'); + assert.areEqual('class2', class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js'); + assert.areEqual('class2', new class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js'); + assert.areEqual('class4', class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); + assert.areEqual('class4', new class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); + assert.areEqual('class4', class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); + assert.areEqual('class4', new class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); + import _default from 'ModuleComplexExports.js'; + assert.areEqual('default', _default(), 'Failed to import default from ModuleComplexExports.js'); + `; + testModuleScript(functionBody, "Test importing a variety of exports", false); + } + }, + { + name: "Import an export as a different binding identifier", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo as foo3 } from 'ModuleSimpleExport.js'; + assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); + import { foo2 as foo4 } from 'ModuleComplexExports.js'; + assert.areEqual('foo', foo4(), 'Failed to import foo4 from ModuleComplexExports.js');`; + testModuleScript(functionBody, "Test importing as different binding identifiers", false); + } + }, + { + name: "Import the same export under multiple local binding identifiers", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo as foo3, ModuleSimpleExport_foo as foo4 } from 'ModuleSimpleExport.js'; + assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); + assert.areEqual('ModuleSimpleExport', foo4(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); + assert.isTrue(foo3 === foo4, 'Export has the same value even if rebound');`; + testModuleScript(functionBody, "Test importing the same export under multiple binding identifier", false); + } + }, + { + name: "Exporting module changes exported value", + body: function () { + let functionBody = + `import { target, changeTarget } from 'ModuleComplexExports.js'; + assert.areEqual('before', target(), 'Failed to import target from ModuleComplexExports.js'); + assert.areEqual('ok', changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js'); + assert.areEqual('after', target(), 'changeTarget failed to change export value');`; + testModuleScript(functionBody, "Changing exported value", false); + } + }, + { + name: "Simple re-export forwards import to correct slot", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo } from 'ModuleSimpleReexport.js'; + assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`; + testModuleScript(functionBody, "Simple re-export from one module to another", false); + } + }, + { + name: "Import of renamed re-export forwards import to correct slot", + body: function () { + let functionBody = + `import { ModuleSimpleExport_foo as ModuleSimpleExport_baz } from 'ModuleSimpleReexport.js'; + assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_baz(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`; + testModuleScript(functionBody, "Rename simple re-export", false); + } + }, + { + name: "Renamed re-export and renamed import", + body: function () { + let functionBody = + `import { ModuleComplexReexports_foo as ModuleComplexReexports_baz } from 'ModuleComplexReexports.js'; + assert.areEqual('bar', ModuleComplexReexports_baz(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js');`; + testModuleScript(functionBody, "Rename already renamed re-export", false); + } + }, + { + name: "Explicit export/import to default binding", + body: function () { + let functionBody = + `import { default as baz } from 'ModuleDefaultExport1.js'; + assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultExport1.js');`; + testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false); + } + }, + { + name: "Explicit import of default binding", + body: function () { + let functionBody = + `import { default as baz } from 'ModuleDefaultExport2.js'; + assert.areEqual('ModuleDefaultExport2', baz(), 'Failed to import default from ModuleDefaultExport2.js');`; + testModuleScript(functionBody, "Explicitly import the default export binding", false); + } + }, + { + name: "Implicitly re-export default export", + body: function () { + let functionBody = + `import baz from 'ModuleDefaultReexport.js'; + assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js');`; + testModuleScript(functionBody, "Implicitly re-export the default export binding", false); + } + }, + { + name: "Implicitly re-export default export and rename the imported binding", + body: function () { + let functionBody = + `import { default as baz } from 'ModuleDefaultReexport.js'; + assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js'); + import { not_default as bat } from 'ModuleDefaultReexport.js'; + assert.areEqual('ModuleDefaultExport2', bat(), 'Failed to import not_default from ModuleDefaultReexport.js');`; + testModuleScript(functionBody, "Implicitly re-export the default export binding and rename the import binding", false); + } + }, + { + name: "Exporting module changes value of default export", + body: function () { + let functionBody = + `import ModuleDefaultExport3_default from 'ModuleDefaultExport3.js'; + assert.areEqual(2, ModuleDefaultExport3_default, 'Failed to import default from ModuleDefaultExport3.js'); + import ModuleDefaultExport4_default from 'ModuleDefaultExport4.js'; + assert.areEqual(1, ModuleDefaultExport4_default, 'Failed to import not_default from ModuleDefaultExport4.js');`; + testModuleScript(functionBody, "Exported value incorrectly bound", false); + } + }, + { + name: "Import bindings used in a nested function", + body: function () { + let functionBody = + `function test() { + assert.areEqual('ModuleDefaultExport2', foo(), 'Failed to import default from ModuleDefaultExport2.js'); + } + test(); + import foo from 'ModuleDefaultExport2.js'; + test();`; + testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false); + } + }, + { + name: "Exported name may be any keyword", + body: function () { + let functionBody = + `import { export as baz } from 'ModuleComplexExports.js'; + assert.areEqual('ModuleComplexExports', baz, 'Failed to import export from ModuleDefaultExport2.js'); + import { function as bat } from 'ModuleComplexExports.js'; + assert.areEqual('ModuleComplexExports', bat, 'Failed to import function from ModuleDefaultExport2.js');`; + testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false); + } + }, + { + name: "Import binding of a keyword-named export may not be a keyword unless it is bound to a different binding identifier", + body: function () { + let functionBody = `import { export } from 'ModuleComplexExports.js';`; + testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (export)", true); + functionBody = `import { function } from 'ModuleComplexExports.js';`; + testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (function)", true); + functionBody = `import { switch } from 'ModuleComplexReexports.js';`; + testModuleScript(functionBody, "Import binding must be binding identifier even if re-export name is not (switch)", true); + } + }, + { + name: "Exported name may be any keyword testing re-exports", + body: function () { + let functionBody = + `import { switch as baz } from 'ModuleComplexReexports.js'; + assert.areEqual('ModuleComplexExports', baz, 'Failed to import switch from ModuleComplexReexports.js');`; + testModuleScript(functionBody, "Exported name may be a keyword including re-epxort chains", false); + } + }, + { + name: "Odd case of 'export { as as as }; import { as as as };'", + body: function () { + let functionBody = + `import { as as as } from 'ModuleComplexExports.js'; + assert.areEqual('as', as(), 'String "as" is not reserved word');`; + testModuleScript(functionBody, "Test 'import { as as as}'", false); + } + }, + { + name: "Typeof a module export", + body: function () { + let functionBody = + `import _default from 'ModuleDefaultExport2.js'; + assert.areEqual('function', typeof _default, 'typeof default export from ModuleDefaultExport2.js is function');`; + + WScript.LoadModule(functionBody, 'samethread'); + } + }, + { + name: "Circular module dependency", + body: function () { + let functionBody = + `import { circular_foo } from 'ModuleCircularFoo.js'; + assert.areEqual(2, circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each'); + import { circular_bar } from 'ModuleCircularBar.js'; + assert.areEqual(4, circular_bar(), 'Second call originates in the other module but still increments the counter twice');`; + + WScript.LoadModule(functionBody, 'samethread'); + } + }, + { + name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", + body: function () { + let functionBody = + `import { foo, baz, localfoo, bar, localfoo2, bar2, bar2 as bar3 } from 'ModuleComplexReexports.js'; + assert.areEqual('foo', foo(), 'Simple implicit re-export'); + assert.areEqual('foo', baz(), 'Renamed export imported and renamed during implicit re-export'); + assert.areEqual('foo', localfoo(), 'Export renamed as import and implicitly re-exported'); + assert.areEqual('foo', bar(), 'Renamed export renamed as import and renamed again during implicit re-exported'); + assert.areEqual('foo', localfoo2(), 'Renamed export renamed as import and implicitly re-exported'); + assert.areEqual('foo', bar2(), 'Renamed export renamed as import and renamed again during implicit re-export'); + assert.areEqual('foo', bar3(), 'Renamed export renamed as import renamed during implicit re-export and renamed in final import');`; + + WScript.LoadModule(functionBody, 'samethread'); + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es6/module-syntax.js b/test/es6/module-syntax.js new file mode 100644 index 00000000000..3437f06e968 --- /dev/null +++ b/test/es6/module-syntax.js @@ -0,0 +1,149 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// ES6 Module syntax tests -- verifies syntax of import and export statements + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +function testModuleScript(source, message, shouldFail) { + let testfunc = () => WScript.LoadModule(source, 'samethread'); + + if (shouldFail) { + let caught = false; + + // We can't use assert.throws here because the SyntaxError used to construct the thrown error + // is from a different context so it won't be strictly equal to our SyntaxError. + try { + testfunc(); + } catch(e) { + caught = true; + + // Compare toString output of SyntaxError and other context SyntaxError constructor. + assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message); + } + + assert.isTrue(caught, `Expected error not thrown: ${message}`); + } else { + assert.doesNotThrow(testfunc, message); + } +} + +var tests = [ + { + name: "All valid (non-default) export statements", + body: function () { + assert.doesNotThrow(function () { WScript.LoadModuleFile('ValidExportStatements.js', 'samethread'); }, "Valid export statements"); + } + }, + { + name: "Valid default export statements", + body: function () { + testModuleScript('export default function () { };', 'Unnamed function expression default export'); + testModuleScript('export default function _fn2 () { }', 'Named function expression default export'); + testModuleScript('export default function* () { };', 'Unnamed generator function expression default export'); + testModuleScript('export default function* _gn2 () { }', 'Named generator function expression default export'); + testModuleScript('export default class { };', 'Unnamed class expression default export'); + testModuleScript('export default class _cl2 { }', 'Named class default expression export'); + testModuleScript('export default 1;', 'Primitive type default export'); + testModuleScript('var a; export default a = 10;', 'Variable in assignment expression default export'); + testModuleScript('export default () => 3', 'Simple default lambda expression export statement'); + testModuleScript('function _default() { }; export default _default', 'Named function statement default export'); + testModuleScript('function* g() { }; export default g', 'Named generator function statement default export'); + testModuleScript('class c { }; export default c', 'Named class statement default export'); + testModuleScript("var _ = { method: function() { return 'method_result'; }, method2: function() { return 'method2_result'; } }; export default _", 'Export object with methods - framework model'); + } + }, + { + name: "Syntax error export statements", + body: function () { + testModuleScript('export const const1;', 'Syntax error if const decl is missing initializer', true); + testModuleScript('function foo() { }; export foo;', "Syntax error if we're trying to export an identifier without default or curly braces", true); + testModuleScript('export function () { }', 'Syntax error if function declaration is missing binding identifier', true); + testModuleScript('export function* () { }', 'Syntax error if generator declaration is missing binding identifier', true); + testModuleScript('export class { }', 'Syntax error if class declaration is missing binding identifier', true); + testModuleScript('function foo() { }; export [ foo ];', 'Syntax error if we use brackets instead of curly braces in export statement', true); + testModuleScript('function foo() { export default function() { } }', 'Syntax error if export statement is in a nested function', true); + testModuleScript('function foo() { }; export { , foo };', 'Syntax error if named export list contains an empty element', true); + testModuleScript('function foo() { }; () => { export { foo }; }', 'Syntax error if export statement is in arrow function', true); + testModuleScript('function foo() { }; try { export { foo }; } catch(e) { }', 'Syntax error if export statement is in try catch statement', true); + testModuleScript('function foo() { }; { export { foo }; }', 'Syntax error if export statement is in any block', true); + testModuleScript('export default 1, 2, 3;', "Export default takes an assignment expression which doesn't allow comma expressions", true); + testModuleScript('export 12;', 'Syntax error if export is followed by non-identifier', true); + testModuleScript("export 'string_constant';", 'Syntax error if export is followed by string constant', true); + testModuleScript('export ', 'Syntax error if export is followed by EOF', true); + testModuleScript('function foo() { }; export { foo as 100 };', 'Syntax error in named export clause if trying to export as numeric constant', true); + } + }, + { + name: "Syntax error import statements", + body: function () { + testModuleScript('function foo() { import foo from "ValidExportStatements.js"; }', 'Syntax error if import statement is in nested function', true); + testModuleScript('import foo, bar from "ValidExportStatements.js";', 'Syntax error if import statement has multiple default bindings', true); + testModuleScript('import { foo, foo } from "ValidExportStatements.js";', 'Redeclaration error if multiple imports have the same local name', true); + testModuleScript('import { foo, bar as foo } from "ValidExportStatements.js";', 'Redeclaration error if multiple imports have the same local name', true); + testModuleScript('const foo = 12; import { foo } from "ValidExportStatements.js";', 'Syntax error if module body has a const declaration bound to the same name as a module import', true); + testModuleScript('function foo() { }; import { foo } from "ValidExportStatements.js";', 'Syntax error if module body has a function declaration bound to the same name as a module import', true); + testModuleScript('import foo;', 'Syntax error if import statement is missing from clause', true); + testModuleScript('import * as foo, from "ValidExportStatements.js";', 'Syntax error if import statement has comma after namespace import', true); + testModuleScript('import * as foo, bar from "ValidExportStatements.js";', 'Syntax error if import statement has default binding after namespace import', true); + testModuleScript('import * as foo, { bar } from "ValidExportStatements.js";', 'Syntax error if import statement has named import list after namespace import', true); + testModuleScript('import { foo }, from "ValidExportStatements.js";', 'Syntax error if import statement has comma after named import list', true); + testModuleScript('import { foo }, bar from "ValidExportStatements.js";', 'Syntax error if import statement has default binding after named import list', true); + testModuleScript('import { foo }, * as ns1 from "ValidExportStatements.js";', 'Syntax error if import statement has namespace import after named import list', true); + testModuleScript('import { foo }', 'Syntax error if import statement is missing from clause', true); + testModuleScript('import [ foo ] from "ValidExportStatements.js";', 'Syntax error if named import clause uses brackets', true); + testModuleScript('import * foo from "ValidExportStatements.js";', 'Syntax error if namespace import is missing "as" keyword', true); + testModuleScript('import * as "foo" from "ValidExportStatements.js";', 'Syntax error if namespace imported binding name is not identifier', true); + testModuleScript('import { , foo } from "ValidExportStatements.js";', 'Syntax error if named import list contains an empty element', true); + testModuleScript('import foo from "ValidExportStatements.js"; import foo from "ValidExportStatements.js";', 'Default import cannot be bound to the same symbol', true); + testModuleScript('import { foo } from "ValidExportStatements.js"; import { foo } from "ValidExportStatements.js";', 'Multiple named imports cannot be bound to the same symbol', true); + testModuleScript('import * as foo from "ValidExportStatements.js"; import * as foo from "ValidExportStatements.js";', 'Multiple namespace imports cannot be bound to the same symbol', true); + testModuleScript('import { foo as bar, bar } from "ValidExportStatements.js";', 'Named import clause may not contain multiple binding identifiers with the same name', true); + testModuleScript('import foo from "ValidExportStatements.js"; import * as foo from "ValidExportStatements.js";', 'Imported bindings cannot be overwritten by later imports', true); + testModuleScript('() => { import arrow from ""; }', 'Syntax error if import statement is in arrow function', true); + testModuleScript('try { import _try from ""; } catch(e) { }', 'Syntax error if import statement is in try catch statement', true); + testModuleScript('{ import in_block from ""; }', 'Syntax error if import statement is in any block', true); + testModuleScript('import {', 'Named import clause which has EOF after left curly', true); + testModuleScript('import { foo', 'Named import clause which has EOF after identifier', true); + testModuleScript('import { foo as ', 'Named import clause which has EOF after identifier as', true); + testModuleScript('import { foo as bar ', 'Named import clause which has EOF after identifier as identifier', true); + testModuleScript('import { foo as bar, ', 'Named import clause which has EOF after identifier as identifier comma', true); + testModuleScript('import { switch } from "module";', 'Named import clause which has non-identifier token as the first token', true); + testModuleScript('import { foo bar } from "module";', 'Named import clause missing "as" token', true); + testModuleScript('import { foo as switch } from "module";', 'Named import clause with non-identifier token after "as"', true); + testModuleScript('import { foo, , } from "module";', 'Named import clause with too many trailing commas', true); + } + }, + { + name: "Runtime error import statements", + body: function () { + testModuleScript('import foo from "ValidExportStatements.js"; assert.throws(()=>{ foo =12; }, TypeError, "assignment to const");', 'Imported default bindings are constant bindings', false); + testModuleScript('import { foo } from "ValidExportStatements.js"; assert.throws(()=>{ foo = 12; }, TypeError, "assignment to const");', 'Imported named bindings are constant bindings', false); + + // 'import *' is not yet implemented + //testModuleScript('import * as foo from "ValidExportStatements.js"; assert.throws(()=>{ foo = 12; }, TypeError, "assignment to const");', 'Namespace import bindings are constant bindings', false); + + testModuleScript('import { foo as foo22 } from "ValidExportStatements.js"; assert.throws(()=>{ foo22 = 12; }, TypeError, "assignment to const");', 'Renamed import bindings are constant bindings', false); + } + }, + { + name: "All valid re-export statements", + body: function () { + assert.doesNotThrow(function () { WScript.LoadModuleFile('ValidReExportStatements.js', 'samethread'); }, "Valid re-export statements"); + } + }, + { + name: "HTML comments do not parse in module code", + body: function () { + testModuleScript("", "HTML close comment does not parse in module code", true); + testModuleScript("", "HTML comment does not parse in module code", true); + testModuleScript("/* */ -->", "HTML comment after delimited comment does not parse in module code", true); + testModuleScript("/* */\n-->", "HTML comment after delimited comment does not parse in module code", true); + } + } +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es6/module-syntax1.js b/test/es6/module-syntax1.js new file mode 100644 index 00000000000..53f1cd8b1c3 --- /dev/null +++ b/test/es6/module-syntax1.js @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// ES6 Module syntax tests -- verifies syntax of import and export statements + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +function testModuleScript(source, message, shouldFail) { + let testfunc = () => WScript.LoadModule(source, 'samethread'); + + if (shouldFail) { + let caught = false; + + // We can't use assert.throws here because the SyntaxError used to construct the thrown error + // is from a different context so it won't be strictly equal to our SyntaxError. + try { + testfunc(); + } catch(e) { + caught = true; + + // Compare toString output of SyntaxError and other context SyntaxError constructor. + assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message); + } + + assert.isTrue(caught, `Expected error not thrown: ${message}`); + } else { + assert.doesNotThrow(testfunc, message); + } +} + +var tests = [ + { + name: "All valid import statements", + body: function () { + assert.doesNotThrow(function () { WScript.LoadModuleFile('ValidImportStatements.js', 'samethread'); }, "Valid import statements"); + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es6/moduletest1.js b/test/es6/moduletest1.js new file mode 100644 index 00000000000..7c2e3f1387c --- /dev/null +++ b/test/es6/moduletest1.js @@ -0,0 +1,5 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +WScript.LoadScriptFile("passmodule.js", "module"); diff --git a/test/es6/passmodule.js b/test/es6/passmodule.js new file mode 100644 index 00000000000..263aca02e9a --- /dev/null +++ b/test/es6/passmodule.js @@ -0,0 +1,6 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +import { x } from 'exportmodule.js'; +WScript.Echo(x); \ No newline at end of file diff --git a/test/es6/rlexe.xml b/test/es6/rlexe.xml index 059aaad40ea..1e1a5e29d10 100644 --- a/test/es6/rlexe.xml +++ b/test/es6/rlexe.xml @@ -1243,8 +1243,50 @@ await-futreserved-only-in-modules.js -ES6Module - exclude_dynapogo + exclude_dynapogo, exclude_xplat + + + + moduletest1.js + -ES6Module + exclude_dynapogo, exclude_xplat + + + + + module-syntax.js + -ES6Module -args summary -endargs + exclude_xplat + + + + + module-syntax1.js + -ES6Module -args summary -endargs + exclude_xplat + + + + + module-functionality.js + -ES6Module -ES7AsyncAwait -args summary -endargs + exclude_xplat + + + + + module-syntax.js + -ES6Module -force:deferparse -args summary -endargs + exclude_xplat + + + + + module-syntax1.js + -ES6Module -force:deferparse -args summary -endargs + exclude_xplat +