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;
}
#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 <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)
{
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)
: illegalNames(illegalNames)
, _object(0)

View File

@ -41,6 +41,7 @@
#include <private/qqmljsmemorypool_p.h>
#include <private/qv4codegen_p.h>
#include <private/qv4compiler_p.h>
#include <private/qqmljslexer_p.h>
#include <QTextStream>
#include <QCoreApplication>
@ -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<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
{
Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator)

View File

@ -321,12 +321,14 @@ Function *Script::function()
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::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);

View File

@ -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<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);
};

View File

@ -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; }

View File

@ -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<AST::SourceLocation> _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); }

View File

@ -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 {
const int lineNumber = tokenStartLine();
const int column = tokenStartColumn();
lex(); // skip T_DOT
const int lineNumber = tokenStartLine();
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();
lex(); // skip the first T_IDENTIFIER
for (; _tokenKind == T_DOT; lex()) {
if (lex() != T_IDENTIFIER)
while (true) {
if (!isUriToken(_tokenKind)) {
error->message = QCoreApplication::translate("QQmlParser","Invalid module URI");
error->loc.startLine = tokenStartLine();
error->loc.startColumn = tokenStartColumn();
return false;
}
pathOrUri += QLatin1Char('.');
pathOrUri += tokenText();
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)
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();

View File

@ -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; }

View File

@ -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;

View File

@ -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<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
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);

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/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";

View File

@ -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();
}

View File

@ -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

View File

@ -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());