Added support for namespaces and member expressions in tag names.

This commit is contained in:
Ingvar Stepanyan 2014-07-08 22:46:03 +03:00
parent 283d47c038
commit b4c5d1f2e9
3 changed files with 286 additions and 37 deletions

View File

@ -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 </ should not be considered in the child
if (tokVal === '<' && input.charAt(tokPos) === '/') {
if (tokVal === '<' && nextChar() === '/') {
break;
}
inXJSChild = true;

View File

@ -4,6 +4,7 @@
<title>Acorn test suite</title>
<script src="../acorn.js"></script>
<script src="driver.js"></script>
<script src="tests-jsx.js" charset="utf-8"></script>
<script src="tests.js" charset="utf-8"></script>
<script src="tests-harmony.js" charset="utf-8"></script>
</head>

View File

@ -6,10 +6,10 @@ if (typeof exports != "undefined") {
var testAssert = require("./driver.js").testAssert;
}
test('<a />', {
type: 'Program',
body: [
{
// Simply taken from esprima-fb/fbtest.js
var fbTestFixture = {
'XJS': {
'<a />': {
type: "ExpressionStatement",
expression: {
type: "XJSElement",
@ -18,8 +18,7 @@ test('<a />', {
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('<a />', {
},
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 }
}
},
'<a\n/>': {
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});
}
};
// 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});
}