From 55ae051c81917a81dc8e46beb7026f151ed9e41e Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 16:28:12 +0200 Subject: [PATCH 1/5] Update version after merge. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fcf41caaf9..a3c2300a4a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Alternative, faster React.js JSX parser", "homepage": "https://github.com/RReverser/acorn-jsx", "main": "acorn.js", - "version": "0.11.1-2", + "version": "0.11.1-3", "engines": {"node": ">=0.4.0"}, "maintainers": [{"name": "Ingvar Stepanyan", "email": "me@rreverser.com", From 95479ab6aa69bb4162500700ccebe359028aef70 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 16:51:45 +0200 Subject: [PATCH 2/5] Optimize JSX text reading. --- acorn.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/acorn.js b/acorn.js index 1507882e3a..2447814ebb 100644 --- a/acorn.js +++ b/acorn.js @@ -1534,35 +1534,43 @@ // Reads inline JSX contents token. function readJSXToken() { - var out = "", start = tokPos; + var out = "", chunkStart = tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated JSX contents"); var ch = input.charCodeAt(tokPos); switch (ch) { case 123: // '{' case 60: // '<' - if (tokPos === start) { + if (tokPos === tokStart) { return getTokenFromCode(ch); } + out += input.slice(chunkStart, tokPos); return finishToken(_jsxText, out); case 38: // '&' + out += input.slice(chunkStart, tokPos); out += readJSXEntity(); + chunkStart = tokPos; break; default: - ++tokPos; if (isNewLine(ch)) { + out += input.slice(chunkStart, tokPos); + ++tokPos; if (ch === 13 && input.charCodeAt(tokPos) === 10) { ++tokPos; - ch = 10; + out += "\n"; + } else { + out += String.fromCharCode(ch); } if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } + chunkStart = tokPos; + } else { + ++tokPos; } - out += String.fromCharCode(ch); } } } From e0bcfca03f8b044de2f23306c18f431d3d578c9a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 18:25:49 +0200 Subject: [PATCH 3/5] Add JSX support to loose parser. --- acorn.js | 10 +-- acorn_loose.js | 179 ++++++++++++++++++++++++++++++++++++++++++++-- test/tests-jsx.js | 3 +- 3 files changed, 177 insertions(+), 15 deletions(-) diff --git a/acorn.js b/acorn.js index 2447814ebb..3cc5fa0259 100644 --- a/acorn.js +++ b/acorn.js @@ -463,7 +463,8 @@ dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, arrow: _arrow, template: _template, star: _star, assign: _assign, - backQuote: _backQuote, dollarBraceL: _dollarBraceL}; + backQuote: _backQuote, dollarBraceL: _dollarBraceL, jsxName: _jsxName, + jsxText: _jsxText, jsxTagStart: _jsxTagStart, jsxTagEnd: _jsxTagEnd}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on @@ -3321,9 +3322,6 @@ return node; case _jsxTagStart: - return parseJSXElement(); - - case _jsxText: case _string: return parseExprAtom(); @@ -3337,10 +3335,6 @@ // at the beginning of the next one (right brace). function parseJSXEmptyExpression() { - if (tokType !== _braceR) { - unexpected(); - } - var tmp; tmp = tokStart; diff --git a/acorn_loose.js b/acorn_loose.js index 0f52fcc47a..fa7631b79d 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -256,11 +256,27 @@ return node; } - function dummyIdent() { - var dummy = startNode(); - dummy.name = "✖"; - return finishNode(dummy, "Identifier"); + function finishNodeAt(node, type, pos) { + if (options.locations) { node.loc.end = pos[1]; pos = pos[0]; } + node.type = type; + node.end = pos; + if (options.ranges) + node.range[1] = pos; + return node; } + + function emptyNode(type) { + var dummy = startNodeAt(options.locations ? [lastEnd, lastEndLoc] : lastEnd); + dummy.name = "✖"; + return finishNodeAt(dummy, type, storeCurrentPos()); + } + + function dummyIdent() { + var dummy = emptyNode("Identifier"); + dummy.name = "✖"; + return dummy; + } + function isDummy(node) { return node.name == "✖"; } function eat(type) { @@ -724,7 +740,7 @@ next(); return finishNode(node, "Literal"); - case tt.num: case tt.string: + case tt.num: case tt.string: case tt.jsxText: var node = startNode(); node.value = token.value; node.raw = input.slice(token.start, token.end); @@ -788,6 +804,9 @@ case tt.backQuote: return parseTemplate(); + case tt.jsxTagStart: + return parseJSXElement(); + default: return dummyIdent(); } @@ -1161,4 +1180,154 @@ } return elts; } + + // Parse next token as JSX identifier + + function parseJSXIdentifier() { + var node = startNode(); + node.name = token.type === tt.jsxName ? token.value : token.type.keyword; + next(); + return finishNode(node, "JSXIdentifier"); + } + + // Parse namespaced identifier. + + function parseJSXNamespacedName() { + var start = storeCurrentPos(); + var name = parseJSXIdentifier(); + if (!eat(tt.colon)) return name; + var node = startNodeAt(start); + node.namespace = name; + node.name = parseJSXIdentifier(); + return finishNode(node, "JSXNamespacedName"); + } + + // Parses element name in any form - namespaced, member + // or single identifier. + + function parseJSXElementName() { + var start = storeCurrentPos(); + var node = parseJSXNamespacedName(); + while (eat(tt.dot)) { + var newNode = startNodeAt(start); + newNode.object = node; + newNode.property = parseJSXIdentifier(); + node = finishNode(newNode, "JSXMemberExpression"); + } + return node; + } + + // Parses any type of JSX attribute value. + + function parseJSXAttributeValue() { + return token.type === tt.braceL ? parseJSXExpressionContainer() : parseExprAtom(); + } + + // Parses JSX expression enclosed into curly brackets. + + function parseJSXExpressionContainer(allowEmpty) { + var node = startNode(); + pushCx(); + next(); + node.expression = allowEmpty && token.type === tt.braceR ? emptyNode("JSXEmptyExpression") : parseExpression(); + popCx(); + expect(tt.braceR); + return finishNode(node, "JSXExpressionContainer"); + } + + // Parses following JSX attribute name-value pair. + + function parseJSXAttribute() { + var node = startNode(); + if (token.type === tt.braceL) { + if (lookAhead(1).type === tt.ellipsis) { + next(); + next(); + node.argument = parseMaybeAssign(); + expect(tt.braceR); + return finishNode(node, "JSXSpreadAttribute"); + } else { + node.name = dummyIdent(); + node.value = parseJSXAttributeValue(); + } + } else { + node.name = parseJSXNamespacedName(); + node.value = eat(tt.eq) ? parseJSXAttributeValue() : null; + } + return finishNode(node, "JSXAttribute"); + } + + // Parses JSX opening tag starting after '<'. + + function parseJSXOpeningElementAt(start) { + var node = startNodeAt(start); + node.attributes = []; + node.name = parseJSXElementName(); + while (token.type !== tt.slash && token.type !== tt.jsxTagEnd && token.type !== tt.eof) { + node.attributes.push(parseJSXAttribute()); + } + node.selfClosing = eat(tt.slash); + expect(tt.jsxTagEnd); + if (token.type === tt.eof) node.selfClosing = true; + return finishNode(node, "JSXOpeningElement"); + } + + // Parses JSX closing tag starting after ' Date: Tue, 27 Jan 2015 18:26:03 +0200 Subject: [PATCH 4/5] Update version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3c2300a4a..ce5b5042ba 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Alternative, faster React.js JSX parser", "homepage": "https://github.com/RReverser/acorn-jsx", "main": "acorn.js", - "version": "0.11.1-3", + "version": "0.11.1-4", "engines": {"node": ">=0.4.0"}, "maintainers": [{"name": "Ingvar Stepanyan", "email": "me@rreverser.com", From 9f73a10f615591439e35c4c2ed7e3b29ebb0418e Mon Sep 17 00:00:00 2001 From: Andy VanWagoner Date: Thu, 12 Feb 2015 19:05:07 -0700 Subject: [PATCH 5/5] Allow multiline JSX string attributes. --- acorn.js | 2 +- test/tests-jsx.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index 3cc5fa0259..72c65c3ccb 100644 --- a/acorn.js +++ b/acorn.js @@ -1190,7 +1190,7 @@ out += readJSXEntity(); chunkStart = tokPos; } else { - if (isNewLine(ch)) raise(tokStart, "Unterminated string constant"); + if (isNewLine(ch) && !isJSX) raise(tokStart, "Unterminated string constant"); ++tokPos; } } diff --git a/test/tests-jsx.js b/test/tests-jsx.js index a7dd79e294..c9cfa5af55 100644 --- a/test/tests-jsx.js +++ b/test/tests-jsx.js @@ -3587,6 +3587,42 @@ var fbTestFixture = { closingElement: null, children: [] } + }, + '': { + type: "ExpressionStatement", + expression: { + type: "JSXElement", + range: [0, 64], + openingElement: { + type: "JSXOpeningElement", + range: [0, 64], + attributes: [ + { + type: "JSXAttribute", + range: [6, 62], + name: { + type: "JSXIdentifier", + range: [6, 7], + name: "d" + }, + value: { + type: "Literal", + range: [8, 62], + value: "M230 80\n\t\tA 45 45, 0, 1, 0, 275 125 \r\n L 275 80 Z", + raw: "\"M230 80\n\t\tA 45 45, 0, 1, 0, 275 125 \r\n L 275 80 Z\"" + } + } + ], + name: { + type: "JSXIdentifier", + range: [1, 5], + name: "path" + }, + selfClosing: true + }, + closingElement: null, + children: [] + } } } };