UNPKG

tenko

Version:

A "pixel perfect" 100% spec compliant ES2021 JavaScript parser written in JS.

1,335 lines (1,211 loc) 646 kB
// Char codes aren't really used in the parser (only in the lexer), except for asserts. Inlined for builds. import { $$A_61, $$A_UC_41, $$B_62, $$B_UC_42, $$C_63, $$C_UC_43, $$D_64, $$D_UC_44, $$E_65, $$E_UC_45, $$F_66, $$F_UC_46, $$G_67, $$G_UC_47, $$H_68, $$H_UC_48, $$I_69, $$I_UC_49, $$J_6A, $$J_UC_4A, $$K_6B, $$K_UC_4B, $$L_6C, $$L_UC_4C, $$M_6D, $$M_UC_4D, $$N_6E, $$N_UC_4E, $$O_6F, $$O_UC_4F, $$P_70, $$P_UC_50, $$Q_71, $$Q_UC_51, $$R_72, $$R_UC_52, $$S_73, $$S_UC_53, $$T_74, $$T_UC_54, $$U_75, $$U_UC_55, $$V_76, $$V_UC_56, $$W_77, $$W_UC_57, $$X_78, $$X_UC_58, $$Y_79, $$Y_UC_59, $$Z_7A, $$Z_UC_5A, $$0_30, $$1_31, $$2_32, $$3_33, $$4_34, $$5_35, $$6_36, $$7_37, $$8_38, $$9_39, $$NULL_00, $$BACKSPACE_08, $$TAB_09, $$LF_0A, $$VTAB_0B, $$FF_0C, $$CR_0D, $$SPACE_20, $$EXCL_21, $$DQUOTE_22, $$HASH_23, $$$_24, $$PERCENT_25, $$AND_26, $$SQUOTE_27, $$PAREN_L_28, $$PAREN_R_29, $$STAR_2A, $$PLUS_2B, $$COMMA_2C, $$DASH_2D, $$DOT_2E, $$FWDSLASH_2F, $$COLON_3A, $$SEMI_3B, $$LT_3C, $$IS_3D, $$GT_3E, $$QMARK_3F, $$SQUARE_L_5B, $$BACKSLASH_5C, $$SQUARE_R_5D, $$XOR_5E, $$LODASH_5F, $$TICK_60, $$CURLY_L_7B, $$CURLY_R_7D, $$TILDE_7E, $$OR_7C, $$NBSP_A0, $$ZWS_200B, $$ZWNJ_200C, $$ZWJ_200D, $$LS_2029, $$PS_2028, $$BOM_FEFF, } from './charcodes.mjs'; // Lexerflags are used to signal state from parser to lexer. And organically grew into a wider signaling system. import { INITIAL_LEXER_FLAGS, LF_NO_FLAGS, LF_CAN_NEW_DOT_TARGET, LF_FOR_REGEX, LF_IN_ASYNC, LF_IN_CONSTRUCTOR, LF_IN_FOR_LHS, LF_IN_FUNC_ARGS, LF_IN_GENERATOR, LF_IN_GLOBAL, LF_IN_ITERATION, LF_IN_SWITCH, LF_IN_TEMPLATE, LF_NO_ASI, LF_STRICT_MODE, LF_SUPER_CALL, LF_SUPER_PROP, LF_NOT_KEYWORD, LF_CHAINING, L, } from './lexerflags.mjs'; // Most of these aren't used, except for a few at ASSERT time. Auto-imported for convenience. Auto stripped from a build import { inspect, ASSERT, } from './utils.mjs'; // These define most of the token type manipulation things import { getTokenStart, isWhiteToken, isNewlineToken, isCommentToken, isIdentToken, isNumberToken, isBigintToken, isStringToken, isPunctuatorToken, isRegexToken, isTickToken, isBadTickToken, isNumberStringToken, isNumberStringRegex, // <SCRUB ASSERTS TO COMMENT> // (These are only used to assert a token type group when skipping. For all other use cases use the `isXXXtoken()` funcs $G_WHITE, $G_NEWLINE, $G_COMMENT, $G_IDENT, $G_NUMBER, $G_NUMBER_BIG_INT, $G_PUNCTUATOR, $G_BINOP_ASSIGN, $G_BINOP_NONASSIGN, $G_STRING, $G_REGEX, $G_TICK, $G_TICK_BAD_ESCAPE, $G_OTHER, // </SCRUB ASSERTS TO COMMENT> $UNTYPED, // 0 $SPACE, $TAB, $NL_SOLO, $NL_CRLF, $COMMENT_SINGLE, $COMMENT_MULTI, $COMMENT_HTML, $IDENT, $ID_arguments, $ID_as, $ID_async, $ID_await, $ID_break, $ID_case, $ID_catch, $ID_class, $ID_const, $ID_continue, $ID_debugger, $ID_default, $ID_delete, $ID_do, $ID_else, $ID_enum, $ID_eval, $ID_export, $ID_extends, $ID_false, $ID_finally, $ID_for, $ID_from, $ID_function, $ID_get, $ID_if, $ID_implements, $ID_import, $ID_in, $ID_instanceof, $ID_interface, $ID_let, $ID_new, $ID_null, $ID_of, $ID_package, $ID_private, $ID_protected, $ID_public, $ID_return, $ID_set, $ID_static, $ID_super, $ID_switch, $ID_target, $ID_this, $ID_throw, $ID_true, $ID_try, $ID_typeof, $ID_var, $ID_void, $ID_while, $ID_with, $ID_yield, $NUMBER_HEX, $NUMBER_DEC, $NUMBER_BIN, $NUMBER_OCT, $NUMBER_OLD, $NUMBER_BIG_HEX, $NUMBER_BIG_DEC, $NUMBER_BIG_BIN, $NUMBER_BIG_OCT, $PUNC_EXCL, $PUNC_EXCL_EQ, $PUNC_EXCL_EQ_EQ, $PUNC_PERCENT, $PUNC_PERCENT_EQ, $PUNC_AND, $PUNC_AND_AND, $PUNC_AND_AND_EQ, $PUNC_AND_EQ, $PUNC_PAREN_OPEN, $PUNC_PAREN_CLOSE, $PUNC_STAR, $PUNC_STAR_STAR, $PUNC_STAR_EQ, $PUNC_STAR_STAR_EQ, $PUNC_PLUS, $PUNC_PLUS_PLUS, $PUNC_PLUS_EQ, $PUNC_COMMA, $PUNC_MIN, $PUNC_MIN_MIN, $PUNC_MIN_EQ, $PUNC_MIN_MIN_GT, $PUNC_DOT, $PUNC_DOT_DOT_DOT, $PUNC_DIV, $PUNC_DIV_EQ, $PUNC_COLON, $PUNC_SEMI, $PUNC_LT, $PUNC_LT_LT, $PUNC_LT_EQ, $PUNC_LT_LT_EQ, $PUNC_LT_EXCL_MIN_MIN, $PUNC_EQ, $PUNC_EQ_EQ, $PUNC_EQ_EQ_EQ, $PUNC_EQ_GT, $PUNC_GT, $PUNC_GT_GT, $PUNC_GT_GT_GT, $PUNC_GT_EQ, $PUNC_GT_GT_EQ, $PUNC_GT_GT_GT_EQ, $PUNC_QMARK, $PUNC_QMARK_DOT, $PUNC_QMARK_QMARK, $PUNC_QMARK_QMARK_EQ, $PUNC_BRACKET_OPEN, $PUNC_BRACKET_CLOSE, $PUNC_CARET, $PUNC_CARET_EQ, $PUNC_CURLY_OPEN, $PUNC_OR, $PUNC_OR_OR, $PUNC_OR_OR_EQ, $PUNC_OR_EQ, $PUNC_CURLY_CLOSE, $PUNC_TILDE, $REGEXN, $REGEXU, $STRING_SINGLE, $STRING_DOUBLE, $TICK_HEAD, $TICK_BODY, $TICK_TAIL, $TICK_PURE, $TICK_BAD_HEAD, $TICK_BAD_BODY, $TICK_BAD_TAIL, $TICK_BAD_PURE, $EOF, $ASI, $ERROR, T, // <SCRUB ASSERTS TO COMMENT> ALL_START_TYPES, ALL_GEES, ALL_TOKEN_GROUPS, ALL_TOKEN_TYPES, // </SCRUB ASSERTS TO COMMENT> } from './tokentype.mjs'; // Flags import { COLLECT_TOKENS_NONE, COLLECT_TOKENS_SOLID, COLLECT_TOKENS_ALL, COLLECT_TOKENS_TYPES, FAIL_HARD, GOAL_MODULE, GOAL_SCRIPT, RETURN_ANY_TOKENS, RETURN_COMMENT_TOKENS, RETURN_SOLID_TOKENS, WEB_COMPAT_OFF, WEB_COMPAT_ON, } from './enum_lexer.mjs'; // Actual lexer import { Lexer, } from './lexer.mjs'; function ASSERT_ASSIGN_EXPR(allowAssignment) { ASSERT(allowAssignment === ASSIGN_EXPR_IS_OK || allowAssignment === ASSIGN_EXPR_IS_ERROR, 'allowAssignment is enum', allowAssignment); } function ASSERT_FDS(fdState) { ASSERT([FDS_ILLEGAL, FDS_IFELSE, FDS_LEX, FDS_VAR].includes(fdState), 'FDS enum', fdState); } function ASSERT_BINDING_TYPE(bindingType) { ASSERT([BINDING_TYPE_NONE,BINDING_TYPE_ARG,BINDING_TYPE_VAR,BINDING_TYPE_LET,BINDING_TYPE_CONST,BINDING_TYPE_CLASS,BINDING_TYPE_FUNC_VAR,BINDING_TYPE_FUNC_LEX,BINDING_TYPE_FUNC_STMT,BINDING_TYPE_CATCH_IDENT,BINDING_TYPE_CATCH_OTHER].includes(bindingType), 'bindingType is an enum', bindingType); } function ASSERT_BINDING_ORIGIN(bindingOrigin) { ASSERT([FROM_STATEMENT_START, FROM_FOR_HEADER, FROM_CATCH, FROM_EXPORT_DECL, FROM_ASYNC_ARG, FROM_OTHER_FUNC_ARG].includes(bindingOrigin), 'binding origin enum'); } import { VERSION_EXPONENTIATION, VERSION_ASYNC, VERSION_TRAILING_FUNC_COMMAS, VERSION_ASYNC_GEN, VERSION_OBJECTSPREAD, VERSION_TAGGED_TEMPLATE_BAD_ESCAPES, VERSION_OPTIONAL_CATCH, VERSION_DYNAMIC_IMPORT, VERSION_EXPORT_STAR_AS, VERSION_WHATEVER, IS_ASYNC, NOT_ASYNC, IS_ASYNC_PREFIXED, NOT_ASYNC_PREFIXED, UNDEF_STATIC, UNDEF_ASYNC, UNDEF_STAR, UNDEF_GET, UNDEF_SET, IS_FUNC_DECL, NOT_FUNC_DECL, IS_FUNC_EXPR, NOT_FUNC_EXPR, IDENT_OPTIONAL, IDENT_REQUIRED, PARSE_VALUE_MAYBE, PARSE_VALUE_MUST, YIELD_WITHOUT_VALUE, WITH_ASSIGNABLE, WITH_NON_ASSIGNABLE, IS_ARROW, NOT_ARROW, FROM_STATEMENT_START, FROM_FOR_HEADER, FROM_EXPORT_DECL, FROM_CATCH, FROM_ASYNC_ARG, FROM_OTHER_FUNC_ARG, COAL_SEEN_NEITHER, COAL_SEEN_NULLISH, COAL_SEEN_LOGICAL, BINDING_TYPE_NONE, BINDING_TYPE_ARG, BINDING_TYPE_VAR, BINDING_TYPE_LET, BINDING_TYPE_CONST, BINDING_TYPE_CLASS, BINDING_TYPE_FUNC_VAR, BINDING_TYPE_FUNC_LEX, BINDING_TYPE_FUNC_STMT, BINDING_TYPE_CATCH_IDENT, BINDING_TYPE_CATCH_OTHER, HAS_NO_BINDINGS, ASSIGNMENT_IS_INIT, ASSIGNMENT_IS_DEFAULT, IS_EXPRESSION, IS_STATEMENT, IS_NEW_ARG, NOT_NEW_ARG, IS_OPTIONAL, NOT_OPTIONAL, MIGHT_DESTRUCT, CANT_DESTRUCT, DESTRUCT_ASSIGN_ONLY, MUST_DESTRUCT, ASSIGNABLE_UNDETERMINED, NOT_ASSIGNABLE, IS_ASSIGNABLE, PIGGY_BACK_SAW_AWAIT, PIGGY_BACK_SAW_YIELD, PIGGY_BACK_WAS_CONSTRUCTOR, PIGGY_BACK_WAS_PROTO, PIGGY_BACK_WAS_ARROW, NO_SPREAD, LAST_SPREAD, MID_SPREAD, PARSE_INIT, SKIP_INIT, IS_EXPORT, NOT_EXPORT, IS_QUASI_TAIL, NOT_QUASI_TAIL, PARAM_UNDETERMINED, PARAM_WAS_SIMPLE, PARAM_WAS_NON_STRICT_SIMPLE, PARAM_WAS_COMPLEX, PARAM_WAS_COMPLEX_HAD_INIT, PARAMS_ALL_SIMPLE, PARAMS_SOME_NONSTRICT, PARAMS_SOME_COMPLEX, IS_CONSTRUCTOR, NOT_CONSTRUCTOR, IS_METHOD, NOT_METHOD, ASSIGN_EXPR_IS_OK, ASSIGN_EXPR_IS_ERROR, NO_ID_TO_VERIFY, NO_DUPE_PARAMS, SCOPE_LAYER_GLOBAL, SCOPE_LAYER_FOR_HEADER, SCOPE_LAYER_BLOCK, SCOPE_LAYER_FUNC_PARAMS, SCOPE_LAYER_CATCH_HEAD, SCOPE_LAYER_CATCH_BODY, SCOPE_LAYER_FINALLY, SCOPE_LAYER_SWITCH, SCOPE_LAYER_FUNC_ROOT, SCOPE_LAYER_FUNC_BODY, SCOPE_LAYER_ARROW_PARAMS, SCOPE_LAYER_FAKE_BLOCK, DO_NOT_BIND, UNDEF_EXPORTS, FDS_ILLEGAL, FDS_IFELSE, FDS_LEX, FDS_VAR, IS_GLOBAL_TOPLEVEL, NOT_GLOBAL_TOPLEVEL, IS_LABELLED, NOT_LABELLED, NOT_LHSE, ONLY_LHSE, PARENT_NOT_LABEL, EMPTY_LABEL_SET, copyPiggies, P, } from './enum_parser.mjs'; let ASSERT_ASI_REGEX_NEXT = false; // When set, do not throw assertion error in the semi/asi parser for seeing a regex function sansFlag(flags, flag) { // This function is inlined by the build script... ASSERT(sansFlag.length === arguments.length, 'arg count'); ASSERT(typeof flag === 'number', 'sansFlag flag 1 should be number;', flag, flags); ASSERT(typeof flags === 'number', 'sansFlag flag 2 should be number;', flag, flags); return (flags | flag) ^ flag; } function hasAllFlags(flags1, flags2) { // This function is inlined by the build script... ASSERT(hasAllFlags.length === arguments.length, 'arg count'); ASSERT(typeof flags1 === 'number', 'hasAllFlags flag 1 should be number;', flags1, flags2); ASSERT(typeof flags2 === 'number', 'hasAllFlags flag 2 should be number;', flags1, flags2); return (flags1 & flags2) === flags2; } function hasAnyFlag(flags1, flags2) { // This function is inlined by the build script... ASSERT(hasAnyFlag.length === arguments.length, 'arg count'); ASSERT(typeof flags1 === 'number', 'hasAnyFlag flag 1 should be a number;', flags1, flags2); ASSERT(typeof flags2 === 'number', 'hasAnyFlag flag 2 should be a number;', flags1, flags2); return (flags1 & flags2) !== 0; } function hasNoFlag(flags, flag) { // This function is inlined by the build script... ASSERT(hasNoFlag.length === arguments.length, 'arg count'); ASSERT(typeof flag === 'number', 'hasNoFlag flag 1 should be number;', flag, flags); ASSERT(typeof flags === 'number', 'hasNoFlag flag 2 should be number;', flag, flags); return (flags & flag) === 0; } function Parser(code, options = {}) { let { goalMode: options_goalMode = GOAL_SCRIPT, // GOAL_SCRIPT | GOAL_MODULE | "script" | "module" collectTokens: options_collectTokens = COLLECT_TOKENS_NONE, // COLLECT_TOKENS_NONE | COLLECT_TOKENS_SOLID | COLLECT_TOKENS_ALL | COLLECT_TOKENS_TYPES | "none" | "solid" | "all" | "types" webCompat: options_webCompat = WEB_COMPAT_ON, strictMode: options_strictMode = false, astRoot: options_astRoot = null, tokenStorage: options_tokenStorage, getLexer = null, allowGlobalReturn = false, // you may need this to parse arbitrary code or eval code for example targetEsVersion = VERSION_WHATEVER, // 6, 7, 8, 9, 10, 11, 12, 2015, 2016, 2017, 2018, 2019, 2020, 2021, Infinity exposeScopes: options_exposeScopes = false, // put scopes in the AST under `$scope` property? astUids = false, // add an incremental uid to all ast nodes for debugging ranges: options_ranges = false, // Add `range` to each `loc` object for absolute start/stop index on input? nodeRange: options_nodeRange = false, // Add a `range` to each node itself, being an array. `input.slice(range[0], range[1])` should get you the text of the node. locationTracking: options_locationTracking = true, // Add the `loc` property to all entries? (Much faster without...) templateNewlineNormalization = true, // normalize \r and \rn to \n in the `.raw` of template nodes? Estree spec says yes, but makes it hard to serialize lossless errorCodeFrame = true, // Print input code at error point (you'll probably want this, it helps debugging a lot) truncCodeFrame = true, // Print only context of error in the code frame? Or all the input regardless of size. // You can override the logging functions $log = console.log, $warn = console.warn, $error = console.error, sourceField = '', // This value is used to set the `source` field of the `loc` object of each AST node // ast compatibility stuff? babelCompat = false, babelTokenCompat = false, // Add locs to tokens acornCompat = false, // Should we parse directives as their own AST nodes? (Other parsers do not, they just use ExpressionStatement) // I'm super confused since I read https://github.com/estree/estree/pull/99 as that directives get their own node // and in https://github.com/estree/estree/issues/6 many authors indicate to have adopted this PR, yet none of the // parsers use Directive nodes. So I'm clearly overlooking something silly. *shrug* /* (This comment prevents the buildscript from detecting the ast prefix) */AST_directiveNodes = false, } = options; if (targetEsVersion >= 2015 && targetEsVersion <= 2021) { targetEsVersion -= 2009; // es6 = 2015, etc. 2015-2009=6 } let goalMode = GOAL_SCRIPT; if (typeof options_goalMode === 'string') { if (options_goalMode === 'module') goalMode = GOAL_MODULE; else if (options_goalMode === 'script') goalMode = GOAL_SCRIPT; else throw new Error('Unknown goal symbol value: `' + options_goalMode + '`'); } else { goalMode = options_goalMode; } let collectTokens = COLLECT_TOKENS_NONE; if (typeof options_collectTokens === 'string') { if (options_collectTokens === 'all') collectTokens = COLLECT_TOKENS_ALL; else if (options_collectTokens === 'solid') collectTokens = COLLECT_TOKENS_SOLID; else if (options_collectTokens === 'none') collectTokens = COLLECT_TOKENS_NONE; else if (options_collectTokens === 'types') collectTokens = COLLECT_TOKENS_TYPES; else throw new Error('Unknown collectTokens value: `' + options_collectTokens + '`'); } else { collectTokens = options_collectTokens; } let NODE_NAME_PROPERTY = babelCompat ? 'ObjectProperty' : 'Property'; let NODE_NAME_METHOD_OBJECT = babelCompat ? 'ObjectMethod' : 'Property'; let NODE_NAME_METHOD_CLASS = babelCompat ? 'ClassMethod' : 'MethodDefinition'; let tok = Lexer(code, { targetEsVersion, parsingGoal: goalMode, collectTokens, returnTokens: babelCompat ? RETURN_COMMENT_TOKENS : RETURN_SOLID_TOKENS, webCompat: options_webCompat, gracefulErrors: FAIL_HARD, tokenStorageExternal: options_tokenStorage, babelTokenCompat, errorCodeFrame, truncCodeFrame, $log, $warn, $error, }); let tok_throw = tok.throw; let tok_lexError = tok.lexError; let tok_asi = tok.asi; let tok_prevEndColumn = tok.prevEndColumn; let tok_prevEndLine = tok.prevEndLine; let tok_prevEndPointer = tok.prevEndPointer; let tok_currColumn = tok.currColumn; let tok_currLine = tok.currLine; let tok_currPointer = tok.currPointer; let tok_nextToken = tok.nextToken; let tok_getNlwas = tok.getNlwas; let tok_getCanoN = tok.getCanoN; let tok_getType = tok.getType; let tok_getStart = tok.getStart; let tok_getStop = tok.getStop; let tok_getLine = tok.getLine; let tok_getColumn = tok.getColumn; let tok_sliceInput = tok.sliceInput; let assertExpectedFail = ''; let $tp_assertExpected_start = tok_getStart(); let $tp_assertExpected_stop = tok_getStop(); function ASSERT_VALID(bool, msg) { // An assert that must at least hold when the parser would otherwise accept the input. // This assert Will only throw an assertion error if the parser tripped over this but did not throw any actual error // (This helps with asserting certain syntax errors that would be properly caught without the assert) if (!bool) { assertExpectedFail = msg + '\n' + (new Error().stack); $tp_assertExpected_start = tok_getStart(); $tp_assertExpected_stop = tok_getStop(); } } function ASSERT(bool, desc = '', ...rest) { if (!bool) { THROW_RANGE('Assertion fail: ' + (desc || '<no desc>') + '; ' + JSON.stringify(rest), tok_getStart(), tok_getStop(), ':', ...rest); } } let allowExponentiation = (targetEsVersion >= VERSION_EXPONENTIATION || targetEsVersion === VERSION_WHATEVER); let allowTrailingFunctionComma = targetEsVersion >= VERSION_TRAILING_FUNC_COMMAS || targetEsVersion === VERSION_WHATEVER; let allowAsyncFunctions = targetEsVersion >= VERSION_ASYNC || targetEsVersion === VERSION_WHATEVER; let allowAsyncGenerators = targetEsVersion >= VERSION_ASYNC_GEN || targetEsVersion === VERSION_WHATEVER; let allowBadEscapesInTaggedTemplates = targetEsVersion >= VERSION_TAGGED_TEMPLATE_BAD_ESCAPES || targetEsVersion === VERSION_WHATEVER; let allowOptionalCatchBinding = targetEsVersion >= VERSION_OPTIONAL_CATCH || targetEsVersion === VERSION_WHATEVER; let allowDynamicImport = (targetEsVersion >= VERSION_DYNAMIC_IMPORT || targetEsVersion === VERSION_WHATEVER); let allowExportStarAs = (targetEsVersion >= VERSION_EXPORT_STAR_AS || targetEsVersion === VERSION_WHATEVER); ASSERT(goalMode === GOAL_SCRIPT || goalMode === GOAL_MODULE); ASSERT((targetEsVersion >= 6 && targetEsVersion <= 12) || targetEsVersion === VERSION_WHATEVER, 'version should be 6 7 8 9 10 12 2015 2016 2017 2018 2019 2020 2021 or Infinity'); if (getLexer) getLexer(tok); function THROW_RANGE(desc, tokenStart, tokenStop, ...args) { if (arguments.length < 3) throw new Error('Expecting 3 args for THROW_RANGE, received ' + arguments.length); if (tokenStart > tokenStop) throw new Error('range should be >=0, was [' + tokenStart + ', ' + tokenStop + ']'); $log('\n'); $log('Error in parser:', desc, 'remaining throw args;', args); // The "at eof" suffix also helps for reducing fuzz cases let fullErrmsg = 'Parser error! ' + desc + (tok_getType() === $EOF ? ' (at EOF)' : ''); tok_throw(fullErrmsg, tokenStart, tokenStop, ''); } let uid_counter = 0; // https://github.com/estree/estree // https://github.com/estree/estree/blob/master/es5.md // https://github.com/estree/estree/blob/master/es2015.md // https://astexplorer.net/ let _tree = { type: 'Program', loc: undefined, body: [], }; if (babelCompat) { _tree = { type: 'Program', loc: undefined, body: [], sourceType: goalMode === GOAL_SCRIPT ? 'script' : 'module', interpreter: null, // https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md#interpreterdirective }; } if (acornCompat) { _tree = { type: 'Program', loc: undefined, body: [], sourceType: goalMode === GOAL_SCRIPT ? 'script' : 'module', }; } let _path = [_tree]; let _pnames; ASSERT(_pnames = ['ROOT'], '(dev-only verification and debugging tool)'); if (options_astRoot) { options_astRoot.root = _tree; options_astRoot.path = _path; ASSERT(options_astRoot.pathNames = _pnames, '(dev-only verification and debugging tool)'); } function AST_getClosedLoc($tp_first_start, $tp_first_line, $tp_first_column) { ASSERT(AST_getClosedLoc.length === arguments.length, 'arg count'); ASSERT([$tp_first_start, $tp_first_line, $tp_first_column].every(d => typeof d === 'number' && d >= 0), 'should receive all numbers, all zero-positive'); // Create a loc that is immediately closed return AST_getCloseLoc($tp_first_start, $tp_first_line, $tp_first_column, tok_prevEndPointer(), tok_prevEndLine(), tok_prevEndColumn()); } function AST_getCloseLoc(startIndex, startLine, startColumn, endIndex, endLine, endColumn) { ASSERT(AST_getCloseLoc.length === arguments.length, 'arg count'); ASSERT([startIndex, startLine, startColumn, endIndex, endLine, endColumn].every(d => typeof d === 'number' && d >= 0), 'should receive all numbers, all zero-positive'); if (!options_locationTracking) { if (options_ranges) { // Note: return two distinct object when using ranges to prevent deopt return { range: { start: startIndex | 0, end: endIndex | 0, }, }; } return undefined; } if (options_ranges) { // Note: return two distinct object when using ranges to prevent deopt return { start: { line: startLine | 0, // offset 1 column: startColumn | 0, }, end: { line: endLine | 0, column: endColumn | 0, }, range: { start: startIndex | 0, end: endIndex | 0, }, source: sourceField, // File containing the code being parsed. Source maps may use this. }; } return { start: { line: startLine, // offset 1 column: startColumn, }, end: { line: endLine, column: endColumn, }, source: sourceField, // File containing the code being parsed. Source maps may use this. }; } function AST_open(prop, newNode) { ASSERT(arguments.length === AST_open.length, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(typeof prop === 'string' && prop !== 'undefined', 'prop should be string'); // The column offsets at 0 AST_setNode(prop, newNode); _path[_path.length] = newNode; ASSERT(_pnames.push(prop), '(dev-only verification and debugging tool)'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); } function AST_close($tp_open_start, $tp_open_line, $tp_open_column, names_ASSERT_ONLY) { ASSERT(AST_close.length === arguments.length, 'arg count'); // Note: names_ASSERT_ONLY is stripped in the build... ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(!names_ASSERT_ONLY.includes('TemplateElement'), 'use AST_closeTemplateElement instead'); ASSERT(!names_ASSERT_ONLY.includes('CommentBlock'), 'use AST_closeComment instead'); ASSERT(!names_ASSERT_ONLY.includes('CommentLine'), 'use AST_closeComment instead'); ASSERT(!names_ASSERT_ONLY.includes('Identifier'), 'use AST_closeIdent instead'); AST_set('loc', AST_getCloseLoc($tp_open_start, $tp_open_line, $tp_open_column, tok_prevEndPointer(), tok_prevEndLine(), tok_prevEndColumn())); if (options_nodeRange) AST_set('range', [$tp_open_start, tok_prevEndPointer()]) // <SCRUB ASSERTS> let was = // </SCRUB ASSERTS> _path.pop(); ASSERT(!options_locationTracking || was.loc.start.line <= was.loc.end.line, 'end line should be same or later than start (1)', was.loc); ASSERT(!options_locationTracking || was.loc.start.line < was.loc.end.line || was.loc.start.column <= was.loc.end.column, 'if the node does not span multiple lines then the start column should come before the end column', was.loc); ASSERT(!options_locationTracking || was.loc.start.line >= 1, 'start line should be >= 1', was.loc); ASSERT(!options_locationTracking || was.loc.start.column >= 0, 'start column should be >= 0', was.loc); ASSERT(!options_locationTracking || was.loc.end.line >= 1, 'end line should be >= 1', was.loc); ASSERT(!options_locationTracking || was.loc.end.column >= 0, 'end column should be >= 0', was.loc); ASSERT(!void _pnames.pop(), '(dev-only verification and debugging tool)'); ASSERT(!names_ASSERT_ONLY || (typeof names_ASSERT_ONLY === 'string' && names_ASSERT_ONLY === was.type) || (names_ASSERT_ONLY instanceof Array && names_ASSERT_ONLY.indexOf(was.type) >= 0), 'Expecting to close a node with given name(s), expected: ' + names_ASSERT_ONLY + ' but closed: ' + was.type) } function AST_closeTemplateElement(isTemplateDouble, $tp_tick_start, $tp_tick_line, $tp_tick_column) { ASSERT(AST_closeTemplateElement.length === arguments.length, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); // The column offsets at 0 // For template elements the backticks, `${`, and `}` characters are ignored in the location ranges... so -1 let colEnd = tok_prevEndColumn() - 1; let pointerEnd = tok_prevEndPointer(); if (isTemplateDouble) { // This is for TICK_HEAD and TICK_BODY which start with `${` --colEnd; --pointerEnd; } AST_set('loc', AST_getCloseLoc($tp_tick_start, $tp_tick_line, $tp_tick_column, pointerEnd, tok_prevEndLine(), colEnd)) if (options_nodeRange) { AST_set('range', [$tp_tick_start, pointerEnd]) } // <SCRUB ASSERTS> let was = // </SCRUB ASSERTS> _path.pop(); ASSERT(!options_locationTracking || was.loc.start.line <= was.loc.end.line, 'end line should be same or later than start (2)', was.loc); ASSERT(!options_locationTracking || was.loc.start.line < was.loc.end.line || was.loc.start.column <= was.loc.end.column, 'if the node does not span multiple lines then the start column should come before the end column', was.loc); ASSERT(!options_locationTracking || was.loc.start.line >= 1, 'start line should be >= 1', was.loc); ASSERT(!options_locationTracking || was.loc.start.column >= 0, 'start column should be >= 0', was.loc); ASSERT(!options_locationTracking || was.loc.end.line >= 1, 'end line should be >= 1', was.loc); ASSERT(!options_locationTracking || was.loc.end.column >= 0, 'end column should be >= 0', was.loc); ASSERT(!void _pnames.pop(), '(dev-only verification and debugging tool)'); ASSERT(was.type === 'TemplateElement', 'Expecting to close a TemplateElement node but closed: ' + was.type) } function AST_set(prop, value) { ASSERT(AST_set.length === arguments.length, 'expecting two args'); ASSERT(typeof prop === 'string', 'prop should be string'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(prop[0] === '$' || prop === 'range' || prop === 'directives' || prop === 'extra' || _path[_path.length - 1].hasOwnProperty(prop), 'all ast node members should be predefined', prop, _path[_path.length - 1].type); // Set a property value and expect it to be undefined before ASSERT(_path[_path.length - 1][prop] === undefined, 'use AST_clobber? This func doesnt clobber, prop=' + prop + ', val=' + value); _path[_path.length - 1][prop] = value; } function AST_setNode(astProp, node) { ASSERT(AST_setNode.length === arguments.length, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(typeof node === 'object' && node && typeof node.type === 'string', 'should receive ast node to set', node); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); if (astUids) node.$uid = uid_counter++; //if (options_nodeRange) node.range = [$tp_node_start, tok_prevEndPointer()]; let parentNode = _path[_path.length - 1]; let p = parentNode[astProp]; if (Array.isArray(p)) { p[p.length] = node; } else { ASSERT(p === undefined, `(this invariant does not hold without ASSERTs!) parentNode[astProp] should be empty or an array`, astProp, p && p.type); parentNode[astProp] = node; } } function AST_setClosedNode($tp_node_start, astProp, node) { ASSERT(AST_setClosedNode.length === arguments.length, 'arg count'); // Set a node that is immediately closed // This function's main purpose is to allow setting a range property on the node if (options_nodeRange) node.range = [$tp_node_start, tok_prevEndPointer()]; AST_setNode(astProp, node); } function AST_setNodeDangerously(astProp, $tp_node_start, node) { ASSERT(AST_setNodeDangerously.length === arguments.length, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(typeof node === 'object' && (!node || typeof node.type === 'string'), 'should receive ast node to set or null', node); ASSERT(typeof $tp_node_start === 'number', '$tp_node_start should be the offset of the node', $tp_node_start); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string', astProp); if (astUids && node) node.$uid = uid_counter++; if (options_nodeRange && node) node.range = [$tp_node_start, tok_prevEndPointer()]; let parentNode = _path[_path.length - 1]; let p = parentNode[astProp]; if (Array.isArray(p)) { p[p.length] = node; } else { ASSERT(p === undefined, `(this invariant does not hold without ASSERTs!) parentNode[astProp] should be empty or an array`); parentNode[astProp] = node; } } function AST_setIdent(astProp, $tp_ident_start, $tp_ident_stop, $tp_ident_line, $tp_ident_column, $tp_ident_canon) { ASSERT(AST_setIdent.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); ASSERT($tp_ident_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); let identNode = AST_getIdentNode($tp_ident_start, $tp_ident_stop, $tp_ident_line, $tp_ident_column, $tp_ident_canon); AST_setNode(astProp, identNode); } function AST_getIdentNode($tp_ident_start, $tp_ident_stop, $tp_ident_line, $tp_ident_column, $tp_ident_canon) { ASSERT(AST_getIdentNode.length === arguments.length, 'arg count'); ASSERT(typeof $tp_ident_canon === 'string'); ASSERT($tp_ident_canon === $tp_ident_canon); let col = $tp_ident_column; let line = $tp_ident_line; let len = $tp_ident_stop - $tp_ident_start; // Idents can't contain newlines so the end. column should be start. column+len let colEnd = col + len; let identNode = { type: 'Identifier', loc: AST_getCloseLoc($tp_ident_start, line, col, $tp_ident_stop, line, colEnd), // name value doesn't seem to be specced in estree but it makes sense to use the canonical name here name: $tp_ident_canon, }; if (options_nodeRange) identNode.range = [$tp_ident_start, $tp_ident_stop]; if (babelCompat) identNode.loc.identifierName = $tp_ident_canon; ASSERT(!options_locationTracking || (identNode.loc.end.column - identNode.loc.start.column === ($tp_ident_stop - $tp_ident_start)), 'for idents the location should only span exactly the length of the ident and cannot hold newlines'); return identNode; } function AST_setLiteral(astProp, $tp_lit_type, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column, $tp_lit_canon) { _AST_setLiteral(astProp, $tp_lit_type, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column, $tp_lit_canon, false); } function _AST_setLiteral(astProp, $tp_lit_type, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column, $tp_lit_canon, fromDirective) { ASSERT(_AST_setLiteral.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string', 'prop is string'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(isNumberStringRegex($tp_lit_type), 'should be number or string'); ASSERT($tp_lit_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); if (isStringToken($tp_lit_type)) { AST_setStringLiteral(astProp, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column, $tp_lit_canon, fromDirective); } else if (isNumberToken($tp_lit_type)) { if (isBigintToken($tp_lit_type)) { AST_setBigInt(astProp, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column); } else { AST_setNumberLiteral(astProp, $tp_lit_type, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column); } } else { ASSERT(isRegexToken($tp_lit_type), 'this must be regex now because this function is only called for strings, numbers, and regexes'); ASSERT(tok_sliceInput($tp_lit_start, $tp_lit_stop).split('/').length > 2, 'a regular expression should have at least two forward slashes', tok_sliceInput($tp_lit_start, $tp_lit_stop)); AST_setRegexLiteral(astProp, $tp_lit_start, $tp_lit_stop, $tp_lit_line, $tp_lit_column); } } function AST_getStringNode($tp_string_start, $tp_string_stop, $tp_string_line, $tp_string_column, $tp_string_canon, fromDirective) { ASSERT(AST_getStringNode.length === arguments.length, 'arg count'); if (babelCompat) return AST_babelGetStringNode($tp_string_start, $tp_string_stop, $tp_string_line, $tp_string_column, $tp_string_canon, fromDirective); let node = { type: 'Literal', loc: AST_getCloseLoc($tp_string_start, $tp_string_line, $tp_string_column, tok_prevEndPointer(), tok_prevEndLine(), tok_prevEndColumn()), value: $tp_string_canon, raw: tok_sliceInput($tp_string_start, $tp_string_stop), }; if (options_nodeRange) node.range = [$tp_string_start, $tp_string_stop]; return node; } function AST_setStringLiteral(astProp, $tp_string_start, $tp_string_stop, $tp_string_line, $tp_string_column, $tp_string_canon, fromDirective) { ASSERT(AST_setStringLiteral.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); ASSERT($tp_string_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); // Open a node and immediately close it. Only works if the column offsets do not depend on something being consumed // between open and close (which is often the case). So this is used for literals (while idents have their own func) let stringNode = AST_getStringNode($tp_string_start, $tp_string_stop, $tp_string_line, $tp_string_column, $tp_string_canon, fromDirective); AST_setNode(astProp, stringNode); // for ASSERTs only! } function AST_getNumberNode($tp_number_type, $tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column) { ASSERT(AST_getNumberNode.length === arguments.length, 'arg count'); if (isBigintToken($tp_number_type)) return AST_getBigIntNode($tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); if (babelCompat) return AST_babelGetNumberNode($tp_number_type, $tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); let str = tok_sliceInput($tp_number_start, $tp_number_stop); // Remove numeric separators before using parseFloat/parseInt on it. Should be safe to drop them unconditionally. // If perf is a concern I could always manually check this, or even pass a hint from the lexer. But meh. let descored = str.includes('_') ? str.replace(/_/g, '') : str; let value = $tp_number_type === $NUMBER_DEC ? parseFloat(descored) : // parseFloat also deals with `e` cases $tp_number_type === $NUMBER_HEX ? parseInt(descored.slice(2), 16) : $tp_number_type === $NUMBER_BIN ? parseInt(descored.slice(2), 2) : $tp_number_type === $NUMBER_OCT ? parseInt(descored.slice(2), 8) : ( ASSERT($tp_number_type === $NUMBER_OLD, 'number types are enum and bigint should not reach this'), ASSERT(descored !== '0', 'a zero should just be a decimal'), descored.includes('8') || descored.includes('9') ? parseFloat(descored.slice(1)) : parseInt(descored.slice(1), 8) ); const node = { type: 'Literal', loc: AST_getCloseLoc($tp_number_start, $tp_number_line, $tp_number_column, $tp_number_stop, $tp_number_line, $tp_number_column + ($tp_number_stop - $tp_number_start)), value: value, raw: str, }; if (options_nodeRange) node.range = [$tp_number_start, $tp_number_stop]; return node; } function AST_setNumberLiteral(astProp, $tp_number_type, $tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column) { ASSERT(AST_setNumberLiteral.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); ASSERT($tp_number_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); // Open a node and immediately close it. Only works if the column offsets do not depend on something being consumed // between open and close (which is often the case). So this is used for literals (while idents have their own func) let numberNode = AST_getNumberNode($tp_number_type, $tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); AST_setNode(astProp, numberNode); // for ASSERTs only! } function AST_getBigIntNode($tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column ) { // [v] `45n` // [v] `0b100n` // [v] `0o533n` // [v] `0xabcn` // https://github.com/estree/estree/pull/198/files // Open a node and immediately close it. Only works if the column offsets do not depend on something being consumed // between open and close (which is often the case). So this is used for literals (while idents have their own func) if (acornCompat) return AST_acornGetBigIntNode($tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); if (babelCompat) return AST_babelGetBigIntNode($tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); const node = { type: 'BigIntLiteral', loc: AST_getCloseLoc($tp_number_start, $tp_number_line, $tp_number_column, $tp_number_stop, $tp_number_line, $tp_number_column + ($tp_number_stop - $tp_number_start)), value: null, bigint: tok_sliceInput($tp_number_start, $tp_number_stop - 1), // TODO: Normalize... https://github.com/estree/estree/issues/200 }; if (options_nodeRange) node.range = [$tp_number_start, $tp_number_stop]; return node; } function AST_setBigInt(astProp, $tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column) { ASSERT(AST_setBigInt.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); ASSERT($tp_number_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); let bigintNode = AST_getBigIntNode($tp_number_start, $tp_number_stop, $tp_number_line, $tp_number_column); AST_setNode(astProp, bigintNode); // for ASSERTs only! } function AST_getRegexNode($tp_regex_start, $tp_regex_stop, $tp_regex_line, $tp_regex_column) { ASSERT(AST_getRegexNode.length === arguments.length, 'arg count'); if (acornCompat) return AST_acornGetRegexNode($tp_regex_start, $tp_regex_stop, $tp_regex_line, $tp_regex_column); if (babelCompat) return AST_babelGetRegexNode($tp_regex_start, $tp_regex_stop, $tp_regex_line, $tp_regex_column); // Open a node and immediately close it. Only works if the column offsets do not depend on something being consumed // between open and close (which is often the case). So this is used for literals (while idents have their own func) let str = tok_sliceInput($tp_regex_start, $tp_regex_stop); let pos = str.lastIndexOf('/'); let body = str.slice(1, pos); let tail = str.slice(pos + 1); // https://github.com/estree/estree/blob/master/es5.md#regexpliteral const node = { type: 'Literal', loc: AST_getCloseLoc($tp_regex_start, $tp_regex_line, $tp_regex_column, $tp_regex_stop, $tp_regex_line, $tp_regex_column + ($tp_regex_stop - $tp_regex_start)), value: null, regex: { pattern: body, flags: tail, }, raw: str, }; if (options_nodeRange) node.range = [$tp_regex_start, $tp_regex_stop]; return node; } function AST_setRegexLiteral(astProp, $tp_regex_start, $tp_regex_stop, $tp_regex_line, $tp_regex_column) { ASSERT(AST_setRegexLiteral.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string' && astProp !== 'undefined', 'prop should be string'); ASSERT($tp_regex_start !== tok_getStart(), 'token should be consumed to ensure location data is correct'); let regexNode = AST_getRegexNode($tp_regex_start, $tp_regex_stop, $tp_regex_line, $tp_regex_column); AST_setNode(astProp, regexNode); // for ASSERTs only! } function AST_add(prop, value) { ASSERT(typeof prop === 'string', 'prop should be string'); ASSERT(arguments.length === 2, 'expecting two args'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); ASSERT(Array.isArray(_path[_path.length - 1][prop]), 'expecting to add to an existing array'); let arr = _path[_path.length - 1][prop]; arr[arr.length] = value; } function AST_popNode(prop) { ASSERT(AST_popNode.length === arguments.length, 'arg count'); // Get the current "top" node and either remove it from the parent array, or mark it as `undefined` inside an ASSERT let parent = _path[_path.length-1]; let p = parent[prop]; ASSERT(p || p === null, 'the prop should exist... (and be a node, or null for the init in `for (;;);`)', prop, parent.type); if (Array.isArray(p)) { ASSERT(Array.isArray(p), 'ast nodes do not have a `length` property so this duck type check should have sufficed'); ASSERT(p.length); return p.pop(); } else { ASSERT(!void(parent[prop] = undefined), '(mark as undefined so that assertions dont trip over the value existing)'); return p; } } function AST_wrapClosedCustom(prop, newNode, newProp) { // TODO: a build can strip the second arg ... maybe we can formalize that a little bit ASSERT(AST_wrapClosedCustom.length === arguments.length, 'arg count'); ASSERT(typeof prop === 'string', 'should be string'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths', 'pnames='+_pnames+', path=' + _path.map(p => p.type)); // wrap the "current" node (last of _tree) with an extra node // so the parent of node becomes the parent of a new node and // the old node becomes a child of the new node // a(x:b) -> a(x:c(y:b)) // If the child is an array, replace the last element in the array but not the array itself let child = AST_popNode(prop); AST_open(prop, newNode); // set it as child of new node AST_set(newProp, child); } function AST_wrapClosedIntoArrayCustom(prop, newNode, newProp) { ASSERT(AST_wrapClosedIntoArrayCustom.length === arguments.length, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); // same as AST_wrapClosed except the node is put in an array let child = AST_popNode(prop); AST_open(prop, newNode); // set the node as the first child of the property as an array AST_set(newProp, [child]); } function AST_destruct(prop) { // rename object and array literal nodes to patterns to match the AST spec // this happens when arr/obj literal was parsed (possibly nested) and // then a destructuring assignment was encountered // this is also called for function args and any other destructuring (like catch binding) // recursively walk the tree from the prop in open node and visit // any array and object expression as well as the left side of assignments // for now dont bother with other nodes until we find a reason to // note: this function is usually called after a few nodes have closed (the literal struct). ASSERT(arguments.length === 1, 'arg count'); ASSERT(_path.length > 0, 'path shouldnt be empty'); ASSERT(_pnames.length === _path.length, 'pnames should have as many names as paths'); let parent = _path[_path.length-1]; let node = parent[prop]; ASSERT(node, 'top[' + prop + '] should be a node'); if (Array.isArray(node)) { // The destruct applies to the node just closed, so last in list let last = node.length - 1; AST__destruct(node[last], node, last); return; } AST__destruct(node, parent, prop); } function AST__destruct(node, parent, astProp) { ASSERT(AST__destruct.length === arguments.length, 'arg count'); switch (node.type) { case 'ArrayExpression': node.type = 'ArrayPattern'; let elements = node.elements; let e = elements.length; for (let i = 0; i < e; ++i) { let element = elements[i]; // note: children can be null (elided array destruct) but not undefined if (element) AST__destruct(element, elements, i); } return; case 'ObjectExpression': node.type = 'ObjectPattern'; let properties = node.properties; let n = properties.length; for (let i = 0; i < n; ++i) { if (properties[i].type === NODE_NAME_PROPERTY) { ASSERT(properties[i].value, 'each property should have a value'); } else { ASSERT(properties[i].type === 'SpreadElement', 'expecting only properties, spreads, and assignments here'); ASSERT(properties[i].argument, 'each property should have a value'); } AST__destruct(properties[i], properties, i); } return; case 'AssignmentExpression': // walk the left of the assignment only AST__destruct(node.left, node, 'left'); AST_destructReplaceAssignment(parent, astProp); return; case NODE_NAME_PROPERTY: AST__destruct(node.value, node, 'value'); return; case 'SpreadElement': // `([...x]);` vs `([...x]) => x` // `({...x});` vs `({...x}) => x` // TODO: this seems to deopt. can also set the parent[prop] = {} new node appraoch. need to benchmark that. node.type = 'RestElement'; AST__destruct(node.argument, node, 'argument'); return; } } function AST_destructReplaceAssignment(parentNode, prop) { let oldNode = parentNode[prop]; if (oldNode.operator !== '=') { return THROW_RANGE('The destructuring assignment should be a regular assignment', tok_getStart(), tok_getStop()); } let newNode = { type: 'AssignmentPattern', loc: oldNode.loc, left: oldNode.left, right: oldNode.right, }; if (options_nodeRange) newNode.range = oldNode.range; parentNode[prop] = newNode; } function AST_convertArrayToPattern($tp_eq_type, astProp) { ASSERT(AST_convertArrayToPattern.length === arguments.length, 'arg count'); if ($tp_eq_type === $PUNC_EQ) { let node = _path[_path.length - 1][astProp]; if (Array.isArray(node)) { node = node[node.length - 1]; } if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') { AST_destruct(astProp); } } } function AST_throwIfIllegalUpdateArg(astProp) { ASSERT(AST_throwIfIllegalUpdateArg.length === arguments.length, 'arg count'); ASSERT(typeof astProp === 'string', 'astprop string'); // Using the AST for this because in the current propagation system we can only tell whether the parsed part // is assignable or not, and in this reading something that can be destructured can be assigned to. let head = _path[_path.length - 1]; let prev = head && head[astProp]; // Note: the for-case is nasty because when parsing the lhs the AST is not yet populated with a `for` statement // because that particular node type depends on `in`, `of`, or a semi. So the AST could be an array (block body) if ( !prev || ( prev instanceof Array ? // - `for (x--;;);` !prev.length || (prev[prev.length - 1].type !== 'Identifier' && prev[prev.length - 1].type !== 'MemberExpression') : // - `[]++` (prev.type !== 'Identifier' && prev.type !== 'MemberExpression') ) ) { // - `++[]` // - `--f()` // - `++this` // - `[]++` // - `f()--` // - `this++` return THROW_RANGE('Can only increment or decrement an identifier or member expression', tok_getStart(), tok_getStop()); } } function AST_patchAsyncCall($tp_async_start, $tp_async_stop, $tp_async_line, $tp_async_column, $tp_async_canon, astProp) { ASSERT(AST_patchAsyncCall.length === arguments.length, 'arg count'); let node = _path[_path.length - 1]; let args = node[astProp]; if (args instanceof Array) args = args[0]; ASSERT(args, 'should have parsed someting v1'); if (args.type === 'SequenceExpression') args = args.expressions; else args = [args]; ASSERT(args, 'should have parsed someting v2'); if (node[astProp] instanceof Array) node[astProp] = []; else node[astProp] = undefined; // TODO: verify the args ASSERT(_path[_path.length - 1][astProp] instanceof Array || !void(_path[_path.length - 1][astProp] = undefined), '(there is an assert that confirms that the property is undefined and we expect this not to be the case here)'); AST_setClosedNode($tp_async_start, astProp, { type: 'CallExpression', loc: AST_getClosedLoc($tp_async_start, $tp_async_line, $tp_async_column), optional: false, callee: AST_getIdentNode($tp_async_start, $tp_async_stop, $tp_async_line, $tp_async_column, $tp_async_canon), arguments: args, }); } function AST_babelDirectives() { // Remove Dir