From 283d47c038796f9924eeef3b30eadc10602da102 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 8 Jul 2014 17:44:55 +0300 Subject: [PATCH] Started porting JSX support from esprima-fb. Conflicts: acorn.js test/tests.js --- acorn.js | 151 +++++++++++++++++++++++++++++++++++++++++++++- test/tests-jsx.js | 53 ++++++++++++++++ test/tests.js | 1 + 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 test/tests-jsx.js diff --git a/acorn.js b/acorn.js index acf76f6a6c..c7c937c5e4 100644 --- a/acorn.js +++ b/acorn.js @@ -241,7 +241,7 @@ // that `break` and `continue` have somewhere to jump to, and // `strict` indicates whether strict mode is on. - var inFunction, inGenerator, labels, strict; + var inFunction, inGenerator, labels, strict, inXJSChild, inXJSTag; // This counter is used for checking that arrow expressions did // not contain nested parentheses in argument list. @@ -2015,6 +2015,11 @@ case _bquote: return parseTemplate(); + case _relational: + if (tokVal === '<') { + return parseXJSElement(); + } + default: unexpected(); } @@ -2526,4 +2531,148 @@ return finishNode(node, "ComprehensionExpression"); } + function getQualifiedXJSName(object) { + if (object.type === "XJSIdentifier") { + return object.name; + } + if (object.type === "XJSNamespacedName") { + return object.namespace.name + ':' + object.name.name; + } + if (object.type === "XJSMemberExpression") { + return ( + getQualifiedXJSName(object.object) + '.' + + getQualifiedXJSName(object.property) + ); + } + } + + function parseXJSIdentifier() { + var node = startNode(); + if (tokType === _name) { + node.name = tokVal; + } else { + unexpected(); + } + tokRegexpAllowed = false; + next(); + return finishNode(node, "XJSIdentifier"); + } + + function parseXJSElementName() { + return parseXJSIdentifier(); + } + + function parseXJSChild() { + /* + var token, marker; + if (tokVal === '{') { + token = parseXJSExpressionContainer(); + } else if (lookahead.type === Token.XJSText) { + marker = markerCreatePreserveWhitespace(); + token = markerApply(marker, delegate.createLiteral(lex())); + } else { + token = parseXJSElement(); + } + return token; + */ + return parseXJSElement(); + } + + function parseXJSClosingElement() { + var node = startNode(); + var origInXJSChild = inXJSChild; + var origInXJSTag = inXJSTag; + inXJSChild = false; + inXJSTag = true; + tokRegexpAllowed = false; + tokVal === '<' ? next() : unexpected(); + tokVal === '/' ? next() : unexpected(); + node.name = parseXJSElementName(); + // Because advance() (called by lex() called by expect()) expects there + // to be a valid token after >, it needs to know whether to look for a + // standard JS token or an XJS text node + inXJSChild = origInXJSChild; + inXJSTag = origInXJSTag; + tokVal === '>' ? next() : unexpected(); + return finishNode(node, "XJSClosingElement"); + } + + function parseXJSOpeningElement() { + var node = startNode(), attributes = node.attributes = []; + + var origInXJSChild = inXJSChild; + var origInXJSTag = inXJSTag; + inXJSChild = false; + inXJSTag = true; + + tokVal === '<' ? next() : unexpected(); + + node.name = parseXJSElementName(); + + /* + while (index < length && + lookahead.value !== '/' && + lookahead.value !== '>') { + attributes.push(parseXJSAttribute()); + } + */ + + inXJSTag = origInXJSTag; + + if (tokType === _slash) { + next(); + inXJSChild = origInXJSChild; + node.selfClosing = true; + } else { + inXJSChild = true; + node.selfClosing = false; + } + tokVal === '>' ? next() : unexpected(); + return finishNode(node, "XJSOpeningElement"); + } + + function parseXJSElement() { + var node = startNode(); + var children = []; + + var origInXJSChild = inXJSChild; + var origInXJSTag = inXJSTag; + var openingElement = parseXJSOpeningElement(); + + if (!openingElement.selfClosing) { + while (tokType !== _eof) { + inXJSChild = false; // Call lookahead2() with inXJSChild = false because one
two
; + // + // the default error message is a bit incomprehensible. Since it's + // rarely (never?) useful to write a less-than sign after an XJS + // element, we disallow it here in the parser in order to provide a + // better error message. (In the rare case that the less-than operator + // was intended, the left tag can be wrapped in parentheses.) + if (!origInXJSChild && tokVal === '<') { + raise(tokStart, "Adjacent XJS elements must be wrapped in an enclosing tag"); + } + + node.openingElement = openingElement; + node.closingElement = closingElement; + node.children = children; + return finishNode(node, "XJSElement"); + } + }); diff --git a/test/tests-jsx.js b/test/tests-jsx.js new file mode 100644 index 0000000000..77917f0eaf --- /dev/null +++ b/test/tests-jsx.js @@ -0,0 +1,53 @@ +// React JSX tests + +if (typeof exports != "undefined") { + var test = require("./driver.js").test; + var testFail = require("./driver.js").testFail; + var testAssert = require("./driver.js").testAssert; +} + +test('', { + type: 'Program', + body: [ + { + type: "ExpressionStatement", + expression: { + type: "XJSElement", + openingElement: { + type: "XJSOpeningElement", + name: { + type: "XJSIdentifier", + name: "a", + start: 1, + end: 2, + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 } + } + }, + selfClosing: true, + attributes: [], + start: 0, + end: 5, + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 5 } + } + }, + children: [], + start: 0, + end: 5, + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 5 } + } + }, + start: 0, + end: 5, + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 5 } + } + } + ] +}, {locations: true}); \ No newline at end of file diff --git a/test/tests.js b/test/tests.js index 2259f75e59..5f058bd42e 100644 --- a/test/tests.js +++ b/test/tests.js @@ -6,6 +6,7 @@ if (typeof exports != "undefined") { var testFail = require("./driver.js").testFail; var testAssert = require("./driver.js").testAssert; var acorn = require(".."); + require("./tests-jsx.js"); } test("this\n", {