From b4c5d1f2e952c2c10946cba978beb34d17be270d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 8 Jul 2014 22:46:03 +0300 Subject: [PATCH] Added support for namespaces and member expressions in tag names. --- acorn.js | 88 ++++++++++++----- test/index.html | 1 + test/tests-jsx.js | 234 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 286 insertions(+), 37 deletions(-) diff --git a/acorn.js b/acorn.js index c7c937c5e4..1f1a3638d7 100644 --- a/acorn.js +++ b/acorn.js @@ -1248,6 +1248,20 @@ eat(type) || unexpected(); } + // Expect a char. If found, consume it, otherwise, + // raise an unexpected token error. + + function expectChar(ch) { + if (tokVal === ch) next(); + else unexpected(); + } + + // Get following char. + + function nextChar() { + return input[tokPos]; + } + // Raise an unexpected token error. function unexpected(pos) { @@ -2547,35 +2561,67 @@ } function parseXJSIdentifier() { + var node = parseIdent(true); + node.type = "XJSIdentifier"; + return node; + } + + function parseXJSNamespacedName() { var node = startNode(); - if (tokType === _name) { - node.name = tokVal; - } else { - unexpected(); + + node.namespace = parseXJSIdentifier(); + expect(_colon); + node.name = parseXJSIdentifier(); + + return finishNode(node, "XJSNamespacedName"); + } + + function parseXJSMemberExpression() { + var node = parseXJSIdentifier(); + + while (eat(_dot)) { + var newNode = startNodeFrom(node); + newNode.object = node; + newNode.property = parseXJSIdentifier(); + node = finishNode(newNode, "XJSMemberExpression"); } - tokRegexpAllowed = false; - next(); - return finishNode(node, "XJSIdentifier"); + + return node; } function parseXJSElementName() { + switch (nextChar()) { + case ':': + return parseXJSNamespacedName(); + + case '.': + return parseXJSMemberExpression(); + + default: + return parseXJSIdentifier(); + } + } + + function parseXJSAttributeName() { + if (nextChar() === ':') { + return parseXJSNamespacedName(); + } + return parseXJSIdentifier(); } function parseXJSChild() { + var token; /* - var token, marker; if (tokVal === '{') { token = parseXJSExpressionContainer(); } else if (lookahead.type === Token.XJSText) { marker = markerCreatePreserveWhitespace(); token = markerApply(marker, delegate.createLiteral(lex())); - } else { + } else*/ { token = parseXJSElement(); } return token; - */ - return parseXJSElement(); } function parseXJSClosingElement() { @@ -2585,15 +2631,15 @@ inXJSChild = false; inXJSTag = true; tokRegexpAllowed = false; - tokVal === '<' ? next() : unexpected(); - tokVal === '/' ? next() : unexpected(); + expectChar('<'); + expectChar('/'); 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(); + expectChar('>'); return finishNode(node, "XJSClosingElement"); } @@ -2605,17 +2651,13 @@ inXJSChild = false; inXJSTag = true; - tokVal === '<' ? next() : unexpected(); + expectChar('<'); node.name = parseXJSElementName(); - /* - while (index < length && - lookahead.value !== '/' && - lookahead.value !== '>') { - attributes.push(parseXJSAttribute()); + while (tokType !== _eof && tokVal !== '/' && tokVal !== '>') { + //attributes.push(parseXJSAttribute()); } - */ inXJSTag = origInXJSTag; @@ -2627,7 +2669,7 @@ inXJSChild = true; node.selfClosing = false; } - tokVal === '>' ? next() : unexpected(); + expectChar('>'); return finishNode(node, "XJSOpeningElement"); } @@ -2642,7 +2684,7 @@ if (!openingElement.selfClosing) { while (tokType !== _eof) { inXJSChild = false; // Call lookahead2() with inXJSChild = false because Acorn test suite + diff --git a/test/tests-jsx.js b/test/tests-jsx.js index 77917f0eaf..6f7eaf5eb0 100644 --- a/test/tests-jsx.js +++ b/test/tests-jsx.js @@ -6,10 +6,10 @@ if (typeof exports != "undefined") { var testAssert = require("./driver.js").testAssert; } -test('', { - type: 'Program', - body: [ - { +// Simply taken from esprima-fb/fbtest.js +var fbTestFixture = { + 'XJS': { + '': { type: "ExpressionStatement", expression: { type: "XJSElement", @@ -18,8 +18,7 @@ test('', { name: { type: "XJSIdentifier", name: "a", - start: 1, - end: 2, + range: [1, 2], loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } @@ -27,27 +26,234 @@ test('', { }, selfClosing: true, attributes: [], - start: 0, - end: 5, + range: [0, 5], loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } } }, children: [], - start: 0, - end: 5, + range: [0, 5], loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } } }, - start: 0, - end: 5, + range: [0, 5], loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } } + }, + '': { + type: "ExpressionStatement", + expression: { + type: "XJSElement", + openingElement: { + type: "XJSOpeningElement", + name: { + type: "XJSIdentifier", + name: "a", + range: [ + 1, + 2 + ], + loc: { + start: { + line: 1, + column: 1 + }, + end: { + line: 1, + column: 2 + } + } + }, + selfClosing: true, + attributes: [], + range: [ + 0, + 5 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 2, + column: 2 + } + } + }, + children: [], + range: [ + 0, + 5 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 2, + column: 2 + } + } + }, + range: [ + 0, + 5 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 2, + column: 2 + } + } + }, + '<日本語>': { + type: "ExpressionStatement", + expression: { + type: "XJSElement", + openingElement: { + type: "XJSOpeningElement", + name: { + type: "XJSIdentifier", + name: "日本語", + range: [ + 1, + 4 + ], + loc: { + start: { + line: 1, + column: 1 + }, + end: { + line: 1, + column: 4 + } + } + }, + selfClosing: false, + attributes: [], + range: [ + 0, + 5 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 5 + } + } + }, + closingElement: { + type: "XJSClosingElement", + name: { + type: "XJSIdentifier", + name: "日本語", + range: [ + 7, + 10 + ], + loc: { + start: { + line: 1, + column: 7 + }, + end: { + line: 1, + column: 10 + } + } + }, + range: [ + 5, + 11 + ], + loc: { + start: { + line: 1, + column: 5 + }, + end: { + line: 1, + column: 11 + } + } + }, + children: [], + range: [ + 0, + 11 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 11 + } + } + }, + range: [ + 0, + 11 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 11 + } + } } - ] -}, {locations: true}); \ No newline at end of file + } +}; + +// patching test data to match acorn-specific format instead of esprima-specific one +function esprima2acorn(ast, isTopLevel) { + if ('range' in ast) { + ast.start = ast.range[0]; + ast.end = ast.range[1]; + delete ast.range; + } + + for (var subPropName in ast) { + var subProp = ast[subPropName]; + if (typeof subProp === 'object' && subProp !== null) { + ast[subPropName] = esprima2acorn(subProp); + } + } + + if (isTopLevel) { + ast = { + type: 'Program', + body: [ast], + start: ast.start, + end: ast.end + }; + } + + return ast; +} + +for (var code in fbTestFixture.XJS) { + test(code, esprima2acorn(fbTestFixture.XJS[code], true), {locations: true}); +} \ No newline at end of file