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 === tt.questionDot ||
129 (type.binop && !type.keyword) ||
132 token.type = Token.Punctuator;
133 token.value = this._code.slice(token.start, token.end);
134 } else if (type === tt.jsxName) {
135 token.type = Token.JSXIdentifier;
136 } else if (type.label === "jsxText" || type === tt.jsxAttrValueToken) {
137 token.type = Token.JSXText;
138 } else if (type.keyword) {
139 if (type.keyword === "true" || type.keyword === "false") {
140 token.type = Token.Boolean;
141 } else if (type.keyword === "null") {
142 token.type = Token.Null;
144 token.type = Token.Keyword;
146 } else if (type === tt.num) {
147 token.type = Token.Numeric;
148 token.value = this._code.slice(token.start, token.end);
149 } else if (type === tt.string) {
151 if (extra.jsxAttrValueToken) {
152 extra.jsxAttrValueToken = false;
153 token.type = Token.JSXText;
155 token.type = Token.String;
158 token.value = this._code.slice(token.start, token.end);
159 } else if (type === tt.regexp) {
160 token.type = Token.RegularExpression;
161 const value = token.value;
165 pattern: value.pattern
167 token.value = `/${value.pattern}/${value.flags}`;
174 * Function to call during Acorn's onToken handler.
175 * @param {AcornToken} token The Acorn token.
176 * @param {Object} extra The Espree extra object.
179 onToken(token, extra) {
182 tt = this._acornTokTypes,
183 tokens = extra.tokens,
184 templateTokens = this._tokens;
187 * Flushes the buffered template tokens and resets the template
192 function translateTemplateTokens() {
193 tokens.push(convertTemplatePart(that._tokens, that._code));
197 if (token.type === tt.eof) {
199 // might be one last curlyBrace
200 if (this._curlyBrace) {
201 tokens.push(this.translate(this._curlyBrace, extra));
207 if (token.type === tt.backQuote) {
209 // if there's already a curly, it's not part of the template
210 if (this._curlyBrace) {
211 tokens.push(this.translate(this._curlyBrace, extra));
212 this._curlyBrace = null;
215 templateTokens.push(token);
218 if (templateTokens.length > 1) {
219 translateTemplateTokens();
224 if (token.type === tt.dollarBraceL) {
225 templateTokens.push(token);
226 translateTemplateTokens();
229 if (token.type === tt.braceR) {
231 // if there's already a curly, it's not part of the template
232 if (this._curlyBrace) {
233 tokens.push(this.translate(this._curlyBrace, extra));
236 // store new curly for later
237 this._curlyBrace = token;
240 if (token.type === tt.template || token.type === tt.invalidTemplate) {
241 if (this._curlyBrace) {
242 templateTokens.push(this._curlyBrace);
243 this._curlyBrace = null;
246 templateTokens.push(token);
250 if (this._curlyBrace) {
251 tokens.push(this.translate(this._curlyBrace, extra));
252 this._curlyBrace = null;
255 tokens.push(this.translate(token, extra));
259 //------------------------------------------------------------------------------
261 //------------------------------------------------------------------------------
263 module.exports = TokenTranslator;