Spaces:
Runtime error
Runtime error
/** | |
* @fileoverview Translates tokens between Acorn format and Esprima format. | |
* @author Nicholas C. Zakas | |
*/ | |
/* eslint no-underscore-dangle: 0 */ | |
; | |
//------------------------------------------------------------------------------ | |
// 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; | |