UNPKG

jsdoc-75lb

Version:

An API documentation generator for JavaScript.

1,826 lines (1,567 loc) 155 kB
/* Copyright (C) 2015 Fred K. Schott <fkschott@gmail.com> Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be> Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*eslint no-undefined:0, no-use-before-define: 0*/ "use strict"; var syntax = require("./lib/syntax"), tokenInfo = require("./lib/token-info"), astNodeTypes = require("./lib/ast-node-types"), astNodeFactory = require("./lib/ast-node-factory"), defaultFeatures = require("./lib/features"), Messages = require("./lib/messages"), XHTMLEntities = require("./lib/xhtml-entities"), StringMap = require("./lib/string-map"), commentAttachment = require("./lib/comment-attachment"); var Token = tokenInfo.Token, TokenName = tokenInfo.TokenName, FnExprTokens = tokenInfo.FnExprTokens, Regex = syntax.Regex, PropertyKind, source, strict, index, lineNumber, lineStart, length, lookahead, state, extra; PropertyKind = { Data: 1, Get: 2, Set: 4 }; // Ensure the condition is true, otherwise throw an error. // This is only to have a better contract semantic, i.e. another safety net // to catch a logic error. The condition shall be fulfilled in normal case. // Do NOT use this to enforce a certain condition on any user input. function assert(condition, message) { /* istanbul ignore if */ if (!condition) { throw new Error("ASSERT: " + message); } } // 7.4 Comments function addComment(type, value, start, end, loc) { var comment; assert(typeof start === "number", "Comment must have valid position"); // Because the way the actual token is scanned, often the comments // (if any) are skipped twice during the lexical analysis. // Thus, we need to skip adding a comment if the comment array already // handled it. if (state.lastCommentStart >= start) { return; } state.lastCommentStart = start; comment = { type: type, value: value }; if (extra.range) { comment.range = [start, end]; } if (extra.loc) { comment.loc = loc; } extra.comments.push(comment); if (extra.attachComment) { commentAttachment.addComment(comment); } } function skipSingleLineComment(offset) { var start, loc, ch, comment; start = index - offset; loc = { start: { line: lineNumber, column: index - lineStart - offset } }; while (index < length) { ch = source.charCodeAt(index); ++index; if (syntax.isLineTerminator(ch)) { if (extra.comments) { comment = source.slice(start + offset, index - 1); loc.end = { line: lineNumber, column: index - lineStart - 1 }; addComment("Line", comment, start, index - 1, loc); } if (ch === 13 && source.charCodeAt(index) === 10) { ++index; } ++lineNumber; lineStart = index; return; } } if (extra.comments) { comment = source.slice(start + offset, index); loc.end = { line: lineNumber, column: index - lineStart }; addComment("Line", comment, start, index, loc); } } function skipMultiLineComment() { var start, loc, ch, comment; if (extra.comments) { start = index - 2; loc = { start: { line: lineNumber, column: index - lineStart - 2 } }; } while (index < length) { ch = source.charCodeAt(index); if (syntax.isLineTerminator(ch)) { if (ch === 0x0D && source.charCodeAt(index + 1) === 0x0A) { ++index; } ++lineNumber; ++index; lineStart = index; if (index >= length) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } } else if (ch === 0x2A) { // Block comment ends with "*/". if (source.charCodeAt(index + 1) === 0x2F) { ++index; ++index; if (extra.comments) { comment = source.slice(start + 2, index - 2); loc.end = { line: lineNumber, column: index - lineStart }; addComment("Block", comment, start, index, loc); } return; } ++index; } else { ++index; } } throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } function skipComment() { var ch, start; start = (index === 0); while (index < length) { ch = source.charCodeAt(index); if (syntax.isWhiteSpace(ch)) { ++index; } else if (syntax.isLineTerminator(ch)) { ++index; if (ch === 0x0D && source.charCodeAt(index) === 0x0A) { ++index; } ++lineNumber; lineStart = index; start = true; } else if (ch === 0x2F) { // U+002F is "/" ch = source.charCodeAt(index + 1); if (ch === 0x2F) { ++index; ++index; skipSingleLineComment(2); start = true; } else if (ch === 0x2A) { // U+002A is "*" ++index; ++index; skipMultiLineComment(); } else { break; } } else if (start && ch === 0x2D) { // U+002D is "-" // U+003E is ">" if ((source.charCodeAt(index + 1) === 0x2D) && (source.charCodeAt(index + 2) === 0x3E)) { // "-->" is a single-line comment index += 3; skipSingleLineComment(3); } else { break; } } else if (ch === 0x3C) { // U+003C is "<" if (source.slice(index + 1, index + 4) === "!--") { ++index; // `<` ++index; // `!` ++index; // `-` ++index; // `-` skipSingleLineComment(4); } else { break; } } else { break; } } } function scanHexEscape(prefix) { var i, len, ch, code = 0; len = (prefix === "u") ? 4 : 2; for (i = 0; i < len; ++i) { if (index < length && syntax.isHexDigit(source[index])) { ch = source[index++]; code = code * 16 + "0123456789abcdef".indexOf(ch.toLowerCase()); } else { return ""; } } return String.fromCharCode(code); } /** * Scans an extended unicode code point escape sequence from source. Throws an * error if the sequence is empty or if the code point value is too large. * @returns {string} The string created by the Unicode escape sequence. * @private */ function scanUnicodeCodePointEscape() { var ch, code, cu1, cu2; ch = source[index]; code = 0; // At least one hex digit is required. if (ch === "}") { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } while (index < length) { ch = source[index++]; if (!syntax.isHexDigit(ch)) { break; } code = code * 16 + "0123456789abcdef".indexOf(ch.toLowerCase()); } if (code > 0x10FFFF || ch !== "}") { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } // UTF-16 Encoding if (code <= 0xFFFF) { return String.fromCharCode(code); } cu1 = ((code - 0x10000) >> 10) + 0xD800; cu2 = ((code - 0x10000) & 1023) + 0xDC00; return String.fromCharCode(cu1, cu2); } function getEscapedIdentifier() { var ch, id; ch = source.charCodeAt(index++); id = String.fromCharCode(ch); // "\u" (U+005C, U+0075) denotes an escaped character. if (ch === 0x5C) { if (source.charCodeAt(index) !== 0x75) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } ++index; ch = scanHexEscape("u"); if (!ch || ch === "\\" || !syntax.isIdentifierStart(ch.charCodeAt(0))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } id = ch; } while (index < length) { ch = source.charCodeAt(index); if (!syntax.isIdentifierPart(ch)) { break; } ++index; id += String.fromCharCode(ch); // "\u" (U+005C, U+0075) denotes an escaped character. if (ch === 0x5C) { id = id.substr(0, id.length - 1); if (source.charCodeAt(index) !== 0x75) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } ++index; ch = scanHexEscape("u"); if (!ch || ch === "\\" || !syntax.isIdentifierPart(ch.charCodeAt(0))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } id += ch; } } return id; } function getIdentifier() { var start, ch; start = index++; while (index < length) { ch = source.charCodeAt(index); if (ch === 0x5C) { // Blackslash (U+005C) marks Unicode escape sequence. index = start; return getEscapedIdentifier(); } if (syntax.isIdentifierPart(ch)) { ++index; } else { break; } } return source.slice(start, index); } function scanIdentifier() { var start, id, type; start = index; // Backslash (U+005C) starts an escaped character. id = (source.charCodeAt(index) === 0x5C) ? getEscapedIdentifier() : getIdentifier(); // There is no keyword or literal with only one character. // Thus, it must be an identifier. if (id.length === 1) { type = Token.Identifier; } else if (syntax.isKeyword(id, strict, extra.ecmaFeatures)) { type = Token.Keyword; } else if (id === "null") { type = Token.NullLiteral; } else if (id === "true" || id === "false") { type = Token.BooleanLiteral; } else { type = Token.Identifier; } return { type: type, value: id, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } // 7.7 Punctuators function scanPunctuator() { var start = index, code = source.charCodeAt(index), code2, ch1 = source[index], ch2, ch3, ch4; switch (code) { // Check for most common single-character punctuators. case 40: // ( open bracket case 41: // ) close bracket case 59: // ; semicolon case 44: // , comma case 91: // [ case 93: // ] case 58: // : case 63: // ? case 126: // ~ ++index; if (extra.tokenize && code === 40) { extra.openParenToken = extra.tokens.length; } return { type: Token.Punctuator, value: String.fromCharCode(code), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; case 123: // { open curly brace case 125: // } close curly brace ++index; if (extra.tokenize && code === 123) { extra.openCurlyToken = extra.tokens.length; } // lookahead2 function can cause tokens to be scanned twice and in doing so // would wreck the curly stack by pushing the same token onto the stack twice. // curlyLastIndex ensures each token is pushed or popped exactly once if (index > state.curlyLastIndex) { state.curlyLastIndex = index; if (code === 123) { state.curlyStack.push("{"); } else { state.curlyStack.pop(); } } return { type: Token.Punctuator, value: String.fromCharCode(code), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; default: code2 = source.charCodeAt(index + 1); // "=" (char #61) marks an assignment or comparison operator. if (code2 === 61) { switch (code) { case 37: // % case 38: // & case 42: // *: case 43: // + case 45: // - case 47: // / case 60: // < case 62: // > case 94: // ^ case 124: // | index += 2; return { type: Token.Punctuator, value: String.fromCharCode(code) + String.fromCharCode(code2), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; case 33: // ! case 61: // = index += 2; // !== and === if (source.charCodeAt(index) === 61) { ++index; } return { type: Token.Punctuator, value: source.slice(start, index), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; default: break; } } break; } // Peek more characters. ch2 = source[index + 1]; ch3 = source[index + 2]; ch4 = source[index + 3]; // 4-character punctuator: >>>= if (ch1 === ">" && ch2 === ">" && ch3 === ">") { if (ch4 === "=") { index += 4; return { type: Token.Punctuator, value: ">>>=", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } } // 3-character punctuators: === !== >>> <<= >>= if (ch1 === ">" && ch2 === ">" && ch3 === ">") { index += 3; return { type: Token.Punctuator, value: ">>>", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } if (ch1 === "<" && ch2 === "<" && ch3 === "=") { index += 3; return { type: Token.Punctuator, value: "<<=", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } if (ch1 === ">" && ch2 === ">" && ch3 === "=") { index += 3; return { type: Token.Punctuator, value: ">>=", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } // The ... operator (spread, restParams, JSX, etc.) if (extra.ecmaFeatures.spread || extra.ecmaFeatures.restParams || extra.ecmaFeatures.experimentalObjectRestSpread || (extra.ecmaFeatures.jsx && state.inJSXSpreadAttribute) ) { if (ch1 === "." && ch2 === "." && ch3 === ".") { index += 3; return { type: Token.Punctuator, value: "...", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } } // Other 2-character punctuators: ++ -- << >> && || if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) { index += 2; return { type: Token.Punctuator, value: ch1 + ch2, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } // the => for arrow functions if (extra.ecmaFeatures.arrowFunctions) { if (ch1 === "=" && ch2 === ">") { index += 2; return { type: Token.Punctuator, value: "=>", lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } } if ("<>=!+-*%&|^/".indexOf(ch1) >= 0) { ++index; return { type: Token.Punctuator, value: ch1, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } if (ch1 === ".") { ++index; return { type: Token.Punctuator, value: ch1, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } // 7.8.3 Numeric Literals function scanHexLiteral(start) { var number = ""; while (index < length) { if (!syntax.isHexDigit(source[index])) { break; } number += source[index++]; } if (number.length === 0) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } if (syntax.isIdentifierStart(source.charCodeAt(index))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } return { type: Token.NumericLiteral, value: parseInt("0x" + number, 16), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function scanBinaryLiteral(start) { var ch, number = ""; while (index < length) { ch = source[index]; if (ch !== "0" && ch !== "1") { break; } number += source[index++]; } if (number.length === 0) { // only 0b or 0B throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } if (index < length) { ch = source.charCodeAt(index); /* istanbul ignore else */ if (syntax.isIdentifierStart(ch) || syntax.isDecimalDigit(ch)) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } } return { type: Token.NumericLiteral, value: parseInt(number, 2), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function scanOctalLiteral(prefix, start) { var number, octal; if (syntax.isOctalDigit(prefix)) { octal = true; number = "0" + source[index++]; } else { octal = false; ++index; number = ""; } while (index < length) { if (!syntax.isOctalDigit(source[index])) { break; } number += source[index++]; } if (!octal && number.length === 0) { // only 0o or 0O throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } if (syntax.isIdentifierStart(source.charCodeAt(index)) || syntax.isDecimalDigit(source.charCodeAt(index))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } return { type: Token.NumericLiteral, value: parseInt(number, 8), octal: octal, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function scanNumericLiteral() { var number, start, ch; ch = source[index]; assert(syntax.isDecimalDigit(ch.charCodeAt(0)) || (ch === "."), "Numeric literal must start with a decimal digit or a decimal point"); start = index; number = ""; if (ch !== ".") { number = source[index++]; ch = source[index]; // Hex number starts with "0x". // Octal number starts with "0". if (number === "0") { if (ch === "x" || ch === "X") { ++index; return scanHexLiteral(start); } // Binary number in ES6 starts with '0b' if (extra.ecmaFeatures.binaryLiterals) { if (ch === "b" || ch === "B") { ++index; return scanBinaryLiteral(start); } } if ((extra.ecmaFeatures.octalLiterals && (ch === "o" || ch === "O")) || syntax.isOctalDigit(ch)) { return scanOctalLiteral(ch, start); } // decimal number starts with "0" such as "09" is illegal. if (ch && syntax.isDecimalDigit(ch.charCodeAt(0))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } } while (syntax.isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === ".") { number += source[index++]; while (syntax.isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === "e" || ch === "E") { number += source[index++]; ch = source[index]; if (ch === "+" || ch === "-") { number += source[index++]; } if (syntax.isDecimalDigit(source.charCodeAt(index))) { while (syntax.isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } } else { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } } if (syntax.isIdentifierStart(source.charCodeAt(index))) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } return { type: Token.NumericLiteral, value: parseFloat(number), lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } /** * Scan a string escape sequence and return its special character. * @param {string} ch The starting character of the given sequence. * @returns {Object} An object containing the character and a flag * if the escape sequence was an octal. * @private */ function scanEscapeSequence(ch) { var code, unescaped, restore, escapedCh, octal = false; // An escape sequence cannot be empty if (!ch) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } if (syntax.isLineTerminator(ch.charCodeAt(0))) { ++lineNumber; if (ch === "\r" && source[index] === "\n") { ++index; } lineStart = index; escapedCh = ""; } else if (ch === "u" && source[index] === "{") { // Handle ES6 extended unicode code point escape sequences. if (extra.ecmaFeatures.unicodeCodePointEscapes) { ++index; escapedCh = scanUnicodeCodePointEscape(); } else { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } } else if (ch === "u" || ch === "x") { // Handle other unicode and hex codes normally restore = index; unescaped = scanHexEscape(ch); if (unescaped) { escapedCh = unescaped; } else { index = restore; escapedCh = ch; } } else if (ch === "n") { escapedCh = "\n"; } else if (ch === "r") { escapedCh = "\r"; } else if (ch === "t") { escapedCh = "\t"; } else if (ch === "b") { escapedCh = "\b"; } else if (ch === "f") { escapedCh = "\f"; } else if (ch === "v") { escapedCh = "\v"; } else if (syntax.isOctalDigit(ch)) { code = "01234567".indexOf(ch); // \0 is not octal escape sequence if (code !== 0) { octal = true; } if (index < length && syntax.isOctalDigit(source[index])) { octal = true; code = code * 8 + "01234567".indexOf(source[index++]); // 3 digits are only allowed when string starts with 0, 1, 2, 3 if ("0123".indexOf(ch) >= 0 && index < length && syntax.isOctalDigit(source[index])) { code = code * 8 + "01234567".indexOf(source[index++]); } } escapedCh = String.fromCharCode(code); } else { escapedCh = ch; } return { ch: escapedCh, octal: octal }; } function scanStringLiteral() { var str = "", ch, escapedSequence, octal = false, start = index, startLineNumber = lineNumber, startLineStart = lineStart, quote = source[index]; assert((quote === "'" || quote === "\""), "String literal must starts with a quote"); ++index; while (index < length) { ch = source[index++]; if (syntax.isLineTerminator(ch.charCodeAt(0))) { break; } else if (ch === quote) { quote = ""; break; } else if (ch === "\\") { ch = source[index++]; escapedSequence = scanEscapeSequence(ch); str += escapedSequence.ch; octal = escapedSequence.octal || octal; } else { str += ch; } } if (quote !== "") { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } return { type: Token.StringLiteral, value: str, octal: octal, startLineNumber: startLineNumber, startLineStart: startLineStart, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } /** * Scan a template string and return a token. This scans both the first and * subsequent pieces of a template string and assumes that the first backtick * or the closing } have already been scanned. * @returns {Token} The template string token. * @private */ function scanTemplate() { var cooked = "", ch, escapedSequence, start = index, terminated = false, tail = false, head = (source[index] === "`"); ++index; while (index < length) { ch = source[index++]; if (ch === "`") { tail = true; terminated = true; break; } else if (ch === "$") { if (source[index] === "{") { ++index; terminated = true; break; } cooked += ch; } else if (ch === "\\") { ch = source[index++]; escapedSequence = scanEscapeSequence(ch); if (escapedSequence.octal) { throwError({}, Messages.TemplateOctalLiteral); } cooked += escapedSequence.ch; } else if (syntax.isLineTerminator(ch.charCodeAt(0))) { ++lineNumber; if (ch === "\r" && source[index] === "\n") { ++index; } lineStart = index; cooked += "\n"; } else { cooked += ch; } } if (!terminated) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } if (index > state.curlyLastIndex) { state.curlyLastIndex = index; if (!tail) { state.curlyStack.push("template"); } if (!head) { state.curlyStack.pop(); } } return { type: Token.Template, value: { cooked: cooked, raw: source.slice(start + 1, index - ((tail) ? 1 : 2)) }, head: head, tail: tail, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function testRegExp(pattern, flags) { var tmp = pattern, validFlags = "gmsi"; if (extra.ecmaFeatures.regexYFlag) { validFlags += "y"; } if (extra.ecmaFeatures.regexUFlag) { validFlags += "u"; } if (!RegExp("^[" + validFlags + "]*$").test(flags)) { throwError({}, Messages.InvalidRegExpFlag); } if (flags.indexOf("u") >= 0) { // Replace each astral symbol and every Unicode code point // escape sequence with a single ASCII symbol to avoid throwing on // regular expressions that are only valid in combination with the // `/u` flag. // Note: replacing with the ASCII symbol `x` might cause false // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a // perfectly valid pattern that is equivalent to `[a-b]`, but it // would be replaced by `[x-b]` which throws an error. tmp = tmp .replace(/\\u\{([0-9a-fA-F]+)\}/g, function ($0, $1) { if (parseInt($1, 16) <= 0x10FFFF) { return "x"; } throwError({}, Messages.InvalidRegExp); }) .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x"); } // First, detect invalid regular expressions. try { RegExp(tmp); } catch (e) { throwError({}, Messages.InvalidRegExp); } // Return a regular expression object for this pattern-flag pair, or // `null` in case the current environment doesn't support the flags it // uses. try { return new RegExp(pattern, flags); } catch (exception) { return null; } } function scanRegExpBody() { var ch, str, classMarker, terminated, body; ch = source[index]; assert(ch === "/", "Regular expression literal must start with a slash"); str = source[index++]; classMarker = false; terminated = false; while (index < length) { ch = source[index++]; str += ch; if (ch === "\\") { ch = source[index++]; // ECMA-262 7.8.5 if (syntax.isLineTerminator(ch.charCodeAt(0))) { throwError({}, Messages.UnterminatedRegExp); } str += ch; } else if (syntax.isLineTerminator(ch.charCodeAt(0))) { throwError({}, Messages.UnterminatedRegExp); } else if (classMarker) { if (ch === "]") { classMarker = false; } } else { if (ch === "/") { terminated = true; break; } else if (ch === "[") { classMarker = true; } } } if (!terminated) { throwError({}, Messages.UnterminatedRegExp); } // Exclude leading and trailing slash. body = str.substr(1, str.length - 2); return { value: body, literal: str }; } function scanRegExpFlags() { var ch, str, flags, restore; str = ""; flags = ""; while (index < length) { ch = source[index]; if (!syntax.isIdentifierPart(ch.charCodeAt(0))) { break; } ++index; if (ch === "\\" && index < length) { ch = source[index]; if (ch === "u") { ++index; restore = index; ch = scanHexEscape("u"); if (ch) { flags += ch; for (str += "\\u"; restore < index; ++restore) { str += source[restore]; } } else { index = restore; flags += "u"; str += "\\u"; } throwErrorTolerant({}, Messages.UnexpectedToken, "ILLEGAL"); } else { str += "\\"; throwErrorTolerant({}, Messages.UnexpectedToken, "ILLEGAL"); } } else { flags += ch; str += ch; } } return { value: flags, literal: str }; } function scanRegExp() { var start, body, flags, value; lookahead = null; skipComment(); start = index; body = scanRegExpBody(); flags = scanRegExpFlags(); value = testRegExp(body.value, flags.value); if (extra.tokenize) { return { type: Token.RegularExpression, value: value, regex: { pattern: body.value, flags: flags.value }, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } return { literal: body.literal + flags.literal, value: value, regex: { pattern: body.value, flags: flags.value }, range: [start, index] }; } function collectRegex() { var pos, loc, regex, token; skipComment(); pos = index; loc = { start: { line: lineNumber, column: index - lineStart } }; regex = scanRegExp(); loc.end = { line: lineNumber, column: index - lineStart }; /* istanbul ignore next */ if (!extra.tokenize) { // Pop the previous token, which is likely "/" or "/=" if (extra.tokens.length > 0) { token = extra.tokens[extra.tokens.length - 1]; if (token.range[0] === pos && token.type === "Punctuator") { if (token.value === "/" || token.value === "/=") { extra.tokens.pop(); } } } extra.tokens.push({ type: "RegularExpression", value: regex.literal, regex: regex.regex, range: [pos, index], loc: loc }); } return regex; } function isIdentifierName(token) { return token.type === Token.Identifier || token.type === Token.Keyword || token.type === Token.BooleanLiteral || token.type === Token.NullLiteral; } function advanceSlash() { var prevToken, checkToken; // Using the following algorithm: // https://github.com/mozilla/sweet.js/wiki/design prevToken = extra.tokens[extra.tokens.length - 1]; if (!prevToken) { // Nothing before that: it cannot be a division. return collectRegex(); } if (prevToken.type === "Punctuator") { if (prevToken.value === "]") { return scanPunctuator(); } if (prevToken.value === ")") { checkToken = extra.tokens[extra.openParenToken - 1]; if (checkToken && checkToken.type === "Keyword" && (checkToken.value === "if" || checkToken.value === "while" || checkToken.value === "for" || checkToken.value === "with")) { return collectRegex(); } return scanPunctuator(); } if (prevToken.value === "}") { // Dividing a function by anything makes little sense, // but we have to check for that. if (extra.tokens[extra.openCurlyToken - 3] && extra.tokens[extra.openCurlyToken - 3].type === "Keyword") { // Anonymous function. checkToken = extra.tokens[extra.openCurlyToken - 4]; if (!checkToken) { return scanPunctuator(); } } else if (extra.tokens[extra.openCurlyToken - 4] && extra.tokens[extra.openCurlyToken - 4].type === "Keyword") { // Named function. checkToken = extra.tokens[extra.openCurlyToken - 5]; if (!checkToken) { return collectRegex(); } } else { return scanPunctuator(); } // checkToken determines whether the function is // a declaration or an expression. if (FnExprTokens.indexOf(checkToken.value) >= 0) { // It is an expression. return scanPunctuator(); } // It is a declaration. return collectRegex(); } return collectRegex(); } if (prevToken.type === "Keyword") { return collectRegex(); } return scanPunctuator(); } function advance() { var ch, allowJSX = extra.ecmaFeatures.jsx, allowTemplateStrings = extra.ecmaFeatures.templateStrings; /* * If JSX isn't allowed or JSX is allowed and we're not inside an JSX child, * then skip any comments. */ if (!allowJSX || !state.inJSXChild) { skipComment(); } if (index >= length) { return { type: Token.EOF, lineNumber: lineNumber, lineStart: lineStart, range: [index, index] }; } // if inside an JSX child, then abort regular tokenization if (allowJSX && state.inJSXChild) { return advanceJSXChild(); } ch = source.charCodeAt(index); // Very common: ( and ) and ; if (ch === 0x28 || ch === 0x29 || ch === 0x3B) { return scanPunctuator(); } // String literal starts with single quote (U+0027) or double quote (U+0022). if (ch === 0x27 || ch === 0x22) { if (allowJSX && state.inJSXTag) { return scanJSXStringLiteral(); } return scanStringLiteral(); } if (allowJSX && state.inJSXTag && syntax.isJSXIdentifierStart(ch)) { return scanJSXIdentifier(); } // Template strings start with backtick (U+0096) or closing curly brace (125) and backtick. if (allowTemplateStrings) { // template strings start with backtick (96) or open curly (125) but only if the open // curly closes a previously opened curly from a template. if (ch === 96 || (ch === 125 && state.curlyStack[state.curlyStack.length - 1] === "template")) { return scanTemplate(); } } if (syntax.isIdentifierStart(ch)) { return scanIdentifier(); } // Dot (.) U+002E can also start a floating-point number, hence the need // to check the next character. if (ch === 0x2E) { if (syntax.isDecimalDigit(source.charCodeAt(index + 1))) { return scanNumericLiteral(); } return scanPunctuator(); } if (syntax.isDecimalDigit(ch)) { return scanNumericLiteral(); } // Slash (/) U+002F can also start a regex. if (extra.tokenize && ch === 0x2F) { return advanceSlash(); } return scanPunctuator(); } function collectToken() { var loc, token, range, value, entry, allowJSX = extra.ecmaFeatures.jsx; /* istanbul ignore else */ if (!allowJSX || !state.inJSXChild) { skipComment(); } loc = { start: { line: lineNumber, column: index - lineStart } }; token = advance(); loc.end = { line: lineNumber, column: index - lineStart }; if (token.type !== Token.EOF) { range = [token.range[0], token.range[1]]; value = source.slice(token.range[0], token.range[1]); entry = { type: TokenName[token.type], value: value, range: range, loc: loc }; if (token.regex) { entry.regex = { pattern: token.regex.pattern, flags: token.regex.flags }; } extra.tokens.push(entry); } return token; } function lex() { var token; token = lookahead; index = token.range[1]; lineNumber = token.lineNumber; lineStart = token.lineStart; lookahead = (typeof extra.tokens !== "undefined") ? collectToken() : advance(); index = token.range[1]; lineNumber = token.lineNumber; lineStart = token.lineStart; return token; } function peek() { var pos, line, start; pos = index; line = lineNumber; start = lineStart; lookahead = (typeof extra.tokens !== "undefined") ? collectToken() : advance(); index = pos; lineNumber = line; lineStart = start; } function lookahead2() { var adv, pos, line, start, result; // If we are collecting the tokens, don't grab the next one yet. /* istanbul ignore next */ adv = (typeof extra.advance === "function") ? extra.advance : advance; pos = index; line = lineNumber; start = lineStart; // Scan for the next immediate token. /* istanbul ignore if */ if (lookahead === null) { lookahead = adv(); } index = lookahead.range[1]; lineNumber = lookahead.lineNumber; lineStart = lookahead.lineStart; // Grab the token right after. result = adv(); index = pos; lineNumber = line; lineStart = start; return result; } //------------------------------------------------------------------------------ // JSX //------------------------------------------------------------------------------ function getQualifiedJSXName(object) { if (object.type === astNodeTypes.JSXIdentifier) { return object.name; } if (object.type === astNodeTypes.JSXNamespacedName) { return object.namespace.name + ":" + object.name.name; } /* istanbul ignore else */ if (object.type === astNodeTypes.JSXMemberExpression) { return ( getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property) ); } /* istanbul ignore next */ throwUnexpected(object); } function scanJSXIdentifier() { var ch, start, value = ""; start = index; while (index < length) { ch = source.charCodeAt(index); if (!syntax.isJSXIdentifierPart(ch)) { break; } value += source[index++]; } return { type: Token.JSXIdentifier, value: value, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function scanJSXEntity() { var ch, str = "", start = index, count = 0, code; ch = source[index]; assert(ch === "&", "Entity must start with an ampersand"); index++; while (index < length && count++ < 10) { ch = source[index++]; if (ch === ";") { break; } str += ch; } // Well-formed entity (ending was found). if (ch === ";") { // Numeric entity. if (str[0] === "#") { if (str[1] === "x") { code = +("0" + str.substr(1)); } else { // Removing leading zeros in order to avoid treating as octal in old browsers. code = +str.substr(1).replace(Regex.LeadingZeros, ""); } if (!isNaN(code)) { return String.fromCharCode(code); } /* istanbul ignore else */ } else if (XHTMLEntities[str]) { return XHTMLEntities[str]; } } // Treat non-entity sequences as regular text. index = start + 1; return "&"; } function scanJSXText(stopChars) { var ch, str = "", start; start = index; while (index < length) { ch = source[index]; if (stopChars.indexOf(ch) !== -1) { break; } if (ch === "&") { str += scanJSXEntity(); } else { index++; if (ch === "\r" && source[index] === "\n") { str += ch; ch = source[index]; index++; } if (syntax.isLineTerminator(ch.charCodeAt(0))) { ++lineNumber; lineStart = index; } str += ch; } } return { type: Token.JSXText, value: str, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] }; } function scanJSXStringLiteral() { var innerToken, quote, start; quote = source[index]; assert((quote === "\"" || quote === "'"), "String literal must starts with a quote"); start = index; ++index; innerToken = scanJSXText([quote]); if (quote !== source[index]) { throwError({}, Messages.UnexpectedToken, "ILLEGAL"); } ++index; innerToken.range = [start, index]; return innerToken; } /* * Between JSX opening and closing tags (e.g. <foo>HERE</foo>), anything that * is not another JSX tag and is not an expression wrapped by {} is text. */ function advanceJSXChild() { var ch = source.charCodeAt(index); // { (123) and < (60) if (ch !== 123 && ch !== 60) { return scanJSXText(["<", "{"]); } return scanPunctuator(); } function parseJSXIdentifier() { var token, marker = markerCreate(); if (lookahead.type !== Token.JSXIdentifier) { throwUnexpected(lookahead); } token = lex(); return markerApply(marker, astNodeFactory.createJSXIdentifier(token.value)); } function parseJSXNamespacedName() { var namespace, name, marker = markerCreate(); namespace = parseJSXIdentifier(); expect(":"); name = parseJSXIdentifier(); return markerApply(marker, astNodeFactory.createJSXNamespacedName(namespace, name)); } function parseJSXMemberExpression() { var marker = markerCreate(), expr = parseJSXIdentifier(); while (match(".")) { lex(); expr = markerApply(marker, astNodeFactory.createJSXMemberExpression(expr, parseJSXIdentifier())); } return expr; } function parseJSXElementName() { if (lookahead2().value === ":") { return parseJSXNamespacedName(); } if (lookahead2().value === ".") { return parseJSXMemberExpression(); } return parseJSXIdentifier(); } function parseJSXAttributeName() { if (lookahead2().value === ":") { return parseJSXNamespacedName(); } return parseJSXIdentifier(); } function parseJSXAttributeValue() { var value, marker; if (match("{")) { value = parseJSXExpressionContainer(); if (value.expression.type === astNodeTypes.JSXEmptyExpression) { throwError( value, "JSX attributes must only be assigned a non-empty " + "expression" ); } } else if (match("<")) { value = parseJSXElement(); } else if (lookahead.type === Token.JSXText) { marker = markerCreate(); value = markerApply(marker, astNodeFactory.createLiteralFromSource(lex(), source)); } else { throwError({}, Messages.InvalidJSXAttributeValue); } return value; } function parseJSXEmptyExpression() { var marker = markerCreatePreserveWhitespace(); while (source.charAt(index) !== "}") { index++; } return markerApply(marker, astNodeFactory.createJSXEmptyExpression()); } function parseJSXExpressionContainer() { var expression, origInJSXChild, origInJSXTag, marker = markerCreate(); origInJSXChild = state.inJSXChild; origInJSXTag = state.inJSXTag; state.inJSXChild = false; state.inJSXTag = false; expect("{"); if (match("}")) { expression = parseJSXEmptyExpression(); } else { expression = parseExpression(); } state.inJSXChild = origInJSXChild; state.inJSXTag = origInJSXTag; expect("}"); return markerApply(marker, astNodeFactory.createJSXExpressionContainer(expression)); } function parseJSXSpreadAttribute() { var expression, origInJSXChild, origInJSXTag, marker = markerCreate(); origInJSXChild = state.inJSXChild; origInJSXTag = state.inJSXTag; state.inJSXChild = false; state.inJSXTag = false; state.inJSXSpreadAttribute = true; expect("{"); expect("..."); state.inJSXSpreadAttribute = false; expression = parseAssignmentExpression(); state.inJSXChild = origInJSXChild; state.inJSXTag = origInJSXTag; expect("}"); return markerApply(marker, astNodeFactory.createJSXSpreadAttribute(expression)); } function parseJSXAttribute() { var name, marker; if (match("{")) { return parseJSXSpreadAttribute(); } marker = markerCreate(); name = parseJSXAttributeName(); // HTML e