mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-08-09 17:25:38 +02:00
Bug 480238 - Acorn-QML Parse QML Types File
Added the 'mode' option to acorn-qml that, when set to "qmltypes", will allow it to parse the slightly different syntax that qmltypes files have. Added a few tests to check this functionality. Change-Id: Ica240c33ad9a153953f099845cdcb71e241c3a8d Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
parent
1d36f36ef8
commit
e91dc6f389
6 changed files with 154 additions and 49 deletions
|
@ -18,6 +18,9 @@
|
|||
'use strict';
|
||||
|
||||
exports.inject = function (acorn) {
|
||||
// Add the 'mode' option to acorn
|
||||
acorn.defaultOptions.mode = "qml";
|
||||
|
||||
// Acorn token types
|
||||
var tt = acorn.tokTypes;
|
||||
|
||||
|
@ -411,13 +414,16 @@
|
|||
* - QML Script Binding
|
||||
*/
|
||||
pp.qml_parseBinding = function () {
|
||||
if (this.options.mode === "qmltypes") {
|
||||
return this.qml_parseScriptBinding(false);
|
||||
}
|
||||
|
||||
// TODO: solve ambiguity where a QML Object Literal starts with a
|
||||
// Qualified Id that looks very similar to a MemberExpression in
|
||||
// JavaScript. For now, we just won't parse statements like:
|
||||
// test: QMLObject { }
|
||||
// test: QMLObject.QualifiedId { }
|
||||
|
||||
return this.qml_parseScriptBinding();
|
||||
return this.qml_parseScriptBinding(true);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -425,10 +431,10 @@
|
|||
* - Single JavaScript Expression
|
||||
* - QML Statement Block (A block of JavaScript statements)
|
||||
*/
|
||||
pp.qml_parseScriptBinding = function () {
|
||||
pp.qml_parseScriptBinding = function (allowStatementBlock) {
|
||||
var node = this.startNode();
|
||||
node.block = false;
|
||||
if (this.type === tt.braceL) {
|
||||
if (allowStatementBlock && this.type === tt.braceL) {
|
||||
node.block = true;
|
||||
node.script = this.qml_parseStatementBlock();
|
||||
} else {
|
||||
|
@ -565,24 +571,30 @@
|
|||
throw new Error("QML only supports ECMA Script Language Specification 5 or older");
|
||||
}
|
||||
|
||||
// Force strict mode
|
||||
this.strict = true;
|
||||
if (this.options.mode === "qml" || this.options.mode === "qmltypes") {
|
||||
// Force strict mode
|
||||
this.strict = true;
|
||||
|
||||
// Most of QML's constructs sit at the top-level of the parse tree,
|
||||
// replacing JavaScripts top-level. Here we are parsing such things
|
||||
// as the root object literal and header statements of QML. Eventually,
|
||||
// these rules will delegate down to JavaScript expressions.
|
||||
node.headerStatements = this.qml_parseHeaderStatements();
|
||||
node.rootObject = null;
|
||||
if (this.type !== tt.eof) {
|
||||
node.rootObject = this.qml_parseObjectLiteral();
|
||||
// Most of QML's constructs sit at the top-level of the parse tree,
|
||||
// replacing JavaScripts top-level. Here we are parsing such things
|
||||
// as the root object literal and header statements of QML. Eventually,
|
||||
// these rules will delegate down to JavaScript expressions.
|
||||
node.headerStatements = this.qml_parseHeaderStatements();
|
||||
node.rootObject = null;
|
||||
if (this.type !== tt.eof) {
|
||||
node.rootObject = this.qml_parseObjectLiteral();
|
||||
}
|
||||
|
||||
if (!this.eat(tt.eof)) {
|
||||
this.raise(this.pos, "Expected EOF after QML Root Object");
|
||||
}
|
||||
|
||||
return this.finishNode(node, "QMLProgram");
|
||||
} else if (this.options.mode === "js") {
|
||||
return nextMethod.call(this, node);
|
||||
} else {
|
||||
throw new Error("Unknown mode '" + this.options.mode + "'");
|
||||
}
|
||||
|
||||
if (!this.eat(tt.eof)) {
|
||||
this.raise(this.pos, "Expected EOF after QML Root Object");
|
||||
}
|
||||
|
||||
return this.finishNode(node, "QMLProgram");
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -417,8 +417,12 @@ var injectQMLLoose;
|
|||
* - QML Script Binding
|
||||
*/
|
||||
lp.qml_parseBinding = function (start) {
|
||||
if (this.options.mode === "qmltypes") {
|
||||
return this.qml_parseScriptBinding(start, false);
|
||||
}
|
||||
|
||||
if (this.tok.type === tt.braceL) {
|
||||
return this.qml_parseScriptBinding(start);
|
||||
return this.qml_parseScriptBinding(start, true);
|
||||
}
|
||||
// Perform look ahead to determine whether this is an expression or
|
||||
// a QML Object Literal
|
||||
|
@ -436,7 +440,7 @@ var injectQMLLoose;
|
|||
if (la.type === tt.braceL) {
|
||||
return this.qml_parseObjectLiteral();
|
||||
} else {
|
||||
return this.qml_parseScriptBinding(start);
|
||||
return this.qml_parseScriptBinding(start, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -445,12 +449,12 @@ var injectQMLLoose;
|
|||
* - Single JavaScript Expression
|
||||
* - QML Statement Block (A block of JavaScript statements)
|
||||
*/
|
||||
lp.qml_parseScriptBinding = function (start) {
|
||||
lp.qml_parseScriptBinding = function (start, allowStatementBlock) {
|
||||
// Help out Tern a little by starting the Script Binding at the end of
|
||||
// the colon token (only if we consume invalid syntax).
|
||||
var node = this.startNodeAt(start);
|
||||
node.block = false;
|
||||
if (this.tok.type === tt.braceL) {
|
||||
if (allowStatementBlock && this.tok.type === tt.braceL) {
|
||||
node.block = true;
|
||||
node.script = this.qml_parseStatementBlock();
|
||||
} else {
|
||||
|
@ -579,18 +583,24 @@ var injectQMLLoose;
|
|||
throw new Error("QML only supports ECMA Script Language Specification 5 or older");
|
||||
}
|
||||
|
||||
// Most of QML's constructs sit at the top-level of the parse tree,
|
||||
// replacing JavaScripts top-level. Here we are parsing such things
|
||||
// as the root object literal and header statements of QML. Eventually,
|
||||
// these rules will delegate down to JavaScript expressions.
|
||||
var node = this.startNode();
|
||||
node.headerStatements = this.qml_parseHeaderStatements();
|
||||
node.rootObject = null;
|
||||
if (this.tok.type !== tt.eof) {
|
||||
node.rootObject = this.qml_parseObjectLiteral();
|
||||
}
|
||||
if (this.options.mode === "qml" || this.options.mode === "qmltypes") {
|
||||
// Most of QML's constructs sit at the top-level of the parse tree,
|
||||
// replacing JavaScripts top-level. Here we are parsing such things
|
||||
// as the root object literal and header statements of QML. Eventually,
|
||||
// these rules will delegate down to JavaScript expressions.
|
||||
var node = this.startNode();
|
||||
node.headerStatements = this.qml_parseHeaderStatements();
|
||||
node.rootObject = null;
|
||||
if (this.tok.type !== tt.eof) {
|
||||
node.rootObject = this.qml_parseObjectLiteral();
|
||||
}
|
||||
|
||||
return this.finishNode(node, "QMLProgram");
|
||||
return this.finishNode(node, "QMLProgram");
|
||||
} else if (this.options.mode === "js") {
|
||||
return nextMethod.call(this, node);
|
||||
} else {
|
||||
throw new Error("Unknown mode '" + this.options.mode + "'");
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -24,12 +24,16 @@ exports.testAssert = function(code, assert, options) {
|
|||
|
||||
exports.runTests = function(config, callback) {
|
||||
var parse = config.parse;
|
||||
var opts = config.options || {};
|
||||
|
||||
for (var i = 0; i < tests.length; ++i) {
|
||||
var test = tests[i];
|
||||
if (config.filter && !config.filter(test)) continue;
|
||||
try {
|
||||
var testOpts = test.options || {locations: true};
|
||||
for (var opt in opts) {
|
||||
testOpts[opt] = opts[opt];
|
||||
}
|
||||
var expected = {};
|
||||
if ((expected.onComment = testOpts.onComment)) {
|
||||
testOpts.onComment = [];
|
||||
|
|
|
@ -37,26 +37,48 @@ function log(title, message) {
|
|||
}
|
||||
|
||||
var stats, modes = {
|
||||
Normal: {
|
||||
"Normal QML": {
|
||||
config: {
|
||||
parse: acorn.parse,
|
||||
normal: true,
|
||||
filter: function (test) {
|
||||
var opts = test.options || {};
|
||||
return opts.normal !== false;
|
||||
return opts.normal !== false && opts.qmltypes !== true;
|
||||
}
|
||||
}
|
||||
},
|
||||
Loose: {
|
||||
"Loose QML": {
|
||||
config: {
|
||||
parse: acorn.parse_dammit,
|
||||
loose: true,
|
||||
filter: function (test) {
|
||||
var opts = test.options || {};
|
||||
return opts.loose !== false;
|
||||
return opts.loose !== false && opts.qmltypes !== true;
|
||||
}
|
||||
}
|
||||
},
|
||||
"Normal QMLTypes": {
|
||||
config: {
|
||||
parse: acorn.parse,
|
||||
options: {
|
||||
mode: "qmltypes"
|
||||
},
|
||||
filter: function (test) {
|
||||
var opts = test.options || {};
|
||||
return opts.normal !== false && opts.qmltypes !== false;
|
||||
}
|
||||
}
|
||||
},
|
||||
"Loose QMLTypes": {
|
||||
config: {
|
||||
parse: acorn.parse_dammit,
|
||||
options: {
|
||||
mode: "qmltypes"
|
||||
},
|
||||
filter: function (test) {
|
||||
var opts = test.options || {};
|
||||
return opts.loose !== false && opts.qmltypes !== false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function report(state, code, message) {
|
||||
|
|
|
@ -775,9 +775,9 @@ test('a{ signal {} }', rootObjectMembers([{
|
|||
}
|
||||
}]));
|
||||
|
||||
//testFail('a{ readonly property var as: 3 }',
|
||||
// "Unexpected token (1:25)",
|
||||
// { locations: true, loose: false });
|
||||
testFail('a{ readonly property var as: 3 }',
|
||||
"Unexpected token (1:25)",
|
||||
{ locations: true, loose: false });
|
||||
|
||||
test('a{ readonly property var w: 3 }', rootObjectMembers([{
|
||||
type: "QMLPropertyDeclaration",
|
||||
|
@ -2266,7 +2266,7 @@ test('import QtQuick 2.3\nimport QtQuick.Window 2.2\nWindow {\n\tvisible: true\n
|
|||
]
|
||||
}
|
||||
}
|
||||
));
|
||||
), { locations: true, qmltypes: false });
|
||||
|
||||
/***************************************************************************
|
||||
* Loose Parser Tests *
|
||||
|
@ -2949,7 +2949,7 @@ testLoose('a{ property var b: Window {} }', rootObjectMembers([{
|
|||
members: []
|
||||
}
|
||||
}
|
||||
}]));
|
||||
}]), { locations: true, qmltypes: false });
|
||||
|
||||
// TODO: Allow this to run with the normal parser once the ambiguity is solved
|
||||
testLoose('a{ b: Window {} }', rootObjectMembers([{
|
||||
|
@ -2991,7 +2991,7 @@ testLoose('a{ b: Window {} }', rootObjectMembers([{
|
|||
members: []
|
||||
}
|
||||
}
|
||||
}]));
|
||||
}]), { locations: true, qmltypes: false });
|
||||
|
||||
testLoose('a{ signal }', rootObjectMembers([{
|
||||
type: "QMLSignalDefinition",
|
||||
|
@ -3230,6 +3230,63 @@ testLoose('Window {\n\tfunction (\n\tproperty var prop\n}', rootObjectMembers([
|
|||
}
|
||||
]));
|
||||
|
||||
/***************************************************************************
|
||||
* QMLTypes Parser Tests *
|
||||
****************************************************************************/
|
||||
function testQMLTypes(code, ast, options) {
|
||||
var opts = options || {};
|
||||
opts.qmltypes = true;
|
||||
opts.locations = true;
|
||||
test(code, ast, opts);
|
||||
}
|
||||
|
||||
testQMLTypes("a{ b: {} }", javaScript({
|
||||
type: "ObjectExpression",
|
||||
loc: {
|
||||
start: { line: 1, column: 6 },
|
||||
end: { line: 1, column: 8 }
|
||||
},
|
||||
properties: []
|
||||
}));
|
||||
|
||||
testQMLTypes('a{ b: "test" }', javaScript({
|
||||
type: "Literal",
|
||||
loc: {
|
||||
start: { line: 1, column: 6 },
|
||||
end: { line: 1, column: 12 }
|
||||
},
|
||||
value: "test",
|
||||
raw: '"test"'
|
||||
}));
|
||||
|
||||
testQMLTypes('a{ b: ["one", "two"] }', javaScript({
|
||||
type: "ArrayExpression",
|
||||
loc: {
|
||||
start: { line: 1, column: 6 },
|
||||
end: { line: 1, column: 20 }
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
type: "Literal",
|
||||
loc: {
|
||||
start: { line: 1, column: 7 },
|
||||
end: { line: 1, column: 12 }
|
||||
},
|
||||
value: "one",
|
||||
raw: '"one"'
|
||||
},
|
||||
{
|
||||
type: "Literal",
|
||||
loc: {
|
||||
start: { line: 1, column: 14 },
|
||||
end: { line: 1, column: 19 }
|
||||
},
|
||||
value: "two",
|
||||
raw: '"two"'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
/*
|
||||
* Creates a Program with 'headerStatements' and 'rootObject' as the program's expected
|
||||
* body.
|
||||
|
|
|
@ -953,12 +953,12 @@
|
|||
} else if (query.text) {
|
||||
// Parse the file manually and get the AST.
|
||||
var text = query.text;
|
||||
var options = {
|
||||
var options = query.options || {
|
||||
allowReturnOutsideFunction: true,
|
||||
allowImportExportEverywhere: true,
|
||||
ecmaVersion: srv.options.ecmaVersion
|
||||
};
|
||||
srv.signal("preParse", text, options);
|
||||
srv.signalReturnFirst("preParse", text, options);
|
||||
try {
|
||||
ast = acorn.parse(text, options);
|
||||
} catch (e) {
|
||||
|
|
Loading…
Add table
Reference in a new issue