From 9d7b27f5bf44a46707e6d50ebf51ecf73f91dd1b Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 5 Dec 2014 16:32:56 +0100 Subject: Clean up JS .import/.pragma directive scanning There's a scanner in QQmlJS::Lexer::scanDirectives that can parse those, so let's get rid of extra parser that operates on a string. Instead this way we can do the scanning all in one shot, avoid detaching a copy of the source code string and (most importantly) bring the parser closer to the copy in Qt Creator, which uses the directives approach to extract imports and pragma. Change-Id: Iff6eb8d91a45d8a70f383f953115692be48259de Reviewed-by: Fawzi Mohamed --- src/qml/compiler/qqmlirbuilder.cpp | 285 +++------------------ src/qml/compiler/qqmlirbuilder_p.h | 16 +- src/qml/jsruntime/qv4script.cpp | 4 +- src/qml/jsruntime/qv4script_p.h | 7 +- src/qml/parser/qqmljsengine_p.cpp | 8 +- src/qml/parser/qqmljsengine_p.h | 5 + src/qml/parser/qqmljslexer.cpp | 159 ++++++++++-- src/qml/parser/qqmljslexer_p.h | 11 +- src/qml/parser/qqmljsparser.cpp | 19 +- src/qml/qml/qqmltypeloader.cpp | 17 +- tests/auto/qml/qmlmin/tst_qmlmin.cpp | 3 + .../auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 6 +- tools/qmlimportscanner/main.cpp | 79 +++--- tools/qmlmin/main.cpp | 14 +- 14 files changed, 307 insertions(+), 326 deletions(-) diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 07ea2a6fff..dc2389c30e 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -216,60 +216,6 @@ static void replaceWithSpace(QString &str, int idx, int n) *data++ = space; } -#define CHECK_LINE if (l.tokenStartLine() != startLine) return; -#define CHECK_TOKEN(t) if (token != QQmlJSGrammar:: t) return; - -static const int uriTokens[] = { - QQmlJSGrammar::T_IDENTIFIER, - QQmlJSGrammar::T_PROPERTY, - QQmlJSGrammar::T_SIGNAL, - QQmlJSGrammar::T_READONLY, - QQmlJSGrammar::T_ON, - QQmlJSGrammar::T_BREAK, - QQmlJSGrammar::T_CASE, - QQmlJSGrammar::T_CATCH, - QQmlJSGrammar::T_CONTINUE, - QQmlJSGrammar::T_DEFAULT, - QQmlJSGrammar::T_DELETE, - QQmlJSGrammar::T_DO, - QQmlJSGrammar::T_ELSE, - QQmlJSGrammar::T_FALSE, - QQmlJSGrammar::T_FINALLY, - QQmlJSGrammar::T_FOR, - QQmlJSGrammar::T_FUNCTION, - QQmlJSGrammar::T_IF, - QQmlJSGrammar::T_IN, - QQmlJSGrammar::T_INSTANCEOF, - QQmlJSGrammar::T_NEW, - QQmlJSGrammar::T_NULL, - QQmlJSGrammar::T_RETURN, - QQmlJSGrammar::T_SWITCH, - QQmlJSGrammar::T_THIS, - QQmlJSGrammar::T_THROW, - QQmlJSGrammar::T_TRUE, - QQmlJSGrammar::T_TRY, - QQmlJSGrammar::T_TYPEOF, - QQmlJSGrammar::T_VAR, - QQmlJSGrammar::T_VOID, - QQmlJSGrammar::T_WHILE, - QQmlJSGrammar::T_CONST, - QQmlJSGrammar::T_DEBUGGER, - QQmlJSGrammar::T_RESERVED_WORD, - QQmlJSGrammar::T_WITH, - - QQmlJSGrammar::EOF_SYMBOL -}; -static inline bool isUriToken(int token) -{ - const int *current = uriTokens; - while (*current != QQmlJSGrammar::EOF_SYMBOL) { - if (*current == token) - return true; - ++current; - } - return false; -} - void Document::collectTypeReferences() { foreach (Object *obj, objects) { @@ -296,198 +242,6 @@ void Document::collectTypeReferences() } } -void Document::extractScriptMetaData(QString &script, QQmlJS::DiagnosticMessage *error) -{ - Q_ASSERT(error); - - const QString js(QLatin1String(".js")); - const QString library(QLatin1String("library")); - - QQmlJS::MemoryPool *pool = jsParserEngine.pool(); - - QQmlJS::Lexer l(0); - l.setCode(script, 0); - - int token = l.lex(); - - while (true) { - if (token != QQmlJSGrammar::T_DOT) - return; - - int startOffset = l.tokenOffset(); - int startLine = l.tokenStartLine(); - int startColumn = l.tokenStartColumn(); - - error->loc.startLine = startLine + 1; // 0-based, adjust to be 1-based - - token = l.lex(); - - CHECK_LINE; - - if (token == QQmlJSGrammar::T_IMPORT) { - - // .import as - // .import as - - token = l.lex(); - - CHECK_LINE; - QV4::CompiledData::Import *import = pool->New(); - - if (token == QQmlJSGrammar::T_STRING_LITERAL) { - - QString file = l.tokenText(); - - if (!file.endsWith(js)) { - error->message = QCoreApplication::translate("QQmlParser","Imported file must be a script"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - bool invalidImport = false; - - token = l.lex(); - - if ((token != QQmlJSGrammar::T_AS) || (l.tokenStartLine() != startLine)) { - invalidImport = true; - } else { - token = l.lex(); - - if ((token != QQmlJSGrammar::T_IDENTIFIER) || (l.tokenStartLine() != startLine)) - invalidImport = true; - } - - - if (invalidImport) { - error->message = QCoreApplication::translate("QQmlParser","File import requires a qualifier"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - int endOffset = l.tokenLength() + l.tokenOffset(); - - QString importId = script.mid(l.tokenOffset(), l.tokenLength()); - - token = l.lex(); - - if (!importId.at(0).isUpper() || (l.tokenStartLine() == startLine)) { - error->message = QCoreApplication::translate("QQmlParser","Invalid import qualifier"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - replaceWithSpace(script, startOffset, endOffset - startOffset); - - import->type = QV4::CompiledData::Import::ImportScript; - import->uriIndex = registerString(file); - import->qualifierIndex = registerString(importId); - import->location.line = startLine; - import->location.column = startColumn; - imports << import; - } else { - // URI - QString uri; - - while (true) { - if (!isUriToken(token)) { - error->message = QCoreApplication::translate("QQmlParser","Invalid module URI"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - uri.append(l.tokenText()); - - token = l.lex(); - CHECK_LINE; - if (token != QQmlJSGrammar::T_DOT) - break; - - uri.append(QLatin1Char('.')); - - token = l.lex(); - CHECK_LINE; - } - - if (token != QQmlJSGrammar::T_NUMERIC_LITERAL) { - error->message = QCoreApplication::translate("QQmlParser","Module import requires a version"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - int vmaj, vmin; - IRBuilder::extractVersion(QStringRef(&script, l.tokenOffset(), l.tokenLength()), - &vmaj, &vmin); - - bool invalidImport = false; - - token = l.lex(); - - if ((token != QQmlJSGrammar::T_AS) || (l.tokenStartLine() != startLine)) { - invalidImport = true; - } else { - token = l.lex(); - - if ((token != QQmlJSGrammar::T_IDENTIFIER) || (l.tokenStartLine() != startLine)) - invalidImport = true; - } - - - if (invalidImport) { - error->message = QCoreApplication::translate("QQmlParser","Module import requires a qualifier"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - int endOffset = l.tokenLength() + l.tokenOffset(); - - QString importId = script.mid(l.tokenOffset(), l.tokenLength()); - - token = l.lex(); - - if (!importId.at(0).isUpper() || (l.tokenStartLine() == startLine)) { - error->message = QCoreApplication::translate("QQmlParser","Invalid import qualifier"); - error->loc.startColumn = l.tokenStartColumn(); - return; - } - - replaceWithSpace(script, startOffset, endOffset - startOffset); - - import->type = QV4::CompiledData::Import::ImportLibrary; - import->uriIndex = registerString(uri); - import->majorVersion = vmaj; - import->minorVersion = vmin; - import->qualifierIndex = registerString(importId); - import->location.line = startLine; - import->location.column = startColumn; - imports << import; - } - } else if (token == QQmlJSGrammar::T_PRAGMA) { - token = l.lex(); - - CHECK_TOKEN(T_IDENTIFIER); - CHECK_LINE; - - QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength()); - int endOffset = l.tokenLength() + l.tokenOffset(); - - if (pragmaValue == library) { - unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary; - replaceWithSpace(script, startOffset, endOffset - startOffset); - } else { - return; - } - - token = l.lex(); - if (l.tokenStartLine() == startLine) - return; - - } else { - return; - } - } - return; -} - void Document::removeScriptPragmas(QString &script) { const QString pragma(QLatin1String("pragma")); @@ -541,6 +295,45 @@ Document::Document(bool debugMode) { } +ScriptDirectivesCollector::ScriptDirectivesCollector(QQmlJS::Engine *engine, QV4::Compiler::JSUnitGenerator *unitGenerator) + : engine(engine) + , jsGenerator(unitGenerator) + , hasPragmaLibrary(false) +{ +} + +void ScriptDirectivesCollector::pragmaLibrary() +{ + hasPragmaLibrary = true; +} + +void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString &module, int lineNumber, int column) +{ + QV4::CompiledData::Import *import = engine->pool()->New(); + import->type = QV4::CompiledData::Import::ImportScript; + import->uriIndex = jsGenerator->registerString(jsfile); + import->qualifierIndex = jsGenerator->registerString(module); + import->location.line = lineNumber; + import->location.column = column; + imports << import; +} + +void ScriptDirectivesCollector::importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column) +{ + QV4::CompiledData::Import *import = engine->pool()->New(); + import->type = QV4::CompiledData::Import::ImportLibrary; + import->uriIndex = jsGenerator->registerString(uri); + int vmaj; + int vmin; + IRBuilder::extractVersion(QStringRef(&version), &vmaj, &vmin); + import->majorVersion = vmaj; + import->minorVersion = vmin; + import->qualifierIndex = jsGenerator->registerString(module); + import->location.line = lineNumber; + import->location.column = column; + imports << import; +} + IRBuilder::IRBuilder(const QSet &illegalNames) : illegalNames(illegalNames) , _object(0) diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index cc22023f8e..6177b96878 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -326,10 +327,23 @@ struct Q_QML_PRIVATE_EXPORT Document int registerString(const QString &str) { return jsGenerator.registerString(str); } QString stringAt(int index) const { return jsGenerator.stringForIndex(index); } - void extractScriptMetaData(QString &script, QQmlJS::DiagnosticMessage *error); static void removeScriptPragmas(QString &script); }; +struct Q_QML_PRIVATE_EXPORT ScriptDirectivesCollector : public QQmlJS::Directives +{ + ScriptDirectivesCollector(QQmlJS::Engine *engine, QV4::Compiler::JSUnitGenerator *unitGenerator); + + QQmlJS::Engine *engine; + QV4::Compiler::JSUnitGenerator *jsGenerator; + QList imports; + bool hasPragmaLibrary; + + virtual void pragmaLibrary(); + virtual void importFile(const QString &jsfile, const QString &module, int lineNumber, int column); + virtual void importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column); +}; + struct Q_QML_PRIVATE_EXPORT IRBuilder : public QQmlJS::AST::Visitor { Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator) diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index 9191b53cfc..4337fc1101 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -321,12 +321,14 @@ Function *Script::function() return vmFunction; } -QQmlRefPointer Script::precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList *reportedErrors) +QQmlRefPointer Script::precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList *reportedErrors, QQmlJS::Directives *directivesCollector) { using namespace QQmlJS; using namespace QQmlJS::AST; QQmlJS::Engine ee; + if (directivesCollector) + ee.setDirectives(directivesCollector); QQmlJS::Lexer lexer(&ee); lexer.setCode(source, /*line*/1, /*qml mode*/false); QQmlJS::Parser parser(&ee); diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h index 467e8af3e5..894859ce5e 100644 --- a/src/qml/jsruntime/qv4script_p.h +++ b/src/qml/jsruntime/qv4script_p.h @@ -43,6 +43,10 @@ QT_BEGIN_NAMESPACE class QQmlContextData; +namespace QQmlJS { +class Directives; +} + namespace QV4 { struct ContextStateSaver { @@ -137,7 +141,8 @@ struct Q_QML_EXPORT Script { Function *function(); - static QQmlRefPointer precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList *reportedErrors = 0); + static QQmlRefPointer precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, + QList *reportedErrors = 0, QQmlJS::Directives *directivesCollector = 0); static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, Object *scopeObject); }; diff --git a/src/qml/parser/qqmljsengine_p.cpp b/src/qml/parser/qqmljsengine_p.cpp index 7ac5dbc9bb..fac2d82ff2 100644 --- a/src/qml/parser/qqmljsengine_p.cpp +++ b/src/qml/parser/qqmljsengine_p.cpp @@ -114,7 +114,7 @@ double integerFromString(const QString &str, int radix) Engine::Engine() - : _lexer(0) + : _lexer(0), _directives(0) { } Engine::~Engine() @@ -135,6 +135,12 @@ Lexer *Engine::lexer() const void Engine::setLexer(Lexer *lexer) { _lexer = lexer; } +Directives *Engine::directives() const +{ return _directives; } + +void Engine::setDirectives(Directives *directives) +{ _directives = directives; } + MemoryPool *Engine::pool() { return &_pool; } diff --git a/src/qml/parser/qqmljsengine_p.h b/src/qml/parser/qqmljsengine_p.h index 661681d19c..81b2aa88e7 100644 --- a/src/qml/parser/qqmljsengine_p.h +++ b/src/qml/parser/qqmljsengine_p.h @@ -57,6 +57,7 @@ QT_QML_BEGIN_NAMESPACE namespace QQmlJS { class Lexer; +class Directives; class MemoryPool; class QML_PARSER_EXPORT DiagnosticMessage @@ -84,6 +85,7 @@ public: class QML_PARSER_EXPORT Engine { Lexer *_lexer; + Directives *_directives; MemoryPool _pool; QList _comments; QString _extraCode; @@ -102,6 +104,9 @@ public: Lexer *lexer() const; void setLexer(Lexer *lexer); + Directives *directives() const; + void setDirectives(Directives *directives); + MemoryPool *pool(); inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); } diff --git a/src/qml/parser/qqmljslexer.cpp b/src/qml/parser/qqmljslexer.cpp index 2e2a4e5f8b..a69fa21c32 100644 --- a/src/qml/parser/qqmljslexer.cpp +++ b/src/qml/parser/qqmljslexer.cpp @@ -1224,12 +1224,60 @@ bool Lexer::canInsertAutomaticSemicolon(int token) const || _followsClosingBrace; } -bool Lexer::scanDirectives(Directives *directives) +static const int uriTokens[] = { + QQmlJSGrammar::T_IDENTIFIER, + QQmlJSGrammar::T_PROPERTY, + QQmlJSGrammar::T_SIGNAL, + QQmlJSGrammar::T_READONLY, + QQmlJSGrammar::T_ON, + QQmlJSGrammar::T_BREAK, + QQmlJSGrammar::T_CASE, + QQmlJSGrammar::T_CATCH, + QQmlJSGrammar::T_CONTINUE, + QQmlJSGrammar::T_DEFAULT, + QQmlJSGrammar::T_DELETE, + QQmlJSGrammar::T_DO, + QQmlJSGrammar::T_ELSE, + QQmlJSGrammar::T_FALSE, + QQmlJSGrammar::T_FINALLY, + QQmlJSGrammar::T_FOR, + QQmlJSGrammar::T_FUNCTION, + QQmlJSGrammar::T_IF, + QQmlJSGrammar::T_IN, + QQmlJSGrammar::T_INSTANCEOF, + QQmlJSGrammar::T_NEW, + QQmlJSGrammar::T_NULL, + QQmlJSGrammar::T_RETURN, + QQmlJSGrammar::T_SWITCH, + QQmlJSGrammar::T_THIS, + QQmlJSGrammar::T_THROW, + QQmlJSGrammar::T_TRUE, + QQmlJSGrammar::T_TRY, + QQmlJSGrammar::T_TYPEOF, + QQmlJSGrammar::T_VAR, + QQmlJSGrammar::T_VOID, + QQmlJSGrammar::T_WHILE, + QQmlJSGrammar::T_CONST, + QQmlJSGrammar::T_DEBUGGER, + QQmlJSGrammar::T_RESERVED_WORD, + QQmlJSGrammar::T_WITH, + + QQmlJSGrammar::EOF_SYMBOL +}; +static inline bool isUriToken(int token) { - if (_qmlMode) { - // the directives are a Javascript-only extension. - return false; + const int *current = uriTokens; + while (*current != QQmlJSGrammar::EOF_SYMBOL) { + if (*current == token) + return true; + ++current; } + return false; +} + +bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error) +{ + Q_ASSERT(!_qmlMode); lex(); // fetch the first token @@ -1237,24 +1285,33 @@ bool Lexer::scanDirectives(Directives *directives) return true; do { - lex(); // skip T_DOT - const int lineNumber = tokenStartLine(); + const int column = tokenStartColumn(); + + lex(); // skip T_DOT if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD)) - return false; // expected a valid QML/JS directive + return true; // expected a valid QML/JS directive const QString directiveName = tokenText(); if (! (directiveName == QLatin1String("pragma") || - directiveName == QLatin1String("import"))) + directiveName == QLatin1String("import"))) { + error->message = QCoreApplication::translate("QQmlParser", "Syntax error"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; // not a valid directive name + } // it must be a pragma or an import directive. if (directiveName == QLatin1String("pragma")) { // .pragma library - if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) + if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) { + error->message = QCoreApplication::translate("QQmlParser", "Syntax error"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; // expected `library + } // we found a .pragma library directive directives->pragmaLibrary(); @@ -1273,22 +1330,53 @@ bool Lexer::scanDirectives(Directives *directives) fileImport = true; pathOrUri = tokenText(); + if (!pathOrUri.endsWith(QLatin1String("js"))) { + error->message = QCoreApplication::translate("QQmlParser","Imported file must be a script"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); + return false; + } + } else if (_tokenKind == T_IDENTIFIER) { // .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER - pathOrUri = tokenText(); + while (true) { + if (!isUriToken(_tokenKind)) { + error->message = QCoreApplication::translate("QQmlParser","Invalid module URI"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); + return false; + } - lex(); // skip the first T_IDENTIFIER - for (; _tokenKind == T_DOT; lex()) { - if (lex() != T_IDENTIFIER) + pathOrUri.append(tokenText()); + + lex(); + if (tokenStartLine() != lineNumber) { + error->message = QCoreApplication::translate("QQmlParser","Invalid module URI"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; + } + if (_tokenKind != QQmlJSGrammar::T_DOT) + break; + + pathOrUri.append(QLatin1Char('.')); - pathOrUri += QLatin1Char('.'); - pathOrUri += tokenText(); + lex(); + if (tokenStartLine() != lineNumber) { + error->message = QCoreApplication::translate("QQmlParser","Invalid module URI"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); + return false; + } } - if (_tokenKind != T_NUMERIC_LITERAL) + if (_tokenKind != T_NUMERIC_LITERAL) { + error->message = QCoreApplication::translate("QQmlParser","Module import requires a version"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; // expected the module version number + } version = tokenText(); } @@ -1296,22 +1384,51 @@ bool Lexer::scanDirectives(Directives *directives) // // recognize the mandatory `as' followed by the module name // - if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("as"))) + if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("as") && tokenStartLine() == lineNumber)) { + if (fileImport) + error->message = QCoreApplication::translate("QQmlParser", "File import requires a qualifier"); + else + error->message = QCoreApplication::translate("QQmlParser", "Module import requires a qualifier"); + if (tokenStartLine() != lineNumber) { + error->loc.startLine = lineNumber; + error->loc.startColumn = column; + } else { + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); + } return false; // expected `as' + } - if (lex() != T_IDENTIFIER) + if (lex() != T_IDENTIFIER || tokenStartLine() != lineNumber) { + if (fileImport) + error->message = QCoreApplication::translate("QQmlParser", "File import requires a qualifier"); + else + error->message = QCoreApplication::translate("QQmlParser", "Module import requires a qualifier"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; // expected module name + } const QString module = tokenText(); + if (!module.at(0).isUpper()) { + error->message = QCoreApplication::translate("QQmlParser","Invalid import qualifier"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); + return false; + } if (fileImport) - directives->importFile(pathOrUri, module); + directives->importFile(pathOrUri, module, lineNumber, column); else - directives->importModule(pathOrUri, version, module); + directives->importModule(pathOrUri, version, module, lineNumber, column); } - if (tokenStartLine() != lineNumber) + if (tokenStartLine() != lineNumber) { + error->message = QCoreApplication::translate("QQmlParser", "Syntax error"); + error->loc.startLine = tokenStartLine(); + error->loc.startColumn = tokenStartColumn(); return false; // the directives cannot span over multiple lines + } // fetch the first token after the .pragma/.import directive lex(); diff --git a/src/qml/parser/qqmljslexer_p.h b/src/qml/parser/qqmljslexer_p.h index 9106c94477..b5415f8777 100644 --- a/src/qml/parser/qqmljslexer_p.h +++ b/src/qml/parser/qqmljslexer_p.h @@ -55,6 +55,7 @@ QT_QML_BEGIN_NAMESPACE namespace QQmlJS { class Engine; +class DiagnosticMessage; class QML_PARSER_EXPORT Directives { public: @@ -64,17 +65,21 @@ public: { } - virtual void importFile(const QString &jsfile, const QString &module) + virtual void importFile(const QString &jsfile, const QString &module, int line, int column) { Q_UNUSED(jsfile); Q_UNUSED(module); + Q_UNUSED(line); + Q_UNUSED(column); } - virtual void importModule(const QString &uri, const QString &version, const QString &module) + virtual void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) { Q_UNUSED(uri); Q_UNUSED(version); Q_UNUSED(module); + Q_UNUSED(line); + Q_UNUSED(column); } }; @@ -146,7 +151,7 @@ public: int lex(); bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); - bool scanDirectives(Directives *directives); + bool scanDirectives(Directives *directives, DiagnosticMessage *error); int regExpFlags() const { return _patternFlags; } QString regExpPattern() const { return _tokenText; } diff --git a/src/qml/parser/qqmljsparser.cpp b/src/qml/parser/qqmljsparser.cpp index 762e60c827..35eb0e3ad5 100644 --- a/src/qml/parser/qqmljsparser.cpp +++ b/src/qml/parser/qqmljsparser.cpp @@ -161,7 +161,24 @@ bool Parser::parse(int startToken) token_buffer[0].token = startToken; first_token = &token_buffer[0]; - last_token = &token_buffer[1]; + if (startToken == T_FEED_JS_PROGRAM && !lexer->qmlMode()) { + Directives ignoreDirectives; + Directives *directives = driver->directives(); + if (!directives) + directives = &ignoreDirectives; + DiagnosticMessage error; + if (!lexer->scanDirectives(directives, &error)) { + diagnostic_messages.append(error); + return false; + } + token_buffer[1].token = lexer->tokenKind(); + token_buffer[1].dval = lexer->tokenValue(); + token_buffer[1].loc = location(lexer); + token_buffer[1].spell = lexer->tokenSpell(); + last_token = &token_buffer[2]; + } else { + last_token = &token_buffer[1]; + } tos = -1; program = 0; diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 927577a9e1..4b7fe9eb04 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -2584,20 +2584,10 @@ void QQmlScriptBlob::dataReceived(const Data &data) QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_typeLoader->engine()); QmlIR::Document irUnit(v4->debugger != 0); - QQmlJS::DiagnosticMessage metaDataError; - irUnit.extractScriptMetaData(source, &metaDataError); - if (!metaDataError.message.isEmpty()) { - QQmlError e; - e.setUrl(finalUrl()); - e.setLine(metaDataError.loc.startLine); - e.setColumn(metaDataError.loc.startColumn); - e.setDescription(metaDataError.message); - setError(e); - return; - } + QmlIR::ScriptDirectivesCollector collector(&irUnit.jsParserEngine, &irUnit.jsGenerator); QList errors; - QQmlRefPointer unit = QV4::Script::precompile(&irUnit.jsModule, &irUnit.jsGenerator, v4, finalUrl(), source, &errors); + QQmlRefPointer unit = QV4::Script::precompile(&irUnit.jsModule, &irUnit.jsGenerator, v4, finalUrl(), source, &errors, &collector); // No need to addref on unit, it's initial refcount is 1 source.clear(); if (!errors.isEmpty()) { @@ -2608,6 +2598,9 @@ void QQmlScriptBlob::dataReceived(const Data &data) unit.take(new EmptyCompilationUnit); } irUnit.javaScriptCompilationUnit = unit; + irUnit.imports = collector.imports; + if (collector.hasPragmaLibrary) + irUnit.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary; QmlIR::QmlUnitGenerator qmlGenerator; QV4::CompiledData::Unit *unitData = qmlGenerator.generate(irUnit); diff --git a/tests/auto/qml/qmlmin/tst_qmlmin.cpp b/tests/auto/qml/qmlmin/tst_qmlmin.cpp index 85d1cc6836..1ff259b74c 100644 --- a/tests/auto/qml/qmlmin/tst_qmlmin.cpp +++ b/tests/auto/qml/qmlmin/tst_qmlmin.cpp @@ -107,9 +107,12 @@ void tst_qmlmin::initTestCase() invalidFiles << "tests/auto/qml/parserstress/tests/ecma_3/Unicode/regress-352044-02-n.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon_error1.qml"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedFileQualifier.js"; + invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedFileQualifier.2.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedImport.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedModule.js"; + invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedFile.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedModuleQualifier.js"; + invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedModuleQualifier.2.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedModuleVersion.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingFileQualifier.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingModuleQualifier.js"; diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index cd1302e5c8..d4c39e9112 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -4107,7 +4107,7 @@ void tst_qqmlecmascript::importScripts_data() << testFileUrl("jsimportfail/malformedImport.qml") << false /* compilation should succeed */ << QString() - << (QStringList() << testFileUrl("jsimportfail/malformedImport.js").toString() + QLatin1String(":1:1: Syntax error")) + << (QStringList() << testFileUrl("jsimportfail/malformedImport.js").toString() + QLatin1String(":1:2: Syntax error")) << QStringList() << QVariantList(); @@ -4139,7 +4139,7 @@ void tst_qqmlecmascript::importScripts_data() << testFileUrl("jsimportfail/malformedFileQualifier.2.qml") << false /* compilation should succeed */ << QString() - << (QStringList() << testFileUrl("jsimportfail/malformedFileQualifier.2.js").toString() + QLatin1String(":1:1: Invalid import qualifier")) + << (QStringList() << testFileUrl("jsimportfail/malformedFileQualifier.2.js").toString() + QLatin1String(":1:23: Invalid import qualifier")) << QStringList() << QVariantList(); @@ -4187,7 +4187,7 @@ void tst_qqmlecmascript::importScripts_data() << testFileUrl("jsimportfail/malformedModuleQualifier.2.qml") << false /* compilation should succeed */ << QString() - << (QStringList() << testFileUrl("jsimportfail/malformedModuleQualifier.2.js").toString() + QLatin1String(":1:1: Invalid import qualifier")) + << (QStringList() << testFileUrl("jsimportfail/malformedModuleQualifier.2.js").toString() + QLatin1String(":1:24: Invalid import qualifier")) << QStringList() << QVariantList(); } diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 6f5ec28c4d..08710d358f 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -246,6 +246,41 @@ static QVariantList findQmlImportsInQmlFile(const QString &filePath) return findQmlImportsInQmlCode(filePath, code); } +struct ImportCollector : public QQmlJS::Directives +{ + QVariantList imports; + + virtual void importFile(const QString &jsfile, const QString &module, int line, int column) + { + QVariantMap entry; + entry[QLatin1String("type")] = QStringLiteral("javascript"); + entry[QLatin1String("path")] = jsfile; + imports << entry; + + Q_UNUSED(module); + Q_UNUSED(line); + Q_UNUSED(column); + } + + virtual void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) + { + QVariantMap entry; + if (uri.contains(QLatin1Char('/'))) { + entry[QLatin1String("type")] = QStringLiteral("directory"); + entry[QLatin1String("name")] = uri; + } else { + entry[QLatin1String("type")] = QStringLiteral("module"); + entry[QLatin1String("name")] = uri; + entry[QLatin1String("version")] = version; + } + imports << entry; + + Q_UNUSED(module); + Q_UNUSED(line); + Q_UNUSED(column); + } +}; + // Scan a single javascrupt file for import statements QVariantList findQmlImportsInJavascriptFile(const QString &filePath) { @@ -256,42 +291,22 @@ QVariantList findQmlImportsInJavascriptFile(const QString &filePath) return QVariantList(); } - QVariantList imports; - QString sourceCode = QString::fromUtf8(file.readAll()); file.close(); - QmlIR::Document doc(/*debug mode*/false); - QQmlJS::DiagnosticMessage error; - doc.extractScriptMetaData(sourceCode, &error); - if (!error.message.isEmpty()) - return imports; - foreach (const QV4::CompiledData::Import *import, doc.imports) { - QVariantMap entry; - const QString name = doc.stringAt(import->uriIndex); - switch (import->type) { - case QV4::CompiledData::Import::ImportScript: - entry[QStringLiteral("type")] = QStringLiteral("javascript"); - entry[QStringLiteral("path")] = name; - break; - case QV4::CompiledData::Import::ImportLibrary: - if (name.contains(QLatin1Char('/'))) { - entry[QStringLiteral("type")] = QStringLiteral("directory"); - entry[QStringLiteral("name")] = name; - } else { - entry[QStringLiteral("type")] = QStringLiteral("module"); - entry[QStringLiteral("name")] = name; - entry[QStringLiteral("version")] = QString::number(import->majorVersion) + QLatin1Char('.') + QString::number(import->minorVersion); - } - break; - default: - Q_UNREACHABLE(); - continue; - } - imports << entry; - } + QQmlJS::Engine ee; + ImportCollector collector; + ee.setDirectives(&collector); + QQmlJS::Lexer lexer(&ee); + lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false); + QQmlJS::Parser parser(&ee); + parser.parseProgram(); - return imports; + foreach (const QQmlJS::DiagnosticMessage &m, parser.diagnosticMessages()) + if (m.isError()) + return QVariantList(); + + return collector.imports; } // Scan a single qml or js file for import statements diff --git a/tools/qmlmin/main.cpp b/tools/qmlmin/main.cpp index aa3adf053d..aa99b242a7 100644 --- a/tools/qmlmin/main.cpp +++ b/tools/qmlmin/main.cpp @@ -93,7 +93,7 @@ public: _directives += QLatin1String(".pragma library\n"); } - virtual void importFile(const QString &jsfile, const QString &module) + virtual void importFile(const QString &jsfile, const QString &module, int line, int column) { _directives += QLatin1String(".import"); _directives += QLatin1Char('"'); @@ -102,9 +102,11 @@ public: _directives += QLatin1String("as "); _directives += module; _directives += QLatin1Char('\n'); + Q_UNUSED(line); + Q_UNUSED(column); } - virtual void importModule(const QString &uri, const QString &version, const QString &module) + virtual void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) { _directives += QLatin1String(".import "); _directives += uri; @@ -113,6 +115,8 @@ public: _directives += QLatin1String(" as "); _directives += module; _directives += QLatin1Char('\n'); + Q_UNUSED(line); + Q_UNUSED(column); } protected: @@ -262,7 +266,8 @@ bool Minify::parse(int startToken) if (startToken == T_FEED_JS_PROGRAM) { // parse optional pragma directive - if (scanDirectives(this)) { + DiagnosticMessage error; + if (scanDirectives(this, &error)) { // append the scanned directives to the minifier code. append(directives()); @@ -433,7 +438,8 @@ bool Tokenize::parse(int startToken) if (startToken == T_FEED_JS_PROGRAM) { // parse optional pragma directive - if (scanDirectives(this)) { + DiagnosticMessage error; + if (scanDirectives(this, &error)) { // append the scanned directives as one token to // the token stream. _minifiedCode.append(directives()); -- cgit v1.2.3