diff --git a/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/inject.js b/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/inject.js index 5583131462e..18903c07add 100644 --- a/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/inject.js +++ b/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/inject.js @@ -41,75 +41,158 @@ module.exports = function(acorn) { // QML parser methods var pp = acorn.Parser.prototype; + /* + * Parses a set of QML Header Statements which can either be of + * the type import or pragma + */ pp.qml_parseHeaderStatements = function() { var node = this.startNode() node.statements = []; - loop: { + var loop = true; + while (loop) { switch (this.type) { case tt._import: var qmlImport = this.qml_parseImportStatement(); node.statements.push(qmlImport); - break loop; + break; case tt._pragma: - // TODO: parse QML pragma statement + var qmlPragma = this.qml_parsePragmaStatement(); + node.statements.push(qmlPragma); + break; + default: + loop = false; } } - return this.finishNode(node, "QMLHeaderStatements") + return this.finishNode(node, "QMLHeaderStatements"); } + /* + * Parses a QML Pragma statement of the form: + * 'pragma' + */ + pp.qml_parsePragmaStatement = function() { + var node = this.startNode(); + this.next(); + node.identifier = this.parseIdent(false); + this.semicolon(); + return this.finishNode(node, "QMLPragmaStatement"); + } + + /* + * Parses a QML Import statement of the form: + * 'import' [as ] + * 'import' [as ] + * + * as specified by http://doc.qt.io/qt-5/qtqml-syntax-imports.html + */ pp.qml_parseImportStatement = function() { var node = this.startNode(); - - // Advance to the next token since this method should only be called - // when the current token is 'import' this.next(); - node.module = this.qml_parseModuleIdentifier(); - // TODO: parse the 'as Identifier' portion of an import statement + + // The type of import varies solely on the next token + switch(this.type) { + case tt.name: + node.module = this.qml_parseModule(); + break; + case tt.string: + node.directoryPath = this.parseLiteral(this.value); + break; + default: + this.unexpected(); + break; + } + + // Parse the qualifier, if any + if (this.type === tt._as) { + node.qualifier = this.qml_parseQualifier(); + } + this.semicolon(); return this.finishNode(node, "QMLImportStatement"); }; - pp.qml_parseModuleIdentifier = function() { + /* + * Parses a QML Module of the form: + * + */ + pp.qml_parseModule = function() { var node = this.startNode(); - // Parse the qualified id/string - if (this.type == tt.name) - node.qualifiedId = this.qml_parseQualifiedId(); - else if (this.type == tt.string) { - node.file = this.value; - this.next(); - } else + node.qualifiedId = this.qml_parseQualifiedId(); + if (this.type === tt.num) { + node.version = this.qml_parseVersionLiteral(); + } else { this.unexpected(); + } - // Parse the version number - if (this.type == tt.num) { - node.version = this.parseLiteral(this.value); - // TODO: check that version number has major and minor - } else - this.unexpected(); - return this.finishNode(node, "QMLModuleIdentifier"); + return this.finishNode(node, "QMLModule"); }; - pp.qml_parseQualifiedId = function() { - var id = this.value; + /* + * Parses a QML Version Literal which consists of a major and minor + * version separated by a '.' + */ + pp.qml_parseVersionLiteral = function() { + var node = this.startNode(); + + node.raw = this.input.slice(this.start, this.end); + node.value = this.value; + var matches; + if (matches = /(\d+)\.(\d+)/.exec(node.raw)) { + node.major = parseInt(matches[1]); + node.minor = parseInt(matches[2]); + } else { + this.raise(this.start, "QML module must specify major and minor version"); + } + this.next(); - while(this.type == tt.dot) { + return this.finishNode(node, "QMLVersionLiteral"); + } + + /* + * Parses a QML Qualifier of the form: + * 'as' + */ + pp.qml_parseQualifier = function() { + var node = this.startNode(); + this.next(); + node.identifier = this.parseIdent(false); + return this.finishNode(node, "QMLQualifier"); + } + + /* + * Parses a Qualified ID of the form: + * ('.' )* + */ + pp.qml_parseQualifiedId = function() { + var node = this.startNode(); + + node.parts = []; + var id = this.value; + node.parts.push(this.value); + this.next(); + while(this.type === tt.dot) { id += '.'; this.next(); - if (this.type == tt.name) + if (this.type === tt.name) { id += this.value; - else + node.parts.push(this.value); + } else { this.unexpected(); + } this.next(); } - return id; + node.raw = id; + + return this.finishNode(node, "QMLQualifiedID"); } /* * Returns a TokenType that matches the given word or undefined if - * no such TokenType could be found. + * no such TokenType could be found. This method only matches + * QML-specific keywords. * * Uses contextual information to determine whether or not a keyword * such as 'color' is being used as an identifier. If this is found @@ -125,7 +208,7 @@ module.exports = function(acorn) { return tt._readonly; case "import": // Make sure that 'import' is recognized as a keyword - // regardless of ecma version set in acorn. + // regardless of the ecma version set in acorn. return tt._import; case "color": return tt._color; @@ -150,7 +233,7 @@ module.exports = function(acorn) { node.body = []; var headerStmts = this.qml_parseHeaderStatements(); - node.body.push(headerStmts) + node.body.push(headerStmts); // TODO: Parse QML object root @@ -166,8 +249,9 @@ module.exports = function(acorn) { var word = this.readWord1(); var type = this.qml_getTokenType(word); - if (type !== undefined) + if (type !== undefined) { return this.finishToken(type, word); + } // If we were unable to find a QML keyword, call acorn's implementation // of the readWord method. Since we don't have access to _tokentype, and @@ -181,5 +265,5 @@ module.exports = function(acorn) { }); } - return acorn + return acorn; }; \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/test/tests-qml.js b/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/test/tests-qml.js index 6cd9aa6fa52..acc460c576e 100644 --- a/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/test/tests-qml.js +++ b/qt/org.eclipse.cdt.qt.core.acorn/acorn-qml/test/tests-qml.js @@ -9,6 +9,10 @@ * QNX Software Systems - Initial API and implementation *******************************************************************************/ +var test = require("./driver.js").test; +var testFail = require("./driver.js").testFail; +var tokTypes = require("../").tokTypes; + var testFixture = { 'QML': { 'import QtQuick 2.2': { @@ -27,46 +31,509 @@ var testFixture = { end: { line: 1, column: 18 } }, module: { - type: "QMLModuleIdentifier", - qualifiedId: "QtQuick", + type: "QMLModule", range: [7, 18], loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 18 } }, + qualifiedId: { + type: "QMLQualifiedID", + range: [7, 14], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 14 } + }, + parts: [ "QtQuick" ], + raw: "QtQuick" + }, version: { - type: "Literal", + type: "QMLVersionLiteral", range: [15, 18], loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 18 } }, value: 2.2, + major: 2, + minor: 2, raw: "2.2" } } } ] + }, + + 'import "./file.js"': { + type: "QMLHeaderStatements", + range: [0, 18], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 18 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 18], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 18 } + }, + directoryPath: { + type:"Literal", + range: [7, 18], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 18 } + }, + value: "./file.js", + raw: "\"./file.js\"" + } + } + ] + }, + + 'import "./file.js" as MyModule': { + type: "QMLHeaderStatements", + range: [0, 30], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 30 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 30], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 30 } + }, + directoryPath: { + type:"Literal", + range: [7, 18], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 18 } + }, + value: "./file.js", + raw: "\"./file.js\"" + }, + qualifier: { + type: "QMLQualifier", + range: [19, 30], + loc: { + start: { line: 1, column: 19 }, + end: { line: 1, column: 30 } + }, + identifier: { + type:"Identifier", + range: [22, 30], + loc: { + start: { line: 1, column: 22 }, + end: { line: 1, column: 30 } + }, + name: "MyModule" + } + } + } + ] + }, + + 'import QtQuick ver': "Unexpected token (1:15)", + + 'import QtQuick 0x01': "QML module must specify major and minor version (1:15)", + + 'import QtQuick 1': "QML module must specify major and minor version (1:15)", + + 'import QtQuick 2.2\nimport "./file.js"': { + type: "QMLHeaderStatements", + range: [0, 37], + loc: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 18 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 18], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 18 } + }, + module: { + type: "QMLModule", + range: [7, 18], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 18 } + }, + qualifiedId: { + type: "QMLQualifiedID", + range: [7, 14], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 14 } + }, + parts: [ "QtQuick" ], + raw: "QtQuick" + }, + version: { + type: "QMLVersionLiteral", + range: [15, 18], + loc: { + start: { line: 1, column: 15 }, + end: { line: 1, column: 18 } + }, + value: 2.2, + major: 2, + minor: 2, + raw: "2.2" + } + } + }, + { + type: "QMLImportStatement", + range: [19, 37], + loc: { + start: { line: 2, column: 0 }, + end: { line: 2, column: 18 } + }, + directoryPath: { + type:"Literal", + range: [26, 37], + loc: { + start: { line: 2, column: 7 }, + end: { line: 2, column: 18 } + }, + value: "./file.js", + raw: "\"./file.js\"" + } + } + ] + }, + + 'import QtQuick 2.2;import "./file.js"': { + type: "QMLHeaderStatements", + range: [0, 37], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 37 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 19], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 19 } + }, + module: { + type: "QMLModule", + range: [7, 18], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 18 } + }, + qualifiedId: { + type: "QMLQualifiedID", + range: [7, 14], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 14 } + }, + parts: [ "QtQuick" ], + raw: "QtQuick" + }, + version: { + type: "QMLVersionLiteral", + range: [15, 18], + loc: { + start: { line: 1, column: 15 }, + end: { line: 1, column: 18 } + }, + value: 2.2, + major: 2, + minor: 2, + raw: "2.2" + } + } + }, + { + type: "QMLImportStatement", + range: [19, 37], + loc: { + start: { line: 1, column: 19 }, + end: { line: 1, column: 37 } + }, + directoryPath: { + type:"Literal", + range: [26, 37], + loc: { + start: { line: 1, column: 26 }, + end: { line: 1, column: 37 } + }, + value: "./file.js", + raw: "\"./file.js\"" + } + } + ] + }, + + 'import Module 1.0 as MyModule': { + type: "QMLHeaderStatements", + range: [0, 29], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 29 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 29], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 29 } + }, + module: { + type: "QMLModule", + range: [7, 17], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 17 } + }, + qualifiedId: { + type: "QMLQualifiedID", + range: [7, 13], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 13 } + }, + parts: [ "Module" ], + raw: "Module" + }, + version: { + type: "QMLVersionLiteral", + range: [14, 17], + loc: { + start: { line: 1, column: 14 }, + end: { line: 1, column: 17 } + }, + value: 1, + major: 1, + minor: 0, + raw: "1.0" + } + }, + qualifier: { + type: "QMLQualifier", + range: [18, 29], + loc: { + start: { line: 1, column: 18 }, + end: { line: 1, column: 29 } + }, + identifier: { + type:"Identifier", + range: [21, 29], + loc: { + start: { line: 1, column: 21 }, + end: { line: 1, column: 29 } + }, + name: "MyModule" + } + } + } + ] + }, + + 'import Qualified.Id.Test 1.0': { + type: "QMLHeaderStatements", + range: [0, 28], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 28 } + }, + statements: [ + { + type: "QMLImportStatement", + range: [0, 28], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 28 } + }, + module: { + type: "QMLModule", + range: [7, 28], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 28 } + }, + qualifiedId: { + type: "QMLQualifiedID", + range: [7, 24], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 24 } + }, + parts: [ + "Qualified", + "Id", + "Test" + ], + raw: "Qualified.Id.Test" + }, + version: { + type: "QMLVersionLiteral", + range: [25, 28], + loc: { + start: { line: 1, column: 25 }, + end: { line: 1, column: 28 } + }, + value: 1, + major: 1, + minor: 0, + raw: "1.0" + } + } + } + ] + }, + + 'pragma Singleton': { + type: "QMLHeaderStatements", + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 } + }, + statements: [ + { + type: "QMLPragmaStatement", + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 } + }, + identifier: { + type: "Identifier", + range: [7, 16], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 16 } + }, + name: "Singleton" + } + } + ] + }, + + 'pragma Singleton\npragma Other': { + type: "QMLHeaderStatements", + range: [0, 29], + loc: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 12 } + }, + statements: [ + { + type: "QMLPragmaStatement", + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 } + }, + identifier: { + type: "Identifier", + range: [7, 16], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 16 } + }, + name: "Singleton" + } + }, + { + type: "QMLPragmaStatement", + range: [17, 29], + loc: { + start: { line: 2, column: 0 }, + end: { line: 2, column: 12 } + }, + identifier: { + type: "Identifier", + range: [24, 29], + loc: { + start: { line: 2, column: 7 }, + end: { line: 2, column: 12 } + }, + name: "Other" + } + } + ] + }, + + 'pragma Singleton;pragma Other': { + type: "QMLHeaderStatements", + range: [0, 29], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 29 } + }, + statements: [ + { + type: "QMLPragmaStatement", + range: [0, 17], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 17 } + }, + identifier: { + type: "Identifier", + range: [7, 16], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 16 } + }, + name: "Singleton" + } + }, + { + type: "QMLPragmaStatement", + range: [17, 29], + loc: { + start: { line: 1, column: 17 }, + end: { line: 1, column: 29 } + }, + identifier: { + type: "Identifier", + range: [24, 29], + loc: { + start: { line: 1, column: 24 }, + end: { line: 1, column: 29 } + }, + name: "Other" + } + } + ] } } }; -if (typeof exports !== "undefined") { - var test = require("./driver.js").test; - var testFail = require("./driver.js").testFail; - var tokTypes = require("../").tokTypes; -} - for (var ns in testFixture) { ns = testFixture[ns]; for (var code in ns) { - test(code, { - type: 'Program', - body: [ns[code]] - }, { - ecmaVersion: 6, - locations: true, - ranges: true - }); + if (typeof ns[code] === "string") { + // Expected test result holds an error message + testFail(code, ns[code], { ecmaVersion: 6 }); + } else { + // Expected test result holds an AST + test(code, { + type: "Program", + body: [ns[code]] + }, { + ecmaVersion: 6, + locations: true, + ranges: true, + }); + } } } \ No newline at end of file