diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js index 16ff8d63165..bd47ebc3651 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js @@ -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"); }; }); }; diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js index f54e0f943a4..40cdf0405bb 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js @@ -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 + "'"); + } }; }); }; diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js index b379ea5263d..8c5fdcc9ffd 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js @@ -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 = []; diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js index 88b1184d665..f50d6f80b65 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js index ea43827d7a4..f8470162483 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js @@ -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. diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js index 09d03f79503..8738e9dffa6 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js @@ -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) {