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',