1
0
Fork 0
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:
Matthew Bastien 2015-12-09 17:12:15 -05:00
parent 1d36f36ef8
commit e91dc6f389
6 changed files with 154 additions and 49 deletions

View file

@ -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");
};
});
};

View file

@ -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 + "'");
}
};
});
};

View file

@ -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 = [];

View file

@ -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) {

View file

@ -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.

View file

@ -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) {