2 * @fileoverview Translates tokens between Acorn format and Esprima format.
3 * @author Nicholas C. Zakas
5 /* eslint no-underscore-dangle: 0 */
9 //------------------------------------------------------------------------------
11 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
17 //------------------------------------------------------------------------------
20 // Esprima Token Types
24 Identifier: "Identifier",
28 Punctuator: "Punctuator",
30 RegularExpression: "RegularExpression",
32 JSXIdentifier: "JSXIdentifier",
37 * Converts part of a template into an Esprima token.
38 * @param {AcornToken[]} tokens The Acorn tokens representing the template.
39 * @param {string} code The source code.
40 * @returns {EsprimaToken} The Esprima equivalent of the template token.
43 function convertTemplatePart(tokens, code) {
44 const firstToken = tokens[0],
45 lastTemplateToken = tokens[tokens.length - 1];
49 value: code.slice(firstToken.start, lastTemplateToken.end)
54 start: firstToken.loc.start,
55 end: lastTemplateToken.loc.end
59 if (firstToken.range) {
60 token.start = firstToken.range[0];
61 token.end = lastTemplateToken.range[1];
62 token.range = [token.start, token.end];
69 * Contains logic to translate Acorn tokens into Esprima tokens.
70 * @param {Object} acornTokTypes The Acorn token types.
71 * @param {string} code The source code Acorn is parsing. This is necessary
72 * to correct the "value" property of some tokens.
75 function TokenTranslator(acornTokTypes, code) {
78 this._acornTokTypes = acornTokTypes;
80 // token buffer for templates
83 // track the last curly brace
84 this._curlyBrace = null;
91 TokenTranslator.prototype = {
92 constructor: TokenTranslator,
95 * Translates a single Esprima token to a single Acorn token. This may be
96 * inaccurate due to how templates are handled differently in Esprima and
97 * Acorn, but should be accurate for all other tokens.
98 * @param {AcornToken} token The Acorn token to translate.
99 * @param {Object} extra Espree extra object.
100 * @returns {EsprimaToken} The Esprima version of the token.
102 translate(token, extra) {
104 const type = token.type,
105 tt = this._acornTokTypes;
107 if (type === tt.name) {
108 token.type = Token.Identifier;
110 // TODO: See if this is an Acorn bug
111 if (token.value === "static") {
112 token.type = Token.Keyword;
115 if (extra.ecmaVersion > 5 && (token.value === "yield" || token.value === "let")) {
116 token.type = Token.Keyword;
119 } else if (type === tt.semi || type === tt.comma ||
120 type === tt.parenL || type === tt.parenR ||
121 type === tt.braceL || type === tt.braceR ||
122 type === tt.dot || type === tt.bracketL ||
123 type === tt.colon || type === tt.question ||
124 type === tt.bracketR || type === tt.ellipsis ||
125 type === tt.arrow || type === tt.jsxTagStart ||
126 type === tt.incDec || type === tt.starstar ||
127 type === tt.jsxTagEnd || type === tt.prefix ||
128 (type.binop && !type.keyword) ||
131 token.type = Token.Punctuator;
132 token.value = this._code.slice(token.start, token.end);
133 } else if (type === tt.jsxName) {
134 token.type = Token.JSXIdentifier;
135 } else if (type.label === "jsxText" || type === tt.jsxAttrValueToken) {
136 token.type = Token.JSXText;
137 } else if (type.keyword) {
138 if (type.keyword === "true" || type.keyword === "false") {
139 token.type = Token.Boolean;
140 } else if (type.keyword === "null") {
141 token.type = Token.Null;
143 token.type = Token.Keyword;
145 } else if (type === tt.num) {
146 token.type = Token.Numeric;
147 token.value = this._code.slice(token.start, token.end);
148 } else if (type === tt.string) {
150 if (extra.jsxAttrValueToken) {
151 extra.jsxAttrValueToken = false;
152 token.type = Token.JSXText;
154 token.type = Token.String;
157 token.value = this._code.slice(token.start, token.end);
158 } else if (type === tt.regexp) {
159 token.type = Token.RegularExpression;
160 const value = token.value;
164 pattern: value.pattern
166 token.value = `/${value.pattern}/${value.flags}`;
173 * Function to call during Acorn's onToken handler.
174 * @param {AcornToken} token The Acorn token.
175 * @param {Object} extra The Espree extra object.
178 onToken(token, extra) {
181 tt = this._acornTokTypes,
182 tokens = extra.tokens,
183 templateTokens = this._tokens;
186 * Flushes the buffered template tokens and resets the template
191 function translateTemplateTokens() {
192 tokens.push(convertTemplatePart(that._tokens, that._code));
196 if (token.type === tt.eof) {
198 // might be one last curlyBrace
199 if (this._curlyBrace) {
200 tokens.push(this.translate(this._curlyBrace, extra));
206 if (token.type === tt.backQuote) {
208 // if there's already a curly, it's not part of the template
209 if (this._curlyBrace) {
210 tokens.push(this.translate(this._curlyBrace, extra));
211 this._curlyBrace = null;
214 templateTokens.push(token);
217 if (templateTokens.length > 1) {
218 translateTemplateTokens();
223 if (token.type === tt.dollarBraceL) {
224 templateTokens.push(token);
225 translateTemplateTokens();
228 if (token.type === tt.braceR) {
230 // if there's already a curly, it's not part of the template
231 if (this._curlyBrace) {
232 tokens.push(this.translate(this._curlyBrace, extra));
235 // store new curly for later
236 this._curlyBrace = token;
239 if (token.type === tt.template || token.type === tt.invalidTemplate) {
240 if (this._curlyBrace) {
241 templateTokens.push(this._curlyBrace);
242 this._curlyBrace = null;
245 templateTokens.push(token);
249 if (this._curlyBrace) {
250 tokens.push(this.translate(this._curlyBrace, extra));
251 this._curlyBrace = null;
254 tokens.push(this.translate(token, extra));
258 //------------------------------------------------------------------------------
260 //------------------------------------------------------------------------------
262 module.exports = TokenTranslator;