|
|
/** * @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: "<end>", 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 === tt.questionDot || (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;
|