index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. "use strict";Object.defineProperty(exports, "__esModule", {value: true});
  2. var _index = require('../../tokenizer/index');
  3. var _types = require('../../tokenizer/types');
  4. var _base = require('../../traverser/base');
  5. var _expression = require('../../traverser/expression');
  6. var _util = require('../../traverser/util');
  7. var _charcodes = require('../../util/charcodes');
  8. var _identifier = require('../../util/identifier');
  9. var _typescript = require('../typescript');
  10. /**
  11. * Read token with JSX contents.
  12. *
  13. * In addition to detecting jsxTagStart and also regular tokens that might be
  14. * part of an expression, this code detects the start and end of text ranges
  15. * within JSX children. In order to properly count the number of children, we
  16. * distinguish jsxText from jsxEmptyText, which is a text range that simplifies
  17. * to the empty string after JSX whitespace trimming.
  18. *
  19. * It turns out that a JSX text range will simplify to the empty string if and
  20. * only if both of these conditions hold:
  21. * - The range consists entirely of whitespace characters (only counting space,
  22. * tab, \r, and \n).
  23. * - The range has at least one newline.
  24. * This can be proven by analyzing any implementation of whitespace trimming,
  25. * e.g. formatJSXTextLiteral in Sucrase or cleanJSXElementLiteralChild in Babel.
  26. */
  27. function jsxReadToken() {
  28. let sawNewline = false;
  29. let sawNonWhitespace = false;
  30. while (true) {
  31. if (_base.state.pos >= _base.input.length) {
  32. _util.unexpected.call(void 0, "Unterminated JSX contents");
  33. return;
  34. }
  35. const ch = _base.input.charCodeAt(_base.state.pos);
  36. if (ch === _charcodes.charCodes.lessThan || ch === _charcodes.charCodes.leftCurlyBrace) {
  37. if (_base.state.pos === _base.state.start) {
  38. if (ch === _charcodes.charCodes.lessThan) {
  39. _base.state.pos++;
  40. _index.finishToken.call(void 0, _types.TokenType.jsxTagStart);
  41. return;
  42. }
  43. _index.getTokenFromCode.call(void 0, ch);
  44. return;
  45. }
  46. if (sawNewline && !sawNonWhitespace) {
  47. _index.finishToken.call(void 0, _types.TokenType.jsxEmptyText);
  48. } else {
  49. _index.finishToken.call(void 0, _types.TokenType.jsxText);
  50. }
  51. return;
  52. }
  53. // This is part of JSX text.
  54. if (ch === _charcodes.charCodes.lineFeed) {
  55. sawNewline = true;
  56. } else if (ch !== _charcodes.charCodes.space && ch !== _charcodes.charCodes.carriageReturn && ch !== _charcodes.charCodes.tab) {
  57. sawNonWhitespace = true;
  58. }
  59. _base.state.pos++;
  60. }
  61. }
  62. function jsxReadString(quote) {
  63. _base.state.pos++;
  64. for (;;) {
  65. if (_base.state.pos >= _base.input.length) {
  66. _util.unexpected.call(void 0, "Unterminated string constant");
  67. return;
  68. }
  69. const ch = _base.input.charCodeAt(_base.state.pos);
  70. if (ch === quote) {
  71. _base.state.pos++;
  72. break;
  73. }
  74. _base.state.pos++;
  75. }
  76. _index.finishToken.call(void 0, _types.TokenType.string);
  77. }
  78. // Read a JSX identifier (valid tag or attribute name).
  79. //
  80. // Optimized version since JSX identifiers can't contain
  81. // escape characters and so can be read as single slice.
  82. // Also assumes that first character was already checked
  83. // by isIdentifierStart in readToken.
  84. function jsxReadWord() {
  85. let ch;
  86. do {
  87. if (_base.state.pos > _base.input.length) {
  88. _util.unexpected.call(void 0, "Unexpectedly reached the end of input.");
  89. return;
  90. }
  91. ch = _base.input.charCodeAt(++_base.state.pos);
  92. } while (_identifier.IS_IDENTIFIER_CHAR[ch] || ch === _charcodes.charCodes.dash);
  93. _index.finishToken.call(void 0, _types.TokenType.jsxName);
  94. }
  95. // Parse next token as JSX identifier
  96. function jsxParseIdentifier() {
  97. nextJSXTagToken();
  98. }
  99. // Parse namespaced identifier.
  100. function jsxParseNamespacedName(identifierRole) {
  101. jsxParseIdentifier();
  102. if (!_index.eat.call(void 0, _types.TokenType.colon)) {
  103. // Plain identifier, so this is an access.
  104. _base.state.tokens[_base.state.tokens.length - 1].identifierRole = identifierRole;
  105. return;
  106. }
  107. // Process the second half of the namespaced name.
  108. jsxParseIdentifier();
  109. }
  110. // Parses element name in any form - namespaced, member
  111. // or single identifier.
  112. function jsxParseElementName() {
  113. const firstTokenIndex = _base.state.tokens.length;
  114. jsxParseNamespacedName(_index.IdentifierRole.Access);
  115. let hadDot = false;
  116. while (_index.match.call(void 0, _types.TokenType.dot)) {
  117. hadDot = true;
  118. nextJSXTagToken();
  119. jsxParseIdentifier();
  120. }
  121. // For tags like <div> with a lowercase letter and no dots, the name is
  122. // actually *not* an identifier access, since it's referring to a built-in
  123. // tag name. Remove the identifier role in this case so that it's not
  124. // accidentally transformed by the imports transform when preserving JSX.
  125. if (!hadDot) {
  126. const firstToken = _base.state.tokens[firstTokenIndex];
  127. const firstChar = _base.input.charCodeAt(firstToken.start);
  128. if (firstChar >= _charcodes.charCodes.lowercaseA && firstChar <= _charcodes.charCodes.lowercaseZ) {
  129. firstToken.identifierRole = null;
  130. }
  131. }
  132. }
  133. // Parses any type of JSX attribute value.
  134. function jsxParseAttributeValue() {
  135. switch (_base.state.type) {
  136. case _types.TokenType.braceL:
  137. _index.next.call(void 0, );
  138. _expression.parseExpression.call(void 0, );
  139. nextJSXTagToken();
  140. return;
  141. case _types.TokenType.jsxTagStart:
  142. jsxParseElement();
  143. nextJSXTagToken();
  144. return;
  145. case _types.TokenType.string:
  146. nextJSXTagToken();
  147. return;
  148. default:
  149. _util.unexpected.call(void 0, "JSX value should be either an expression or a quoted JSX text");
  150. }
  151. }
  152. // Parse JSX spread child, after already processing the {
  153. // Does not parse the closing }
  154. function jsxParseSpreadChild() {
  155. _util.expect.call(void 0, _types.TokenType.ellipsis);
  156. _expression.parseExpression.call(void 0, );
  157. }
  158. // Parses JSX opening tag starting after "<".
  159. // Returns true if the tag was self-closing.
  160. // Does not parse the last token.
  161. function jsxParseOpeningElement(initialTokenIndex) {
  162. if (_index.match.call(void 0, _types.TokenType.jsxTagEnd)) {
  163. // This is an open-fragment.
  164. return false;
  165. }
  166. jsxParseElementName();
  167. if (_base.isTypeScriptEnabled) {
  168. _typescript.tsTryParseJSXTypeArgument.call(void 0, );
  169. }
  170. let hasSeenPropSpread = false;
  171. while (!_index.match.call(void 0, _types.TokenType.slash) && !_index.match.call(void 0, _types.TokenType.jsxTagEnd) && !_base.state.error) {
  172. if (_index.eat.call(void 0, _types.TokenType.braceL)) {
  173. hasSeenPropSpread = true;
  174. _util.expect.call(void 0, _types.TokenType.ellipsis);
  175. _expression.parseMaybeAssign.call(void 0, );
  176. // }
  177. nextJSXTagToken();
  178. continue;
  179. }
  180. if (
  181. hasSeenPropSpread &&
  182. _base.state.end - _base.state.start === 3 &&
  183. _base.input.charCodeAt(_base.state.start) === _charcodes.charCodes.lowercaseK &&
  184. _base.input.charCodeAt(_base.state.start + 1) === _charcodes.charCodes.lowercaseE &&
  185. _base.input.charCodeAt(_base.state.start + 2) === _charcodes.charCodes.lowercaseY
  186. ) {
  187. _base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.KeyAfterPropSpread;
  188. }
  189. jsxParseNamespacedName(_index.IdentifierRole.ObjectKey);
  190. if (_index.match.call(void 0, _types.TokenType.eq)) {
  191. nextJSXTagToken();
  192. jsxParseAttributeValue();
  193. }
  194. }
  195. const isSelfClosing = _index.match.call(void 0, _types.TokenType.slash);
  196. if (isSelfClosing) {
  197. // /
  198. nextJSXTagToken();
  199. }
  200. return isSelfClosing;
  201. }
  202. // Parses JSX closing tag starting after "</".
  203. // Does not parse the last token.
  204. function jsxParseClosingElement() {
  205. if (_index.match.call(void 0, _types.TokenType.jsxTagEnd)) {
  206. // Fragment syntax, so we immediately have a tag end.
  207. return;
  208. }
  209. jsxParseElementName();
  210. }
  211. // Parses entire JSX element, including its opening tag
  212. // (starting after "<"), attributes, contents and closing tag.
  213. // Does not parse the last token.
  214. function jsxParseElementAt() {
  215. const initialTokenIndex = _base.state.tokens.length - 1;
  216. _base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.NoChildren;
  217. let numExplicitChildren = 0;
  218. const isSelfClosing = jsxParseOpeningElement(initialTokenIndex);
  219. if (!isSelfClosing) {
  220. nextJSXExprToken();
  221. while (true) {
  222. switch (_base.state.type) {
  223. case _types.TokenType.jsxTagStart:
  224. nextJSXTagToken();
  225. if (_index.match.call(void 0, _types.TokenType.slash)) {
  226. nextJSXTagToken();
  227. jsxParseClosingElement();
  228. // Key after prop spread takes precedence over number of children,
  229. // since it means we switch to createElement, which doesn't care
  230. // about number of children.
  231. if (_base.state.tokens[initialTokenIndex].jsxRole !== _index.JSXRole.KeyAfterPropSpread) {
  232. if (numExplicitChildren === 1) {
  233. _base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.OneChild;
  234. } else if (numExplicitChildren > 1) {
  235. _base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.StaticChildren;
  236. }
  237. }
  238. return;
  239. }
  240. numExplicitChildren++;
  241. jsxParseElementAt();
  242. nextJSXExprToken();
  243. break;
  244. case _types.TokenType.jsxText:
  245. numExplicitChildren++;
  246. nextJSXExprToken();
  247. break;
  248. case _types.TokenType.jsxEmptyText:
  249. nextJSXExprToken();
  250. break;
  251. case _types.TokenType.braceL:
  252. _index.next.call(void 0, );
  253. if (_index.match.call(void 0, _types.TokenType.ellipsis)) {
  254. jsxParseSpreadChild();
  255. nextJSXExprToken();
  256. // Spread children are a mechanism to explicitly mark children as
  257. // static, so count it as 2 children to satisfy the "more than one
  258. // child" condition.
  259. numExplicitChildren += 2;
  260. } else {
  261. // If we see {}, this is an empty pseudo-expression that doesn't
  262. // count as a child.
  263. if (!_index.match.call(void 0, _types.TokenType.braceR)) {
  264. numExplicitChildren++;
  265. _expression.parseExpression.call(void 0, );
  266. }
  267. nextJSXExprToken();
  268. }
  269. break;
  270. // istanbul ignore next - should never happen
  271. default:
  272. _util.unexpected.call(void 0, );
  273. return;
  274. }
  275. }
  276. }
  277. }
  278. // Parses entire JSX element from current position.
  279. // Does not parse the last token.
  280. function jsxParseElement() {
  281. nextJSXTagToken();
  282. jsxParseElementAt();
  283. } exports.jsxParseElement = jsxParseElement;
  284. // ==================================
  285. // Overrides
  286. // ==================================
  287. function nextJSXTagToken() {
  288. _base.state.tokens.push(new (0, _index.Token)());
  289. _index.skipSpace.call(void 0, );
  290. _base.state.start = _base.state.pos;
  291. const code = _base.input.charCodeAt(_base.state.pos);
  292. if (_identifier.IS_IDENTIFIER_START[code]) {
  293. jsxReadWord();
  294. } else if (code === _charcodes.charCodes.quotationMark || code === _charcodes.charCodes.apostrophe) {
  295. jsxReadString(code);
  296. } else {
  297. // The following tokens are just one character each.
  298. ++_base.state.pos;
  299. switch (code) {
  300. case _charcodes.charCodes.greaterThan:
  301. _index.finishToken.call(void 0, _types.TokenType.jsxTagEnd);
  302. break;
  303. case _charcodes.charCodes.lessThan:
  304. _index.finishToken.call(void 0, _types.TokenType.jsxTagStart);
  305. break;
  306. case _charcodes.charCodes.slash:
  307. _index.finishToken.call(void 0, _types.TokenType.slash);
  308. break;
  309. case _charcodes.charCodes.equalsTo:
  310. _index.finishToken.call(void 0, _types.TokenType.eq);
  311. break;
  312. case _charcodes.charCodes.leftCurlyBrace:
  313. _index.finishToken.call(void 0, _types.TokenType.braceL);
  314. break;
  315. case _charcodes.charCodes.dot:
  316. _index.finishToken.call(void 0, _types.TokenType.dot);
  317. break;
  318. case _charcodes.charCodes.colon:
  319. _index.finishToken.call(void 0, _types.TokenType.colon);
  320. break;
  321. default:
  322. _util.unexpected.call(void 0, );
  323. }
  324. }
  325. } exports.nextJSXTagToken = nextJSXTagToken;
  326. function nextJSXExprToken() {
  327. _base.state.tokens.push(new (0, _index.Token)());
  328. _base.state.start = _base.state.pos;
  329. jsxReadToken();
  330. }