UNPKG

sucrase

Version:

Super-fast alternative to Babel for when you can target modern JS runtimes

804 lines (803 loc) 30.8 kB
"use strict"; /* eslint max-len: 0 */ Object.defineProperty(exports, "__esModule", { value: true }); const tokenizer_1 = require("../tokenizer"); const types_1 = require("../tokenizer/types"); const expression_1 = require("./expression"); class StatementParser extends expression_1.default { // ### Statement parsing parseTopLevel() { this.parseBlockBody(true, types_1.types.eof); this.state.scopes.push({ startTokenIndex: 0, endTokenIndex: this.state.tokens.length, isFunctionScope: true, }); return { tokens: this.state.tokens, scopes: this.state.scopes, }; } // Parse a single statement. // // If expecting a statement and finding a slash operator, parse a // regular expression literal. This is to handle cases like // `if (foo) /blah/.exec(foo)`, where looking at the previous token // does not help. parseStatement(declaration, topLevel = false) { if (this.match(types_1.types.at)) { this.parseDecorators(); } this.parseStatementContent(declaration, topLevel); } parseStatementContent(declaration, topLevel) { const starttype = this.state.type; // Most types of statements are recognized by the keyword they // start with. Many are trivial to parse, some require a bit of // complexity. switch (starttype) { case types_1.types._break: case types_1.types._continue: this.parseBreakContinueStatement(); return; case types_1.types._debugger: this.parseDebuggerStatement(); return; case types_1.types._do: this.parseDoStatement(); return; case types_1.types._for: this.parseForStatement(); return; case types_1.types._function: if (this.lookaheadType() === types_1.types.dot) break; if (!declaration) this.unexpected(); this.parseFunctionStatement(); return; case types_1.types._class: if (!declaration) this.unexpected(); this.parseClass(true); return; case types_1.types._if: this.parseIfStatement(); return; case types_1.types._return: this.parseReturnStatement(); return; case types_1.types._switch: this.parseSwitchStatement(); return; case types_1.types._throw: this.parseThrowStatement(); return; case types_1.types._try: this.parseTryStatement(); return; case types_1.types._let: case types_1.types._const: if (!declaration) this.unexpected(); // NOTE: falls through to _var case types_1.types._var: this.parseVarStatement(starttype); return; case types_1.types._while: this.parseWhileStatement(); return; case types_1.types.braceL: this.parseBlock(); return; case types_1.types.semi: this.parseEmptyStatement(); return; case types_1.types._export: case types_1.types._import: { const nextType = this.lookaheadType(); if (nextType === types_1.types.parenL || nextType === types_1.types.dot) { break; } this.next(); if (starttype === types_1.types._import) { this.parseImport(); } else { this.parseExport(); } return; } case types_1.types.name: if (this.state.value === "async") { const functionStart = this.state.start; // peek ahead and see if next token is a function const snapshot = this.state.snapshot(); this.next(); if (this.match(types_1.types._function) && !this.canInsertSemicolon()) { this.expect(types_1.types._function); this.parseFunction(functionStart, true, false); return; } else { this.state.restoreFromSnapshot(snapshot); } } default: // Do nothing. break; } // If the statement does not start with a statement keyword or a // brace, it's an ExpressionStatement or LabeledStatement. We // simply start parsing an expression, and afterwards, if the // next token is a colon and the expression was a simple // Identifier node, we switch to interpreting it as a label. const initialTokensLength = this.state.tokens.length; this.parseExpression(); let simpleName = null; if (this.state.tokens.length === initialTokensLength + 1) { const token = this.state.tokens[this.state.tokens.length - 1]; if (token.type === types_1.types.name) { simpleName = token.value; } } if (simpleName == null) { this.semicolon(); return; } if (this.eat(types_1.types.colon)) { this.parseLabeledStatement(); } else { // This was an identifier, so we might want to handle flow/typescript-specific cases. this.parseIdentifierStatement(simpleName); } } parseDecorators() { while (this.match(types_1.types.at)) { this.parseDecorator(); } } parseDecorator() { this.next(); this.parseIdentifier(); while (this.eat(types_1.types.dot)) { this.parseIdentifier(); } if (this.eat(types_1.types.parenL)) { this.parseCallExpressionArguments(types_1.types.parenR); } } parseBreakContinueStatement() { this.next(); if (!this.isLineTerminator()) { this.parseIdentifier(); this.semicolon(); } } parseDebuggerStatement() { this.next(); this.semicolon(); } parseDoStatement() { this.next(); this.parseStatement(false); this.expect(types_1.types._while); this.parseParenExpression(); this.eat(types_1.types.semi); } parseForStatement() { const startTokenIndex = this.state.tokens.length; this.parseAmbiguousForStatement(); const endTokenIndex = this.state.tokens.length; this.state.scopes.push({ startTokenIndex, endTokenIndex, isFunctionScope: false }); } // Disambiguating between a `for` and a `for`/`in` or `for`/`of` // loop is non-trivial. Basically, we have to parse the init `var` // statement or expression, disallowing the `in` operator (see // the second parameter to `parseExpression`), and then check // whether the next token is `in` or `of`. When there is no init // part (semicolon immediately after the opening parenthesis), it // is a regular `for` loop. parseAmbiguousForStatement() { this.next(); let forAwait = false; if (this.isContextual("await")) { forAwait = true; this.next(); } this.expect(types_1.types.parenL); if (this.match(types_1.types.semi)) { if (forAwait) { this.unexpected(); } this.parseFor(); return; } if (this.match(types_1.types._var) || this.match(types_1.types._let) || this.match(types_1.types._const)) { const varKind = this.state.type; this.next(); this.parseVar(true, varKind); if (this.match(types_1.types._in) || this.isContextual("of")) { this.parseForIn(forAwait); return; } this.parseFor(); return; } this.parseExpression(true); if (this.match(types_1.types._in) || this.isContextual("of")) { this.parseForIn(forAwait); return; } if (forAwait) { this.unexpected(); } this.parseFor(); } parseFunctionStatement() { const functionStart = this.state.start; this.next(); this.parseFunction(functionStart, true); } parseIfStatement() { this.next(); this.parseParenExpression(); this.parseStatement(false); if (this.eat(types_1.types._else)) { this.parseStatement(false); } } parseReturnStatement() { this.next(); // In `return` (and `break`/`continue`), the keywords with // optional arguments, we eagerly look for a semicolon or the // possibility to insert one. if (!this.isLineTerminator()) { this.parseExpression(); this.semicolon(); } } parseSwitchStatement() { this.next(); this.parseParenExpression(); const startTokenIndex = this.state.tokens.length; this.expect(types_1.types.braceL); // Don't bother validation; just go through any sequence of cases, defaults, and statements. while (!this.match(types_1.types.braceR)) { if (this.match(types_1.types._case) || this.match(types_1.types._default)) { const isCase = this.match(types_1.types._case); this.next(); if (isCase) { this.parseExpression(); } this.expect(types_1.types.colon); } else { this.parseStatement(true); } } this.next(); // Closing brace const endTokenIndex = this.state.tokens.length; this.state.scopes.push({ startTokenIndex, endTokenIndex, isFunctionScope: false }); } parseThrowStatement() { this.next(); this.parseExpression(); this.semicolon(); } parseTryStatement() { this.next(); this.parseBlock(); if (this.match(types_1.types._catch)) { this.next(); let catchBindingStartTokenIndex = null; if (this.match(types_1.types.parenL)) { catchBindingStartTokenIndex = this.state.tokens.length; this.expect(types_1.types.parenL); this.parseBindingAtom(true /* isBlockScope */); this.expect(types_1.types.parenR); } this.parseBlock(); if (catchBindingStartTokenIndex != null) { // We need a special scope for the catch binding which includes the binding itself and the // catch block. const endTokenIndex = this.state.tokens.length; this.state.scopes.push({ startTokenIndex: catchBindingStartTokenIndex, endTokenIndex, isFunctionScope: false, }); } } if (this.eat(types_1.types._finally)) { this.parseBlock(); } } parseVarStatement(kind) { this.next(); this.parseVar(false, kind); this.semicolon(); } parseWhileStatement() { this.next(); this.parseParenExpression(); this.parseStatement(false); } parseEmptyStatement() { this.next(); } parseLabeledStatement() { this.parseStatement(true); } /** * Parse a statement starting with an identifier of the given name. Subclasses match on the name * to handle statements like "declare". */ parseIdentifierStatement(name) { this.semicolon(); } // Parse a semicolon-enclosed block of statements, handling `"use // strict"` declarations when `allowStrict` is true (used for // function bodies). parseBlock(allowDirectives = false, isFunctionScope = false, contextId) { const startTokenIndex = this.state.tokens.length; this.expect(types_1.types.braceL); this.state.tokens[this.state.tokens.length - 1].contextId = contextId; this.parseBlockBody(false, types_1.types.braceR); this.state.tokens[this.state.tokens.length - 1].contextId = contextId; const endTokenIndex = this.state.tokens.length; this.state.scopes.push({ startTokenIndex, endTokenIndex, isFunctionScope }); } parseBlockBody(topLevel, end) { while (!this.eat(end)) { this.parseStatement(true, topLevel); } } // Parse a regular `for` loop. The disambiguation code in // `parseStatement` will already have parsed the init statement or // expression. parseFor() { this.expect(types_1.types.semi); if (!this.match(types_1.types.semi)) { this.parseExpression(); } this.expect(types_1.types.semi); if (!this.match(types_1.types.parenR)) { this.parseExpression(); } this.expect(types_1.types.parenR); this.parseStatement(false); } // Parse a `for`/`in` and `for`/`of` loop, which are almost // same from parser's perspective. parseForIn(forAwait) { if (forAwait) { this.eatContextual("of"); } else { this.next(); } this.parseExpression(); this.expect(types_1.types.parenR); this.parseStatement(false); } // Parse a list of variable declarations. parseVar(isFor, kind) { while (true) { const isBlockScope = kind === types_1.types._const || kind === types_1.types._let; this.parseVarHead(isBlockScope); if (this.eat(types_1.types.eq)) { this.parseMaybeAssign(isFor); } if (!this.eat(types_1.types.comma)) break; } } parseVarHead(isBlockScope) { this.parseBindingAtom(isBlockScope); } // Parse a function declaration or literal (depending on the // `isStatement` parameter). parseFunction(functionStart, isStatement, allowExpressionBody, optionalId) { const oldInGenerator = this.state.inGenerator; let isGenerator = false; if (this.match(types_1.types.star)) { isGenerator = true; this.next(); } if (isStatement && !optionalId && !this.match(types_1.types.name) && !this.match(types_1.types._yield)) { this.unexpected(); } let nameScopeStartTokenIndex = null; // When parsing function expression, the binding identifier is parsed // according to the rules inside the function. // e.g. (function* yield() {}) is invalid because "yield" is disallowed in // generators. // This isn't the case with function declarations: function* yield() {} is // valid because yield is parsed as if it was outside the generator. // Therefore, this.state.inGenerator is set before or after parsing the // function id according to the "isStatement" parameter. if (!isStatement) this.state.inGenerator = isGenerator; if (this.match(types_1.types.name) || this.match(types_1.types._yield)) { // Expression-style functions should limit their name's scope to the function body, so we make // a new function scope to enforce that. if (!isStatement) { nameScopeStartTokenIndex = this.state.tokens.length; } this.parseBindingIdentifier(); this.state.tokens[this.state.tokens.length - 1].identifierRole = tokenizer_1.IdentifierRole.FunctionScopedDeclaration; } if (isStatement) this.state.inGenerator = isGenerator; const startTokenIndex = this.state.tokens.length; this.parseFunctionParams(); this.parseFunctionBodyAndFinish(functionStart, isGenerator, allowExpressionBody); const endTokenIndex = this.state.tokens.length; // In addition to the block scope of the function body, we need a separate function-style scope // that includes the params. this.state.scopes.push({ startTokenIndex, endTokenIndex, isFunctionScope: true }); if (nameScopeStartTokenIndex !== null) { this.state.scopes.push({ startTokenIndex: nameScopeStartTokenIndex, endTokenIndex, isFunctionScope: true, }); } this.state.inGenerator = oldInGenerator; } parseFunctionParams(allowModifiers, funcContextId) { this.expect(types_1.types.parenL); this.state.tokens[this.state.tokens.length - 1].contextId = funcContextId; this.parseBindingList(types_1.types.parenR, false /* isBlockScope */, false /* allowEmpty */, allowModifiers); this.state.tokens[this.state.tokens.length - 1].contextId = funcContextId; } // Parse a class declaration or literal (depending on the // `isStatement` parameter). parseClass(isStatement, optionalId = false) { // Put a context ID on the class keyword, the open-brace, and the close-brace, so that later // code can easily navigate to meaningful points on the class. const contextId = this.nextContextId++; this.next(); this.state.tokens[this.state.tokens.length - 1].contextId = contextId; this.state.tokens[this.state.tokens.length - 1].isExpression = !isStatement; // Like with functions, we declare a special "name scope" from the start of the name to the end // of the class, but only with expression-style classes, to represent the fact that the name is // available to the body of the class but not an outer declaration. let nameScopeStartTokenIndex = null; if (!isStatement) { nameScopeStartTokenIndex = this.state.tokens.length; } this.parseClassId(isStatement, optionalId); this.parseClassSuper(); const openBraceIndex = this.state.tokens.length; this.parseClassBody(contextId); this.state.tokens[openBraceIndex].contextId = contextId; this.state.tokens[this.state.tokens.length - 1].contextId = contextId; if (nameScopeStartTokenIndex !== null) { const endTokenIndex = this.state.tokens.length; this.state.scopes.push({ startTokenIndex: nameScopeStartTokenIndex, endTokenIndex, isFunctionScope: false, }); } } isClassProperty() { return this.match(types_1.types.eq) || this.match(types_1.types.semi) || this.match(types_1.types.braceR); } isClassMethod() { return this.match(types_1.types.parenL); } parseClassBody(classContextId) { this.expect(types_1.types.braceL); while (!this.eat(types_1.types.braceR)) { if (this.eat(types_1.types.semi)) { continue; } if (this.match(types_1.types.at)) { this.parseDecorator(); continue; } const memberStart = this.state.start; this.parseClassMember(memberStart, classContextId); } } parseClassMember(memberStart, classContextId) { let isStatic = false; if (this.match(types_1.types.name) && this.state.value === "static") { this.parseIdentifier(); // eats 'static' if (this.isClassMethod()) { this.parseClassMethod(memberStart, false, /* isConstructor */ false); return; } else if (this.isClassProperty()) { this.parseClassProperty(); return; } // otherwise something static this.state.tokens[this.state.tokens.length - 1].type = types_1.types._static; isStatic = true; } this.parseClassMemberWithIsStatic(memberStart, isStatic, classContextId); } parseClassMemberWithIsStatic(memberStart, isStatic, classContextId) { if (this.eat(types_1.types.star)) { // a generator this.parseClassPropertyName(classContextId); this.parseClassMethod(memberStart, true, /* isConstructor */ false); return; } // Get the identifier name so we can tell if it's actually a keyword like "async", "get", or // "set". this.parseClassPropertyName(classContextId); let simpleName = null; let isConstructor = false; const token = this.state.tokens[this.state.tokens.length - 1]; if (token.type === types_1.types.name) { simpleName = token.value; } // We allow "constructor" as either an identifier or a string. if (token.value === "constructor") { isConstructor = true; } this.parsePostMemberNameModifiers(); if (this.isClassMethod()) { this.parseClassMethod(memberStart, false, isConstructor); } else if (this.isClassProperty()) { this.parseClassProperty(); } else if (simpleName === "async" && !this.isLineTerminator()) { this.state.tokens[this.state.tokens.length - 1].type = types_1.types._async; // an async method const isGenerator = this.match(types_1.types.star); if (isGenerator) { this.next(); } // The so-called parsed name would have been "async": get the real name. this.parseClassPropertyName(classContextId); this.parseClassMethod(memberStart, isGenerator, false /* isConstructor */); } else if ((simpleName === "get" || simpleName === "set") && !(this.isLineTerminator() && this.match(types_1.types.star))) { if (simpleName === "get") { this.state.tokens[this.state.tokens.length - 1].type = types_1.types._get; } else { this.state.tokens[this.state.tokens.length - 1].type = types_1.types._set; } // `get\n*` is an uninitialized property named 'get' followed by a generator. // a getter or setter // The so-called parsed name would have been "get/set": get the real name. this.parseClassPropertyName(classContextId); this.parseClassMethod(memberStart, false, /* isConstructor */ false); } else if (this.isLineTerminator()) { // an uninitialized class property (due to ASI, since we don't otherwise recognize the next token) this.parseClassProperty(); } else { this.unexpected(); } } parseClassMethod(functionStart, isGenerator, isConstructor) { this.parseMethod(functionStart, isGenerator, isConstructor); } // Return the name of the class property, if it is a simple identifier. parseClassPropertyName(classContextId) { this.parsePropertyName(classContextId); } // Overridden in typescript.js parsePostMemberNameModifiers() { } parseClassProperty() { if (this.match(types_1.types.eq)) { const equalsTokenIndex = this.state.tokens.length; this.next(); this.parseMaybeAssign(); this.state.tokens[equalsTokenIndex].rhsEndIndex = this.state.tokens.length; } this.semicolon(); } parseClassId(isStatement, optionalId = false) { if (this.match(types_1.types.name)) { this.parseIdentifier(); this.state.tokens[this.state.tokens.length - 1].identifierRole = tokenizer_1.IdentifierRole.BlockScopedDeclaration; } } // Returns true if there was a superclass. parseClassSuper() { if (this.eat(types_1.types._extends)) { this.parseExprSubscripts(); return true; } return false; } // Parses module export declaration. parseExport() { // export * from '...' if (this.shouldParseExportStar()) { this.parseExportStar(); } else if (this.isExportDefaultSpecifier()) { // export default from this.parseIdentifier(); if (this.match(types_1.types.comma) && this.lookaheadType() === types_1.types.star) { this.expect(types_1.types.comma); this.expect(types_1.types.star); this.expectContextual("as"); this.parseIdentifier(); } else { this.parseExportSpecifiersMaybe(); } this.parseExportFrom(); } else if (this.eat(types_1.types._default)) { // export default ... this.parseExportDefaultExpression(); } else if (this.shouldParseExportDeclaration()) { this.parseExportDeclaration(); } else { // export { x, y as z } [from '...'] this.parseExportSpecifiers(); this.parseExportFrom(); } } parseExportDefaultExpression() { const functionStart = this.state.start; if (this.eat(types_1.types._function)) { this.parseFunction(functionStart, true, false, true); } else if (this.isContextual("async") && this.lookaheadType() === types_1.types._function) { // async function declaration this.eatContextual("async"); this.eat(types_1.types._function); this.parseFunction(functionStart, true, false, true); } else if (this.match(types_1.types._class)) { this.parseClass(true, true); } else { this.parseMaybeAssign(); this.semicolon(); } } // eslint-disable-next-line no-unused-vars parseExportDeclaration() { this.parseStatement(true); } isExportDefaultSpecifier() { if (this.match(types_1.types.name)) { return this.state.value !== "async"; } if (!this.match(types_1.types._default)) { return false; } const lookahead = this.lookaheadTypeAndValue(); return (lookahead.type === types_1.types.comma || (lookahead.type === types_1.types.name && lookahead.value === "from")); } parseExportSpecifiersMaybe() { if (this.eat(types_1.types.comma)) { this.parseExportSpecifiers(); } } parseExportFrom() { if (this.eatContextual("from")) { this.parseExprAtom(); } this.semicolon(); } shouldParseExportStar() { return this.match(types_1.types.star); } parseExportStar() { this.expect(types_1.types.star); if (this.isContextual("as")) { this.parseExportNamespace(); } else { this.parseExportFrom(); } } parseExportNamespace() { this.next(); this.state.tokens[this.state.tokens.length - 1].type = types_1.types._as; this.parseIdentifier(); this.parseExportSpecifiersMaybe(); this.parseExportFrom(); } shouldParseExportDeclaration() { return (this.state.type.keyword === "var" || this.state.type.keyword === "const" || this.state.type.keyword === "let" || this.state.type.keyword === "function" || this.state.type.keyword === "class" || this.isContextual("async") || this.match(types_1.types.at)); } // Parses a comma-separated list of module exports. parseExportSpecifiers() { let first = true; // export { x, y as z } [from '...'] this.expect(types_1.types.braceL); while (!this.eat(types_1.types.braceR)) { if (first) { first = false; } else { this.expect(types_1.types.comma); if (this.eat(types_1.types.braceR)) break; } this.parseIdentifier(); this.state.tokens[this.state.tokens.length - 1].identifierRole = tokenizer_1.IdentifierRole.ExportAccess; if (this.eatContextual("as")) { this.parseIdentifier(); } } } // Parses import declaration. parseImport() { // import '...' if (this.match(types_1.types.string)) { this.parseExprAtom(); } else { this.parseImportSpecifiers(); this.expectContextual("from"); this.parseExprAtom(); } this.semicolon(); } // eslint-disable-next-line no-unused-vars shouldParseDefaultImport() { return this.match(types_1.types.name); } parseImportSpecifierLocal() { this.parseIdentifier(); } // Parses a comma-separated list of module imports. parseImportSpecifiers() { let first = true; if (this.shouldParseDefaultImport()) { // import defaultObj, { x, y as z } from '...' this.parseImportSpecifierLocal(); if (!this.eat(types_1.types.comma)) return; } if (this.match(types_1.types.star)) { this.next(); this.expectContextual("as"); this.parseImportSpecifierLocal(); return; } this.expect(types_1.types.braceL); while (!this.eat(types_1.types.braceR)) { if (first) { first = false; } else { // Detect an attempt to deep destructure if (this.eat(types_1.types.colon)) { this.unexpected(null, "ES2015 named imports do not destructure. Use another statement for destructuring after the import."); } this.expect(types_1.types.comma); if (this.eat(types_1.types.braceR)) break; } this.parseImportSpecifier(); } } parseImportSpecifier() { this.parseIdentifier(); if (this.eatContextual("as")) { this.parseIdentifier(); } } } exports.default = StatementParser;