1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-09-07 10:33:26 +02:00

Bug 480238 - Parse QML Header Statements

The acorn parser is now able to parse QML import and pragma statements.

Change-Id: Iaeebaa21f8b013935f8cdf2c2a2ff511038e1069
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-10-21 10:29:36 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent e18311d2a2
commit 89f0a08d77
2 changed files with 603 additions and 52 deletions

View file

@ -41,75 +41,158 @@ module.exports = function(acorn) {
// QML parser methods // QML parser methods
var pp = acorn.Parser.prototype; 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() { pp.qml_parseHeaderStatements = function() {
var node = this.startNode() var node = this.startNode()
node.statements = []; node.statements = [];
loop: { var loop = true;
while (loop) {
switch (this.type) { switch (this.type) {
case tt._import: case tt._import:
var qmlImport = this.qml_parseImportStatement(); var qmlImport = this.qml_parseImportStatement();
node.statements.push(qmlImport); node.statements.push(qmlImport);
break loop; break;
case tt._pragma: 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' <Identifier>
*/
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' <ModuleIdentifier> <Version.Number> [as <Qualifier>]
* 'import' <DirectoryPath> [as <Qualifier>]
*
* as specified by http://doc.qt.io/qt-5/qtqml-syntax-imports.html
*/
pp.qml_parseImportStatement = function() { pp.qml_parseImportStatement = function() {
var node = this.startNode(); var node = this.startNode();
// Advance to the next token since this method should only be called
// when the current token is 'import'
this.next(); 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(); this.semicolon();
return this.finishNode(node, "QMLImportStatement"); return this.finishNode(node, "QMLImportStatement");
}; };
pp.qml_parseModuleIdentifier = function() { /*
* Parses a QML Module of the form:
* <QualifiedId> <VersionLiteral>
*/
pp.qml_parseModule = function() {
var node = this.startNode(); var node = this.startNode();
// Parse the qualified id/string node.qualifiedId = this.qml_parseQualifiedId();
if (this.type == tt.name) if (this.type === tt.num) {
node.qualifiedId = this.qml_parseQualifiedId(); node.version = this.qml_parseVersionLiteral();
else if (this.type == tt.string) { } else {
node.file = this.value;
this.next();
} else
this.unexpected(); this.unexpected();
}
// Parse the version number return this.finishNode(node, "QMLModule");
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");
}; };
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(); this.next();
while(this.type == tt.dot) { return this.finishNode(node, "QMLVersionLiteral");
}
/*
* Parses a QML Qualifier of the form:
* 'as' <Identifier>
*/
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:
* <Identifier> ('.' <Identifier>)*
*/
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 += '.'; id += '.';
this.next(); this.next();
if (this.type == tt.name) if (this.type === tt.name) {
id += this.value; id += this.value;
else node.parts.push(this.value);
} else {
this.unexpected(); this.unexpected();
}
this.next(); this.next();
} }
return id; node.raw = id;
return this.finishNode(node, "QMLQualifiedID");
} }
/* /*
* Returns a TokenType that matches the given word or undefined if * 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 * Uses contextual information to determine whether or not a keyword
* such as 'color' is being used as an identifier. If this is found * such as 'color' is being used as an identifier. If this is found
@ -125,7 +208,7 @@ module.exports = function(acorn) {
return tt._readonly; return tt._readonly;
case "import": case "import":
// Make sure that 'import' is recognized as a keyword // 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; return tt._import;
case "color": case "color":
return tt._color; return tt._color;
@ -150,7 +233,7 @@ module.exports = function(acorn) {
node.body = []; node.body = [];
var headerStmts = this.qml_parseHeaderStatements(); var headerStmts = this.qml_parseHeaderStatements();
node.body.push(headerStmts) node.body.push(headerStmts);
// TODO: Parse QML object root // TODO: Parse QML object root
@ -166,8 +249,9 @@ module.exports = function(acorn) {
var word = this.readWord1(); var word = this.readWord1();
var type = this.qml_getTokenType(word); var type = this.qml_getTokenType(word);
if (type !== undefined) if (type !== undefined) {
return this.finishToken(type, word); return this.finishToken(type, word);
}
// If we were unable to find a QML keyword, call acorn's implementation // 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 // 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;
}; };

View file

@ -9,6 +9,10 @@
* QNX Software Systems - Initial API and implementation * 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 = { var testFixture = {
'QML': { 'QML': {
'import QtQuick 2.2': { 'import QtQuick 2.2': {
@ -27,46 +31,509 @@ var testFixture = {
end: { line: 1, column: 18 } end: { line: 1, column: 18 }
}, },
module: { module: {
type: "QMLModuleIdentifier", type: "QMLModule",
qualifiedId: "QtQuick",
range: [7, 18], range: [7, 18],
loc: { loc: {
start: { line: 1, column: 7 }, start: { line: 1, column: 7 },
end: { line: 1, column: 18 } 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: { version: {
type: "Literal", type: "QMLVersionLiteral",
range: [15, 18], range: [15, 18],
loc: { loc: {
start: { line: 1, column: 15 }, start: { line: 1, column: 15 },
end: { line: 1, column: 18 } end: { line: 1, column: 18 }
}, },
value: 2.2, value: 2.2,
major: 2,
minor: 2,
raw: "2.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) { for (var ns in testFixture) {
ns = testFixture[ns]; ns = testFixture[ns];
for (var code in ns) { for (var code in ns) {
test(code, { if (typeof ns[code] === "string") {
type: 'Program', // Expected test result holds an error message
body: [ns[code]] testFail(code, ns[code], { ecmaVersion: 6 });
}, { } else {
ecmaVersion: 6, // Expected test result holds an AST
locations: true, test(code, {
ranges: true type: "Program",
}); body: [ns[code]]
}, {
ecmaVersion: 6,
locations: true,
ranges: true,
});
}
} }
} }