diff --git a/Tmain/list-roles.d/stdout-expected.txt b/Tmain/list-roles.d/stdout-expected.txt index aa0bdfac9c..3e50e7968d 100644 --- a/Tmain/list-roles.d/stdout-expected.txt +++ b/Tmain/list-roles.d/stdout-expected.txt @@ -67,6 +67,12 @@ HTML C/stylesheet extFile on 0 referen HTML J/script extFile on 0 referenced as external files HTML c/class attribute on 0 assigned as attributes Java p/package imported on 0 imported package +JavaScript N/module aggregated on 2 aggregated +JavaScript N/module imported on 2 imported +JavaScript Y/unknown exported on 2 exported +JavaScript Y/unknown exportedAsDefault on 2 +JavaScript Y/unknown imported on 2 imported from the other module +JavaScript Y/unknown importedAsDefault on 2 JavaScript c/class chainElt off 0 (EXPERIMENTAL)used as an element in a name chain like a.b.c JavaScript f/function foreigndecl on 1 declared in foreign languages JavaScript v/variable chainElt off 0 (EXPERIMENTAL)used as an element in a name chain like a.b.c @@ -217,6 +223,12 @@ HTML C/stylesheet extFile on 0 referen HTML J/script extFile on 0 referenced as external files HTML c/class attribute on 0 assigned as attributes Java p/package imported on 0 imported package +JavaScript N/module aggregated on 2 aggregated +JavaScript N/module imported on 2 imported +JavaScript Y/unknown exported on 2 exported +JavaScript Y/unknown exportedAsDefault on 2 +JavaScript Y/unknown imported on 2 imported from the other module +JavaScript Y/unknown importedAsDefault on 2 JavaScript c/class chainElt off 0 (EXPERIMENTAL)used as an element in a name chain like a.b.c JavaScript f/function foreigndecl on 1 declared in foreign languages JavaScript v/variable chainElt off 0 (EXPERIMENTAL)used as an element in a name chain like a.b.c diff --git a/Units/parser-javascript.r/es-export.d/input.mjs b/Units/parser-javascript.r/es-export.d/input.mjs new file mode 100644 index 0000000000..9f01f2426c --- /dev/null +++ b/Units/parser-javascript.r/es-export.d/input.mjs @@ -0,0 +1,32 @@ +// Derrived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export +// Exporting declarations +export let name1, name2/*, … */; // also var +export const name3 = 1, name4 = 2/*, … */; // also var, let +export function functionName1() { /* … */ } +export class ClassName1 { /* … */ } +export function* generatorFunctionName1() { /* … */ } +export const { name5, name6: bar } = o; +export const [ name7, name8 ] = array; + +// Export list +export { name9, /* …, */ nameA }; +export { variable1 as nameB, variable2 as nameC, /* …, */ nameD }; +export { variable3 as "string name" }; +export { nameE as default /*, … */ }; + +// Default exports +export default expression; +export default function functionName2() { /* … */ } +export default class ClassName2 { /* … */ } +export default function* generatorFunctionName2() { /* … */ } +export default function () { /* … */ } +export default class { /* … */ } +export default function* () { /* … */ } + +// Aggregating modules +export * from "module-name1"; +export * as nameF from "module-name2"; +export { nameG, /* …, */ nameH } from "module-name3"; +export { import1 as nameI, import2 as nameJ, /* …, */ nameK } from "module-name4"; +export { default, /* …, */ } from "module-name5"; +export { default as nameL } from "module-name6"; diff --git a/parsers/jscript.c b/parsers/jscript.c index 56a1b699c5..6e8bbfa552 100644 --- a/parsers/jscript.c +++ b/parsers/jscript.c @@ -51,6 +51,7 @@ #include "options.h" #include "mbcs.h" #include "trace.h" +#include "numarray.h" #include "x-html.h" #include "x-jscript.h" @@ -98,6 +99,10 @@ enum eKeywordId { KEYWORD_async, KEYWORD_get, KEYWORD_set, + KEYWORD_import, + KEYWORD_from, + KEYWORD_as, + KEYWORD_with, }; typedef int keywordId; /* to allow KEYWORD_NONE */ @@ -255,6 +260,71 @@ typedef enum { JS_CLASS_CHAINELT, } jsClassRole; +/* Kinds and Roles related to `import' + * ==================================== + * + * ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import + * + * import Y from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:importedAsDefault, module:X) + * + * import * as Y from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown?, nameref:*?, module:X) + * + * import { Y } from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:imported, module:X) + * + * import { Y as Z } from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:imported, module:X) + * Z = (kind:unknown, nameref:unknown:Y) + * + * import { default as Y } from "X"; + * X = (kind:module, role:imported) + * ANON? = (kind:unknown, role:importedAsDefault, module:X, extras:anon) + * Y = (kind:unknown, nameref:unknown:ANON?) + * + * import { Y0, Y1 } from "X"; + * X = (kind:module, role:imported) + * Y0, Y1 = (kind:unknown, role:imported, module:X) + * + * import { Y0, Y1 as Z1, ... } from "X"; + * X = (kind:module, role:imported) + * Y0, Y1 = (kind:unknown, role:imported, module:X) + * Z1 = (kind:unknown, nameref:unknown:Y1) + * + * import { "Y" as Z } from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:imported, module:X) + * Z = (kind:unknown, nameref:unknown:Y) + * + * import Y, { Y1, ... } from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:importedAsDefault, module:X) + * Y1 = (kind:unknown, role:imported, module:X) + * + * import Y, * as Z from "X"; + * X = (kind:module, role:imported) + * Y = (kind:unknown, role:importedAsDefault, module:X) + * Z = (kind:unknown?, nameref:*?) + * + */ + +typedef enum { + JS_MODULE_IMPORTED, + JS_MODULE_AGGREGATED, +} jsModuleRole; + +typedef enum { + JS_UNKNOWN_IMPORTED, + JS_UNKNOWN_IMPORTED_AS_DEFAULT, /* property? */ + JS_UNKNOWN_EXPORTED, + JS_UNKNOWN_EXPORTED_AS_DEFAULT,/* property? */ +} jsUnknownRole; + static roleDefinition JsFunctionRoles [] = { /* Currently V parser wants this items. */ { true, "foreigndecl", "declared in foreign languages", @@ -269,6 +339,24 @@ static roleDefinition JsClassRoles [] = { { false, "chainElt", "(EXPERIMENTAL)used as an element in a name chain like a.b.c" }, }; +static roleDefinition JsModuleRoles [] = { + { true, "imported", "imported", + .version = 2, }, + { true, "aggregated", "aggregated", + .version = 2, }, +}; + +static roleDefinition JsUnknownRoles [] = { + { true, "imported", "imported from the other module", + .version = 2, }, + { true, "importedAsDefault", "", + .version = 2, }, + { true, "exported", "exported", + .version = 2, }, + { true, "exportedAsDefault", "", + .version = 2, }, +}; + static kindDefinition JsKinds [] = { { true, 'f', "function", "functions", .referenceOnly = false, ATTACH_ROLES(JsFunctionRoles) }, @@ -283,6 +371,14 @@ static kindDefinition JsKinds [] = { { true, 'G', "getter", "getters" }, { true, 'S', "setter", "setters" }, { true, 'M', "field", "fields" }, + { true, 'N', "module", "modules", + .referenceOnly = true, ATTACH_ROLES(JsModuleRoles), + .version = 2, + }, + { true, 'Y', "unknown", "", + .referenceOnly = false, ATTACH_ROLES(JsUnknownRoles), + .version = 2, + }, }; static fieldDefinition JsFields[] = { @@ -314,7 +410,7 @@ static const keywordTable JsKeywordTable [] = { { "try", KEYWORD_try }, { "catch", KEYWORD_catch }, { "finally", KEYWORD_finally }, - { "sap", KEYWORD_sap }, + { "sap", KEYWORD_sap }, { "return", KEYWORD_return }, { "class", KEYWORD_class }, { "extends", KEYWORD_extends }, @@ -324,6 +420,10 @@ static const keywordTable JsKeywordTable [] = { { "async", KEYWORD_async }, { "get", KEYWORD_get }, { "set", KEYWORD_set }, + { "import", KEYWORD_import }, + { "from", KEYWORD_from }, + { "as", KEYWORD_as }, + { "with", KEYWORD_with }, }; /* @@ -2025,6 +2125,7 @@ static bool parseFunction (tokenInfo *const token, tokenInfo *const lhs_name, co f = p; parseBlock (token, f); + setTagEndLineToCorkEntry (f, token->lineNumber); } if ( lhs_name == NULL ) @@ -2399,6 +2500,7 @@ static bool parseMethods (tokenInfo *const token, int class_index, index_for_name = makeJsTagMaybePrivate (name, kind, signature, NULL, is_static, is_private); parseBlock (token, index_for_name); + setTagEndLineToCorkEntry (index_for_name, token->lineNumber); /* * If we aren't parsing an ES6 class (for which there @@ -2987,8 +3089,8 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st state->isTerminated = false; } } - else if (! isType (token, TOKEN_KEYWORD) && - token->nestLevel == 0 && state->isGlobal ) + else if (! isType (token, TOKEN_KEYWORD) && true + /* token->nestLevel == 0 && state->isGlobal */) { /* * Only create variables for global scope @@ -3037,7 +3139,11 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st readToken (token); if (isType (token, TOKEN_OPEN_CURLY)) - parseBlock (token, state->indexForName); + { + int i = state->indexForName; + parseBlock (token, i); + setTagEndLineToCorkEntry (i, token->lineNumber); + } else if (! isType (token, TOKEN_SEMICOLON)) state->isTerminated = false; } @@ -3077,7 +3183,10 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st readToken (token); if (isType (token, TOKEN_OPEN_CURLY)) { - parseBlock (token, state->indexForName); + int i = state->indexForName; + parseBlock (token, i); + setTagEndLineToCorkEntry (i, token->lineNumber); + /* here we're at the close curly but it's part of the arrow * function body, so skip over not to confuse further code */ readTokenFull(token, true, NULL); @@ -3322,13 +3431,19 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) } readToken(token); - if (state.isGlobal) + if (state.isGlobal || 1) { bool found = false; if (isType (token, TOKEN_OPEN_CURLY)) + { + TRACE_PRINT("parseObjectDestructuring"); found = parseObjectDestructuring (token, state.isConst); + } else if (isType (token, TOKEN_OPEN_SQUARE)) + { + TRACE_PRINT("parseArrayDestructuring"); found = parseArrayDestructuring (token, state.isConst); + } if (found) { @@ -3468,6 +3583,9 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) * } */ state.isTerminated = findCmdTerm (token, true, true); + if (state.indexForName != CORK_NIL) + setTagEndLineToCorkEntry (state.indexForName, token->lineNumber); + /* if we're at a comma, try and read a second var */ if (isType (token, TOKEN_COMMA)) { @@ -3626,6 +3744,232 @@ static bool parseLine (tokenInfo *const token, bool is_inside_class) return is_terminated; } +static void parseImportObjectDestructuring (tokenInfo *const token, + intArray *us) +{ + while (true) + { + readToken (token); + if (isType (token, TOKEN_EOF)) + return; + + if (isType (token, TOKEN_IDENTIFIER)) + { + int d = makeSimpleRefTag (token->string, JSTAG_UNKNOWN, JS_UNKNOWN_IMPORTED); + intArrayAdd (us, d); + readToken (token); + if (isKeyword (token, KEYWORD_as)) + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + { + makeSimpleTag (token->string, JSTAG_UNKNOWN); + readToken (token); + } + } + } + + if (isType (token, TOKEN_COMMA)) + continue; + if (isType (token, TOKEN_CLOSE_CURLY )) + { + readToken (token); + return; + } + } +} + +static void skipObject (tokenInfo *const token) +{ + readToken (token); + if (isType (token, TOKEN_EOF)) + return; + + int depth = 1; + do + { + if (isType (token, TOKEN_CLOSE_CURLY)) + depth--; + readToken (token); + } + while (depth != 0); +} + +static void parseImport (tokenInfo *const token) +{ + readToken (token); + if (isType (token, TOKEN_EOF)) + return; + + intArray *us = intArrayNew (); + if (isType (token, TOKEN_IDENTIFIER)) + { + int d = makeSimpleRefTag (token->string, JSTAG_UNKNOWN, JS_UNKNOWN_IMPORTED_AS_DEFAULT); + intArrayAdd (us, d); + readToken (token); + if (isType (token, TOKEN_COMMA)) + readToken (token); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + parseImportObjectDestructuring (token, us); + + if (isKeyword (token, KEYWORD_from)) + readToken (token); + + int m = CORK_NIL; + if (isType (token, TOKEN_STRING)) + { + if (vStringLength (token->string) > 0) + m = makeSimpleRefTag (token->string, JSTAG_MODULE, JS_MODULE_IMPORTED); + readToken (token); + } + + if (m != CORK_NIL && !intArrayIsEmpty (us)) + { + for (unsigned int i = 0; i < intArrayCount (us); i++) + { + tagEntryInfo *e; + int u = intArrayItem (us, i); + e = getEntryInCorkQueue (u); + if (e) + e->extensionFields.scopeIndex = m; + } + } + intArrayDelete (us); + + if (isKeyword (token, KEYWORD_with)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_CURLY)) + skipObject (token); + } + + findCmdTerm (token, false, false); +} + +/* TODO: we should separate exported objects and aggregated objects. */ +static void parseExportObjectDestructuring (tokenInfo *const token, intArray *us) +{ + while (true) + { + readToken (token); + if (isType (token, TOKEN_EOF)) + return; + + if (isType (token, TOKEN_IDENTIFIER) + || ( isType (token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_default))) + { + bool isDefault = false; + tokenInfo * nameToken = newToken (); + copyToken (nameToken, token, false); + + readToken (token); + if (isKeyword (token, KEYWORD_as)) + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER) || isType (token, TOKEN_STRING)) + { + makeSimpleTag (token->string, JSTAG_UNKNOWN); + readToken (token); + } + else if (isType (token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_default)) + { + isDefault = true; + readToken (token); + } + } + + if (isType (nameToken, TOKEN_IDENTIFIER)) + { + int role = isDefault? JS_UNKNOWN_EXPORTED_AS_DEFAULT: JS_UNKNOWN_EXPORTED; + + tagEntryInfo e; + initRefTagEntry (&e, vStringValue (nameToken->string), JSTAG_UNKNOWN, role); + updateTagLine (&e, nameToken->lineNumber, nameToken->filePosition); + int d = makeTagEntry (&e); + deleteToken (nameToken); + intArrayAdd (us, d); + } + } + + if (isType (token, TOKEN_COMMA)) + continue; + if (isType (token, TOKEN_CLOSE_CURLY )) + { + readToken (token); + return; + } + } +} + +static void parseExport (tokenInfo *const token) +{ + bool isDefault = false; + + readToken (token); + if (isType (token, TOKEN_EOF)) + return; + + if (isType (token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_default)) + { + isDefault = true; + readToken (token); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + intArray *us = intArrayNew (); + int m = CORK_NIL; + parseExportObjectDestructuring (token, us); + if (isKeyword (token, KEYWORD_from)) + { + readToken (token); + if (isType (token, TOKEN_STRING)) + { + m = makeSimpleRefTag (token->string, JSTAG_MODULE, JS_MODULE_AGGREGATED); + readToken (token); + } + /* TODO: put m to fileds of tags in us. */ + } + intArrayDelete (us); + } + else if (isType (token, TOKEN_STAR)) + { + int u = CORK_NIL; + int m = CORK_NIL; + /* TODO */ + readToken (token); + if (isKeyword (token, KEYWORD_as)) + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER) + || isType (token, TOKEN_STRING)) + { + u = makeSimpleTag (token->string, JSTAG_UNKNOWN); + readToken (token); + } + } + if (isKeyword (token, KEYWORD_from)) + { + readToken (token); + if (isType (token, TOKEN_STRING)) + { + m = makeSimpleRefTag (token->string, JSTAG_MODULE, JS_MODULE_AGGREGATED); + readToken (token); + } + /* TODO: put m to fileds of u. */ + } + } + else + { + parseLine (token, false); + return; + } + + findCmdTerm (token, false, false); +} + static void parseJsFile (tokenInfo *const token) { TRACE_ENTER(); @@ -3636,9 +3980,10 @@ static void parseJsFile (tokenInfo *const token) if (isType (token, TOKEN_KEYWORD) && token->keyword == KEYWORD_sap) parseUI5 (token); - else if (isType (token, TOKEN_KEYWORD) && (token->keyword == KEYWORD_export || - token->keyword == KEYWORD_default)) - /* skip those at top-level */; + else if (isType (token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_export)) + parseExport (token); + else if (isType (token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_import)) + parseImport (token); else parseLine (token, false); } while (! isType (token, TOKEN_EOF)); diff --git a/parsers/x-jscript.h b/parsers/x-jscript.h index 75ae3c6ff7..8196bee988 100644 --- a/parsers/x-jscript.h +++ b/parsers/x-jscript.h @@ -22,6 +22,8 @@ typedef enum { JSTAG_GETTER, JSTAG_SETTER, JSTAG_FIELD, + JSTAG_MODULE, + JSTAG_UNKNOWN, JSTAG_COUNT } jsKind;