token-translator.js 8.0 KB

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