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:
parent
1d36f36ef8
commit
e91dc6f389
6 changed files with 154 additions and 49 deletions
|
@ -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");
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 + "'");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue