1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-08-10 17:55:39 +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'; 'use strict';
exports.inject = function (acorn) { exports.inject = function (acorn) {
// Add the 'mode' option to acorn
acorn.defaultOptions.mode = "qml";
// Acorn token types // Acorn token types
var tt = acorn.tokTypes; var tt = acorn.tokTypes;
@ -411,13 +414,16 @@
* - QML Script Binding * - QML Script Binding
*/ */
pp.qml_parseBinding = function () { 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 // TODO: solve ambiguity where a QML Object Literal starts with a
// Qualified Id that looks very similar to a MemberExpression in // Qualified Id that looks very similar to a MemberExpression in
// JavaScript. For now, we just won't parse statements like: // JavaScript. For now, we just won't parse statements like:
// test: QMLObject { } // test: QMLObject { }
// test: QMLObject.QualifiedId { } // test: QMLObject.QualifiedId { }
return this.qml_parseScriptBinding(true);
return this.qml_parseScriptBinding();
}; };
/* /*
@ -425,10 +431,10 @@
* - Single JavaScript Expression * - Single JavaScript Expression
* - QML Statement Block (A block of JavaScript statements) * - QML Statement Block (A block of JavaScript statements)
*/ */
pp.qml_parseScriptBinding = function () { pp.qml_parseScriptBinding = function (allowStatementBlock) {
var node = this.startNode(); var node = this.startNode();
node.block = false; node.block = false;
if (this.type === tt.braceL) { if (allowStatementBlock && this.type === tt.braceL) {
node.block = true; node.block = true;
node.script = this.qml_parseStatementBlock(); node.script = this.qml_parseStatementBlock();
} else { } else {
@ -565,24 +571,30 @@
throw new Error("QML only supports ECMA Script Language Specification 5 or older"); throw new Error("QML only supports ECMA Script Language Specification 5 or older");
} }
// Force strict mode if (this.options.mode === "qml" || this.options.mode === "qmltypes") {
this.strict = true; // Force strict mode
this.strict = true;
// Most of QML's constructs sit at the top-level of the parse tree, // Most of QML's constructs sit at the top-level of the parse tree,
// replacing JavaScripts top-level. Here we are parsing such things // replacing JavaScripts top-level. Here we are parsing such things
// as the root object literal and header statements of QML. Eventually, // as the root object literal and header statements of QML. Eventually,
// these rules will delegate down to JavaScript expressions. // these rules will delegate down to JavaScript expressions.
node.headerStatements = this.qml_parseHeaderStatements(); node.headerStatements = this.qml_parseHeaderStatements();
node.rootObject = null; node.rootObject = null;
if (this.type !== tt.eof) { if (this.type !== tt.eof) {
node.rootObject = this.qml_parseObjectLiteral(); 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 * - QML Script Binding
*/ */
lp.qml_parseBinding = function (start) { lp.qml_parseBinding = function (start) {
if (this.options.mode === "qmltypes") {
return this.qml_parseScriptBinding(start, false);
}
if (this.tok.type === tt.braceL) { 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 // Perform look ahead to determine whether this is an expression or
// a QML Object Literal // a QML Object Literal
@ -436,7 +440,7 @@ var injectQMLLoose;
if (la.type === tt.braceL) { if (la.type === tt.braceL) {
return this.qml_parseObjectLiteral(); return this.qml_parseObjectLiteral();
} else { } else {
return this.qml_parseScriptBinding(start); return this.qml_parseScriptBinding(start, true);
} }
}; };
@ -445,12 +449,12 @@ var injectQMLLoose;
* - Single JavaScript Expression * - Single JavaScript Expression
* - QML Statement Block (A block of JavaScript statements) * - 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 // Help out Tern a little by starting the Script Binding at the end of
// the colon token (only if we consume invalid syntax). // the colon token (only if we consume invalid syntax).
var node = this.startNodeAt(start); var node = this.startNodeAt(start);
node.block = false; node.block = false;
if (this.tok.type === tt.braceL) { if (allowStatementBlock && this.tok.type === tt.braceL) {
node.block = true; node.block = true;
node.script = this.qml_parseStatementBlock(); node.script = this.qml_parseStatementBlock();
} else { } else {
@ -579,18 +583,24 @@ var injectQMLLoose;
throw new Error("QML only supports ECMA Script Language Specification 5 or older"); 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, if (this.options.mode === "qml" || this.options.mode === "qmltypes") {
// replacing JavaScripts top-level. Here we are parsing such things // Most of QML's constructs sit at the top-level of the parse tree,
// as the root object literal and header statements of QML. Eventually, // replacing JavaScripts top-level. Here we are parsing such things
// these rules will delegate down to JavaScript expressions. // as the root object literal and header statements of QML. Eventually,
var node = this.startNode(); // these rules will delegate down to JavaScript expressions.
node.headerStatements = this.qml_parseHeaderStatements(); var node = this.startNode();
node.rootObject = null; node.headerStatements = this.qml_parseHeaderStatements();
if (this.tok.type !== tt.eof) { node.rootObject = null;
node.rootObject = this.qml_parseObjectLiteral(); 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) { exports.runTests = function(config, callback) {
var parse = config.parse; var parse = config.parse;
var opts = config.options || {};
for (var i = 0; i < tests.length; ++i) { for (var i = 0; i < tests.length; ++i) {
var test = tests[i]; var test = tests[i];
if (config.filter && !config.filter(test)) continue; if (config.filter && !config.filter(test)) continue;
try { try {
var testOpts = test.options || {locations: true}; var testOpts = test.options || {locations: true};
for (var opt in opts) {
testOpts[opt] = opts[opt];
}
var expected = {}; var expected = {};
if ((expected.onComment = testOpts.onComment)) { if ((expected.onComment = testOpts.onComment)) {
testOpts.onComment = []; testOpts.onComment = [];

View file

@ -37,26 +37,48 @@ function log(title, message) {
} }
var stats, modes = { var stats, modes = {
Normal: { "Normal QML": {
config: { config: {
parse: acorn.parse, parse: acorn.parse,
normal: true,
filter: function (test) { filter: function (test) {
var opts = test.options || {}; var opts = test.options || {};
return opts.normal !== false; return opts.normal !== false && opts.qmltypes !== true;
} }
} }
}, },
Loose: { "Loose QML": {
config: { config: {
parse: acorn.parse_dammit, parse: acorn.parse_dammit,
loose: true,
filter: function (test) { filter: function (test) {
var opts = test.options || {}; 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) { function report(state, code, message) {

View file

@ -775,9 +775,9 @@ test('a{ signal {} }', rootObjectMembers([{
} }
}])); }]));
//testFail('a{ readonly property var as: 3 }', testFail('a{ readonly property var as: 3 }',
// "Unexpected token (1:25)", "Unexpected token (1:25)",
// { locations: true, loose: false }); { locations: true, loose: false });
test('a{ readonly property var w: 3 }', rootObjectMembers([{ test('a{ readonly property var w: 3 }', rootObjectMembers([{
type: "QMLPropertyDeclaration", 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 * * Loose Parser Tests *
@ -2949,7 +2949,7 @@ testLoose('a{ property var b: Window {} }', rootObjectMembers([{
members: [] members: []
} }
} }
}])); }]), { locations: true, qmltypes: false });
// TODO: Allow this to run with the normal parser once the ambiguity is solved // TODO: Allow this to run with the normal parser once the ambiguity is solved
testLoose('a{ b: Window {} }', rootObjectMembers([{ testLoose('a{ b: Window {} }', rootObjectMembers([{
@ -2991,7 +2991,7 @@ testLoose('a{ b: Window {} }', rootObjectMembers([{
members: [] members: []
} }
} }
}])); }]), { locations: true, qmltypes: false });
testLoose('a{ signal }', rootObjectMembers([{ testLoose('a{ signal }', rootObjectMembers([{
type: "QMLSignalDefinition", 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 * Creates a Program with 'headerStatements' and 'rootObject' as the program's expected
* body. * body.

View file

@ -953,12 +953,12 @@
} else if (query.text) { } else if (query.text) {
// Parse the file manually and get the AST. // Parse the file manually and get the AST.
var text = query.text; var text = query.text;
var options = { var options = query.options || {
allowReturnOutsideFunction: true, allowReturnOutsideFunction: true,
allowImportExportEverywhere: true, allowImportExportEverywhere: true,
ecmaVersion: srv.options.ecmaVersion ecmaVersion: srv.options.ecmaVersion
}; };
srv.signal("preParse", text, options); srv.signalReturnFirst("preParse", text, options);
try { try {
ast = acorn.parse(text, options); ast = acorn.parse(text, options);
} catch (e) { } catch (e) {