diff --git a/esprima.js b/esprima.js index 23612eb7b96..e54cc31496f 100644 --- a/esprima.js +++ b/esprima.js @@ -189,6 +189,7 @@ parseYieldExpression: true WhileStatement: 'WhileStatement', WithStatement: 'WithStatement', XJSIdentifier: 'XJSIdentifier', + XJSMemberExpression: 'XJSMemberExpression', XJSEmptyExpression: 'XJSEmptyExpression', XJSExpressionContainer: 'XJSExpressionContainer', XJSElement: 'XJSElement', @@ -266,6 +267,7 @@ parseYieldExpression: true EachNotAllowed: 'Each is not supported', InvalidXJSTagName: 'XJS tag name can not be empty', InvalidXJSAttributeValue: 'XJS value should be either an expression or a quoted XJS text', + XJSMemberExpressionNamespaceNotAllowed: 'XJS member expressions does not support namespaces', ExpectedXJSClosingTag: 'Expected corresponding XJS closing tag for %0' }; @@ -1801,6 +1803,18 @@ parseYieldExpression: true }; }, + createXJSMemberExpression: function (object, property) { + if (property.namespace) { + throwError({}, Messages.XJSMemberExpressionNamespaceNotAllowed); + } + + return { + type: Syntax.XJSMemberExpression, + object: object, + property: property + }; + }, + createXJSElement: function (openingElement, closingElement, children) { return { type: Syntax.XJSElement, @@ -5292,6 +5306,18 @@ parseYieldExpression: true diams: '\u2666' }; + function getQualifiedXJSName(object) { + if (object.type === Syntax.XJSIdentifier) { + return (object.namespace ? object.namespace + ':' : '') + object.name; + } + if (object.type === Syntax.XJSMemberExpression) { + return ( + getQualifiedXJSName(object.object) + '.' + + getQualifiedXJSName(object.property) + ); + } + } + function isXJSIdentifierStart(ch) { // exclude backslash (\) return (ch !== 92) && isIdentifierStart(ch); @@ -5442,6 +5468,30 @@ parseYieldExpression: true return markerApply(marker, delegate.createXJSIdentifier(token.value, token.namespace)); } + function parseXJSMemberExpression() { + var marker = markerCreate(), + expr = parseXJSIdentifier(); + + if (expr.namespace) { + throwError({}, Messages.XJSMemberExpressionNamespaceNotAllowed); + } + + while (match('.')) { + lex(); + expr = markerApply(marker, delegate.createXJSMemberExpression(expr, parseXJSIdentifier())); + } + + return expr; + } + + function parseXJSElementName() { + if (lookahead2().value === '.') { + return parseXJSMemberExpression(); + } + + return parseXJSIdentifier(); + } + function parseXJSAttributeValue() { var value, marker; if (match('{')) { @@ -5531,7 +5581,7 @@ parseYieldExpression: true state.inXJSTag = true; expect('<'); expect('/'); - name = parseXJSIdentifier(); + 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 @@ -5551,7 +5601,7 @@ parseYieldExpression: true expect('<'); - name = parseXJSIdentifier(); + name = parseXJSElementName(); while (index < length && lookahead.value !== '/' && @@ -5596,8 +5646,8 @@ parseYieldExpression: true state.inXJSChild = origInXJSChild; state.inXJSTag = origInXJSTag; closingElement = parseXJSClosingElement(); - if (closingElement.name.namespace !== openingElement.name.namespace || closingElement.name.name !== openingElement.name.name) { - throwError({}, Messages.ExpectedXJSClosingTag, openingElement.name.namespace ? openingElement.name.namespace + ':' + openingElement.name.name : openingElement.name.name); + if (getQualifiedXJSName(closingElement.name) !== getQualifiedXJSName(openingElement.name)) { + throwError({}, Messages.ExpectedXJSClosingTag, getQualifiedXJSName(openingElement.name)); } } diff --git a/test/fbtest.js b/test/fbtest.js index 6570cbf82a9..220d1cbc3dc 100644 --- a/test/fbtest.js +++ b/test/fbtest.js @@ -1736,6 +1736,216 @@ var fbTestFixture = { "column": 57 } } + }, + + '': { + type: 'ExpressionStatement', + expression: { + type: 'XJSElement', + openingElement: { + type: 'XJSOpeningElement', + name: { + type: 'XJSMemberExpression', + object: { + type: 'XJSIdentifier', + name: 'a', + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'b', + range: [3, 4], + loc: { + start: { line: 1, column: 3 }, + end: { line: 1, column: 4 } + } + }, + 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: 'XJSMemberExpression', + object: { + type: 'XJSIdentifier', + name: 'a', + range: [7, 8], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 8 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'b', + range: [9, 10], + loc: { + start: { line: 1, column: 9 }, + end: { line: 1, column: 10 } + } + }, + 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 } + } + }, + + '': { + type: 'ExpressionStatement', + expression: { + type: 'XJSElement', + openingElement: { + type: 'XJSOpeningElement', + name: { + type: 'XJSMemberExpression', + object: { + type: 'XJSMemberExpression', + object: { + type: 'XJSIdentifier', + name: 'a', + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'b', + range: [3, 4], + loc: { + start: { line: 1, column: 3 }, + end: { line: 1, column: 4 } + } + }, + range: [1, 4], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 4 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'c', + range: [5, 6], + loc: { + start: { line: 1, column: 5 }, + end: { line: 1, column: 6 } + } + }, + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 } + } + }, + selfClosing: false, + attributes: [], + range: [0, 7], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 7 } + } + }, + closingElement: { + type: 'XJSClosingElement', + name: { + type: 'XJSMemberExpression', + object: { + type: 'XJSMemberExpression', + object: { + type: 'XJSIdentifier', + name: 'a', + range: [9, 10], + loc: { + start: { line: 1, column: 9 }, + end: { line: 1, column: 10 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'b', + range: [11, 12], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 12 } + } + }, + range: [9, 12], + loc: { + start: { line: 1, column: 9 }, + end: { line: 1, column: 12 } + } + }, + property: { + type: 'XJSIdentifier', + name: 'c', + range: [13, 14], + loc: { + start: { line: 1, column: 13 }, + end: { line: 1, column: 14 } + } + }, + range: [9, 14], + loc: { + start: { line: 1, column: 9 }, + end: { line: 1, column: 14 } + } + }, + range: [7, 15], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 15 } + } + }, + children: [], + range: [0, 15], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 15 } + } + }, + range: [0, 15], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 15 } + } } }, @@ -1798,6 +2008,55 @@ var fbTestFixture = { message: "Error: Line 1: Expected corresponding XJS closing tag for a:b", }, + '': { + index: 4, + lineNumber: 1, + column: 5, + message: 'Error: Line 1: XJS member expressions does not support namespaces' + }, + + '': { + index: 6, + lineNumber: 1, + column: 7, + message: 'Error: Line 1: XJS member expressions does not support namespaces' + }, + + '': { + index: 11, + lineNumber: 1, + column: 12, + message: "Error: Line 1: Expected corresponding XJS closing tag for a.b.c" + }, + + '<.a>': { + index: 1, + lineNumber: 1, + column: 2, + message: "Error: Line 1: Unexpected token ." + }, + + '': { + index: 3, + lineNumber: 1, + column: 4, + message: "Error: Line 1: Unexpected token >" + }, + + '': { + index: 2, + lineNumber: 1, + column: 3, + message: "Error: Line 1: Unexpected token [" + }, + + '': { + index: 2, + lineNumber: 1, + column: 3, + message: "Error: Line 1: Unexpected token [" + }, + '': { index: 8, lineNumber: 1, diff --git a/test/test.js b/test/test.js index 547afabaada..cb3be34b791 100644 --- a/test/test.js +++ b/test/test.js @@ -19511,6 +19511,7 @@ var testFixture = { WhileStatement: 'WhileStatement', WithStatement: 'WithStatement', XJSIdentifier: 'XJSIdentifier', + XJSMemberExpression: "XJSMemberExpression", XJSEmptyExpression: "XJSEmptyExpression", XJSExpressionContainer: "XJSExpressionContainer", XJSElement: 'XJSElement',