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 <fawzi.mohamed@theqtcompany.com>
This commit is contained in:
Simon Hausmann 2014-12-05 16:32:56 +01:00 committed by Simon Hausmann
parent bede2a3ac7
commit 9d7b27f5bf
14 changed files with 308 additions and 327 deletions

View File

@ -216,60 +216,6 @@ static void replaceWithSpace(QString &str, int idx, int n)
*data++ = space; *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() void Document::collectTypeReferences()
{ {
foreach (Object *obj, objects) { 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 <URI> <Version> as <Identifier>
// .import <file.js> as <Identifier>
token = l.lex();
CHECK_LINE;
QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>();
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) void Document::removeScriptPragmas(QString &script)
{ {
const QString pragma(QLatin1String("pragma")); 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<QV4::CompiledData::Import>();
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<QV4::CompiledData::Import>();
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<QString> &illegalNames) IRBuilder::IRBuilder(const QSet<QString> &illegalNames)
: illegalNames(illegalNames) : illegalNames(illegalNames)
, _object(0) , _object(0)

View File

@ -41,6 +41,7 @@
#include <private/qqmljsmemorypool_p.h> #include <private/qqmljsmemorypool_p.h>
#include <private/qv4codegen_p.h> #include <private/qv4codegen_p.h>
#include <private/qv4compiler_p.h> #include <private/qv4compiler_p.h>
#include <private/qqmljslexer_p.h>
#include <QTextStream> #include <QTextStream>
#include <QCoreApplication> #include <QCoreApplication>
@ -326,10 +327,23 @@ struct Q_QML_PRIVATE_EXPORT Document
int registerString(const QString &str) { return jsGenerator.registerString(str); } int registerString(const QString &str) { return jsGenerator.registerString(str); }
QString stringAt(int index) const { return jsGenerator.stringForIndex(index); } QString stringAt(int index) const { return jsGenerator.stringForIndex(index); }
void extractScriptMetaData(QString &script, QQmlJS::DiagnosticMessage *error);
static void removeScriptPragmas(QString &script); 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<const QV4::CompiledData::Import *> 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 struct Q_QML_PRIVATE_EXPORT IRBuilder : public QQmlJS::AST::Visitor
{ {
Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator) Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator)

View File

@ -321,12 +321,14 @@ Function *Script::function()
return vmFunction; return vmFunction;
} }
QQmlRefPointer<QV4::CompiledData::CompilationUnit> Script::precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList<QQmlError> *reportedErrors) QQmlRefPointer<QV4::CompiledData::CompilationUnit> Script::precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList<QQmlError> *reportedErrors, QQmlJS::Directives *directivesCollector)
{ {
using namespace QQmlJS; using namespace QQmlJS;
using namespace QQmlJS::AST; using namespace QQmlJS::AST;
QQmlJS::Engine ee; QQmlJS::Engine ee;
if (directivesCollector)
ee.setDirectives(directivesCollector);
QQmlJS::Lexer lexer(&ee); QQmlJS::Lexer lexer(&ee);
lexer.setCode(source, /*line*/1, /*qml mode*/false); lexer.setCode(source, /*line*/1, /*qml mode*/false);
QQmlJS::Parser parser(&ee); QQmlJS::Parser parser(&ee);

View File

@ -43,6 +43,10 @@ QT_BEGIN_NAMESPACE
class QQmlContextData; class QQmlContextData;
namespace QQmlJS {
class Directives;
}
namespace QV4 { namespace QV4 {
struct ContextStateSaver { struct ContextStateSaver {
@ -137,7 +141,8 @@ struct Q_QML_EXPORT Script {
Function *function(); Function *function();
static QQmlRefPointer<CompiledData::CompilationUnit> precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList<QQmlError> *reportedErrors = 0); static QQmlRefPointer<CompiledData::CompilationUnit> precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source,
QList<QQmlError> *reportedErrors = 0, QQmlJS::Directives *directivesCollector = 0);
static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, Object *scopeObject); static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, Object *scopeObject);
}; };

View File

@ -114,7 +114,7 @@ double integerFromString(const QString &str, int radix)
Engine::Engine() Engine::Engine()
: _lexer(0) : _lexer(0), _directives(0)
{ } { }
Engine::~Engine() Engine::~Engine()
@ -135,6 +135,12 @@ Lexer *Engine::lexer() const
void Engine::setLexer(Lexer *lexer) void Engine::setLexer(Lexer *lexer)
{ _lexer = lexer; } { _lexer = lexer; }
Directives *Engine::directives() const
{ return _directives; }
void Engine::setDirectives(Directives *directives)
{ _directives = directives; }
MemoryPool *Engine::pool() MemoryPool *Engine::pool()
{ return &_pool; } { return &_pool; }

View File

@ -57,6 +57,7 @@ QT_QML_BEGIN_NAMESPACE
namespace QQmlJS { namespace QQmlJS {
class Lexer; class Lexer;
class Directives;
class MemoryPool; class MemoryPool;
class QML_PARSER_EXPORT DiagnosticMessage class QML_PARSER_EXPORT DiagnosticMessage
@ -84,6 +85,7 @@ public:
class QML_PARSER_EXPORT Engine class QML_PARSER_EXPORT Engine
{ {
Lexer *_lexer; Lexer *_lexer;
Directives *_directives;
MemoryPool _pool; MemoryPool _pool;
QList<AST::SourceLocation> _comments; QList<AST::SourceLocation> _comments;
QString _extraCode; QString _extraCode;
@ -102,6 +104,9 @@ public:
Lexer *lexer() const; Lexer *lexer() const;
void setLexer(Lexer *lexer); void setLexer(Lexer *lexer);
Directives *directives() const;
void setDirectives(Directives *directives);
MemoryPool *pool(); MemoryPool *pool();
inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); } inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); }

View File

@ -1224,37 +1224,94 @@ bool Lexer::canInsertAutomaticSemicolon(int token) const
|| _followsClosingBrace; || _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) { const int *current = uriTokens;
// the directives are a Javascript-only extension. while (*current != QQmlJSGrammar::EOF_SYMBOL) {
if (*current == token)
return true;
++current;
}
return false; return false;
} }
bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error)
{
Q_ASSERT(!_qmlMode);
lex(); // fetch the first token lex(); // fetch the first token
if (_tokenKind != T_DOT) if (_tokenKind != T_DOT)
return true; return true;
do { do {
const int lineNumber = tokenStartLine();
const int column = tokenStartColumn();
lex(); // skip T_DOT lex(); // skip T_DOT
const int lineNumber = tokenStartLine();
if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD)) 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(); const QString directiveName = tokenText();
if (! (directiveName == QLatin1String("pragma") || 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 return false; // not a valid directive name
}
// it must be a pragma or an import directive. // it must be a pragma or an import directive.
if (directiveName == QLatin1String("pragma")) { if (directiveName == QLatin1String("pragma")) {
// .pragma library // .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 return false; // expected `library
}
// we found a .pragma library directive // we found a .pragma library directive
directives->pragmaLibrary(); directives->pragmaLibrary();
@ -1273,22 +1330,53 @@ bool Lexer::scanDirectives(Directives *directives)
fileImport = true; fileImport = true;
pathOrUri = tokenText(); 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) { } else if (_tokenKind == T_IDENTIFIER) {
// .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER // .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER
pathOrUri = tokenText(); while (true) {
if (!isUriToken(_tokenKind)) {
lex(); // skip the first T_IDENTIFIER error->message = QCoreApplication::translate("QQmlParser","Invalid module URI");
for (; _tokenKind == T_DOT; lex()) { error->loc.startLine = tokenStartLine();
if (lex() != T_IDENTIFIER) error->loc.startColumn = tokenStartColumn();
return false; return false;
pathOrUri += QLatin1Char('.');
pathOrUri += tokenText();
} }
if (_tokenKind != T_NUMERIC_LITERAL) 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('.'));
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) {
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 return false; // expected the module version number
}
version = tokenText(); version = tokenText();
} }
@ -1296,22 +1384,51 @@ bool Lexer::scanDirectives(Directives *directives)
// //
// recognize the mandatory `as' followed by the module name // 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)) {
return false; // expected `as'
if (lex() != T_IDENTIFIER)
return false; // expected module name
const QString module = tokenText();
if (fileImport) if (fileImport)
directives->importFile(pathOrUri, module); error->message = QCoreApplication::translate("QQmlParser", "File import requires a qualifier");
else else
directives->importModule(pathOrUri, version, module); 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 (tokenStartLine() != lineNumber) 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, lineNumber, column);
else
directives->importModule(pathOrUri, version, module, lineNumber, column);
}
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 return false; // the directives cannot span over multiple lines
}
// fetch the first token after the .pragma/.import directive // fetch the first token after the .pragma/.import directive
lex(); lex();

View File

@ -55,6 +55,7 @@ QT_QML_BEGIN_NAMESPACE
namespace QQmlJS { namespace QQmlJS {
class Engine; class Engine;
class DiagnosticMessage;
class QML_PARSER_EXPORT Directives { class QML_PARSER_EXPORT Directives {
public: 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(jsfile);
Q_UNUSED(module); 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(uri);
Q_UNUSED(version); Q_UNUSED(version);
Q_UNUSED(module); Q_UNUSED(module);
Q_UNUSED(line);
Q_UNUSED(column);
} }
}; };
@ -146,7 +151,7 @@ public:
int lex(); int lex();
bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix);
bool scanDirectives(Directives *directives); bool scanDirectives(Directives *directives, DiagnosticMessage *error);
int regExpFlags() const { return _patternFlags; } int regExpFlags() const { return _patternFlags; }
QString regExpPattern() const { return _tokenText; } QString regExpPattern() const { return _tokenText; }

View File

@ -161,7 +161,24 @@ bool Parser::parse(int startToken)
token_buffer[0].token = startToken; token_buffer[0].token = startToken;
first_token = &token_buffer[0]; first_token = &token_buffer[0];
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]; last_token = &token_buffer[1];
}
tos = -1; tos = -1;
program = 0; program = 0;

View File

@ -2584,20 +2584,10 @@ void QQmlScriptBlob::dataReceived(const Data &data)
QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_typeLoader->engine()); QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_typeLoader->engine());
QmlIR::Document irUnit(v4->debugger != 0); QmlIR::Document irUnit(v4->debugger != 0);
QQmlJS::DiagnosticMessage metaDataError; QmlIR::ScriptDirectivesCollector collector(&irUnit.jsParserEngine, &irUnit.jsGenerator);
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;
}
QList<QQmlError> errors; QList<QQmlError> errors;
QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Script::precompile(&irUnit.jsModule, &irUnit.jsGenerator, v4, finalUrl(), source, &errors); QQmlRefPointer<QV4::CompiledData::CompilationUnit> 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 // No need to addref on unit, it's initial refcount is 1
source.clear(); source.clear();
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
@ -2608,6 +2598,9 @@ void QQmlScriptBlob::dataReceived(const Data &data)
unit.take(new EmptyCompilationUnit); unit.take(new EmptyCompilationUnit);
} }
irUnit.javaScriptCompilationUnit = unit; irUnit.javaScriptCompilationUnit = unit;
irUnit.imports = collector.imports;
if (collector.hasPragmaLibrary)
irUnit.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary;
QmlIR::QmlUnitGenerator qmlGenerator; QmlIR::QmlUnitGenerator qmlGenerator;
QV4::CompiledData::Unit *unitData = qmlGenerator.generate(irUnit); QV4::CompiledData::Unit *unitData = qmlGenerator.generate(irUnit);

View File

@ -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/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/incrDecrSemicolon_error1.qml";
invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedFileQualifier.js"; 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/malformedImport.js";
invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/malformedModule.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.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/malformedModuleVersion.js";
invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingFileQualifier.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingFileQualifier.js";
invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingModuleQualifier.js"; invalidFiles << "tests/auto/qml/qqmlecmascript/data/jsimportfail/missingModuleQualifier.js";

View File

@ -4107,7 +4107,7 @@ void tst_qqmlecmascript::importScripts_data()
<< testFileUrl("jsimportfail/malformedImport.qml") << testFileUrl("jsimportfail/malformedImport.qml")
<< false /* compilation should succeed */ << false /* compilation should succeed */
<< QString() << QString()
<< (QStringList() << testFileUrl("jsimportfail/malformedImport.js").toString() + QLatin1String(":1:1: Syntax error")) << (QStringList() << testFileUrl("jsimportfail/malformedImport.js").toString() + QLatin1String(":1:2: Syntax error"))
<< QStringList() << QStringList()
<< QVariantList(); << QVariantList();
@ -4139,7 +4139,7 @@ void tst_qqmlecmascript::importScripts_data()
<< testFileUrl("jsimportfail/malformedFileQualifier.2.qml") << testFileUrl("jsimportfail/malformedFileQualifier.2.qml")
<< false /* compilation should succeed */ << false /* compilation should succeed */
<< QString() << 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() << QStringList()
<< QVariantList(); << QVariantList();
@ -4187,7 +4187,7 @@ void tst_qqmlecmascript::importScripts_data()
<< testFileUrl("jsimportfail/malformedModuleQualifier.2.qml") << testFileUrl("jsimportfail/malformedModuleQualifier.2.qml")
<< false /* compilation should succeed */ << false /* compilation should succeed */
<< QString() << 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() << QStringList()
<< QVariantList(); << QVariantList();
} }

View File

@ -246,6 +246,41 @@ static QVariantList findQmlImportsInQmlFile(const QString &filePath)
return findQmlImportsInQmlCode(filePath, code); 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 // Scan a single javascrupt file for import statements
QVariantList findQmlImportsInJavascriptFile(const QString &filePath) QVariantList findQmlImportsInJavascriptFile(const QString &filePath)
{ {
@ -256,42 +291,22 @@ QVariantList findQmlImportsInJavascriptFile(const QString &filePath)
return QVariantList(); return QVariantList();
} }
QVariantList imports;
QString sourceCode = QString::fromUtf8(file.readAll()); QString sourceCode = QString::fromUtf8(file.readAll());
file.close(); 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) { QQmlJS::Engine ee;
QVariantMap entry; ImportCollector collector;
const QString name = doc.stringAt(import->uriIndex); ee.setDirectives(&collector);
switch (import->type) { QQmlJS::Lexer lexer(&ee);
case QV4::CompiledData::Import::ImportScript: lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false);
entry[QStringLiteral("type")] = QStringLiteral("javascript"); QQmlJS::Parser parser(&ee);
entry[QStringLiteral("path")] = name; parser.parseProgram();
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;
}
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 // Scan a single qml or js file for import statements

View File

@ -93,7 +93,7 @@ public:
_directives += QLatin1String(".pragma library\n"); _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 += QLatin1String(".import");
_directives += QLatin1Char('"'); _directives += QLatin1Char('"');
@ -102,9 +102,11 @@ public:
_directives += QLatin1String("as "); _directives += QLatin1String("as ");
_directives += module; _directives += module;
_directives += QLatin1Char('\n'); _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 += QLatin1String(".import ");
_directives += uri; _directives += uri;
@ -113,6 +115,8 @@ public:
_directives += QLatin1String(" as "); _directives += QLatin1String(" as ");
_directives += module; _directives += module;
_directives += QLatin1Char('\n'); _directives += QLatin1Char('\n');
Q_UNUSED(line);
Q_UNUSED(column);
} }
protected: protected:
@ -262,7 +266,8 @@ bool Minify::parse(int startToken)
if (startToken == T_FEED_JS_PROGRAM) { if (startToken == T_FEED_JS_PROGRAM) {
// parse optional pragma directive // parse optional pragma directive
if (scanDirectives(this)) { DiagnosticMessage error;
if (scanDirectives(this, &error)) {
// append the scanned directives to the minifier code. // append the scanned directives to the minifier code.
append(directives()); append(directives());
@ -433,7 +438,8 @@ bool Tokenize::parse(int startToken)
if (startToken == T_FEED_JS_PROGRAM) { if (startToken == T_FEED_JS_PROGRAM) {
// parse optional pragma directive // parse optional pragma directive
if (scanDirectives(this)) { DiagnosticMessage error;
if (scanDirectives(this, &error)) {
// append the scanned directives as one token to // append the scanned directives as one token to
// the token stream. // the token stream.
_minifiedCode.append(directives()); _minifiedCode.append(directives());