diff options
Diffstat (limited to 'tools/qmllint')
-rw-r--r-- | tools/qmllint/fakemetaobject.cpp | 14 | ||||
-rw-r--r-- | tools/qmllint/fakemetaobject.h | 3 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.cpp | 205 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.h | 7 | ||||
-rw-r--r-- | tools/qmllint/main.cpp | 9 | ||||
-rw-r--r-- | tools/qmllint/qcoloroutput.cpp | 11 | ||||
-rw-r--r-- | tools/qmllint/qcoloroutput_p.h | 2 | ||||
-rw-r--r-- | tools/qmllint/qmljstypedescriptionreader.cpp | 67 | ||||
-rw-r--r-- | tools/qmllint/scopetree.cpp | 44 | ||||
-rw-r--r-- | tools/qmllint/scopetree.h | 5 |
10 files changed, 238 insertions, 129 deletions
diff --git a/tools/qmllint/fakemetaobject.cpp b/tools/qmllint/fakemetaobject.cpp index 514bb2fe42..8319ae6713 100644 --- a/tools/qmllint/fakemetaobject.cpp +++ b/tools/qmllint/fakemetaobject.cpp @@ -46,8 +46,8 @@ QString FakeMetaEnum::name() const void FakeMetaEnum::setName(const QString &name) { m_name = name; } -void FakeMetaEnum::addKey(const QString &key, int value) -{ m_keys.append(key); m_values.append(value); } +void FakeMetaEnum::addKey(const QString &key) +{ m_keys.append(key); } QString FakeMetaEnum::key(int index) const { return m_keys.at(index); } @@ -73,10 +73,6 @@ void FakeMetaEnum::addToHash(QCryptographicHash &hash) const hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar)); } - len = m_values.size(); - hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); - foreach (int value, m_values) - hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); } QString FakeMetaEnum::describe(int baseIndent) const @@ -84,16 +80,14 @@ QString FakeMetaEnum::describe(int baseIndent) const QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); QString res = QLatin1String("Enum "); res += name(); - res += QLatin1String(":{"); + res += QLatin1String(": ["); for (int i = 0; i < keyCount(); ++i) { res += newLine; res += QLatin1String(" "); res += key(i); - res += QLatin1String(": "); - res += QString::number(m_values.value(i, -1)); } res += newLine; - res += QLatin1Char('}'); + res += QLatin1Char(']'); return res; } diff --git a/tools/qmllint/fakemetaobject.h b/tools/qmllint/fakemetaobject.h index 4e0ea1f8b3..ae76596343 100644 --- a/tools/qmllint/fakemetaobject.h +++ b/tools/qmllint/fakemetaobject.h @@ -46,7 +46,6 @@ namespace LanguageUtils { class FakeMetaEnum { QString m_name; QStringList m_keys; - QList<int> m_values; public: FakeMetaEnum(); @@ -57,7 +56,7 @@ public: QString name() const; void setName(const QString &name); - void addKey(const QString &key, int value); + void addKey(const QString &key); QString key(int index) const; int keyCount() const; QStringList keys() const; diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 27939608d7..ee2a0c38c1 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -39,10 +39,18 @@ #include <private/qqmljslexer_p.h> #include <private/qqmljsparser_p.h> #include <private/qv4codegen_p.h> +#include <private/qqmldirparser_p.h> -QDebug operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc); +static QQmlDirParser createQmldirParserForFile(const QString &filename) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + QQmlDirParser parser; + parser.parse(f.readAll()); + return parser; +} -static QQmlJS::TypeDescriptionReader createReaderForFile(QString const &filename) +static QQmlJS::TypeDescriptionReader createQmltypesReaderForFile(QString const &filename) { QFile f(filename); f.open(QFile::ReadOnly); @@ -60,13 +68,12 @@ void FindUnqualifiedIDVisitor::leaveEnvironment() m_currentScope = m_currentScope->parentScope(); } -enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned }; +enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; -QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) +QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) { static const QLatin1Char Slash('/'); static const QLatin1Char Backslash('\\'); - static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), QString::SkipEmptyParts); @@ -96,7 +103,7 @@ QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePat return str; }; - for (int version = FullyVersioned; version <= Unversioned; ++version) { + for (int version = FullyVersioned; version <= BasePath; ++version) { const QString ver = versionString(vmaj, vmin, static_cast<ImportVersion>(version)); for (const QString &path : basePaths) { @@ -104,20 +111,23 @@ QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePat if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) dir += Slash; - // append to the end - qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + SlashPluginsDotQmltypes; + if (version == BasePath) { + qmlDirPathsPaths += dir; + } else { + // append to the end + qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; + } - if (version != Unversioned) { + if (version < Unversioned) { // insert in the middle for (int index = parts.count() - 2; index >= 0; --index) { qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash) + ver + Slash - + joinStringRefs(parts.mid(index + 1), Slash) + SlashPluginsDotQmltypes; + + joinStringRefs(parts.mid(index + 1), Slash); } } } } - return qmlDirPathsPaths; } @@ -130,19 +140,50 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo m_alreadySeenImports.insert(importId); } id = id.replace(QLatin1String("/"), QLatin1String(".")); - auto qmltypesPaths = completeQmltypesPaths(id, m_qmltypeDirs, major, minor); + auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor); QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects; QList<QQmlJS::ModuleApiInfo> moduleApis; QStringList dependencies; + static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); + static const QLatin1String SlashQmldir("/qmldir"); for (auto const &qmltypesPath : qmltypesPaths) { - if (QFile::exists(qmltypesPath)) { - auto reader = createReaderForFile(qmltypesPath); - auto succ = reader(&objects, &moduleApis, &dependencies); - if (!succ) { - qDebug() << reader.errorMessage(); + if (QFile::exists(qmltypesPath + SlashQmldir)) { + auto reader = createQmldirParserForFile(qmltypesPath + SlashQmldir); + const auto imports = reader.imports(); + for (const QString &import : imports) + importHelper(import, prefix, major, minor); + + QHash<QString, LanguageUtils::FakeMetaObject *> qmlComponents; + const auto components = reader.components(); + for (auto it = components.begin(), end = components.end(); it != end; ++it) { + const QString filePath = qmltypesPath + QLatin1Char('/') + it->fileName; + if (!QFile::exists(filePath)) { + m_colorOut.write(QLatin1String("warning: "), Warning); + m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ") + + qmltypesPath + SlashQmldir + + QLatin1String(" but does not exist.\n")); + continue; + } + + auto mo = qmlComponents.find(it.key()); + if (mo == qmlComponents.end()) + mo = qmlComponents.insert(it.key(), localQmlFile2FakeMetaObject(filePath)); + + (*mo)->addExport( + it.key(), reader.typeNamespace(), + LanguageUtils::ComponentVersion(it->majorVersion, it->minorVersion)); } - break; + for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) { + objects.insert(it.key(), + QSharedPointer<const LanguageUtils::FakeMetaObject>(it.value())); + } + } + if (QFile::exists(qmltypesPath + SlashPluginsDotQmltypes)) { + auto reader = createQmltypesReaderForFile(qmltypesPath + SlashPluginsDotQmltypes); + auto succ = reader(&objects, &moduleApis, &dependencies); + if (!succ) + m_colorOut.writeUncolored(reader.errorMessage()); } } for (auto const &dependency : qAsConst(dependencies)) { @@ -170,7 +211,8 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) { using namespace QQmlJS::AST; auto fake = new LanguageUtils::FakeMetaObject; - fake->setClassName(QFileInfo { filePath }.baseName()); + QString baseName = QFileInfo { filePath }.baseName(); + fake->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); QFile file(filePath); if (!file.open(QFile::ReadOnly)) { return fake; @@ -273,6 +315,7 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) auto sourceElement = static_cast<UiSourceElement *>(initMembers->member); if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { LanguageUtils::FakeMetaMethod method; + method.setMethodName(fexpr->name.toString()); method.setMethodType(LanguageUtils::FakeMetaMethod::Method); FormalParameterList *parameters = fexpr->formals; while (parameters) { @@ -290,13 +333,17 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) } else if (cast<VariableStatement *>(sourceElement->sourceElement)) { // nothing to do } else { - qDebug() << "unsupportedd sourceElement at" << sourceElement->firstSourceLocation() - << sourceElement->sourceElement->kind; + const auto loc = sourceElement->firstSourceLocation(); + m_colorOut.writeUncolored( + "unsupportedd sourceElement at " + + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) + + QString::number(sourceElement->sourceElement->kind)); } break; } default: { - qDebug() << "unsupported element of kind" << initMembers->member->kind; + m_colorOut.writeUncolored("unsupported element of kind " + + QString::number(initMembers->member->kind)); } } initMembers = initMembers->next; @@ -304,6 +351,22 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) return fake; } +void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const QString &prefix) +{ + QString dirname = directory; + QFileInfo info { dirname }; + if (info.isRelative()) + dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); + + QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; + while (it.hasNext()) { + LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); + m_exportedName2MetaObject.insert( + prefix + fake->className(), + QSharedPointer<const LanguageUtils::FakeMetaObject>(fake)); + } +} + void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name) { for (;;) { @@ -348,11 +411,10 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, QDirIterator::Subdirectories }; while (it.hasNext()) { - auto reader = createReaderForFile(it.next()); + auto reader = createQmltypesReaderForFile(it.next()); auto succ = reader(&objects, &moduleApis, &dependencies); - if (!succ) { - qDebug() << reader.errorMessage(); - } + if (!succ) + m_colorOut.writeUncolored(reader.errorMessage()); } } // add builtins @@ -377,6 +439,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) meta->addProperty(LanguageUtils::FakeMetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0}); meta->addProperty(LanguageUtils::FakeMetaProperty {"target", "QObject", false, false, false, 0}); m_exportedName2MetaObject["Connections"] = LanguageUtils::FakeMetaObject::ConstPtr { meta }; + + importDirectory(".", QString()); return true; } @@ -476,6 +540,23 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) leaveEnvironment(); } +static QString signalName(const QStringRef &handlerName) +{ + if (handlerName.startsWith("on") && handlerName.size() > 2) { + QString signal = handlerName.mid(2).toString(); + for (int i = 0; i < signal.length(); ++i) { + QCharRef ch = signal[i]; + if (ch.isLower()) + return QString(); + if (ch.isUpper()) { + ch = ch.toLower(); + return signal; + } + } + } + return QString(); +} + bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) { using namespace QQmlJS::AST; @@ -489,8 +570,17 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) if (m_currentScope->isVisualRootScope()) { m_rootId = identexp->name.toString(); } - } else if (name.startsWith("on") && name.size() > 2 && name.at(2).isUpper()) { - auto statement = uisb->statement; + } else { + const QString signal = signalName(name); + if (signal.isEmpty()) + return true; + + if (!m_currentScope->methods().contains(signal)) { + m_currentScope->addUnmatchedSignalHandler(name.toString(), uisb->firstSourceLocation()); + return true; + } + + const auto statement = uisb->statement; if (statement->kind == Node::Kind::Kind_ExpressionStatement) { if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) { // functions are already handled @@ -499,17 +589,14 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) return true; } } - QString signal = name.mid(2).toString(); - signal[0] = signal[0].toLower(); - if (!m_currentScope->methods().contains(signal)) { - qDebug() << "Info file does not contain signal" << signal; - } else { - auto method = m_currentScope->methods()[signal]; - for (auto const ¶m : method.parameterNames()) { - auto firstSourceLocation = uisb->statement->firstSourceLocation(); - bool hasMultilineStatementBody = uisb->statement->lastSourceLocation().startLine > firstSourceLocation.startLine; - m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, hasMultilineStatementBody); - } + + auto method = m_currentScope->methods()[signal]; + for (auto const ¶m : method.parameterNames()) { + const auto firstSourceLocation = statement->firstSourceLocation(); + bool hasMultilineStatementBody + = statement->lastSourceLocation().startLine > firstSourceLocation.startLine; + m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, + hasMultilineStatementBody); } return true; } @@ -535,13 +622,15 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) } FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, - const QString &code, const QString &fileName) + const QString &code, const QString &fileName, + bool silent) : m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }), m_currentScope(m_rootScope.get()), m_qmltypeDirs(qmltypeDirs), m_code(code), m_rootId(QLatin1String("<id>")), - m_filePath(fileName) + m_filePath(fileName), + m_colorOut(silent) { // setup color output m_colorOut.insertColorMapping(Error, ColorOutput::RedForeground); @@ -649,18 +738,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) prefix += import->importId + QLatin1Char('.'); } auto dirname = import->fileName.toString(); - if (!dirname.isEmpty()) { - QFileInfo info { dirname }; - if (info.isRelative()) { - dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); - } - QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; - while (it.hasNext()) { - LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); - m_exportedName2MetaObject.insert( - fake->className(), QSharedPointer<const LanguageUtils::FakeMetaObject>(fake)); - } - } + if (!dirname.isEmpty()) + importDirectory(dirname, prefix); + QString path {}; if (!import->importId.isEmpty()) { m_qmlid2meta.insert(import->importId.toString(), {}); // TODO: do not put imported ids into the same space as qml IDs @@ -768,16 +848,19 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) return true; } -void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::PatternElement *element) { - leaveEnvironment(); + if (element->isVariableDeclaration()) { + QQmlJS::AST::BoundNames names; + element->boundNames(&names); + for (const auto &name : names) + m_currentScope->insertJSIdentifier(name.id, element->scope); + } + + return true; } -QDebug operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc) +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) { - QDebugStateSaver saver(dbg); - dbg.nospace() << loc.startLine; - dbg.nospace() << ":"; - dbg.nospace() << loc.startColumn; - return dbg.maybeSpace(); + leaveEnvironment(); } diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 181f42f265..80413bd402 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -43,7 +43,8 @@ enum class ScopeType; class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor { public: - explicit FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, const QString& code, const QString& fileName); + explicit FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, const QString& code, + const QString& fileName, bool silent); ~FindUnqualifiedIDVisitor() override; bool check(); @@ -70,7 +71,7 @@ private: void importHelper(QString id, QString prefix, int major, int minor); LanguageUtils::FakeMetaObject* localQmlFile2FakeMetaObject(QString filePath); - + void importDirectory(const QString &directory, const QString &prefix); void importExportedNames(QStringRef prefix, QString name); void throwRecursionDepthError() override; @@ -125,6 +126,8 @@ private: // expression handling bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; + + bool visit(QQmlJS::AST::PatternElement *) override; }; diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 235ec16c6e..56f72dd020 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -50,7 +50,8 @@ static bool lint_file(const QString &filename, const bool silent, const bool war { QFile file(filename); if (!file.open(QFile::ReadOnly)) { - qWarning() << "Failed to open file" << filename << file.error(); + if (!silent) + qWarning() << "Failed to open file" << filename << file.error(); return false; } @@ -76,7 +77,7 @@ static bool lint_file(const QString &filename, const bool silent, const bool war if (success && !isJavaScript && warnUnqualied) { auto root = parser.rootNode(); - FindUnqualifiedIDVisitor v { qmltypeDirs, code, filename}; + FindUnqualifiedIDVisitor v { qmltypeDirs, code, filename, silent }; root->accept(&v); success = v.check(); } @@ -122,9 +123,9 @@ int main(int argv, char *argc[]) // use host qml import path as a sane default if nothing else has been provided QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) ? parser.values(qmltypesDirsOption) #ifndef QT_BOOTSTRAPPED - : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)}; + : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath), QLatin1String(".")}; #else - : QStringList{}; + : QStringList{QLatin1String(".")}; #endif #else bool silent = false; diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp index d2e723700a..eb4c721663 100644 --- a/tools/qmllint/qcoloroutput.cpp +++ b/tools/qmllint/qcoloroutput.cpp @@ -39,7 +39,7 @@ class ColorOutputPrivate { public: - ColorOutputPrivate() : currentColorID(-1) + ColorOutputPrivate(bool silent) : currentColorID(-1), silent(silent) { /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance @@ -53,6 +53,7 @@ public: ColorOutput::ColorMapping colorMapping; int currentColorID; bool coloringEnabled; + bool silent; static const char *const foregrounds[]; static const char *const backgrounds[]; @@ -238,7 +239,7 @@ ColorOutput::ColorMapping ColorOutput::colorMapping() const /*! Constructs a ColorOutput instance, ready for use. */ -ColorOutput::ColorOutput() : d(new ColorOutputPrivate()) +ColorOutput::ColorOutput(bool silent) : d(new ColorOutputPrivate(silent)) { } @@ -258,7 +259,8 @@ ColorOutput::~ColorOutput() = default; // must be here so that QScopedPointer ha */ void ColorOutput::write(const QString &message, int colorID) { - d->write(colorify(message, colorID)); + if (!d->silent) + d->write(colorify(message, colorID)); } /*! @@ -269,7 +271,8 @@ void ColorOutput::write(const QString &message, int colorID) */ void ColorOutput::writeUncolored(const QString &message) { - d->write(message + QLatin1Char('\n')); + if (!d->silent) + d->write(message + QLatin1Char('\n')); } /*! diff --git a/tools/qmllint/qcoloroutput_p.h b/tools/qmllint/qcoloroutput_p.h index 710bf5db74..aefa765a87 100644 --- a/tools/qmllint/qcoloroutput_p.h +++ b/tools/qmllint/qcoloroutput_p.h @@ -89,7 +89,7 @@ public: typedef QFlags<ColorCodeComponent> ColorCode; typedef QHash<int, ColorCode> ColorMapping; - ColorOutput(); + ColorOutput(bool silent); ~ColorOutput(); void setColorMapping(const ColorMapping &cMapping); diff --git a/tools/qmllint/qmljstypedescriptionreader.cpp b/tools/qmllint/qmljstypedescriptionreader.cpp index 542cdf99eb..b8aecdddb1 100644 --- a/tools/qmllint/qmljstypedescriptionreader.cpp +++ b/tools/qmllint/qmljstypedescriptionreader.cpp @@ -123,15 +123,13 @@ void TypeDescriptionReader::readDocument(UiProgram *ast) return; } - ComponentVersion version; - const QString versionString = _source.mid(import->versionToken.offset, import->versionToken.length); - const int dotIdx = versionString.indexOf(QLatin1Char('.')); - if (dotIdx != -1) { - version = ComponentVersion(versionString.leftRef(dotIdx).toInt(), - versionString.midRef(dotIdx + 1).toInt()); - } - if (version.majorVersion() != 1) { - addError(import->versionToken, tr("Major version different from 1 not supported.")); + if (!import->version) { + addError(import->firstSourceLocation(), tr("Import statement without version.")); + return; + } + + if (import->version->majorVersion != 1) { + addError(import->version->firstSourceLocation(), tr("Major version different from 1 not supported.")); return; } @@ -666,39 +664,34 @@ void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUt return; } - ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + auto *expStmt = AST::cast<ExpressionStatement *>(ast->statement); if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected object literal after colon.")); + addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); return; } - ObjectPattern *objectLit = AST::cast<ObjectPattern *>(expStmt->expression); - if (!objectLit) { - addError(expStmt->firstSourceLocation(), tr("Expected object literal after colon.")); - return; - } - - for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { - PatternProperty *assignement = AST::cast<PatternProperty *>(it->property); - if (assignement) { - StringLiteralPropertyName *propName = AST::cast<StringLiteralPropertyName *>(assignement->name); - NumericLiteral *value = AST::cast<NumericLiteral *>(assignement->initializer); - UnaryMinusExpression *minus = AST::cast<UnaryMinusExpression *>(assignement->initializer); - if (minus) - value = AST::cast<NumericLiteral *>(minus->expression); - if (!propName || !value) { - addError(objectLit->firstSourceLocation(), tr("Expected object literal to contain only 'string: number' elements.")); - continue; + if (auto *objectLit = AST::cast<ObjectPattern *>(expStmt->expression)) { + for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { + if (PatternProperty *assignement = it->property) { + if (auto *name = AST::cast<StringLiteralPropertyName *>(assignement->name)) { + fme->addKey(name->id.toString()); + continue; + } } - - double v = value->value; - if (minus) - v = -v; - fme->addKey(propName->id.toString(), v); - continue; + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else if (auto *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression)) { + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + if (PatternElement *element = it->element) { + if (auto *name = AST::cast<StringLiteral *>(element->initializer)) { + fme->addKey(name->value.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); } - PatternPropertyList *getterSetter = AST::cast<PatternPropertyList *>(it->next); - if (getterSetter) - addError(objectLit->firstSourceLocation(), tr("Enum should not contain getter and setters, but only 'string: number' elements.")); + } else { + addError(ast->statement->firstSourceLocation(), + tr("Expected either array or object literal as enum definition.")); } } diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 2eff3fa319..1e873cca8f 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -81,6 +81,12 @@ void ScopeTree::insertPropertyIdentifier(QString id) this->addMethod(method); } +void ScopeTree::addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::AST::SourceLocation &location) +{ + m_unmatchedSignalHandlers.append(qMakePair(handler, location)); +} + bool ScopeTree::isIdInCurrentScope(const QString &id) const { return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); @@ -132,6 +138,15 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan workQueue.enqueue(this); while (!workQueue.empty()) { const ScopeTree* currentScope = workQueue.dequeue(); + for (const auto &handler : currentScope->m_unmatchedSignalHandlers) { + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "no matching signal found for handler \"%1\" at %2:%3\n") + .arg(handler.first).arg(handler.second.startLine) + .arg(handler.second.startColumn), Normal); + printContext(colorOut, code, handler.second); + } + for (auto idLocationPair : currentScope->m_accessedIdentifiers) { if (qmlIDs.contains(idLocationPair.first)) continue; @@ -141,13 +156,11 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan noUnqualifiedIdentifier = false; colorOut.write("Warning: ", Warning); auto location = idLocationPair.second; - colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, location.startColumn), Normal); - IssueLocationWithContext issueLocationWithContext {code, location}; - colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); - colorOut.write(issueLocationWithContext.issueText.toString(), Error); - colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); - colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + QString("\t").repeated(tabCount) + QString("^").repeated(location.length) + QLatin1Char('\n'), Normal); + colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, + location.startColumn), Normal); + + printContext(colorOut, code, location); + // root(JS) --> program(qml) --> (first element) if (root->m_childScopes[0]->m_childScopes[0]->m_currentScopeQMLIdentifiers.contains(idLocationPair.first)) { ScopeTree *parentScope = currentScope->m_parentScope; @@ -161,6 +174,7 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan colorOut.write("Note: ", Warning); colorOut.write(("You first have to give the root element an id\n")); } + IssueLocationWithContext issueLocationWithContext {code, location}; colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); colorOut.write(rootId + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText.toString(), Normal); @@ -212,7 +226,7 @@ QMap<QString, LanguageUtils::FakeMetaMethod>const &ScopeTree::methods() const bool ScopeTree::isIdInCurrentQMlScopes(QString id) const { auto qmlScope = getCurrentQMLScope(); - return qmlScope->m_currentScopeQMLIdentifiers.contains(id); + return qmlScope->m_currentScopeQMLIdentifiers.contains(id) || qmlScope->m_methods.contains(id); } bool ScopeTree::isIdInCurrentJSScopes(QString id) const @@ -250,6 +264,20 @@ ScopeTree *ScopeTree::getCurrentQMLScope() return qmlScope; } +void ScopeTree::printContext(ColorOutput &colorOut, const QString &code, + const QQmlJS::AST::SourceLocation &location) const +{ + IssueLocationWithContext issueLocationWithContext {code, location}; + colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); + colorOut.write(issueLocationWithContext.issueText.toString(), Error); + colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); + int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); + colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + + QString("\t").repeated(tabCount) + + QString("^").repeated(location.length) + + QLatin1Char('\n'), Normal); +} + ScopeType ScopeTree::scopeType() {return m_scopeType;} void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method) diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index 872a509123..52cdc45e96 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -73,6 +73,8 @@ public: void insertQMLIdentifier(QString id); void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody); void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding + void addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::AST::SourceLocation &location); bool isIdInCurrentScope(QString const &id) const; void addIdToAccssedIfNotInParentScopes(QPair<QString, QQmlJS::AST::SourceLocation> const& id_loc_pair, const QSet<QString>& unknownImports); @@ -96,11 +98,14 @@ private: ScopeTree *m_parentScope; QString m_name; ScopeType m_scopeType; + QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_unmatchedSignalHandlers; bool isIdInCurrentQMlScopes(QString id) const; bool isIdInCurrentJSScopes(QString id) const; bool isIdInjectedFromSignal(QString id) const; const ScopeTree* getCurrentQMLScope() const; ScopeTree* getCurrentQMLScope(); + void printContext(ColorOutput& colorOut, const QString &code, + const QQmlJS::AST::SourceLocation &location) const; }; #endif // SCOPETREE_H |