diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2021-03-25 15:34:42 +0100 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2021-03-29 12:31:01 +0100 |
commit | 954071f372b70f3656386793e2fca382c1b0a1c0 (patch) | |
tree | ace5d46d8c00b5d96463365832eb0c14b75f812b /tools/qmllint | |
parent | b82ae347397595241dcd4a5848ad0c6bfae4574b (diff) |
qmlcompiler/qmllint: Use unified logger
Improves the logging situation greatly by allowing all logging messages to be assigned different severities,
highlighting the code that caused them and by now ensuring a qmllint warning will always result in a non-zero exit code.
A later patch will expose more of these options to the user.
Change-Id: Id9b036fe3ba80dd18e9f8cb1b05efa891713d5a8
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools/qmllint')
-rw-r--r-- | tools/qmllint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/qmllint/checkidentifiers.cpp | 229 | ||||
-rw-r--r-- | tools/qmllint/checkidentifiers.h | 16 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.cpp | 192 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.h | 9 | ||||
-rw-r--r-- | tools/qmllint/main.cpp | 8 | ||||
-rw-r--r-- | tools/qmllint/qcoloroutput.cpp | 336 | ||||
-rw-r--r-- | tools/qmllint/qcoloroutput.h | 121 |
8 files changed, 152 insertions, 760 deletions
diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt index a133af3cb7..77e22598f3 100644 --- a/tools/qmllint/CMakeLists.txt +++ b/tools/qmllint/CMakeLists.txt @@ -12,7 +12,6 @@ qt_internal_add_tool(${target_name} checkidentifiers.cpp checkidentifiers.h findwarnings.cpp findwarnings.h main.cpp - qcoloroutput.cpp qcoloroutput.h PUBLIC_LIBRARIES Qt::CorePrivate Qt::QmlCompilerPrivate diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp index 2c64fd9198..a08cb9654c 100644 --- a/tools/qmllint/checkidentifiers.cpp +++ b/tools/qmllint/checkidentifiers.cpp @@ -27,49 +27,13 @@ ****************************************************************************/ #include "checkidentifiers.h" -#include "qcoloroutput.h" + +#include <QtQmlCompiler/private/qcoloroutput_p.h> #include <QtCore/qqueue.h> #include <QtCore/qsharedpointer.h> #include <stack> -/*! - \internal - Used to print the the line containing the location of a certain error - */ -class IssueLocationWithContext -{ -public: - /*! - \internal - \param code: The whole text of a translation unit - \param location: The location where an error occurred. - */ - IssueLocationWithContext(QStringView code, const QQmlJS::SourceLocation &location) { - int before = qMax(0,code.lastIndexOf(QLatin1Char('\n'), location.offset)); - - if (before != 0) before++; - - m_beforeText = code.mid(before, location.offset - before); - m_issueText = code.mid(location.offset, location.length); - int after = code.indexOf(QLatin1Char('\n'), location.offset + location.length); - m_afterText = code.mid(location.offset + location.length, - after - (location.offset+location.length)); - } - - // returns start of the line till first character of location - QStringView beforeText() const { return m_beforeText; } - // returns the text at location - QStringView issueText() const { return m_issueText; } - // returns any text after location until the end of the line is reached - QStringView afterText() const { return m_afterText; } - -private: - QStringView m_beforeText; - QStringView m_issueText; - QStringView m_afterText; -}; - static const QStringList unknownBuiltins = { QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. @@ -79,23 +43,6 @@ static const QStringList unknownBuiltins = { QStringLiteral("var") }; -void CheckIdentifiers::printContext( - const QString &code, ColorOutput *output, const QQmlJS::SourceLocation &location) -{ - IssueLocationWithContext issueLocationWithContext { code, location }; - if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty()) - output->write(beforeText, Normal); - output->write(issueLocationWithContext.issueText(), Error); - if (const QStringView afterText = issueLocationWithContext.afterText(); !afterText.isEmpty()) - output->write(afterText + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t')); - output->write(QString::fromLatin1(" ").repeated( - issueLocationWithContext.beforeText().length() - tabCount) - + QString::fromLatin1("\t").repeated(tabCount) - + QString::fromLatin1("^").repeated(location.length) - + QLatin1Char('\n'), Normal); -} - template<typename Visitor> static bool walkRelatedScopes(QQmlJSScope::ConstPtr rootType, const Visitor &visit) { @@ -125,7 +72,7 @@ static bool walkRelatedScopes(QQmlJSScope::ConstPtr rootType, const Visitor &vis return false; } -bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, +void CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, const QQmlJSScope::ConstPtr &outerScope, const QQmlJSMetaProperty *prop) const { @@ -144,16 +91,12 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, const FieldMember &access = members.at(i); if (scope.isNull()) { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("Type \"%1\" of base \"%2\" not found when accessing member \"%3\" at %4:%5:%6.\n") + m_logger->log( + QString::fromLatin1("Type \"%1\" of base \"%2\" not found when accessing member \"%3\"") .arg(detectedRestrictiveKind) .arg(detectedRestrictiveName) - .arg(access.m_name) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; + .arg(access.m_name), Log_Type, access.m_location); + return; } if (!detectedRestrictiveKind.isEmpty()) { @@ -162,16 +105,12 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, continue; } - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5:%6\n") - .arg(detectedRestrictiveName) - .arg(detectedRestrictiveKind) - .arg(access.m_name) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; + m_logger->log( + QLatin1String("\"%1\" is a %2. You cannot access \"%3\" it from here") + .arg(detectedRestrictiveName) + .arg(detectedRestrictiveKind) + .arg(access.m_name), Log_Type, access.m_location); + return; } const auto property = scope->property(access.m_name); @@ -204,7 +143,7 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, } if (unknownBuiltins.contains(typeName)) - return true; + return; const auto it = m_types.find(typeName); if (it == m_types.end()) { @@ -219,7 +158,7 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, } if (scope->hasMethod(access.m_name)) - return true; // Access to property of JS function + return; // Access to property of JS function auto checkEnums = [&](const QQmlJSScope::ConstPtr &scope) { if (scope->hasEnumeration(access.m_name)) { @@ -294,29 +233,21 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, } } - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "Property \"%1\" not found on type \"%2\" at %3:%4:%5\n") - .arg(access.m_name) - .arg(scope->internalName().isEmpty() - ? scope->baseTypeName() : scope->internalName()) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; + m_logger->log(QLatin1String( + "Property \"%1\" not found on type \"%2\"") + .arg(access.m_name) + .arg(scope->internalName().isEmpty() + ? scope->baseTypeName() : scope->internalName()), Log_Type, access.m_location); + return; } - - return true; } -bool CheckIdentifiers::operator()( +void CheckIdentifiers::operator()( const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs, const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers, const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root, const QString &rootId) const { - bool identifiersClean = true; - // revisit all scopes QQueue<QQmlJSScope::ConstPtr> workQueue; workQueue.enqueue(root); @@ -332,17 +263,14 @@ bool CheckIdentifiers::operator()( const auto jsId = currentScope->findJSIdentifier(memberAccessBase.m_name); if (jsId.has_value() && jsId->kind != QQmlJSScope::JavaScriptIdentifier::Injected) { if (memberAccessBase.m_location.end() < jsId->location.begin()) { - m_colorOut->writePrefixedMessage( + // TODO: Is there a more fitting category? + m_logger->log( QStringLiteral( - "Variable \"%1\" is used before its declaration at %2:%3. " + "Variable \"%1\" is used here before its declaration. " "The declaration is at %4:%5.\n") - .arg(memberAccessBase.m_name) - .arg(memberAccessBase.m_location.startLine) - .arg(memberAccessBase.m_location.startColumn) - .arg(jsId->location.startLine) - .arg(jsId->location.startColumn), Warning); - printContext(m_code, m_colorOut, memberAccessBase.m_location); - identifiersClean = false; + .arg(memberAccessBase.m_name) + .arg(jsId->location.startLine) + .arg(jsId->location.startColumn), Log_Type, memberAccessBase.m_location); } continue; } @@ -350,8 +278,7 @@ bool CheckIdentifiers::operator()( auto it = qmlIDs.find(memberAccessBase.m_name); if (it != qmlIDs.end()) { if (!it->isNull()) { - if (!checkMemberAccess(memberAccessChain, *it)) - identifiersClean = false; + checkMemberAccess(memberAccessChain, *it); continue; } else if (!memberAccessChain.isEmpty()) { // It could be a qualified type name @@ -362,8 +289,7 @@ bool CheckIdentifiers::operator()( const auto typeIt = m_types.find(qualified); if (typeIt != m_types.end()) { memberAccessChain.takeFirst(); - if (!checkMemberAccess(memberAccessChain, *typeIt)) - identifiersClean = false; + checkMemberAccess(memberAccessChain, *typeIt); continue; } } @@ -388,14 +314,7 @@ bool CheckIdentifiers::operator()( if (!deprecation.reason.isEmpty()) message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason)); - message.append(QStringLiteral(" at %2:%3:%4") - .arg(m_fileName) - .arg(memberAccessBase.m_location.startLine) - .arg(memberAccessBase.m_location.startColumn) - ); - - m_colorOut->writePrefixedMessage(message, Warning); - identifiersClean = false; + m_logger->log(message, Log_Deprecation, memberAccessBase.m_location); } } @@ -403,16 +322,11 @@ bool CheckIdentifiers::operator()( continue; if (!property.type()) { - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "Type of property \"%2\" not found at %3:%4:%5\n") - .arg(memberAccessBase.m_name) - .arg(m_fileName) - .arg(memberAccessBase.m_location.startLine) - .arg(memberAccessBase.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, memberAccessBase.m_location); - identifiersClean = false; - } else if (!checkMemberAccess(memberAccessChain, property.type(), &property)) { - identifiersClean = false; + m_logger->log(QString::fromLatin1( + "Type of property \"%2\" not found") + .arg(memberAccessBase.m_name), Log_Type, memberAccessBase.m_location); + } else { + checkMemberAccess(memberAccessChain, property.type(), &property); } continue; @@ -436,82 +350,79 @@ bool CheckIdentifiers::operator()( } if (typeIt != m_types.end() && !typeIt->isNull()) { - if (!checkMemberAccess(memberAccessChain, *typeIt)) - identifiersClean = false; + checkMemberAccess(memberAccessChain, *typeIt); continue; } - identifiersClean = false; const auto location = memberAccessBase.m_location; if (baseIsPrefixed) { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("type not found in namespace at %1:%2:%3\n") - .arg(m_fileName) - .arg(location.startLine).arg(location.startColumn), - Warning); + m_logger->log( + QLatin1String("Type not found in namespace"), + Log_Type, location); } else { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("unqualified access at %1:%2:%3\n") - .arg(m_fileName) - .arg(location.startLine).arg(location.startColumn), - Warning); + m_logger->log( + QLatin1String("Unqualified access"), + Log_UnqualifiedAccess, location); } - printContext(m_code, m_colorOut, location); - // root(JS) --> (first element) const auto firstElement = root->childScopes()[0]; - if (firstElement->hasProperty(memberAccessBase.m_name) + + QColorOutput &colorOut = m_logger->colorOutput(); + + if (!m_logger->isCategorySilent(Log_UnqualifiedAccess) && + (firstElement->hasProperty(memberAccessBase.m_name) || firstElement->hasMethod(memberAccessBase.m_name) - || firstElement->hasEnumeration(memberAccessBase.m_name)) { - m_colorOut->writePrefixedMessage( + || firstElement->hasEnumeration(memberAccessBase.m_name))) { + + colorOut.writePrefixedMessage( memberAccessBase.m_name + QLatin1String(" is a member of the root element\n") + QLatin1String(" You can qualify the access with its id " "to avoid this warning:\n"), - Info, QStringLiteral("Note")); + QtInfoMsg, QStringLiteral("Note")); + IssueLocationWithContext issueLocationWithContext {m_code, location}; + colorOut.write(issueLocationWithContext.beforeText().toString()); + colorOut.write(rootId + QLatin1Char('.'), QtDebugMsg); + colorOut.write(issueLocationWithContext.issueText().toString()); + colorOut.write(issueLocationWithContext.afterText() + QLatin1String("\n\n")); + if (rootId == QLatin1String("<id>")) { - m_colorOut->writePrefixedMessage( + colorOut.writePrefixedMessage( QLatin1String("You first have to give the root element an id\n"), - Warning, QStringLiteral("Note")); + QtInfoMsg, QStringLiteral("Note")); } - IssueLocationWithContext issueLocationWithContext {m_code, location}; - m_colorOut->write(issueLocationWithContext.beforeText(), Normal); - m_colorOut->write(rootId + QLatin1Char('.'), Hint); - m_colorOut->write(issueLocationWithContext.issueText(), Normal); - m_colorOut->write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); } else if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) { const QQmlJSScope::JavaScriptIdentifier id = jsId.value(); - m_colorOut->writePrefixedMessage( + colorOut.writePrefixedMessage( memberAccessBase.m_name + QString::fromLatin1( " is accessible in this scope because " "you are handling a signal at %1:%2:%3\n") .arg(m_fileName) .arg(id.location.startLine).arg(id.location.startColumn), - Info, QStringLiteral("Note")); - m_colorOut->write(QLatin1String("Consider using a function instead\n"), Normal); + QtInfoMsg, QStringLiteral("Note")); + colorOut.write(QLatin1String("Consider using a function instead\n")); IssueLocationWithContext context {m_code, id.location}; - m_colorOut->write(context.beforeText() + QLatin1Char(' ')); + colorOut.write(context.beforeText() + QLatin1Char(' ')); const auto handler = signalHandlers[id.location]; - m_colorOut->write(QLatin1String(handler.isMultiline ? "function(" : "("), Hint); + colorOut.write(QLatin1String(handler.isMultiline ? "function(" : "("), QtInfoMsg); const auto parameters = handler.signal.parameterNames(); for (int numParams = parameters.size(); numParams > 0; --numParams) { - m_colorOut->write(parameters.at(parameters.size() - numParams), Hint); + colorOut.write(parameters.at(parameters.size() - numParams), QtInfoMsg); if (numParams > 1) - m_colorOut->write(QLatin1String(", "), Hint); + colorOut.write(QLatin1String(", "), QtInfoMsg); } - m_colorOut->write(QLatin1String(handler.isMultiline ? ")" : ") => "), Hint); - m_colorOut->write(QLatin1String(" {..."), Normal); + colorOut.write(QLatin1String(handler.isMultiline ? ")" : ") => "), QtInfoMsg); + colorOut.write(QLatin1String(" {...")); } - m_colorOut->write(QLatin1String("\n\n\n"), Normal); + colorOut.write(QLatin1String("\n\n\n")); } const auto childScopes = currentScope->childScopes(); for (auto const &childScope : childScopes) workQueue.enqueue(childScope); } - return identifiersClean; } diff --git a/tools/qmllint/checkidentifiers.h b/tools/qmllint/checkidentifiers.h index f4fd8c9f14..c23a64f9bd 100644 --- a/tools/qmllint/checkidentifiers.h +++ b/tools/qmllint/checkidentifiers.h @@ -29,10 +29,11 @@ #ifndef CHECKIDENTIFIERS_H #define CHECKIDENTIFIERS_H +#include <QtQmlCompiler/private/qqmljslogger_p.h> #include <QtQmlCompiler/private/qqmljsscope_p.h> #include <QtQmlCompiler/private/qqmljsimporter_p.h> -class ColorOutput; +class QColorOutput; struct SignalHandler { QQmlJSMetaMethod signal; @@ -51,25 +52,22 @@ using MemberAccessChains = QHash<QQmlJSScope::ConstPtr, QVector<QVector<FieldMem class CheckIdentifiers { public: - CheckIdentifiers(ColorOutput *colorOut, const QString &code, + CheckIdentifiers(QQmlJSLogger *logger, const QString &code, const QQmlJSImporter::ImportedTypes &types, const QString &fileName) : - m_colorOut(colorOut), m_code(code), m_types(types), m_fileName(fileName) + m_logger(logger), m_code(code), m_types(types), m_fileName(fileName) {} - bool operator ()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs, + void operator ()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs, const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers, const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root, const QString &rootId) const; - static void printContext(const QString &code, ColorOutput *output, - const QQmlJS::SourceLocation &location); - private: - bool checkMemberAccess(const QVector<FieldMember> &members, + void checkMemberAccess(const QVector<FieldMember> &members, const QQmlJSScope::ConstPtr &outerScope, const QQmlJSMetaProperty *prop = nullptr) const; - ColorOutput *m_colorOut = nullptr; + QQmlJSLogger *m_logger = nullptr; QString m_code; QQmlJSImporter::ImportedTypes m_types; QString m_fileName; diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp index dd87293eb6..67d40e8a1d 100644 --- a/tools/qmllint/findwarnings.cpp +++ b/tools/qmllint/findwarnings.cpp @@ -59,11 +59,8 @@ void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) if (!deprecation.reason.isEmpty()) message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason)); - m_errors.append({ - message, - QtWarningMsg, - originalScope->sourceLocation() - }); + m_logger.log(message, Log_Deprecation, originalScope->sourceLocation() + ); } } @@ -75,15 +72,9 @@ void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) inheritenceCycle.append(seen->baseTypeName()); } - if (m_warnInheritanceCycle) { - m_errors.append({ - QStringLiteral("%1 is part of an inheritance cycle: %2\n") - .arg(scope->internalName()) - .arg(inheritenceCycle), - QtWarningMsg, - QQmlJS::SourceLocation() - }); - } + m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2\n") + .arg(scope->internalName()) + .arg(inheritenceCycle), Log_InheritanceCycle); m_unknownImports.insert(scope->internalName()); break; @@ -96,12 +87,9 @@ void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) } else if (auto newScope = scope->baseType()) { scope = newScope; } else { - m_errors.append({ - scope->baseTypeName() + QStringLiteral( + m_logger.log(scope->baseTypeName() + QStringLiteral( " was not found. Did you add all import paths?\n"), - QtWarningMsg, - QQmlJS::SourceLocation() - }); + Log_Import); m_unknownImports.insert(scope->baseTypeName()); break; } @@ -118,15 +106,15 @@ void FindWarningVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr sco case QQmlJSScope::GroupedPropertyScope: case QQmlJSScope::AttachedPropertyScope: if (!childScope->baseType()) { - m_errors.append({ - QStringLiteral("unknown %1 property scope %2.") - .arg(type == QQmlJSScope::GroupedPropertyScope - ? QStringLiteral("grouped") - : QStringLiteral("attached"), - childScope->internalName()), - QtWarningMsg, - childScope->sourceLocation() - }); + m_logger.log( + QStringLiteral("unknown %1 property scope %2.") + .arg(type == QQmlJSScope::GroupedPropertyScope + ? QStringLiteral("grouped") + : QStringLiteral("attached"), + childScope->internalName()), + Log_UnqualifiedAccess, + childScope->sourceLocation() + ); } children.append(childScope->childScopes()); default: @@ -165,8 +153,8 @@ void FindWarningVisitor::checkDefaultProperty(const QQmlJSScope::ConstPtr &scope } } if (defaultPropertyName.isEmpty()) { - m_errors.append({ QStringLiteral("Cannot assign to non-existent default property"), - QtWarningMsg, scope->sourceLocation() }); + m_logger.log(QStringLiteral("Cannot assign to non-existent default property"), + Log_Property, scope->sourceLocation() ); return; } @@ -179,9 +167,8 @@ void FindWarningVisitor::checkDefaultProperty(const QQmlJSScope::ConstPtr &scope if (m_scopeHasDefaultPropertyAssignment[scopeOfDefaultProperty] && !defaultProp.isList()) { // already has some object assigned to a default property and // this default property is not a list property - m_errors.append( - { QStringLiteral("Cannot assign multiple objects to a default non-list property"), - QtWarningMsg, scope->sourceLocation() }); + m_logger.log(QStringLiteral("Cannot assign multiple objects to a default non-list property"), + Log_Property, scope->sourceLocation()); } m_scopeHasDefaultPropertyAssignment[scopeOfDefaultProperty] = true; @@ -195,8 +182,8 @@ void FindWarningVisitor::checkDefaultProperty(const QQmlJSScope::ConstPtr &scope return; } - m_errors.append({ QStringLiteral("Cannot assign to default property of incompatible type"), - QtWarningMsg, scope->sourceLocation() }); + m_logger.log(QStringLiteral("Cannot assign to default property of incompatible type"), + Log_Property, scope->sourceLocation()); } void FindWarningVisitor::throwRecursionDepthError() @@ -232,16 +219,12 @@ bool FindWarningVisitor::visit(QQmlJS::AST::Block *block) bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement) { - if (m_warnWithStatement) { - m_errors.append({ - QStringLiteral( - "with statements are strongly discouraged in QML " + m_logger.log(QStringLiteral( + "with statements are strongly discouraged in QML " "and might cause false positives when analysing unqualified " "identifiers\n"), - QtWarningMsg, - withStatement->firstSourceLocation() - }); - } + Log_WithStatement, + withStatement->firstSourceLocation()); return QQmlJSImportVisitor::visit(withStatement); } @@ -295,37 +278,38 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) } if (!qmlScope->hasProperty(name.toString())) { - m_errors.append({ - QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" " + // TODO: Can this be in a better suited category? + m_logger.log( + QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" " "exists in the current element.\n").arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); + Log_Type, + uisb->firstSourceLocation() + ); return true; } const auto property = qmlScope->property(name.toString()); if (!property.type()) { - m_errors.append({ - QStringLiteral("No type found for property \"%1\". This may be due " + m_logger.log( + QStringLiteral("No type found for property \"%1\". This may be due " "to a missing import statement or incomplete " "qmltypes files.\n").arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); + Log_Type, + uisb->firstSourceLocation() + ); } return true; } - if (!qmlScope->hasMethod(signal) && m_warnUnqualified) { - m_errors.append({ - QStringLiteral("no matching signal found for handler \"%1\"\n") - .arg(name.toString()), - QtWarningMsg, - uisb->firstSourceLocation() - }); + if (!qmlScope->hasMethod(signal)) { + m_logger.log( + QStringLiteral("no matching signal found for handler \"%1\"\n") + .arg(name.toString()), + Log_UnqualifiedAccess, + uisb->firstSourceLocation() + ); return true; } @@ -352,13 +336,13 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) for (FormalParameterList *formal = func->formals; formal; ++i, formal = formal->next) { if (i == end) { - m_errors.append({ - QStringLiteral("Signal handler for \"%2\" has more formal" + m_logger.log( + QStringLiteral("Signal handler for \"%2\" has more formal" " parameters than the signal it handles.") - .arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); + .arg(name), + Log_Signal, + uisb->firstSourceLocation() + ); } const QStringView handlerParameter = formal->element->bindingIdentifier; @@ -366,14 +350,14 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) if (j == i || j < 0) continue; - m_errors.append({ - QStringLiteral("Parameter %1 to signal handler for \"%2\"" + m_logger.log( + QStringLiteral("Parameter %1 to signal handler for \"%2\"" " is called \"%3\". The signal has a parameter" " of the same name in position %4.\n") - .arg(i + 1).arg(name, handlerParameter).arg(j + 1), - QtWarningMsg, - uisb->firstSourceLocation() - }); + .arg(i + 1).arg(name, handlerParameter).arg(j + 1), + Log_Signal, + uisb->firstSourceLocation() + ); } return true; @@ -403,26 +387,16 @@ bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) FindWarningVisitor::FindWarningVisitor( QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName, - bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle) + bool silent) : QQmlJSImportVisitor(importer, implicitImportDirectory(fileName, importer->resourceFileMapper()), - qmltypesFiles), + qmltypesFiles, m_filePath, m_code, silent), m_code(std::move(code)), m_rootId(QLatin1String("<id>")), - m_filePath(std::move(fileName)), - m_colorOut(silent), - m_warnUnqualified(warnUnqualified), - m_warnWithStatement(warnWithStatement), - m_warnInheritanceCycle(warnInheritanceCycle) + m_filePath(std::move(fileName)) { m_currentScope->setInternalName("global"); - // setup color output - m_colorOut.insertMapping(Error, ColorOutput::RedForeground); - m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground); - m_colorOut.insertMapping(Info, ColorOutput::BlueForeground); - m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor); - m_colorOut.insertMapping(Hint, ColorOutput::GreenForeground); QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */ // console/debug api @@ -449,36 +423,8 @@ FindWarningVisitor::FindWarningVisitor( m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript); } -static MessageColors messageColor(QtMsgType type) -{ - switch (type) { - case QtDebugMsg: - return Normal; - case QtWarningMsg: - return Warning; - case QtCriticalMsg: - case QtFatalMsg: - return Error; - case QtInfoMsg: - return Info; - } - - return Normal; -} - bool FindWarningVisitor::check() { - for (const auto &error : qAsConst(m_errors)) { - if (error.loc.isValid()) { - m_colorOut.writePrefixedMessage( - QStringLiteral("%1:%2: %3") - .arg(error.loc.startLine).arg(error.loc.startColumn).arg(error.message), - messageColor(error.type)); - } else { - m_colorOut.writePrefixedMessage(error.message, messageColor(error.type)); - } - } - // now that all ids are known, revisit any Connections whose target were perviously unknown for (auto const &outstandingConnection: m_outstandingConnections) { auto targetScope = m_scopesById[outstandingConnection.targetName]; @@ -505,20 +451,17 @@ bool FindWarningVisitor::check() } for (const auto &import : unusedImports) { - m_colorOut.writePrefixedMessage( + m_logger.log( QString::fromLatin1("Unused import at %1:%2:%3\n") - .arg(m_filePath) - .arg(import.startLine).arg(import.startColumn), - Info); - CheckIdentifiers::printContext(m_code, &m_colorOut, import); + .arg(m_filePath) + .arg(import.startLine).arg(import.startColumn), + Log_UnusedImport, import); } - if (!m_warnUnqualified) - return m_errors.isEmpty(); + CheckIdentifiers check(&m_logger, m_code, m_rootScopeImports, m_filePath); + check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId); - CheckIdentifiers check(&m_colorOut, m_code, m_rootScopeImports, m_filePath); - return check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId) - && m_errors.isEmpty(); + return !m_logger.hasWarnings() && !m_logger.hasErrors(); } bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) @@ -617,8 +560,7 @@ void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod) auto childScope = m_currentScope; QQmlJSImportVisitor::endVisit(uiod); - if (m_warnUnqualified) - checkGroupedAndAttachedScopes(childScope); + checkGroupedAndAttachedScopes(childScope); if (m_currentScope == m_globalScope || m_currentScope->baseTypeName() == QStringLiteral("Component")) { diff --git a/tools/qmllint/findwarnings.h b/tools/qmllint/findwarnings.h index 9d7e90f342..d5363a177b 100644 --- a/tools/qmllint/findwarnings.h +++ b/tools/qmllint/findwarnings.h @@ -39,9 +39,9 @@ // // We mean it. -#include "qcoloroutput.h" #include "checkidentifiers.h" +#include <QtQmlCompiler/private/qcoloroutput_p.h> #include <QtQmlCompiler/private/qqmljstypedescriptionreader_p.h> #include <QtQmlCompiler/private/qqmljsscope_p.h> #include <QtQmlCompiler/private/qqmljsimporter_p.h> @@ -59,7 +59,7 @@ class FindWarningVisitor : public QQmlJSImportVisitor public: explicit FindWarningVisitor( QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName, - bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle); + bool silent); ~FindWarningVisitor() override = default; bool check(); @@ -74,11 +74,6 @@ private: QString m_rootId; QString m_filePath; QSet<QString> m_unknownImports; - ColorOutput m_colorOut; - - bool m_warnUnqualified; - bool m_warnWithStatement; - bool m_warnInheritanceCycle; struct OutstandingConnection { diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 64388c78ae..c1befeb3e7 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -90,8 +90,12 @@ static bool lint_file(const QString &filename, const bool silent, const bool war if (success && !isJavaScript) { const auto check = [&](QQmlJSResourceFileMapper *mapper) { QQmlJSImporter importer(qmlImportPaths, mapper); - FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent, - warnUnqualified, warnWithStatement, warnInheritanceCycle }; + FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent }; + + v.logger().setCategorySilent(Log_WithStatement, !warnWithStatement); + v.logger().setCategorySilent(Log_InheritanceCycle, !warnInheritanceCycle); + v.logger().setCategorySilent(Log_UnqualifiedAccess, !warnUnqualified); + parser.rootNode()->accept(&v); success = v.check(); }; diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp deleted file mode 100644 index 6e0a483050..0000000000 --- a/tools/qmllint/qcoloroutput.cpp +++ /dev/null @@ -1,336 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qcoloroutput.h" - -#include <QtCore/qfile.h> -#include <QtCore/qhash.h> - -#ifndef Q_OS_WIN -#include <unistd.h> -#endif - -class ColorOutputPrivate -{ -public: - ColorOutputPrivate(bool silent) : m_currentColorID(-1), m_silent(silent) - { - /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, - * performance is considered of lower priority. - */ - m_out.open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered); - m_coloringEnabled = isColoringPossible(); - } - - static const char *const foregrounds[]; - static const char *const backgrounds[]; - - inline void write(const QString &msg) { m_out.write(msg.toLocal8Bit()); } - - static QString escapeCode(const QString &in) - { - const ushort escapeChar = 0x1B; - QString result; - result.append(QChar(escapeChar)); - result.append(QLatin1Char('[')); - result.append(in); - result.append(QLatin1Char('m')); - return result; - } - - void insertColor(int id, ColorOutput::ColorCode code) { m_colorMapping.insert(id, code); } - ColorOutput::ColorCode color(int id) const { return m_colorMapping.value(id); } - bool containsColor(int id) const { return m_colorMapping.contains(id); } - - bool isSilent() const { return m_silent; } - void setCurrentColorID(int colorId) { m_currentColorID = colorId; } - - bool coloringEnabled() const { return m_coloringEnabled; } - -private: - QFile m_out; - ColorOutput::ColorMapping m_colorMapping; - int m_currentColorID; - bool m_coloringEnabled; - bool m_silent; - - /*! - Returns true if it's suitable to send colored output to \c stderr. - */ - inline bool isColoringPossible() const - { -#if defined(Q_OS_WIN) - /* Windows doesn't at all support ANSI escape codes, unless - * the user install a "device driver". See the Wikipedia links in the - * class documentation for details. */ - return false; -#else - /* We use QFile::handle() to get the file descriptor. It's a bit unsure - * whether it's 2 on all platforms and in all cases, so hopefully this layer - * of abstraction helps handle such cases. */ - return isatty(m_out.handle()); -#endif - } -}; - -const char *const ColorOutputPrivate::foregrounds[] = -{ - "0;30", - "0;34", - "0;32", - "0;36", - "0;31", - "0;35", - "0;33", - "0;37", - "1;30", - "1;34", - "1;32", - "1;36", - "1;31", - "1;35", - "1;33", - "1;37" -}; - -const char *const ColorOutputPrivate::backgrounds[] = -{ - "0;40", - "0;44", - "0;42", - "0;46", - "0;41", - "0;45", - "0;43" -}; - -/*! - \class ColorOutput - \nonreentrant - \brief Outputs colored messages to \c stderr. - \internal - - ColorOutput is a convenience class for outputting messages to \c - stderr using color escape codes, as mandated in ECMA-48. ColorOutput - will only color output when it is detected to be suitable. For - instance, if \c stderr is detected to be attached to a file instead - of a TTY, no coloring will be done. - - ColorOutput does its best attempt. but it is generally undefined - what coloring or effect the various coloring flags has. It depends - strongly on what terminal software that is being used. - - When using `echo -e 'my escape sequence'`, \c{\033} works as an - initiator but not when printing from a C++ program, despite having - escaped the backslash. That's why we below use characters with - value 0x1B. - - It can be convenient to subclass ColorOutput with a private scope, - such that the functions are directly available in the class using - it. - - \section1 Usage - - To output messages, call write() or writeUncolored(). write() takes - as second argument an integer, which ColorOutput uses as a lookup - key to find the color it should color the text in. The mapping from - keys to colors is done using insertMapping(). Typically this is used - by having enums for the various kinds of messages, which - subsequently are registered. - - \code - enum MyMessage - { - Error, - Important - }; - - ColorOutput output; - output.insertMapping(Error, ColorOutput::RedForeground); - output.insertMapping(Import, ColorOutput::BlueForeground); - - output.write("This is important", Important); - output.write("Jack, I'm only the selected official!", Error); - \endcode - - \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors}, - {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade}, - {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International}, - {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code}, - {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala} - */ - -/*! - \enum ColorOutput::ColorCodeComponent - \value BlackForeground - \value BlueForeground - \value GreenForeground - \value CyanForeground - \value RedForeground - \value PurpleForeground - \value BrownForeground - \value LightGrayForeground - \value DarkGrayForeground - \value LightBlueForeground - \value LightGreenForeground - \value LightCyanForeground - \value LightRedForeground - \value LightPurpleForeground - \value YellowForeground - \value WhiteForeground - \value BlackBackground - \value BlueBackground - \value GreenBackground - \value CyanBackground - \value RedBackground - \value PurpleBackground - \value BrownBackground - - \value DefaultColor ColorOutput performs no coloring. This typically - means black on white or white on black, depending - on the settings of the user's terminal. - */ - -/*! - Constructs a ColorOutput instance, ready for use. - */ -ColorOutput::ColorOutput(bool silent) : d(new ColorOutputPrivate(silent)) {} - -// must be here so that QScopedPointer has access to the complete type -ColorOutput::~ColorOutput() = default; - -/*! - Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID. - - If \a color isn't available in the color mapping, result and behavior is undefined. - - If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput - is initialized to not color at all. - - If \a message is empty, effects are undefined. - - \a message will be printed as is. For instance, no line endings will be inserted. - */ -void ColorOutput::write(QStringView message, int colorID) -{ - if (!d->isSilent()) - d->write(colorify(message, colorID)); -} - -void ColorOutput::writePrefixedMessage(const QString &message, MessageColors type, - const QString &prefix) -{ - static const QStringList prefixes = { - QStringLiteral("Error"), - QStringLiteral("Warning"), - QStringLiteral("Info"), - QStringLiteral("Normal"), - QStringLiteral("Hint"), - }; - - Q_ASSERT(prefixes.length() > qsizetype(type)); - Q_ASSERT(prefix.isEmpty() || prefix.front().isUpper()); - write((prefix.isEmpty() ? prefixes[type] : prefix) + QStringLiteral(": "), type); - writeUncolored(message); -} - -/*! - Writes \a message to \c stderr as if for instance - QTextStream would have been used, and adds a line ending at the end. - - This function can be practical to use such that one can use ColorOutput for all forms of writing. - */ -void ColorOutput::writeUncolored(const QString &message) -{ - if (!d->isSilent()) - d->write(message + QLatin1Char('\n')); -} - -/*! - Treats \a message and \a colorID identically to write(), but instead of writing - \a message to \c stderr, it is prepared for being written to \c stderr, but is then - returned. - - This is useful when the colored string is inserted into a translated string(dividing - the string into several small strings prevents proper translation). - */ -QString ColorOutput::colorify(QStringView message, int colorID) const -{ - Q_ASSERT_X(colorID == -1 || d->containsColor(colorID), Q_FUNC_INFO, - qPrintable(QString::fromLatin1("There is no color registered by id %1") - .arg(colorID))); - Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO, - "It makes no sense to attempt to print an empty string."); - - if (colorID != -1) - d->setCurrentColorID(colorID); - - if (d->coloringEnabled() && colorID != -1) { - const int color = d->color(colorID); - - /* If DefaultColor is set, we don't want to color it. */ - if (color & DefaultColor) - return message.toString(); - - const int foregroundCode = (color & ForegroundMask) >> ForegroundShift; - const int backgroundCode = (color & BackgroundMask) >> BackgroundShift; - QString finalMessage; - bool closureNeeded = false; - - if (foregroundCode > 0) { - finalMessage.append( - ColorOutputPrivate::escapeCode( - QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); - closureNeeded = true; - } - - if (backgroundCode > 0) { - finalMessage.append( - ColorOutputPrivate::escapeCode( - QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); - closureNeeded = true; - } - - finalMessage.append(message); - - if (closureNeeded) - finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String("0"))); - - return finalMessage; - } - - return message.toString(); -} - -/*! - Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance. - */ -void ColorOutput::insertMapping(int colorID, const ColorCode colorCode) -{ - d->insertColor(colorID, colorCode); -} diff --git a/tools/qmllint/qcoloroutput.h b/tools/qmllint/qcoloroutput.h deleted file mode 100644 index 3b171da631..0000000000 --- a/tools/qmllint/qcoloroutput.h +++ /dev/null @@ -1,121 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QCOLOROUTPUT_H -#define QCOLOROUTPUT_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include <QtCore/qglobal.h> -#include <QtCore/qscopedpointer.h> -#include <QtCore/qstring.h> - -class ColorOutputPrivate; - -enum MessageColors -{ - Error, - Warning, - Info, - Normal, - Hint -}; - -class ColorOutput -{ - enum - { - ForegroundShift = 10, - BackgroundShift = 20, - SpecialShift = 20, - ForegroundMask = 0x1f << ForegroundShift, - BackgroundMask = 0x7 << BackgroundShift - }; - -public: - enum ColorCodeComponent - { - BlackForeground = 1 << ForegroundShift, - BlueForeground = 2 << ForegroundShift, - GreenForeground = 3 << ForegroundShift, - CyanForeground = 4 << ForegroundShift, - RedForeground = 5 << ForegroundShift, - PurpleForeground = 6 << ForegroundShift, - BrownForeground = 7 << ForegroundShift, - LightGrayForeground = 8 << ForegroundShift, - DarkGrayForeground = 9 << ForegroundShift, - LightBlueForeground = 10 << ForegroundShift, - LightGreenForeground = 11 << ForegroundShift, - LightCyanForeground = 12 << ForegroundShift, - LightRedForeground = 13 << ForegroundShift, - LightPurpleForeground = 14 << ForegroundShift, - YellowForeground = 15 << ForegroundShift, - WhiteForeground = 16 << ForegroundShift, - - BlackBackground = 1 << BackgroundShift, - BlueBackground = 2 << BackgroundShift, - GreenBackground = 3 << BackgroundShift, - CyanBackground = 4 << BackgroundShift, - RedBackground = 5 << BackgroundShift, - PurpleBackground = 6 << BackgroundShift, - BrownBackground = 7 << BackgroundShift, - DefaultColor = 1 << SpecialShift - }; - - using ColorCode = QFlags<ColorCodeComponent>; - using ColorMapping = QHash<int, ColorCode>; - - ColorOutput(bool silent); - ~ColorOutput(); - - void insertMapping(int colorID, ColorCode colorCode); - - void writeUncolored(const QString &message); - void write(QStringView message, int color = -1); - // handle QStringBuilder case - Q_WEAK_OVERLOAD void write(const QString &message, int color = -1) { write(QStringView(message), color); } - void writePrefixedMessage(const QString &message, MessageColors type, - const QString &prefix = QString()); - QString colorify(QStringView message, int color = -1) const; - -private: - QScopedPointer<ColorOutputPrivate> d; - Q_DISABLE_COPY_MOVE(ColorOutput) -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode) - -#endif // QCOLOROUTPUT_H |