123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /**
- * @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;
|