/** * @fileoverview Translates tokens between Acorn format and Esprima format. * @author Nicholas C. Zakas */ /* eslint no-underscore-dangle: 0 */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ // none! //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ // Esprima Token Types const Token = { Boolean: "Boolean", EOF: "", Identifier: "Identifier", Keyword: "Keyword", Null: "Null", Numeric: "Numeric", Punctuator: "Punctuator", String: "String", RegularExpression: "RegularExpression", Template: "Template", JSXIdentifier: "JSXIdentifier", JSXText: "JSXText" }; /** * Converts part of a template into an Esprima token. * @param {AcornToken[]} tokens The Acorn tokens representing the template. * @param {string} code The source code. * @returns {EsprimaToken} The Esprima equivalent of the template token. * @private */ function convertTemplatePart(tokens, code) { const firstToken = tokens[0], lastTemplateToken = tokens[tokens.length - 1]; const token = { type: Token.Template, value: code.slice(firstToken.start, lastTemplateToken.end) }; if (firstToken.loc) { token.loc = { start: firstToken.loc.start, end: lastTemplateToken.loc.end }; } if (firstToken.range) { token.start = firstToken.range[0]; token.end = lastTemplateToken.range[1]; token.range = [token.start, token.end]; } return token; } /** * Contains logic to translate Acorn tokens into Esprima tokens. * @param {Object} acornTokTypes The Acorn token types. * @param {string} code The source code Acorn is parsing. This is necessary * to correct the "value" property of some tokens. * @constructor */ function TokenTranslator(acornTokTypes, code) { // token types this._acornTokTypes = acornTokTypes; // token buffer for templates this._tokens = []; // track the last curly brace this._curlyBrace = null; // the source code this._code = code; } TokenTranslator.prototype = { constructor: TokenTranslator, /** * Translates a single Esprima token to a single Acorn token. This may be * inaccurate due to how templates are handled differently in Esprima and * Acorn, but should be accurate for all other tokens. * @param {AcornToken} token The Acorn token to translate. * @param {Object} extra Espree extra object. * @returns {EsprimaToken} The Esprima version of the token. */ translate(token, extra) { const type = token.type, tt = this._acornTokTypes; if (type === tt.name) { token.type = Token.Identifier; // TODO: See if this is an Acorn bug if (token.value === "static") { token.type = Token.Keyword; } if (extra.ecmaVersion > 5 && (token.value === "yield" || token.value === "let")) { token.type = Token.Keyword; } } else if (type === tt.semi || type === tt.comma || type === tt.parenL || type === tt.parenR || type === tt.braceL || type === tt.braceR || type === tt.dot || type === tt.bracketL || type === tt.colon || type === tt.question || type === tt.bracketR || type === tt.ellipsis || type === tt.arrow || type === tt.jsxTagStart || type === tt.incDec || type === tt.starstar || type === tt.jsxTagEnd || type === tt.prefix || (type.binop && !type.keyword) || type.isAssign) { token.type = Token.Punctuator; token.value = this._code.slice(token.start, token.end); } else if (type === tt.jsxName) { token.type = Token.JSXIdentifier; } else if (type.label === "jsxText" || type === tt.jsxAttrValueToken) { token.type = Token.JSXText; } else if (type.keyword) { if (type.keyword === "true" || type.keyword === "false") { token.type = Token.Boolean; } else if (type.keyword === "null") { token.type = Token.Null; } else { token.type = Token.Keyword; } } else if (type === tt.num) { token.type = Token.Numeric; token.value = this._code.slice(token.start, token.end); } else if (type === tt.string) { if (extra.jsxAttrValueToken) { extra.jsxAttrValueToken = false; token.type = Token.JSXText; } else { token.type = Token.String; } token.value = this._code.slice(token.start, token.end); } else if (type === tt.regexp) { token.type = Token.RegularExpression; const value = token.value; token.regex = { flags: value.flags, pattern: value.pattern }; token.value = `/${value.pattern}/${value.flags}`; } return token; }, /** * Function to call during Acorn's onToken handler. * @param {AcornToken} token The Acorn token. * @param {Object} extra The Espree extra object. * @returns {void} */ onToken(token, extra) { const that = this, tt = this._acornTokTypes, tokens = extra.tokens, templateTokens = this._tokens; /** * Flushes the buffered template tokens and resets the template * tracking. * @returns {void} * @private */ function translateTemplateTokens() { tokens.push(convertTemplatePart(that._tokens, that._code)); that._tokens = []; } if (token.type === tt.eof) { // might be one last curlyBrace if (this._curlyBrace) { tokens.push(this.translate(this._curlyBrace, extra)); } return; } if (token.type === tt.backQuote) { // if there's already a curly, it's not part of the template if (this._curlyBrace) { tokens.push(this.translate(this._curlyBrace, extra)); this._curlyBrace = null; } templateTokens.push(token); // it's the end if (templateTokens.length > 1) { translateTemplateTokens(); } return; } if (token.type === tt.dollarBraceL) { templateTokens.push(token); translateTemplateTokens(); return; } if (token.type === tt.braceR) { // if there's already a curly, it's not part of the template if (this._curlyBrace) { tokens.push(this.translate(this._curlyBrace, extra)); } // store new curly for later this._curlyBrace = token; return; } if (token.type === tt.template || token.type === tt.invalidTemplate) { if (this._curlyBrace) { templateTokens.push(this._curlyBrace); this._curlyBrace = null; } templateTokens.push(token); return; } if (this._curlyBrace) { tokens.push(this.translate(this._curlyBrace, extra)); this._curlyBrace = null; } tokens.push(this.translate(token, extra)); } }; //------------------------------------------------------------------------------ // Public //------------------------------------------------------------------------------ module.exports = TokenTranslator;