aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2021-06-07 14:25:01 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-06-09 12:47:20 +0000
commit4489266724b98e9777ab0509a57aac9f032a3b30 (patch)
tree3bb8bee97bd03dd2909e39e012fd000dfa4c7193
parentb5b0a39a2aa3ec98e221bc2ef0080d9b879304d5 (diff)
qmllint: Move a lot of warning logic to qmlcompiler
Moves the majority of qmllint warning logic to qmlcompiler, making them available to all tools using the library. The end goal is to get rid off the additional AST visitor in qmllint altogether (We can't quite yet until we have ported over type interference). This also prepares qmlcompiler to move to a two pass approach which isn't fully implemented in here yet due to the size of the change. Change-Id: Id2e108340d26a75085ce6ed97d56dec03ea3a12d Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commit 3c95bf5b6df7cfd12af3755d11b34db721e23f19) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp511
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor_p.h38
-rw-r--r--src/qmlcompiler/qqmljsmetatypes_p.h6
-rw-r--r--tools/qmllint/checkidentifiers.cpp8
-rw-r--r--tools/qmllint/checkidentifiers.h14
-rw-r--r--tools/qmllint/findwarnings.cpp612
-rw-r--r--tools/qmllint/findwarnings.h44
7 files changed, 611 insertions, 622 deletions
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp
index 05bdae8ec6..0417898276 100644
--- a/src/qmlcompiler/qqmljsimportvisitor.cpp
+++ b/src/qmlcompiler/qqmljsimportvisitor.cpp
@@ -32,6 +32,9 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qqueue.h>
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <QtQml/private/qv4codegen_p.h>
#include <algorithm>
@@ -65,17 +68,46 @@ inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::Sco
return scope->baseTypeName();
}
-QQmlJSImportVisitor::QQmlJSImportVisitor(
- QQmlJSImporter *importer, const QString &implicitImportDirectory,
- const QStringList &qmltypesFiles, const QString &fileName, const QString &code, bool silent)
- : m_implicitImportDirectory(implicitImportDirectory)
- , m_qmltypesFiles(qmltypesFiles)
- , m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope))
- , m_importer(importer)
- , m_logger(fileName, code, silent)
+QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer,
+ const QString &implicitImportDirectory,
+ const QStringList &qmltypesFiles, const QString &fileName,
+ const QString &code, bool silent)
+ : m_implicitImportDirectory(implicitImportDirectory),
+ m_code(code),
+ m_filePath(fileName),
+ m_rootId(u"<id>"_qs),
+ m_qmltypesFiles(qmltypesFiles),
+ m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope)),
+ m_importer(importer),
+ m_logger(fileName, code, silent)
{
m_globalScope = m_currentScope;
m_currentScope->setIsComposite(true);
+
+ m_currentScope->setInternalName(u"global"_qs);
+
+ QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */
+ // console/debug api
+ QLatin1String("console"), QLatin1String("print"),
+ // garbage collector
+ QLatin1String("gc"),
+ // i18n
+ QLatin1String("qsTr"), QLatin1String("qsTrId"),
+ QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"),
+ QLatin1String("QT_TRID_NOOP"),
+ // XMLHttpRequest
+ QLatin1String("XMLHttpRequest")
+ };
+
+ QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
+ QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation()
+ };
+ for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr;
+ ++globalName) {
+ m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
+ }
+ for (const auto &jsGlobVar : jsGlobVars)
+ m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
}
void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
@@ -264,6 +296,35 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
void QQmlJSImportVisitor::endVisit(UiProgram *)
{
resolveAliases();
+ processDefaultProperties();
+ checkPropertyBindings();
+ checkSignals();
+
+ for (const auto &scope : m_objectBindingScopes)
+ checkInheritanceCycle(scope);
+
+ for (const auto &scope : m_objectDefinitionScopes) {
+ checkGroupedAndAttachedScopes(scope);
+ checkInheritanceCycle(scope);
+ }
+
+ auto unusedImports = m_importLocations;
+ for (const QString &type : m_usedTypes) {
+ for (const auto &importLocation : m_importTypeLocationMap.values(type))
+ unusedImports.remove(importLocation);
+
+ // If there are no more unused imports left we can abort early
+ if (unusedImports.isEmpty())
+ break;
+ }
+
+ for (const auto &import : unusedImports) {
+ m_logger.log(QString::fromLatin1("Unused import at %1:%2:%3")
+ .arg(m_filePath)
+ .arg(import.startLine)
+ .arg(import.startColumn),
+ Log_UnusedImport, import);
+ }
}
static QQmlJSAnnotation::Value bindingToVariant(QQmlJS::AST::Statement *statement)
@@ -328,6 +389,329 @@ QVector<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiA
return annotationList;
}
+void QQmlJSImportVisitor::processDefaultProperties()
+{
+ for (auto it = m_pendingDefaultProperties.constBegin();
+ it != m_pendingDefaultProperties.constEnd(); ++it) {
+ // We can't expect custom parser default properties to be sensible, discard them for now.
+ if (it.key()->isInCustomParserParent())
+ continue;
+
+ const QQmlJSScope *scopeOfDefaultProperty = nullptr;
+ QString defaultPropertyName;
+ // NB: start looking for default property in parent scope (because this
+ // scope is not suitable), but progress through baseType()
+
+ bool isComponent = false;
+ for (const auto *s = it.key().get(); s; s = s->baseType().get()) {
+ defaultPropertyName = s->defaultPropertyName();
+ if (!defaultPropertyName.isEmpty()) {
+ scopeOfDefaultProperty = s;
+ break;
+ }
+
+ if (s->internalName() == QStringLiteral("QQmlComponent")) {
+ isComponent = true;
+ break;
+ }
+ }
+
+ // If the parent scope is based on Component it can have any child element
+ // TODO: We should also store these somewhere
+ if (isComponent)
+ continue;
+
+ if (defaultPropertyName.isEmpty()) {
+ m_logger.log(QStringLiteral("Cannot assign to non-existent default property"),
+ Log_Property, it.value().constFirst()->sourceLocation());
+ continue;
+ }
+
+ Q_ASSERT(scopeOfDefaultProperty);
+ QQmlJSMetaProperty defaultProp = scopeOfDefaultProperty->property(defaultPropertyName);
+
+ if (it.value().length() > 1 && !defaultProp.isList()) {
+ m_logger.log(
+ QStringLiteral("Cannot assign multiple objects to a default non-list property"),
+ Log_Property, it.value().constFirst()->sourceLocation());
+ }
+
+ // TODO: Currently we only support binding one scope, adjust this once this is no longer
+ // true
+ const QQmlJSScope::ConstPtr scope = it.value().constFirst();
+
+ QQmlJSMetaPropertyBinding binding(defaultProp);
+ binding.setValue(scope);
+ binding.setValueTypeName(getScopeName(scope, QQmlJSScope::QMLScope));
+
+ it.key()->addOwnPropertyBinding(binding);
+
+ auto propType = defaultProp.type();
+ if (propType.isNull() || !propType->isFullyResolved()
+ || !scope->isFullyResolved()) // should be an error somewhere else
+ return;
+
+ // Assigning any element to a QQmlComponent property implicitly wraps it into a Component
+ // Check whether the property can be assigned the scope
+ if (propType->canAssign(scope))
+ return;
+
+ m_logger.log(QStringLiteral("Cannot assign to default property of incompatible type"),
+ Log_Property, scope->sourceLocation());
+ }
+}
+
+void QQmlJSImportVisitor::checkPropertyBindings()
+{
+ for (auto it = m_propertyBindings.constBegin(); it != m_propertyBindings.constEnd(); ++it) {
+ QQmlJSScope::Ptr propertyScope = it.key();
+ QQmlJSScope::Ptr scope = propertyScope->parentScope();
+
+ for (const QString &name : it.value()) {
+ if (!scope->hasProperty(name)) {
+ // These warnings do not apply for custom parsers and their children and need to be
+ // handled on a case by case basis
+
+ if (scope->isInCustomParserParent())
+ continue;
+
+ // 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.")
+ .arg(name),
+ Log_Type, propertyScope->sourceLocation());
+ continue;
+ }
+
+ const auto property = scope->property(name);
+ if (!property.type()) {
+ m_logger.log(QStringLiteral("No type found for property \"%1\". This may be due "
+ "to a missing import statement or incomplete "
+ "qmltypes files.")
+ .arg(name),
+ Log_Type, propertyScope->sourceLocation());
+ }
+
+ const auto &annotations = property.annotations();
+
+ const auto deprecationAnn =
+ std::find_if(annotations.cbegin(), annotations.cend(),
+ [](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
+
+ if (deprecationAnn != annotations.cend()) {
+ const auto deprecation = deprecationAnn->deprecation();
+
+ QString message = QStringLiteral("Binding on deprecated property \"%1\"")
+ .arg(property.propertyName());
+
+ if (!deprecation.reason.isEmpty())
+ message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
+
+ m_logger.log(message, Log_Deprecation, propertyScope->sourceLocation());
+ }
+ }
+ }
+}
+
+static QString signalName(QStringView handlerName)
+{
+ if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
+ QString signal = handlerName.mid(2).toString();
+ for (int i = 0; i < signal.length(); ++i) {
+ QChar &ch = signal[i];
+ if (ch.isLower())
+ return QString();
+ if (ch.isUpper()) {
+ ch = ch.toLower();
+ return signal;
+ }
+ }
+ }
+ return QString();
+}
+
+void QQmlJSImportVisitor::checkSignals()
+{
+ for (auto it = m_signals.constBegin(); it != m_signals.constEnd(); ++it) {
+ for (const auto &pair : it.value()) {
+ const QString signal = signalName(pair.first);
+
+ if (!it.key()->hasMethod(signal)) {
+ m_logger.log(QStringLiteral("no matching signal found for handler \"%1\"")
+ .arg(pair.first),
+ Log_UnqualifiedAccess, m_currentScope->sourceLocation());
+ continue;
+ }
+
+ QQmlJSMetaMethod scopeSignal;
+ for (QQmlJSScope::ConstPtr scope = it.key(); scope; scope = scope->baseType()) {
+ const auto methods = scope->ownMethods();
+ const auto methodsRange = methods.equal_range(signal);
+ for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
+ if (method->methodType() != QQmlJSMetaMethod::Signal)
+ continue;
+ scopeSignal = *method;
+ break;
+ }
+ }
+
+ const QStringList signalParameters = scopeSignal.parameterNames();
+
+ if (pair.second.length() > signalParameters.length()) {
+ m_logger.log(QStringLiteral("Signal handler for \"%2\" has more formal"
+ " parameters than the signal it handles.")
+ .arg(pair.first),
+ Log_Signal, it.key()->sourceLocation());
+ continue;
+ }
+
+ for (qsizetype i = 0; i < pair.second.length(); i++) {
+ const QStringView handlerParameter = pair.second.at(i);
+ const qsizetype j = signalParameters.indexOf(handlerParameter);
+ if (j == i || j < 0)
+ continue;
+
+ 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.")
+ .arg(i + 1)
+ .arg(pair.first, handlerParameter)
+ .arg(j + 1),
+ Log_Signal, it.key()->sourceLocation());
+ }
+ }
+ }
+}
+
+void QQmlJSImportVisitor::addDefaultProperties()
+{
+ if (m_currentScope == m_exportedRootScope || m_currentScope->parentScope()->isArrayScope()
+ || m_currentScope->isInlineComponent()) // inapplicable
+ return;
+
+ m_pendingDefaultProperties[m_currentScope->parentScope()] << m_currentScope;
+}
+
+void QQmlJSImportVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope)
+{
+ QQmlJSScope::ConstPtr originalScope = scope;
+ QList<QQmlJSScope::ConstPtr> scopes;
+ while (!scope.isNull()) {
+ for (const QQmlJSAnnotation &annotation : scope->annotations()) {
+ if (annotation.isDeprecation()) {
+ QQQmlJSDeprecation deprecation = annotation.deprecation();
+
+ QString message =
+ QStringLiteral("Type \"%1\" is deprecated").arg(scope->internalName());
+
+ if (!deprecation.reason.isEmpty())
+ message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
+
+ m_logger.log(message, Log_Deprecation, originalScope->sourceLocation());
+ }
+ }
+
+ if (scopes.contains(scope)) {
+ QString inheritenceCycle;
+ for (const auto &seen : qAsConst(scopes)) {
+ if (!inheritenceCycle.isEmpty())
+ inheritenceCycle.append(QLatin1String(" -> "));
+ inheritenceCycle.append(seen->baseTypeName());
+ }
+
+ m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2")
+ .arg(scope->internalName())
+ .arg(inheritenceCycle),
+ Log_InheritanceCycle);
+ break;
+ }
+
+ scopes.append(scope);
+
+ if (scope->baseTypeName().isEmpty()) {
+ break;
+ } else if (auto newScope = scope->baseType()) {
+ scope = newScope;
+ } else {
+ m_logger.log(scope->baseTypeName()
+ + QStringLiteral(" was not found. Did you add all import paths?"),
+ Log_Import);
+ break;
+ }
+ }
+}
+
+void QQmlJSImportVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
+{
+ // These warnings do not apply for custom parsers and their children and need to be handled on a
+ // case by case basis
+ if (scope->isInCustomParserParent())
+ return;
+
+ auto children = scope->childScopes();
+ while (!children.isEmpty()) {
+ auto childScope = children.takeFirst();
+ const auto type = childScope->scopeType();
+ switch (type) {
+ case QQmlJSScope::GroupedPropertyScope:
+ case QQmlJSScope::AttachedPropertyScope:
+ if (!childScope->baseType()) {
+ 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:
+ break;
+ }
+ }
+}
+
+void QQmlJSImportVisitor::flushPendingSignalParameters()
+{
+ const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
+ for (const QString &parameter : handler.signalParameters) {
+ m_currentScope->insertJSIdentifier(
+ parameter, { QQmlJSScope::JavaScriptIdentifier::Injected, m_pendingSignalHandler });
+ }
+ m_pendingSignalHandler = QQmlJS::SourceLocation();
+}
+
+bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
+{
+ if (m_pendingSignalHandler.isValid()) {
+ enterEnvironment(QQmlJSScope::JSFunctionScope, u"signalhandler"_qs,
+ ast->firstSourceLocation());
+ flushPendingSignalParameters();
+ }
+ return true;
+}
+
+void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
+{
+ if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
+ && m_currentScope->baseTypeName() == u"signalhandler"_qs) {
+ leaveEnvironment();
+ }
+}
+
+bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
+{
+ const QString s = m_code.mid(sl->literalToken.begin(), sl->literalToken.length);
+
+ if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
+ || s.contains(QChar(0x2029u))) {
+ m_logger.log(QStringLiteral("String contains unescaped line terminator which is "
+ "deprecated. Use a template "
+ "literal instead."),
+ Log_MultilineString, sl->literalToken);
+ }
+
+ return true;
+}
bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
{
@@ -356,7 +740,6 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
{
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
-
leaveEnvironment();
}
@@ -418,6 +801,15 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
Log_Alias,
expression->firstSourceLocation());
}
+ } else {
+ const auto name = publicMember->memberType->name.toString();
+ if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].isNull()) {
+ if (m_importTypeLocationMap.contains(name))
+ m_usedTypes.insert(name);
+ } else {
+ m_logger.log(name + QStringLiteral(" was not found. Did you add all import paths?"),
+ Log_Import);
+ }
}
QQmlJSMetaProperty prop;
prop.setPropertyName(publicMember->name.toString());
@@ -441,6 +833,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
break;
}
}
+
return true;
}
@@ -545,34 +938,88 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *)
bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
{
+ auto scope = m_currentScope;
const auto id = scriptBinding->qualifiedId;
const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
if (!id->next && id->name == QLatin1String("id")) {
const auto *idExpression = cast<IdentifierExpression *>(statement->expression);
- m_scopesById.insert(idExpression->name.toString(), m_currentScope);
- } else {
- for (auto group = id; group->next; group = group->next) {
- const QString name = group->name.toString();
+ const QString &name = idExpression->name.toString();
+ m_scopesById.insert(name, m_currentScope);
- if (name.isEmpty())
- break;
+ // TODO: Discard this once we properly store binding values and can just use
+ // QQmlJSScope::property() to obtain this
+ if (m_currentScope->parentScope() && !m_currentScope->parentScope()->parentScope())
+ m_rootId = name;
- enterEnvironmentNonUnique(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
- : QQmlJSScope::GroupedPropertyScope,
- name, group->firstSourceLocation());
- }
+ return true;
+ }
- // TODO: remember the actual binding, once we can process it.
+ for (auto group = id; group->next; group = group->next) {
+ const QString name = group->name.toString();
- while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
- || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
- leaveEnvironment();
+ if (name.isEmpty())
+ break;
+
+ enterEnvironmentNonUnique(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
+ : QQmlJSScope::GroupedPropertyScope,
+ name, group->firstSourceLocation());
+ }
+
+ // TODO: remember the actual binding, once we can process it.
+
+ while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
+ || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
+ leaveEnvironment();
+ }
+
+ if (!statement || !statement->expression->asFunctionDefinition()) {
+ enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
+ scriptBinding->statement->firstSourceLocation());
+ }
+
+ auto name = id->name;
+
+ const QString signal = signalName(name);
+ if (signal.isEmpty()) {
+ for (const auto &childScope : scope->childScopes()) {
+ if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope
+ || childScope->scopeType() == QQmlJSScope::GroupedPropertyScope)
+ && childScope->internalName() == name) {
+ return true;
+ }
}
- if (!statement || !statement->expression->asFunctionDefinition()) {
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
- scriptBinding->statement->firstSourceLocation());
+ m_propertyBindings[m_currentScope] << name.toString();
+ } else {
+ const auto statement = scriptBinding->statement;
+ QStringList signalParameters;
+
+ if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
+ if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
+ for (FormalParameterList *formal = func->formals; formal; formal = formal->next)
+ signalParameters << formal->element->bindingIdentifier.toString();
+ }
}
+ m_signals[scope] << QPair<QString, QStringList> { name.toString(), signalParameters };
+
+ QQmlJSMetaMethod scopeSignal;
+ for (QQmlJSScope::ConstPtr qmlScope = scope; qmlScope; qmlScope = qmlScope->baseType()) {
+ const auto methods = qmlScope->ownMethods();
+ const auto methodsRange = methods.equal_range(signal);
+ for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
+ if (method->methodType() != QQmlJSMetaMethod::Signal)
+ continue;
+ scopeSignal = *method;
+ break;
+ }
+ }
+
+ const auto firstSourceLocation = statement->firstSourceLocation();
+ bool hasMultilineStatementBody =
+ statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
+ m_pendingSignalHandler = firstSourceLocation;
+ m_signalHandlers.insert(firstSourceLocation,
+ { scopeSignal.parameterNames(), hasMultilineStatementBody });
}
return true;
@@ -755,6 +1202,10 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
{
enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("block"),
ast->firstSourceLocation());
+
+ if (m_pendingSignalHandler.isValid())
+ flushPendingSignalParameters();
+
return true;
}
@@ -796,6 +1247,12 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
{
enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("with"),
ast->firstSourceLocation());
+
+ m_logger.log(QStringLiteral("with statements are strongly discouraged in QML "
+ "and might cause false positives when analysing unqualified "
+ "identifiers"),
+ Log_WithStatement, ast->firstSourceLocation());
+
return true;
}
@@ -868,6 +1325,8 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
enterEnvironment(QQmlJSScope::QMLScope, name,
uiob->qualifiedTypeNameId->identifierToken);
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
+
+ m_objectBindingScopes << m_currentScope;
return true;
}
diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h
index 3419f4c21b..5a91de50ac 100644
--- a/src/qmlcompiler/qqmljsimportvisitor_p.h
+++ b/src/qmlcompiler/qqmljsimportvisitor_p.h
@@ -71,6 +71,12 @@ public:
const QString &localFile, QQmlJSResourceFileMapper *mapper);
protected:
+ // Linter warnings, we might want to move this at some point
+ bool visit(QQmlJS::AST::StringLiteral *) override;
+
+ bool visit(QQmlJS::AST::ExpressionStatement *ast) override;
+ void endVisit(QQmlJS::AST::ExpressionStatement *ast) override;
+
bool visit(QQmlJS::AST::UiProgram *) override;
void endVisit(QQmlJS::AST::UiProgram *) override;
bool visit(QQmlJS::AST::UiObjectDefinition *) override;
@@ -125,6 +131,9 @@ protected:
void throwRecursionDepthError() override;
QString m_implicitImportDirectory;
+ QString m_code;
+ QString m_filePath;
+ QString m_rootId;
QStringView m_inlineComponentName;
bool m_nextIsInlineComponent = false;
QStringList m_qmltypesFiles;
@@ -152,11 +161,40 @@ protected:
void leaveEnvironment();
QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list);
+ void addDefaultProperties();
+ void processDefaultProperties();
+ void checkPropertyBindings();
+ void checkSignals();
+ void flushPendingSignalParameters();
+
+ void checkInheritanceCycle(QQmlJSScope::ConstPtr scope);
+ void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope);
QQmlJSLogger m_logger;
// Used to temporarily store annotations for functions and generators wrapped in UiSourceElements
QVector<QQmlJSAnnotation> m_pendingMethodAnnotations;
+
+ QHash<QQmlJSScope::Ptr, QVector<QQmlJSScope::Ptr>> m_pendingDefaultProperties;
+ QVector<QQmlJSScope::Ptr> m_objectBindingScopes;
+ QVector<QQmlJSScope::Ptr> m_objectDefinitionScopes;
+
+ QHash<QQmlJSScope::Ptr, QVector<QString>> m_propertyBindings;
+ QHash<QQmlJSScope::Ptr, QVector<QPair<QString, QStringList>>> m_signals;
+
+ QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> m_signalHandlers;
+ QQmlJS::SourceLocation m_pendingSignalHandler;
+
+ struct OutstandingConnection
+ {
+ QString targetName;
+ QQmlJSScope::Ptr scope;
+ QQmlJS::AST::UiObjectDefinition *uiod;
+ };
+
+ QVarLengthArray<OutstandingConnection, 3>
+ m_outstandingConnections; // Connections whose target we have not encountered
+
private:
void importBaseModules();
void resolveAliases();
diff --git a/src/qmlcompiler/qqmljsmetatypes_p.h b/src/qmlcompiler/qqmljsmetatypes_p.h
index 28b9e07efc..513414d87f 100644
--- a/src/qmlcompiler/qqmljsmetatypes_p.h
+++ b/src/qmlcompiler/qqmljsmetatypes_p.h
@@ -416,6 +416,12 @@ public:
}
};
+struct QQmlJSMetaSignalHandler
+{
+ QStringList signalParameters;
+ bool isMultiline;
+};
+
QT_END_NAMESPACE
#endif // QQMLJSMETATYPES_P_H
diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp
index 72e0d6fd7d..93edb08ba0 100644
--- a/tools/qmllint/checkidentifiers.cpp
+++ b/tools/qmllint/checkidentifiers.cpp
@@ -250,9 +250,9 @@ void CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members,
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
+ const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers,
+ const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root,
+ const QString &rootId) const
{
// revisit all scopes
QQueue<QQmlJSScope::ConstPtr> workQueue;
@@ -442,7 +442,7 @@ void CheckIdentifiers::operator()(
const auto handler = signalHandlers[id.location];
colorOut.write(QLatin1String(handler.isMultiline ? "function(" : "("), QtInfoMsg);
- const auto parameters = handler.signal.parameterNames();
+ const auto parameters = handler.signalParameters;
for (int numParams = parameters.size(); numParams > 0; --numParams) {
colorOut.write(parameters.at(parameters.size() - numParams), QtInfoMsg);
if (numParams > 1)
diff --git a/tools/qmllint/checkidentifiers.h b/tools/qmllint/checkidentifiers.h
index c23a64f9bd..81f86f7aeb 100644
--- a/tools/qmllint/checkidentifiers.h
+++ b/tools/qmllint/checkidentifiers.h
@@ -32,14 +32,10 @@
#include <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlCompiler/private/qqmljsscope_p.h>
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
+#include <QtQmlCompiler/private/qqmljsmetatypes_p.h>
class QColorOutput;
-struct SignalHandler {
- QQmlJSMetaMethod signal;
- bool isMultiline;
-};
-
struct FieldMember
{
QString m_name;
@@ -57,10 +53,10 @@ public:
m_logger(logger), m_code(code), m_types(types), m_fileName(fileName)
{}
- 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;
+ void operator()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs,
+ const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers,
+ const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root,
+ const QString &rootId) const;
private:
void checkMemberAccess(const QVector<FieldMember> &members,
diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp
index 916962a12d..c677e112dd 100644
--- a/tools/qmllint/findwarnings.cpp
+++ b/tools/qmllint/findwarnings.cpp
@@ -36,376 +36,105 @@
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
-#include <QtQml/private/qv4codegen_p.h>
#include <QtQml/private/qqmlimportresolver_p.h>
#include <QtCore/qfile.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qscopedvaluerollback.h>
-void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope)
+bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
- QQmlJSScope::ConstPtr originalScope = scope;
- QList<QQmlJSScope::ConstPtr> scopes;
- while (!scope.isNull()) {
-
- for (const QQmlJSAnnotation &annotation : scope->annotations()) {
- if (annotation.isDeprecation()) {
- QQQmlJSDeprecation deprecation = annotation.deprecation();
-
- QString message = QStringLiteral("Type \"%1\" is deprecated")
- .arg(scope->internalName());
+ QQmlJSImportVisitor::visit(uiod);
- if (!deprecation.reason.isEmpty())
- message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
-
- m_logger.log(message, Log_Deprecation, originalScope->sourceLocation()
- );
- }
- }
+ const QString name = m_currentScope->baseTypeName();
+ if (name.isEmpty() || name.front().isLower())
+ return false; // Ignore grouped properties for now
- if (scopes.contains(scope)) {
- QString inheritenceCycle;
- for (const auto &seen: qAsConst(scopes)) {
- if (!inheritenceCycle.isEmpty())
- inheritenceCycle.append(QLatin1String(" -> "));
- inheritenceCycle.append(seen->baseTypeName());
+ if (name.endsWith(u"Connections"_qs)) {
+ QString target;
+ auto member = uiod->initializer->members;
+ while (member) {
+ if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
+ auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding *>(member->member);
+ if (asBinding->qualifiedId->name == u"target"_qs) {
+ if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
+ auto expr = static_cast<QQmlJS::AST::ExpressionStatement *>(
+ asBinding->statement)
+ ->expression;
+ if (auto idexpr =
+ QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(expr)) {
+ target = idexpr->name.toString();
+ } else {
+ // more complex expressions are not supported
+ }
+ }
+ break;
+ }
}
-
- m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2")
- .arg(scope->internalName())
- .arg(inheritenceCycle),
- Log_InheritanceCycle);
-
- m_unknownImports.insert(scope->internalName());
- break;
+ member = member->next;
}
-
- scopes.append(scope);
-
- if (scope->baseTypeName().isEmpty()) {
- break;
- } else if (auto newScope = scope->baseType()) {
- scope = newScope;
+ QQmlJSScope::ConstPtr targetScope;
+ if (target.isEmpty()) {
+ // no target set, connection comes from parentF
+ QQmlJSScope::Ptr scope = m_currentScope;
+ do {
+ scope = scope->parentScope(); // TODO: rename method
+ } while (scope->scopeType() != QQmlJSScope::QMLScope);
+ targetScope = m_rootScopeImports.value(scope->baseTypeName());
} else {
- m_logger.log(scope->baseTypeName()
- + QStringLiteral(" was not found. Did you add all import paths?"),
- Log_Import);
- m_unknownImports.insert(scope->baseTypeName());
- break;
- }
- }
-}
-
-void FindWarningVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
-{
- // These warnings do not apply for custom parsers and their children and need to be handled on a
- // case by case basis
- if (scope->isInCustomParserParent())
- return;
-
- auto children = scope->childScopes();
- while (!children.isEmpty()) {
- auto childScope = children.takeFirst();
- const auto type = childScope->scopeType();
- switch (type) {
- case QQmlJSScope::GroupedPropertyScope:
- case QQmlJSScope::AttachedPropertyScope:
- if (!childScope->baseType()) {
- m_logger.log(
- QStringLiteral("unknown %1 property scope %2.")
- .arg(type == QQmlJSScope::GroupedPropertyScope
- ? QStringLiteral("grouped")
- : QStringLiteral("attached"),
- childScope->internalName()),
- Log_UnqualifiedAccess,
- childScope->sourceLocation()
- );
+ // there was a target, check if we already can find it
+ auto scopeIt = m_scopesById.find(target);
+ if (scopeIt != m_scopesById.end()) {
+ targetScope = *scopeIt;
+ } else {
+ m_outstandingConnections.push_back({ target, m_currentScope, uiod });
+ return false; // visit children later once target is known
}
- children.append(childScope->childScopes());
- default:
- break;
- }
- }
-}
-
-void FindWarningVisitor::flushPendingSignalParameters()
-{
- const SignalHandler handler = m_signalHandlers[m_pendingSingalHandler];
- for (const QString &parameter : handler.signal.parameterNames()) {
- m_currentScope->insertJSIdentifier(
- parameter, {
- QQmlJSScope::JavaScriptIdentifier::Injected,
- m_pendingSingalHandler
- });
- }
- m_pendingSingalHandler = QQmlJS::SourceLocation();
-}
-
-void FindWarningVisitor::checkDefaultProperty(const QQmlJSScope::ConstPtr &scope)
-{
- if (scope == m_exportedRootScope || scope->isArrayScope()
- || scope->isInlineComponent()) // inapplicable
- return;
-
- // These warnings do not apply for custom parsers and their children and need to be handled on a
- // case by case basis
- if (scope->isInCustomParserParent())
- return;
-
- const QQmlJSScope *scopeOfDefaultProperty = nullptr;
- QString defaultPropertyName;
- // NB: start looking for default property in parent scope (because this
- // scope is not suitable), but progress through baseType()
- for (auto s = scope->parentScope(); s; s = s->baseType()) {
- defaultPropertyName = s->defaultPropertyName();
- if (!defaultPropertyName.isEmpty()) {
- scopeOfDefaultProperty = s.data();
- break;
}
-
- // If the parent scope is based on Component it can have any child element
- if (s->internalName() == QStringLiteral("QQmlComponent"))
- return;
- }
- if (defaultPropertyName.isEmpty()) {
- if (scope->parentScope()->isFullyResolved()) {
- m_logger.log(QStringLiteral("Cannot assign to non-existent default property"),
- Log_Property, scope->sourceLocation());
+ for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) {
+ const auto connectionMethods = targetScope->ownMethods();
+ for (const auto &method : connectionMethods)
+ m_currentScope->addOwnMethod(method);
}
- return;
- }
-
- Q_ASSERT(scopeOfDefaultProperty);
- Q_ASSERT(scope->parentScope());
- QQmlJSMetaProperty defaultProp = scopeOfDefaultProperty->property(defaultPropertyName);
-
- const QQmlJSScope *parentScope = scope->parentScope().get();
-
- // abuse QHash feature to construct default value through
- // operator[]. default bool is false, which is what's needed
- if (m_scopeHasDefaultPropertyAssignment[parentScope] && !defaultProp.isList()) {
- // already has some object assigned to a default property and
- // this default property is not a list property
- m_logger.log(QStringLiteral("Cannot assign multiple objects to a default non-list property"),
- Log_Property, scope->sourceLocation());
}
- m_scopeHasDefaultPropertyAssignment[parentScope] = true;
- auto propType = defaultProp.type();
- if (propType.isNull() || !propType->isFullyResolved()
- || !scope->isFullyResolved()) // should be an error somewhere else
- return;
-
- // Assigning any element to a QQmlComponent property implicitly wraps it into a Component
- // Check whether the property can be assigned the scope
- if (propType->canAssign(scope))
- return;
-
- m_logger.log(QStringLiteral("Cannot assign to default property of incompatible type"),
- Log_Property, scope->sourceLocation());
-}
-
-void FindWarningVisitor::throwRecursionDepthError()
-{
- QQmlJSImportVisitor::throwRecursionDepthError();
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
-{
- if (m_pendingSingalHandler.isValid()) {
- enterEnvironment(QQmlJSScope::JSFunctionScope, "signalhandler", ast->firstSourceLocation());
- flushPendingSignalParameters();
- }
- return true;
-}
-
-void FindWarningVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
-{
- if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
- && m_currentScope->baseTypeName() == "signalhandler") {
- leaveEnvironment();
- }
-}
+ addDefaultProperties();
+ m_objectDefinitionScopes << m_currentScope;
-bool FindWarningVisitor::visit(QQmlJS::AST::Block *block)
-{
- if (!QQmlJSImportVisitor::visit(block))
- return false;
- if (m_pendingSingalHandler.isValid())
- flushPendingSignalParameters();
return true;
}
-bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
-{
- m_logger.log(QStringLiteral("with statements are strongly discouraged in QML "
- "and might cause false positives when analysing unqualified "
- "identifiers"),
- Log_WithStatement, withStatement->firstSourceLocation());
-
- return QQmlJSImportVisitor::visit(withStatement);
-}
-
-static QString signalName(QStringView handlerName)
-{
- if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
- QString signal = handlerName.mid(2).toString();
- for (int i = 0; i < signal.length(); ++i) {
- QChar &ch = signal[i];
- if (ch.isLower())
- return QString();
- if (ch.isUpper()) {
- ch = ch.toLower();
- return signal;
- }
- }
- }
- return QString();
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
+void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod)
{
- using namespace QQmlJS::AST;
-
- const auto qmlScope = m_currentScope;
- if (!QQmlJSImportVisitor::visit(uisb))
- return false;
-
- auto name = uisb->qualifiedId->name;
- if (name == QLatin1String("id")) {
- // Figure out whether the current scope is the root scope.
- if (auto parentScope = qmlScope->parentScope()) {
- if (!parentScope->parentScope()) {
- const auto expstat = cast<ExpressionStatement *>(uisb->statement);
- const auto identexp = cast<IdentifierExpression *>(expstat->expression);
- m_rootId = identexp->name.toString();
- }
- }
- return true;
- }
-
- if (!qmlScope->isFullyResolved())
- return true;
-
- const QString signal = signalName(name);
- if (signal.isEmpty()) {
- for (const auto &childScope : qmlScope->childScopes()) {
- if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope
- || childScope->scopeType() == QQmlJSScope::GroupedPropertyScope)
- && childScope->internalName() == name) {
- return true;
- }
- }
-
- if (!qmlScope->hasProperty(name.toString())) {
- // These warnings do not apply for custom parsers and their children and need to be
- // handled on a case by case basis
-
- if (qmlScope->isInCustomParserParent())
- return true;
-
- // 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.")
- .arg(name),
- Log_Type, uisb->firstSourceLocation());
- return true;
- }
-
- const auto property = qmlScope->property(name.toString());
- if (!property.type()) {
- m_logger.log(QStringLiteral("No type found for property \"%1\". This may be due "
- "to a missing import statement or incomplete "
- "qmltypes files.")
- .arg(name),
- Log_Type, uisb->firstSourceLocation());
- }
-
- const auto &annotations = property.annotations();
-
- const auto deprecationAnn = std::find_if(annotations.cbegin(), annotations.cend(), [](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
-
- if (deprecationAnn != annotations.cend()) {
- const auto deprecation = deprecationAnn->deprecation();
-
- QString message = QStringLiteral("Binding on deprecated property \"%1\"")
- .arg(property.propertyName());
-
- if (!deprecation.reason.isEmpty())
- message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
-
- m_logger.log(message, Log_Deprecation, uisb->firstSourceLocation());
- }
-
+ auto childScope = m_currentScope;
+ QQmlJSImportVisitor::endVisit(uiod);
- return true;
+ if (m_currentScope == m_globalScope
+ || m_currentScope->baseTypeName() == QStringLiteral("Component")) {
+ return;
}
+ QString parentPropertyName;
+ for (QQmlJSScope::ConstPtr scope = childScope; scope; scope = scope->baseType()) {
+ parentPropertyName = scope->parentPropertyName();
+ if (parentPropertyName.isEmpty())
+ continue;
- if (!qmlScope->hasMethod(signal)) {
- m_logger.log(
- QStringLiteral("no matching signal found for handler \"%1\"").arg(name.toString()),
- Log_UnqualifiedAccess, uisb->firstSourceLocation());
- return true;
- }
+ auto property = scope->property(parentPropertyName);
+ property.setType(QQmlJSScope::ConstPtr(m_currentScope));
- QQmlJSMetaMethod scopeSignal;
- for (QQmlJSScope::ConstPtr scope = qmlScope; scope; scope = scope->baseType()) {
- const auto methods = scope->ownMethods();
- const auto methodsRange = methods.equal_range(signal);
- for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
- if (method->methodType() != QQmlJSMetaMethod::Signal)
- continue;
- scopeSignal = *method;
- break;
+ if (childScope->hasOwnProperty(parentPropertyName)) {
+ Q_ASSERT(childScope->ownProperty(parentPropertyName).index() >= 0);
+ } else {
+ // it's a new property, so must adjust the index. the index is
+ // "outdated" as it's a relative index of scope, not childScope (or
+ // it might even be -1 in theory but this is likely an error)
+ property.setIndex(childScope->ownProperties().size());
}
- }
-
- const auto statement = uisb->statement;
- if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
- if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
- // functions are already handled
- // they do not get names inserted according to the signal, but access their formal
- // parameters. Let's still check if the names match, though.
- const QStringList signalParameters = scopeSignal.parameterNames();
- qsizetype i = 0, end = signalParameters.length();
- for (FormalParameterList *formal = func->formals;
- formal; ++i, formal = formal->next) {
- if (i == end) {
- m_logger.log(
- QStringLiteral("Signal handler for \"%2\" has more formal"
- " parameters than the signal it handles.")
- .arg(name),
- Log_Signal,
- uisb->firstSourceLocation()
- );
- }
- const QStringView handlerParameter = formal->element->bindingIdentifier;
- const qsizetype j = signalParameters.indexOf(handlerParameter);
- if (j == i || j < 0)
- continue;
-
- 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.")
- .arg(i + 1)
- .arg(name, handlerParameter)
- .arg(j + 1),
- Log_Signal, uisb->firstSourceLocation());
- }
-
- return true;
- }
+ // TODO: This is bad. We shouldn't add a new property but rather amend the existing one.
+ childScope->addOwnProperty(property);
}
-
- const auto firstSourceLocation = statement->firstSourceLocation();
- bool hasMultilineStatementBody
- = statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
- m_pendingSingalHandler = firstSourceLocation;
- m_signalHandlers.insert(firstSourceLocation, {scopeSignal, hasMultilineStatementBody});
- return true;
}
bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
@@ -426,38 +155,8 @@ FindWarningVisitor::FindWarningVisitor(QQmlJSImporter *importer, QStringList qml
QString fileName, bool silent)
: QQmlJSImportVisitor(importer,
implicitImportDirectory(fileName, importer->resourceFileMapper()),
- qmltypesFiles, fileName, code, silent),
- m_code(code),
- m_rootId(QLatin1String("<id>")),
- m_filePath(fileName)
+ qmltypesFiles, fileName, code, silent)
{
- m_currentScope->setInternalName("global");
-
- QLatin1String jsGlobVars[] = {
- /* Not listed on the MDN page; browser and QML extensions: */
- // console/debug api
- QLatin1String("console"), QLatin1String("print"),
- // garbage collector
- QLatin1String("gc"),
- // i18n
- QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"),
- QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
- // XMLHttpRequest
- QLatin1String("XMLHttpRequest")
- };
-
- QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
- QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- QQmlJS::SourceLocation()
- };
- for (const char **globalName = QV4::Compiler::Codegen::s_globalNames;
- *globalName != nullptr;
- ++globalName) {
- m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
- }
- for (const auto& jsGlobVar: jsGlobVars)
- m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
-
parseComments(comments);
}
@@ -549,106 +248,17 @@ bool FindWarningVisitor::check()
outstandingConnection.scope->addOwnMethod(method);
}
}
- QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope, outstandingConnection.scope);
+ QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope,
+ outstandingConnection.scope);
outstandingConnection.uiod->initializer->accept(this);
}
- auto unusedImports = m_importLocations;
- for (const QString &type : m_usedTypes) {
- for (const auto &importLocation : m_importTypeLocationMap.values(type))
- unusedImports.remove(importLocation);
-
- // If there are no more unused imports left we can abort early
- if (unusedImports.isEmpty())
- break;
- }
-
- for (const auto &import : unusedImports) {
- m_logger.log(QString::fromLatin1("Unused import at %1:%2:%3")
- .arg(m_filePath)
- .arg(import.startLine)
- .arg(import.startColumn),
- Log_UnusedImport, import);
- }
-
CheckIdentifiers check(&m_logger, m_code, m_rootScopeImports, m_filePath);
check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId);
return !m_logger.hasWarnings() && !m_logger.hasErrors();
}
-bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
-{
- if (!QQmlJSImportVisitor::visit(uiob))
- return false;
-
- checkInheritanceCycle(m_currentScope);
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
-{
- using namespace QQmlJS::AST;
-
- if (!QQmlJSImportVisitor::visit(uiod))
- return false;
-
- const QString name = m_currentScope->baseTypeName();
- if (name.isEmpty() || name.front().isLower())
- return false; // Ignore grouped properties for now
-
- checkInheritanceCycle(m_currentScope);
-
- if (name.endsWith("Connections")) {
- QString target;
- auto member = uiod->initializer->members;
- while (member) {
- if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
- auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
- if (asBinding->qualifiedId->name == QLatin1String("target")) {
- if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
- auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
- if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
- target = idexpr->name.toString();
- } else {
- // more complex expressions are not supported
- }
- }
- break;
- }
- }
- member = member->next;
- }
- QQmlJSScope::ConstPtr targetScope;
- if (target.isEmpty()) {
- // no target set, connection comes from parentF
- QQmlJSScope::Ptr scope = m_currentScope;
- do {
- scope = scope->parentScope(); // TODO: rename method
- } while (scope->scopeType() != QQmlJSScope::QMLScope);
- targetScope = m_rootScopeImports.value(scope->baseTypeName());
- } else {
- // there was a target, check if we already can find it
- auto scopeIt = m_scopesById.find(target);
- if (scopeIt != m_scopesById.end()) {
- targetScope = *scopeIt;
- } else {
- m_outstandingConnections.push_back({target, m_currentScope, uiod});
- return false; // visit children later once target is known
- }
- }
- for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) {
- const auto connectionMethods = targetScope->ownMethods();
- for (const auto &method : connectionMethods)
- m_currentScope->addOwnMethod(method);
- }
- }
-
- checkDefaultProperty(m_currentScope);
-
- return true;
-}
-
bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
{
if (element->isVariableDeclaration()) {
@@ -668,46 +278,6 @@ bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
return true;
}
-void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod)
-{
- auto childScope = m_currentScope;
- QQmlJSImportVisitor::endVisit(uiod);
-
- checkGroupedAndAttachedScopes(childScope);
-
- if (m_currentScope == m_globalScope
- || m_currentScope->baseTypeName() == QStringLiteral("Component")) {
- return;
- }
-
- QString parentPropertyName;
- for (QQmlJSScope::ConstPtr scope = childScope; scope; scope = scope->baseType()) {
- parentPropertyName = scope->parentPropertyName();
- if (parentPropertyName.isEmpty())
- continue;
-
- auto property = scope->property(parentPropertyName);
- property.setType(QQmlJSScope::ConstPtr(m_currentScope));
-
- if (childScope->hasOwnProperty(parentPropertyName)) {
- Q_ASSERT(childScope->ownProperty(parentPropertyName).index() >= 0);
- } else {
- // it's a new property, so must adjust the index. the index is
- // "outdated" as it's a relative index of scope, not childScope (or
- // it might even be -1 in theory but this is likely an error)
- property.setIndex(childScope->ownProperties().size());
- }
-
- // TODO: This is bad. We shouldn't add a new property but rather amend the existing one.
- childScope->addOwnProperty(property);
- }
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::FieldMemberExpression *)
-{
- return true;
-}
-
void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
{
using namespace QQmlJS::AST;
@@ -745,11 +315,6 @@ void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMembe
}
}
-bool FindWarningVisitor::visit(QQmlJS::AST::BinaryExpression *)
-{
- return true;
-}
-
void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
{
if (binExp->op == QSOperator::As
@@ -759,36 +324,3 @@ void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
m_fieldMemberBase = nullptr;
}
}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiPublicMember *uipb)
-{
- QQmlJSImportVisitor::visit(uipb);
- if (uipb->type == QQmlJS::AST::UiPublicMember::Property && uipb->memberType != nullptr
- && !uipb->memberType->name.isEmpty() && uipb->memberType->name != QLatin1String("alias")) {
- const auto name = uipb->memberType->name.toString();
- if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].isNull()) {
- if (m_importTypeLocationMap.contains(name))
- m_usedTypes.insert(name);
- } else {
- m_logger.log(name + QStringLiteral(" was not found. Did you add all import paths?"),
- Log_Import);
- }
- }
-
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::StringLiteral *sl)
-{
- const QString s = m_code.mid(sl->literalToken.begin(), sl->literalToken.length);
-
- if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
- || s.contains(QChar(0x2029u))) {
- m_logger.log(QStringLiteral("String contains unescaped line terminator which is "
- "deprecated. Use a template "
- "literal instead."),
- Log_MultilineString, sl->literalToken);
- }
-
- return true;
-}
diff --git a/tools/qmllint/findwarnings.h b/tools/qmllint/findwarnings.h
index e2538fe5b8..b558bcb2c2 100644
--- a/tools/qmllint/findwarnings.h
+++ b/tools/qmllint/findwarnings.h
@@ -64,68 +64,26 @@ public:
bool check();
private:
- QHash<QQmlJS::SourceLocation, SignalHandler> m_signalHandlers;
- QQmlJS::SourceLocation m_pendingSingalHandler;
-
MemberAccessChains m_memberAccessChains;
QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr;
- QString m_code;
- QString m_rootId;
- QString m_filePath;
- QSet<QString> m_unknownImports;
-
- struct OutstandingConnection
- {
- QString targetName;
- QQmlJSScope::Ptr scope;
- QQmlJS::AST::UiObjectDefinition *uiod;
- };
-
- QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered
-
- // records of whether a default property has object assigned to it. for
- // correctness, the scope that defines the default property acts as a key
- QHash<const QQmlJSScope *, bool> m_scopeHasDefaultPropertyAssignment;
void parseComments(const QList<QQmlJS::SourceLocation> &comments);
- void checkInheritanceCycle(QQmlJSScope::ConstPtr scope);
- void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope);
- void flushPendingSignalParameters();
- void checkDefaultProperty(const QQmlJSScope::ConstPtr &scope);
-
- void throwRecursionDepthError() override;
-
// work around compiler error in clang11
using QQmlJSImportVisitor::visit;
using QQmlJSImportVisitor::endVisit;
- // start block/scope handling
- bool visit(QQmlJS::AST::ExpressionStatement *ast) override;
- void endVisit(QQmlJS::AST::ExpressionStatement *ast) override;
- bool visit(QQmlJS::AST::Block *ast) override;
- bool visit(QQmlJS::AST::WithStatement *withStatement) override;
-
- /* --- end block handling --- */
-
- bool visit(QQmlJS::AST::UiObjectBinding *uiob) override;
bool visit(QQmlJS::AST::UiObjectDefinition *uiod) override;
- void endVisit(QQmlJS::AST::UiObjectDefinition *) override;
- bool visit(QQmlJS::AST::UiScriptBinding *uisb) override;
- bool visit(QQmlJS::AST::UiPublicMember *uipb) override;
+ void endVisit(QQmlJS::AST::UiObjectDefinition *uiod) override;
// expression handling
bool visit(QQmlJS::AST::IdentifierExpression *idexp) override;
bool visit(QQmlJS::AST::PatternElement *) override;
- bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override;
void endVisit(QQmlJS::AST::FieldMemberExpression *) override;
- bool visit(QQmlJS::AST::BinaryExpression *) override;
void endVisit(QQmlJS::AST::BinaryExpression *) override;
-
- bool visit(QQmlJS::AST::StringLiteral *) override;
};
#endif // FINDUNQUALIFIED_H