index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = function (instance) {
  6. instance.extend("parseExprAtom", function (inner) {
  7. return function (refShortHandDefaultPos) {
  8. if (this.match(_types.types.jsxText)) {
  9. var node = this.parseLiteral(this.state.value, "JSXText");
  10. // https://github.com/babel/babel/issues/2078
  11. node.extra = null;
  12. return node;
  13. } else if (this.match(_types.types.jsxTagStart)) {
  14. return this.jsxParseElement();
  15. } else {
  16. return inner.call(this, refShortHandDefaultPos);
  17. }
  18. };
  19. });
  20. instance.extend("readToken", function (inner) {
  21. return function (code) {
  22. var context = this.curContext();
  23. if (context === _context.types.j_expr) {
  24. return this.jsxReadToken();
  25. }
  26. if (context === _context.types.j_oTag || context === _context.types.j_cTag) {
  27. if ((0, _identifier.isIdentifierStart)(code)) {
  28. return this.jsxReadWord();
  29. }
  30. if (code === 62) {
  31. ++this.state.pos;
  32. return this.finishToken(_types.types.jsxTagEnd);
  33. }
  34. if ((code === 34 || code === 39) && context === _context.types.j_oTag) {
  35. return this.jsxReadString(code);
  36. }
  37. }
  38. if (code === 60 && this.state.exprAllowed) {
  39. ++this.state.pos;
  40. return this.finishToken(_types.types.jsxTagStart);
  41. }
  42. return inner.call(this, code);
  43. };
  44. });
  45. instance.extend("updateContext", function (inner) {
  46. return function (prevType) {
  47. if (this.match(_types.types.braceL)) {
  48. var curContext = this.curContext();
  49. if (curContext === _context.types.j_oTag) {
  50. this.state.context.push(_context.types.braceExpression);
  51. } else if (curContext === _context.types.j_expr) {
  52. this.state.context.push(_context.types.templateQuasi);
  53. } else {
  54. inner.call(this, prevType);
  55. }
  56. this.state.exprAllowed = true;
  57. } else if (this.match(_types.types.slash) && prevType === _types.types.jsxTagStart) {
  58. this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
  59. this.state.context.push(_context.types.j_cTag); // reconsider as closing tag context
  60. this.state.exprAllowed = false;
  61. } else {
  62. return inner.call(this, prevType);
  63. }
  64. };
  65. });
  66. };
  67. var _xhtml = require("./xhtml");
  68. var _xhtml2 = _interopRequireDefault(_xhtml);
  69. var _types = require("../../tokenizer/types");
  70. var _context = require("../../tokenizer/context");
  71. var _parser = require("../../parser");
  72. var _parser2 = _interopRequireDefault(_parser);
  73. var _identifier = require("../../util/identifier");
  74. var _whitespace = require("../../util/whitespace");
  75. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  76. /* eslint indent: 0 */
  77. var HEX_NUMBER = /^[\da-fA-F]+$/;
  78. var DECIMAL_NUMBER = /^\d+$/;
  79. _context.types.j_oTag = new _context.TokContext("<tag", false);
  80. _context.types.j_cTag = new _context.TokContext("</tag", false);
  81. _context.types.j_expr = new _context.TokContext("<tag>...</tag>", true, true);
  82. _types.types.jsxName = new _types.TokenType("jsxName");
  83. _types.types.jsxText = new _types.TokenType("jsxText", { beforeExpr: true });
  84. _types.types.jsxTagStart = new _types.TokenType("jsxTagStart", { startsExpr: true });
  85. _types.types.jsxTagEnd = new _types.TokenType("jsxTagEnd");
  86. _types.types.jsxTagStart.updateContext = function () {
  87. this.state.context.push(_context.types.j_expr); // treat as beginning of JSX expression
  88. this.state.context.push(_context.types.j_oTag); // start opening tag context
  89. this.state.exprAllowed = false;
  90. };
  91. _types.types.jsxTagEnd.updateContext = function (prevType) {
  92. var out = this.state.context.pop();
  93. if (out === _context.types.j_oTag && prevType === _types.types.slash || out === _context.types.j_cTag) {
  94. this.state.context.pop();
  95. this.state.exprAllowed = this.curContext() === _context.types.j_expr;
  96. } else {
  97. this.state.exprAllowed = true;
  98. }
  99. };
  100. var pp = _parser2.default.prototype;
  101. // Reads inline JSX contents token.
  102. pp.jsxReadToken = function () {
  103. var out = "";
  104. var chunkStart = this.state.pos;
  105. for (;;) {
  106. if (this.state.pos >= this.input.length) {
  107. this.raise(this.state.start, "Unterminated JSX contents");
  108. }
  109. var ch = this.input.charCodeAt(this.state.pos);
  110. switch (ch) {
  111. case 60: // "<"
  112. case 123:
  113. // "{"
  114. if (this.state.pos === this.state.start) {
  115. if (ch === 60 && this.state.exprAllowed) {
  116. ++this.state.pos;
  117. return this.finishToken(_types.types.jsxTagStart);
  118. }
  119. return this.getTokenFromCode(ch);
  120. }
  121. out += this.input.slice(chunkStart, this.state.pos);
  122. return this.finishToken(_types.types.jsxText, out);
  123. case 38:
  124. // "&"
  125. out += this.input.slice(chunkStart, this.state.pos);
  126. out += this.jsxReadEntity();
  127. chunkStart = this.state.pos;
  128. break;
  129. default:
  130. if ((0, _whitespace.isNewLine)(ch)) {
  131. out += this.input.slice(chunkStart, this.state.pos);
  132. out += this.jsxReadNewLine(true);
  133. chunkStart = this.state.pos;
  134. } else {
  135. ++this.state.pos;
  136. }
  137. }
  138. }
  139. };
  140. pp.jsxReadNewLine = function (normalizeCRLF) {
  141. var ch = this.input.charCodeAt(this.state.pos);
  142. var out = void 0;
  143. ++this.state.pos;
  144. if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) {
  145. ++this.state.pos;
  146. out = normalizeCRLF ? "\n" : "\r\n";
  147. } else {
  148. out = String.fromCharCode(ch);
  149. }
  150. ++this.state.curLine;
  151. this.state.lineStart = this.state.pos;
  152. return out;
  153. };
  154. pp.jsxReadString = function (quote) {
  155. var out = "";
  156. var chunkStart = ++this.state.pos;
  157. for (;;) {
  158. if (this.state.pos >= this.input.length) {
  159. this.raise(this.state.start, "Unterminated string constant");
  160. }
  161. var ch = this.input.charCodeAt(this.state.pos);
  162. if (ch === quote) break;
  163. if (ch === 38) {
  164. // "&"
  165. out += this.input.slice(chunkStart, this.state.pos);
  166. out += this.jsxReadEntity();
  167. chunkStart = this.state.pos;
  168. } else if ((0, _whitespace.isNewLine)(ch)) {
  169. out += this.input.slice(chunkStart, this.state.pos);
  170. out += this.jsxReadNewLine(false);
  171. chunkStart = this.state.pos;
  172. } else {
  173. ++this.state.pos;
  174. }
  175. }
  176. out += this.input.slice(chunkStart, this.state.pos++);
  177. return this.finishToken(_types.types.string, out);
  178. };
  179. pp.jsxReadEntity = function () {
  180. var str = "";
  181. var count = 0;
  182. var entity = void 0;
  183. var ch = this.input[this.state.pos];
  184. var startPos = ++this.state.pos;
  185. while (this.state.pos < this.input.length && count++ < 10) {
  186. ch = this.input[this.state.pos++];
  187. if (ch === ";") {
  188. if (str[0] === "#") {
  189. if (str[1] === "x") {
  190. str = str.substr(2);
  191. if (HEX_NUMBER.test(str)) entity = String.fromCharCode(parseInt(str, 16));
  192. } else {
  193. str = str.substr(1);
  194. if (DECIMAL_NUMBER.test(str)) entity = String.fromCharCode(parseInt(str, 10));
  195. }
  196. } else {
  197. entity = _xhtml2.default[str];
  198. }
  199. break;
  200. }
  201. str += ch;
  202. }
  203. if (!entity) {
  204. this.state.pos = startPos;
  205. return "&";
  206. }
  207. return entity;
  208. };
  209. // Read a JSX identifier (valid tag or attribute name).
  210. //
  211. // Optimized version since JSX identifiers can"t contain
  212. // escape characters and so can be read as single slice.
  213. // Also assumes that first character was already checked
  214. // by isIdentifierStart in readToken.
  215. pp.jsxReadWord = function () {
  216. var ch = void 0;
  217. var start = this.state.pos;
  218. do {
  219. ch = this.input.charCodeAt(++this.state.pos);
  220. } while ((0, _identifier.isIdentifierChar)(ch) || ch === 45); // "-"
  221. return this.finishToken(_types.types.jsxName, this.input.slice(start, this.state.pos));
  222. };
  223. // Transforms JSX element name to string.
  224. function getQualifiedJSXName(object) {
  225. if (object.type === "JSXIdentifier") {
  226. return object.name;
  227. }
  228. if (object.type === "JSXNamespacedName") {
  229. return object.namespace.name + ":" + object.name.name;
  230. }
  231. if (object.type === "JSXMemberExpression") {
  232. return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property);
  233. }
  234. }
  235. // Parse next token as JSX identifier
  236. pp.jsxParseIdentifier = function () {
  237. var node = this.startNode();
  238. if (this.match(_types.types.jsxName)) {
  239. node.name = this.state.value;
  240. } else if (this.state.type.keyword) {
  241. node.name = this.state.type.keyword;
  242. } else {
  243. this.unexpected();
  244. }
  245. this.next();
  246. return this.finishNode(node, "JSXIdentifier");
  247. };
  248. // Parse namespaced identifier.
  249. pp.jsxParseNamespacedName = function () {
  250. var startPos = this.state.start,
  251. startLoc = this.state.startLoc;
  252. var name = this.jsxParseIdentifier();
  253. if (!this.eat(_types.types.colon)) return name;
  254. var node = this.startNodeAt(startPos, startLoc);
  255. node.namespace = name;
  256. node.name = this.jsxParseIdentifier();
  257. return this.finishNode(node, "JSXNamespacedName");
  258. };
  259. // Parses element name in any form - namespaced, member
  260. // or single identifier.
  261. pp.jsxParseElementName = function () {
  262. var startPos = this.state.start,
  263. startLoc = this.state.startLoc;
  264. var node = this.jsxParseNamespacedName();
  265. while (this.eat(_types.types.dot)) {
  266. var newNode = this.startNodeAt(startPos, startLoc);
  267. newNode.object = node;
  268. newNode.property = this.jsxParseIdentifier();
  269. node = this.finishNode(newNode, "JSXMemberExpression");
  270. }
  271. return node;
  272. };
  273. // Parses any type of JSX attribute value.
  274. pp.jsxParseAttributeValue = function () {
  275. var node = void 0;
  276. switch (this.state.type) {
  277. case _types.types.braceL:
  278. node = this.jsxParseExpressionContainer();
  279. if (node.expression.type === "JSXEmptyExpression") {
  280. this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
  281. } else {
  282. return node;
  283. }
  284. case _types.types.jsxTagStart:
  285. case _types.types.string:
  286. node = this.parseExprAtom();
  287. node.extra = null;
  288. return node;
  289. default:
  290. this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
  291. }
  292. };
  293. // JSXEmptyExpression is unique type since it doesn't actually parse anything,
  294. // and so it should start at the end of last read token (left brace) and finish
  295. // at the beginning of the next one (right brace).
  296. pp.jsxParseEmptyExpression = function () {
  297. var node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);
  298. return this.finishNodeAt(node, "JSXEmptyExpression", this.start, this.startLoc);
  299. };
  300. // Parse JSX spread child
  301. pp.jsxParseSpreadChild = function () {
  302. var node = this.startNode();
  303. this.expect(_types.types.braceL);
  304. this.expect(_types.types.ellipsis);
  305. node.expression = this.parseExpression();
  306. this.expect(_types.types.braceR);
  307. return this.finishNode(node, "JSXSpreadChild");
  308. };
  309. // Parses JSX expression enclosed into curly brackets.
  310. pp.jsxParseExpressionContainer = function () {
  311. var node = this.startNode();
  312. this.next();
  313. if (this.match(_types.types.braceR)) {
  314. node.expression = this.jsxParseEmptyExpression();
  315. } else {
  316. node.expression = this.parseExpression();
  317. }
  318. this.expect(_types.types.braceR);
  319. return this.finishNode(node, "JSXExpressionContainer");
  320. };
  321. // Parses following JSX attribute name-value pair.
  322. pp.jsxParseAttribute = function () {
  323. var node = this.startNode();
  324. if (this.eat(_types.types.braceL)) {
  325. this.expect(_types.types.ellipsis);
  326. node.argument = this.parseMaybeAssign();
  327. this.expect(_types.types.braceR);
  328. return this.finishNode(node, "JSXSpreadAttribute");
  329. }
  330. node.name = this.jsxParseNamespacedName();
  331. node.value = this.eat(_types.types.eq) ? this.jsxParseAttributeValue() : null;
  332. return this.finishNode(node, "JSXAttribute");
  333. };
  334. // Parses JSX opening tag starting after "<".
  335. pp.jsxParseOpeningElementAt = function (startPos, startLoc) {
  336. var node = this.startNodeAt(startPos, startLoc);
  337. node.attributes = [];
  338. node.name = this.jsxParseElementName();
  339. while (!this.match(_types.types.slash) && !this.match(_types.types.jsxTagEnd)) {
  340. node.attributes.push(this.jsxParseAttribute());
  341. }
  342. node.selfClosing = this.eat(_types.types.slash);
  343. this.expect(_types.types.jsxTagEnd);
  344. return this.finishNode(node, "JSXOpeningElement");
  345. };
  346. // Parses JSX closing tag starting after "</".
  347. pp.jsxParseClosingElementAt = function (startPos, startLoc) {
  348. var node = this.startNodeAt(startPos, startLoc);
  349. node.name = this.jsxParseElementName();
  350. this.expect(_types.types.jsxTagEnd);
  351. return this.finishNode(node, "JSXClosingElement");
  352. };
  353. // Parses entire JSX element, including it"s opening tag
  354. // (starting after "<"), attributes, contents and closing tag.
  355. pp.jsxParseElementAt = function (startPos, startLoc) {
  356. var node = this.startNodeAt(startPos, startLoc);
  357. var children = [];
  358. var openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
  359. var closingElement = null;
  360. if (!openingElement.selfClosing) {
  361. contents: for (;;) {
  362. switch (this.state.type) {
  363. case _types.types.jsxTagStart:
  364. startPos = this.state.start;startLoc = this.state.startLoc;
  365. this.next();
  366. if (this.eat(_types.types.slash)) {
  367. closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
  368. break contents;
  369. }
  370. children.push(this.jsxParseElementAt(startPos, startLoc));
  371. break;
  372. case _types.types.jsxText:
  373. children.push(this.parseExprAtom());
  374. break;
  375. case _types.types.braceL:
  376. if (this.lookahead().type === _types.types.ellipsis) {
  377. children.push(this.jsxParseSpreadChild());
  378. } else {
  379. children.push(this.jsxParseExpressionContainer());
  380. }
  381. break;
  382. default:
  383. this.unexpected();
  384. }
  385. }
  386. if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
  387. this.raise(closingElement.start, "Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">");
  388. }
  389. }
  390. node.openingElement = openingElement;
  391. node.closingElement = closingElement;
  392. node.children = children;
  393. if (this.match(_types.types.relational) && this.state.value === "<") {
  394. this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
  395. }
  396. return this.finishNode(node, "JSXElement");
  397. };
  398. // Parses entire JSX element from current position.
  399. pp.jsxParseElement = function () {
  400. var startPos = this.state.start,
  401. startLoc = this.state.startLoc;
  402. this.next();
  403. return this.jsxParseElementAt(startPos, startLoc);
  404. };