aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2022-01-11 18:03:25 +0100
committerMaximilian Goldstein <max.goldstein@qt.io>2022-01-17 12:36:04 +0100
commit2ffed97aac48e27b01ed29813e8e29b054f99254 (patch)
tree582ac11443caa34549defefccb4556b4e61990cb /src/qmlcompiler
parentf19582f630b7af1498cf4d83c2ecb339d51f6ffa (diff)
qmllint: Offer suggestions for typos
Automatically suggests fixes if a user has a typo in the naming of a component, property or binding. Also now warns about calling undefined functions. [ChangeLog][qmllint][New Feature] qmllint will now automatically suggest fixes if it thinks a component, property or binding was not found due to a typo. Fixes: QTBUG-97693 Change-Id: Ia66c1ba84e187a2eb31bbdf33ca30d7e5141bea9 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qmlcompiler')
-rw-r--r--src/qmlcompiler/CMakeLists.txt2
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp18
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp51
-rw-r--r--src/qmlcompiler/qqmljsutils.cpp76
-rw-r--r--src/qmlcompiler/qqmljsutils_p.h6
5 files changed, 145 insertions, 8 deletions
diff --git a/src/qmlcompiler/CMakeLists.txt b/src/qmlcompiler/CMakeLists.txt
index 41b0fffc6a..110c625b00 100644
--- a/src/qmlcompiler/CMakeLists.txt
+++ b/src/qmlcompiler/CMakeLists.txt
@@ -30,7 +30,7 @@ qt_internal_add_module(QmlCompilerPrivate
qqmljstypereader.cpp qqmljstypereader_p.h
qqmljstyperesolver.cpp qqmljstyperesolver_p.h
qresourcerelocater.cpp qresourcerelocater_p.h
- qqmljsutils_p.h
+ qqmljsutils_p.h qqmljsutils.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::QmlPrivate
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp
index 61e89c6ca4..1530a6cc89 100644
--- a/src/qmlcompiler/qqmljsimportvisitor.cpp
+++ b/src/qmlcompiler/qqmljsimportvisitor.cpp
@@ -772,11 +772,23 @@ void QQmlJSImportVisitor::processPropertyBindings()
continue;
// TODO: Can this be in a better suited category?
+ std::optional<FixSuggestion> fixSuggestion;
+
+ for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
+ baseScope = baseScope->baseType()) {
+ if (auto suggestion = QQmlJSUtils::didYouMean(
+ name, baseScope->ownProperties().keys(), location);
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ break;
+ }
+ }
+
m_logger->logWarning(
QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" "
"exists in the current element.")
.arg(name),
- Log_Type, location);
+ Log_Type, location, true, true, fixSuggestion);
continue;
}
@@ -985,7 +997,9 @@ void QQmlJSImportVisitor::breakInheritanceCycles(const QQmlJSScope::Ptr &origina
m_logger->logWarning(
scope->baseTypeName()
+ QStringLiteral(" was not found. Did you add all import paths?"),
- Log_Import, scope->sourceLocation());
+ Log_Import, scope->sourceLocation(), true, true,
+ QQmlJSUtils::didYouMean(scope->baseTypeName(), m_rootScopeImports.keys(),
+ scope->sourceLocation()));
}
scope = newScope;
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
index bcdea1d785..54cbf1e02b 100644
--- a/src/qmlcompiler/qqmljstypepropagator.cpp
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -27,6 +27,9 @@
****************************************************************************/
#include "qqmljstypepropagator_p.h"
+
+#include "qqmljsutils_p.h"
+
#include <private/qv4compilerscanfunctions_p.h>
QT_BEGIN_NAMESPACE
@@ -342,6 +345,19 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
}
}
+ if (!suggestion.has_value()) {
+ for (QQmlJSScope::ConstPtr baseScope = m_function->qmlScope; !baseScope.isNull();
+ baseScope = baseScope->baseType()) {
+ if (auto didYouMean = QQmlJSUtils::didYouMean(
+ name, baseScope->ownProperties().keys() + baseScope->ownMethods().keys(),
+ location);
+ didYouMean.has_value()) {
+ suggestion = didYouMean;
+ break;
+ }
+ }
+ }
+
m_logger->logWarning(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location, true,
true, suggestion);
}
@@ -621,9 +637,22 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
return;
}
+ std::optional<FixSuggestion> fixSuggestion;
+
+ for (QQmlJSScope::ConstPtr baseScope = baseType; !baseScope.isNull();
+ baseScope = baseScope->baseType()) {
+ if (auto suggestion =
+ QQmlJSUtils::didYouMean(propertyName, baseScope->ownProperties().keys(),
+ getCurrentSourceLocation());
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ break;
+ }
+ }
+
m_logger->logWarning(
u"Property \"%1\" not found on type \"%2\""_qs.arg(propertyName).arg(typeName),
- Log_Type, getCurrentSourceLocation());
+ Log_Type, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
@@ -768,10 +797,21 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
if (checkRestricted(propertyName))
return;
- m_logger->logWarning(
- u"Property \"%1\" not found on type \"%2\""_qs.arg(
- propertyName, m_typeResolver->containedTypeName(callBase)),
- Log_Type, getCurrentSourceLocation());
+ std::optional<FixSuggestion> fixSuggestion;
+
+ for (QQmlJSScope::ConstPtr baseScope = m_typeResolver->containedType(callBase);
+ !baseScope.isNull(); baseScope = baseScope->baseType()) {
+ if (auto suggestion = QQmlJSUtils::didYouMean(
+ propertyName, baseScope->ownMethods().keys(), getCurrentSourceLocation());
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ break;
+ }
+ }
+
+ m_logger->logWarning(u"Property \"%1\" not found on type \"%2\""_qs.arg(
+ propertyName, m_typeResolver->containedTypeName(callBase)),
+ Log_Type, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
@@ -894,6 +934,7 @@ void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName,
m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType());
setError(u"Cannot find function '%1'"_qs.arg(functionName));
+ handleUnqualifiedAccess(functionName);
}
void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
diff --git a/src/qmlcompiler/qqmljsutils.cpp b/src/qmlcompiler/qqmljsutils.cpp
new file mode 100644
index 0000000000..9feb102298
--- /dev/null
+++ b/src/qmlcompiler/qqmljsutils.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 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 "qqmljsutils_p.h"
+
+#include <algorithm>
+
+std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
+ const QStringList &candidates,
+ QQmlJS::SourceLocation location)
+{
+ QString shortestDistanceWord;
+ int shortestDistance = userInput.length();
+ for (const QString &candidate : candidates) {
+ /*
+ * Calculate the distance between the userInput and candidate using Damerau–Levenshtein
+ * Roughly based on
+ * https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows.
+ */
+ QList<int> v0(candidate.length() + 1);
+ QList<int> v1(candidate.length() + 1);
+
+ std::iota(v0.begin(), v0.end(), 0);
+
+ for (qsizetype i = 0; i < userInput.length(); i++) {
+ v1[0] = i + 1;
+ for (qsizetype j = 0; j < candidate.length(); j++) {
+ int deletionCost = v0[j + 1] + 1;
+ int insertionCost = v1[j] + 1;
+ int substitutionCost = userInput[i] == candidate[j] ? v0[j] : v0[j] + 1;
+ v1[j + 1] = std::min({ deletionCost, insertionCost, substitutionCost });
+ }
+ std::swap(v0, v1);
+ }
+
+ int distance = v0[candidate.length()];
+ if (distance < shortestDistance) {
+ shortestDistanceWord = candidate;
+ shortestDistance = distance;
+ }
+ }
+
+ if (shortestDistance
+ < std::min(std::max(userInput.length() / 2, qsizetype(3)), userInput.length())) {
+ return FixSuggestion { { FixSuggestion::Fix {
+ u"Did you mean \"%1\"?"_qs.arg(shortestDistanceWord), location,
+ shortestDistanceWord } } };
+ } else {
+ return {};
+ }
+}
diff --git a/src/qmlcompiler/qqmljsutils_p.h b/src/qmlcompiler/qqmljsutils_p.h
index 1d9224d9db..e55b7fdf6f 100644
--- a/src/qmlcompiler/qqmljsutils_p.h
+++ b/src/qmlcompiler/qqmljsutils_p.h
@@ -39,6 +39,8 @@
//
// We mean it.
+#include "qqmljslogger_p.h"
+
#include <QtCore/qstring.h>
#include <QtCore/qstringview.h>
#include <QtCore/qstringbuilder.h>
@@ -100,6 +102,10 @@ struct QQmlJSUtils
}
return {};
}
+
+ static std::optional<FixSuggestion> didYouMean(const QString &userInput,
+ const QStringList &candidates,
+ QQmlJS::SourceLocation location);
};
QT_END_NAMESPACE