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:
parent
bede2a3ac7
commit
9d7b27f5bf
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -1224,37 +1224,94 @@ 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.
|
||||
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
|
||||
|
||||
if (_tokenKind != T_DOT)
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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")))
|
||||
return false; // expected `as'
|
||||
|
||||
if (lex() != T_IDENTIFIER)
|
||||
return false; // expected module name
|
||||
|
||||
const QString module = tokenText();
|
||||
|
||||
if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("as") && tokenStartLine() == lineNumber)) {
|
||||
if (fileImport)
|
||||
directives->importFile(pathOrUri, module);
|
||||
error->message = QCoreApplication::translate("QQmlParser", "File import requires a qualifier");
|
||||
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
|
||||
}
|
||||
|
||||
// fetch the first token after the .pragma/.import directive
|
||||
lex();
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -161,7 +161,24 @@ bool Parser::parse(int startToken)
|
|||
|
||||
token_buffer[0].token = startToken;
|
||||
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];
|
||||
}
|
||||
|
||||
tos = -1;
|
||||
program = 0;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue