UNPKG

8.17 kBJavaScriptView Raw
1/**
2 * @fileoverview Translates tokens between Acorn format and Esprima format.
3 * @author Nicholas C. Zakas
4 */
5/* eslint no-underscore-dangle: 0 */
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11// none!
12
13//------------------------------------------------------------------------------
14// Private
15//------------------------------------------------------------------------------
16
17
18// Esprima Token Types
19const Token = {
20 Boolean: "Boolean",
21 EOF: "<end>",
22 Identifier: "Identifier",
23 PrivateIdentifier: "PrivateIdentifier",
24 Keyword: "Keyword",
25 Null: "Null",
26 Numeric: "Numeric",
27 Punctuator: "Punctuator",
28 String: "String",
29 RegularExpression: "RegularExpression",
30 Template: "Template",
31 JSXIdentifier: "JSXIdentifier",
32 JSXText: "JSXText"
33};
34
35/**
36 * Converts part of a template into an Esprima token.
37 * @param {AcornToken[]} tokens The Acorn tokens representing the template.
38 * @param {string} code The source code.
39 * @returns {EsprimaToken} The Esprima equivalent of the template token.
40 * @private
41 */
42function convertTemplatePart(tokens, code) {
43 const firstToken = tokens[0],
44 lastTemplateToken = tokens[tokens.length - 1];
45
46 const token = {
47 type: Token.Template,
48 value: code.slice(firstToken.start, lastTemplateToken.end)
49 };
50
51 if (firstToken.loc) {
52 token.loc = {
53 start: firstToken.loc.start,
54 end: lastTemplateToken.loc.end
55 };
56 }
57
58 if (firstToken.range) {
59 token.start = firstToken.range[0];
60 token.end = lastTemplateToken.range[1];
61 token.range = [token.start, token.end];
62 }
63
64 return token;
65}
66
67/**
68 * Contains logic to translate Acorn tokens into Esprima tokens.
69 * @param {Object} acornTokTypes The Acorn token types.
70 * @param {string} code The source code Acorn is parsing. This is necessary
71 * to correct the "value" property of some tokens.
72 * @constructor
73 */
74function TokenTranslator(acornTokTypes, code) {
75
76 // token types
77 this._acornTokTypes = acornTokTypes;
78
79 // token buffer for templates
80 this._tokens = [];
81
82 // track the last curly brace
83 this._curlyBrace = null;
84
85 // the source code
86 this._code = code;
87
88}
89
90TokenTranslator.prototype = {
91 constructor: TokenTranslator,
92
93 /**
94 * Translates a single Esprima token to a single Acorn token. This may be
95 * inaccurate due to how templates are handled differently in Esprima and
96 * Acorn, but should be accurate for all other tokens.
97 * @param {AcornToken} token The Acorn token to translate.
98 * @param {Object} extra Espree extra object.
99 * @returns {EsprimaToken} The Esprima version of the token.
100 */
101 translate(token, extra) {
102
103 const type = token.type,
104 tt = this._acornTokTypes;
105
106 if (type === tt.name) {
107 token.type = Token.Identifier;
108
109 // TODO: See if this is an Acorn bug
110 if (token.value === "static") {
111 token.type = Token.Keyword;
112 }
113
114 if (extra.ecmaVersion > 5 && (token.value === "yield" || token.value === "let")) {
115 token.type = Token.Keyword;
116 }
117
118 } else if (type === tt.privateId) {
119 token.type = Token.PrivateIdentifier;
120
121 } else if (type === tt.semi || type === tt.comma ||
122 type === tt.parenL || type === tt.parenR ||
123 type === tt.braceL || type === tt.braceR ||
124 type === tt.dot || type === tt.bracketL ||
125 type === tt.colon || type === tt.question ||
126 type === tt.bracketR || type === tt.ellipsis ||
127 type === tt.arrow || type === tt.jsxTagStart ||
128 type === tt.incDec || type === tt.starstar ||
129 type === tt.jsxTagEnd || type === tt.prefix ||
130 type === tt.questionDot ||
131 (type.binop && !type.keyword) ||
132 type.isAssign) {
133
134 token.type = Token.Punctuator;
135 token.value = this._code.slice(token.start, token.end);
136 } else if (type === tt.jsxName) {
137 token.type = Token.JSXIdentifier;
138 } else if (type.label === "jsxText" || type === tt.jsxAttrValueToken) {
139 token.type = Token.JSXText;
140 } else if (type.keyword) {
141 if (type.keyword === "true" || type.keyword === "false") {
142 token.type = Token.Boolean;
143 } else if (type.keyword === "null") {
144 token.type = Token.Null;
145 } else {
146 token.type = Token.Keyword;
147 }
148 } else if (type === tt.num) {
149 token.type = Token.Numeric;
150 token.value = this._code.slice(token.start, token.end);
151 } else if (type === tt.string) {
152
153 if (extra.jsxAttrValueToken) {
154 extra.jsxAttrValueToken = false;
155 token.type = Token.JSXText;
156 } else {
157 token.type = Token.String;
158 }
159
160 token.value = this._code.slice(token.start, token.end);
161 } else if (type === tt.regexp) {
162 token.type = Token.RegularExpression;
163 const value = token.value;
164
165 token.regex = {
166 flags: value.flags,
167 pattern: value.pattern
168 };
169 token.value = `/${value.pattern}/${value.flags}`;
170 }
171
172 return token;
173 },
174
175 /**
176 * Function to call during Acorn's onToken handler.
177 * @param {AcornToken} token The Acorn token.
178 * @param {Object} extra The Espree extra object.
179 * @returns {void}
180 */
181 onToken(token, extra) {
182
183 const that = this,
184 tt = this._acornTokTypes,
185 tokens = extra.tokens,
186 templateTokens = this._tokens;
187
188 /**
189 * Flushes the buffered template tokens and resets the template
190 * tracking.
191 * @returns {void}
192 * @private
193 */
194 function translateTemplateTokens() {
195 tokens.push(convertTemplatePart(that._tokens, that._code));
196 that._tokens = [];
197 }
198
199 if (token.type === tt.eof) {
200
201 // might be one last curlyBrace
202 if (this._curlyBrace) {
203 tokens.push(this.translate(this._curlyBrace, extra));
204 }
205
206 return;
207 }
208
209 if (token.type === tt.backQuote) {
210
211 // if there's already a curly, it's not part of the template
212 if (this._curlyBrace) {
213 tokens.push(this.translate(this._curlyBrace, extra));
214 this._curlyBrace = null;
215 }
216
217 templateTokens.push(token);
218
219 // it's the end
220 if (templateTokens.length > 1) {
221 translateTemplateTokens();
222 }
223
224 return;
225 }
226 if (token.type === tt.dollarBraceL) {
227 templateTokens.push(token);
228 translateTemplateTokens();
229 return;
230 }
231 if (token.type === tt.braceR) {
232
233 // if there's already a curly, it's not part of the template
234 if (this._curlyBrace) {
235 tokens.push(this.translate(this._curlyBrace, extra));
236 }
237
238 // store new curly for later
239 this._curlyBrace = token;
240 return;
241 }
242 if (token.type === tt.template || token.type === tt.invalidTemplate) {
243 if (this._curlyBrace) {
244 templateTokens.push(this._curlyBrace);
245 this._curlyBrace = null;
246 }
247
248 templateTokens.push(token);
249 return;
250 }
251
252 if (this._curlyBrace) {
253 tokens.push(this.translate(this._curlyBrace, extra));
254 this._curlyBrace = null;
255 }
256
257 tokens.push(this.translate(token, extra));
258 }
259};
260
261//------------------------------------------------------------------------------
262// Public
263//------------------------------------------------------------------------------
264
265export default TokenTranslator;