diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 371b3e03f5..7f49218678 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -814,7 +814,7 @@ void QQmlJSImportVisitor::checkRequiredProperties() ? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope) : u"here"_s; - std::optional suggestion; + std::optional suggestion; QString message = QStringLiteral( @@ -824,15 +824,15 @@ void QQmlJSImportVisitor::checkRequiredProperties() if (requiredScope != scope) { if (!prevRequiredScope.isNull()) { auto sourceScope = prevRequiredScope->baseType(); - suggestion = FixSuggestion { - { { u"%1:%2:%3: Property marked as required in %4"_s - .arg(sourceScope->filePath()) - .arg(sourceScope->sourceLocation().startLine) - .arg(sourceScope->sourceLocation().startColumn) - .arg(requiredScopeName), - sourceScope->sourceLocation(), QString(), - sourceScope->filePath() } } + suggestion = QQmlJSFixSuggestion { + "%1:%2:%3: Property marked as required in %4"_L1 + .arg(sourceScope->filePath()) + .arg(sourceScope->sourceLocation().startLine) + .arg(sourceScope->sourceLocation().startColumn) + .arg(requiredScopeName), + sourceScope->sourceLocation() }; + suggestion->setFilename(sourceScope->filePath()); } else { message += QStringLiteral(" (marked as required by %1)") .arg(requiredScopeName); @@ -863,7 +863,7 @@ void QQmlJSImportVisitor::processPropertyBindings() continue; // TODO: Can this be in a better suited category? - std::optional fixSuggestion; + std::optional fixSuggestion; for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull(); baseScope = baseScope->baseType()) { @@ -945,7 +945,7 @@ void QQmlJSImportVisitor::checkSignal( } if (!signalMethod.has_value()) { // haven't found anything - std::optional fix; + std::optional fix; // There is a small chance of suggesting this fix for things that are not actually // QtQml/Connections elements, but rather some other thing that is also called @@ -957,15 +957,13 @@ void QQmlJSImportVisitor::checkSignal( const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end()) - location.offset; - fix = FixSuggestion { { FixSuggestion::Fix { - QStringLiteral("Implicitly defining %1 as signal handler in " - "Connections is deprecated. Create a function " - "instead") - .arg(handlerName), + fix = QQmlJSFixSuggestion { + "Implicitly defining %1 as signal handler in Connections is deprecated. " + "Create a function instead"_L1.arg(handlerName), QQmlJS::SourceLocation(location.offset, newLength, location.startLine, location.startColumn), - QStringLiteral("function %1(%2) { ... }") - .arg(handlerName, handlerParameters.join(u", ")) } } }; + "function %1(%2) { ... }"_L1.arg(handlerName, handlerParameters.join(u", ")) + }; } m_logger->log(QStringLiteral("no matching signal found for handler \"%1\"") @@ -1366,9 +1364,12 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl) templateString += c; } - const FixSuggestion suggestion = { { { u"Use a template literal instead"_s, - sl->literalToken, u"`" % templateString % u"`", - QString(), false } } }; + QQmlJSFixSuggestion suggestion = { + "Use a template literal instead"_L1, + sl->literalToken, + u"`" % templateString % u"`" + }; + suggestion.setAutoApplicable(); m_logger->log(QStringLiteral("String contains unescaped line terminator which is " "deprecated."), qmlMultilineStrings, sl->literalToken, true, true, suggestion); diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp index 9d3cad6504..88a29dcecf 100644 --- a/src/qmlcompiler/qqmljslinter.cpp +++ b/src/qmlcompiler/qqmljslinter.cpp @@ -323,7 +323,7 @@ void QQmlJSLinter::parseComments(QQmlJSLogger *logger, } static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message, - QAnyStringView id, const std::optional &suggestion = {}) + QAnyStringView id, const std::optional &suggestion = {}) { QJsonObject jsonMessage; @@ -362,19 +362,35 @@ static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage jsonMessage[u"message"_s] = message.message; QJsonArray suggestions; + const auto convertLocation = [](const QQmlJS::SourceLocation &source, QJsonObject *target) { + target->insert("line"_L1, int(source.startLine)); + target->insert("column"_L1, int(source.startColumn)); + target->insert("charOffset"_L1, int(source.offset)); + target->insert("length"_L1, int(source.length)); + }; if (suggestion.has_value()) { - for (const auto &fix : suggestion->fixes) { - QJsonObject jsonFix; - jsonFix[u"message"] = fix.message; - jsonFix[u"line"_s] = static_cast(fix.cutLocation.startLine); - jsonFix[u"column"_s] = static_cast(fix.cutLocation.startColumn); - jsonFix[u"charOffset"_s] = static_cast(fix.cutLocation.offset); - jsonFix[u"length"_s] = static_cast(fix.cutLocation.length); - jsonFix[u"replacement"_s] = fix.replacementString; - jsonFix[u"isHint"] = fix.isHint; - if (!fix.fileName.isEmpty()) - jsonFix[u"fileName"] = fix.fileName; - suggestions << jsonFix; + QJsonObject jsonFix { + { "message"_L1, suggestion->fixDescription() }, + { "replacement"_L1, suggestion->replacement() }, + { "isHint"_L1, !suggestion->isAutoApplicable() }, + }; + convertLocation(suggestion->location(), &jsonFix); + const QString filename = suggestion->filename(); + if (!filename.isEmpty()) + jsonFix.insert("fileName"_L1, filename); + suggestions << jsonFix; + + const QString hint = suggestion->hint(); + if (!hint.isEmpty()) { + // We need to keep compatibility with the JSON format. + // Therefore the overly verbose encoding of the hint. + QJsonObject jsonHint { + { "message"_L1, hint }, + { "replacement"_L1, QString() }, + { "isHint"_L1, true } + }; + convertLocation(QQmlJS::SourceLocation(), &jsonHint); + suggestions << jsonHint; } } jsonMessage[u"suggestions"] = suggestions; @@ -778,7 +794,7 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent QString code = m_fileContents; - QList fixesToApply; + QList fixesToApply; QFileInfo info(m_logger->fileName()); const QString currentFileAbsolutePath = info.absoluteFilePath(); @@ -792,34 +808,30 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent for (const auto &messages : { m_logger->infos(), m_logger->warnings(), m_logger->errors() }) for (const Message &msg : messages) { - if (!msg.fixSuggestion.has_value()) + if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable()) continue; - for (const auto &fix : msg.fixSuggestion->fixes) { - if (fix.isHint) - continue; - - // Ignore fix suggestions for other files - if (!fix.fileName.isEmpty() - && QFileInfo(fix.fileName).absoluteFilePath() != currentFileAbsolutePath) { - continue; - } - - fixesToApply << fix; + // Ignore fix suggestions for other files + const QString filename = msg.fixSuggestion->filename(); + if (!filename.isEmpty() + && QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) { + continue; } + + fixesToApply << msg.fixSuggestion.value(); } if (fixesToApply.isEmpty()) return NothingToFix; std::sort(fixesToApply.begin(), fixesToApply.end(), - [](FixSuggestion::Fix &a, FixSuggestion::Fix &b) { - return a.cutLocation.offset < b.cutLocation.offset; + [](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) { + return a.location().offset < b.location().offset; }); for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) { - QQmlJS::SourceLocation srcLocA = it->cutLocation; - QQmlJS::SourceLocation srcLocB = (it + 1)->cutLocation; + const QQmlJS::SourceLocation srcLocA = it->location(); + const QQmlJS::SourceLocation srcLocB = (it + 1)->location(); if (srcLocA.offset + srcLocA.length > srcLocB.offset) { if (!silent) qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug " @@ -831,12 +843,14 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent int offsetChange = 0; for (const auto &fix : fixesToApply) { - qsizetype cutLocation = fix.cutLocation.offset + offsetChange; - QString before = code.left(cutLocation); - QString after = code.mid(cutLocation + fix.cutLocation.length); + const QQmlJS::SourceLocation fixLocation = fix.location(); + qsizetype cutLocation = fixLocation.offset + offsetChange; + const QString before = code.left(cutLocation); + const QString after = code.mid(cutLocation + fixLocation.length); - code = before + fix.replacementString + after; - offsetChange += fix.replacementString.size() - fix.cutLocation.length; + const QString replacement = fix.replacement(); + code = before + replacement + after; + offsetChange += replacement.size() - fixLocation.length; } QQmlJS::Engine engine; diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp index 293069a2b6..b51fe5d3ff 100644 --- a/src/qmlcompiler/qqmljslogger.cpp +++ b/src/qmlcompiler/qqmljslogger.cpp @@ -219,7 +219,7 @@ static bool isMsgTypeLess(QtMsgType a, QtMsgType b) void QQmlJSLogger::log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext, - bool showFileName, const std::optional &suggestion, + bool showFileName, const std::optional &suggestion, const QString overrideFileName) { Q_ASSERT(m_categoryLevels.contains(id.name().toString())); @@ -324,59 +324,58 @@ void QQmlJSLogger::printContext(const QString &overrideFileName, + QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n')); } -void QQmlJSLogger::printFix(const FixSuggestion &fix) +void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem) { const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath(); QString code = m_code; QString currentFile; - for (const auto &fixItem : fix.fixes) { - m_output.writePrefixedMessage(fixItem.message, QtInfoMsg); + m_output.writePrefixedMessage(fixItem.fixDescription(), QtInfoMsg); - if (!fixItem.cutLocation.isValid()) - continue; + if (!fixItem.location().isValid()) + return; - if (fixItem.fileName == currentFile) { - // Nothing to do in this case, we've already read the code - } else if (fixItem.fileName.isEmpty() || fixItem.fileName == currentFileAbsPath) { - code = m_code; - } else { - QFile file(fixItem.fileName); - const bool success = file.open(QFile::ReadOnly); - Q_ASSERT(success); - code = QString::fromUtf8(file.readAll()); - currentFile = fixItem.fileName; - } - - IssueLocationWithContext issueLocationWithContext { code, fixItem.cutLocation }; - - if (const QStringView beforeText = issueLocationWithContext.beforeText(); - !beforeText.isEmpty()) { - m_output.write(beforeText); - } - - // The replacement string can be empty if we're only pointing something out to the user - QStringView replacementString = fixItem.replacementString.isEmpty() - ? issueLocationWithContext.issueText() - : fixItem.replacementString; - - // But if there's nothing to change it has to be a hint - if (fixItem.replacementString.isEmpty()) - Q_ASSERT(fixItem.isHint); - - m_output.write(replacementString, QtDebugMsg); - m_output.write(issueLocationWithContext.afterText().toString() + u'\n'); - - int tabCount = issueLocationWithContext.beforeText().count(u'\t'); - - // Do not draw location indicator for multiline replacement strings - if (replacementString.contains(u'\n')) - continue; - - m_output.write(u" "_s.repeated( - issueLocationWithContext.beforeText().size() - tabCount) - + u"\t"_s.repeated(tabCount) - + u"^"_s.repeated(fixItem.replacementString.size()) + u'\n'); + const QString filename = fixItem.filename(); + if (filename == currentFile) { + // Nothing to do in this case, we've already read the code + } else if (filename.isEmpty() || filename == currentFileAbsPath) { + code = m_code; + } else { + QFile file(filename); + const bool success = file.open(QFile::ReadOnly); + Q_ASSERT(success); + code = QString::fromUtf8(file.readAll()); + currentFile = filename; } + + IssueLocationWithContext issueLocationWithContext { code, fixItem.location() }; + + if (const QStringView beforeText = issueLocationWithContext.beforeText(); + !beforeText.isEmpty()) { + m_output.write(beforeText); + } + + // The replacement string can be empty if we're only pointing something out to the user + const QString replacement = fixItem.replacement(); + QStringView replacementString = replacement.isEmpty() + ? issueLocationWithContext.issueText() + : replacement; + + // But if there's nothing to change it cannot be auto-applied + Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable()); + + m_output.write(replacementString, QtDebugMsg); + m_output.write(issueLocationWithContext.afterText().toString() + u'\n'); + + int tabCount = issueLocationWithContext.beforeText().count(u'\t'); + + // Do not draw location indicator for multiline replacement strings + if (replacementString.contains(u'\n')) + return; + + m_output.write(u" "_s.repeated( + issueLocationWithContext.beforeText().size() - tabCount) + + u"\t"_s.repeated(tabCount) + + u"^"_s.repeated(replacement.size()) + u'\n'); } QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmljslogger_p.h b/src/qmlcompiler/qqmljslogger_p.h index ca76bb5f37..dd0b959303 100644 --- a/src/qmlcompiler/qqmljslogger_p.h +++ b/src/qmlcompiler/qqmljslogger_p.h @@ -70,19 +70,37 @@ private: QStringView m_afterText; }; -struct Q_QMLCOMPILER_PRIVATE_EXPORT FixSuggestion +class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSFixSuggestion { - struct Fix - { - QString message; - QQmlJS::SourceLocation cutLocation = QQmlJS::SourceLocation(); - QString replacementString = QString(); - QString fileName = QString(); - // A Fix is a hint if it can not be automatically applied to fix an issue or only points out - // its origin - bool isHint = true; - }; - QList fixes; +public: + QQmlJSFixSuggestion() = default; + QQmlJSFixSuggestion(const QString &fixDescription, const QQmlJS::SourceLocation &location, + const QString &replacement = QString()) + : m_location(location) + , m_fixDescription(fixDescription) + , m_replacement(replacement) + {} + + QString fixDescription() const { return m_fixDescription; } + QQmlJS::SourceLocation location() const { return m_location; } + QString replacement() const { return m_replacement; } + + void setFilename(const QString &filename) { m_filename = filename; } + QString filename() const { return m_filename; } + + void setHint(const QString &hint) { m_hint = hint; } + QString hint() const { return m_hint; } + + void setAutoApplicable(bool autoApply = true) { m_autoApplicable = autoApply; } + bool isAutoApplicable() const { return m_autoApplicable; } + +private: + QQmlJS::SourceLocation m_location; + QString m_fixDescription; + QString m_replacement; + QString m_filename; + QString m_hint; + bool m_autoApplicable = false; }; class Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId @@ -137,7 +155,7 @@ struct Message : public QQmlJS::DiagnosticMessage // This doesn't need to be an owning-reference since the string is expected to outlive any // Message object by virtue of coming from a LoggerWarningId. QAnyStringView id; - std::optional fixSuggestion; + std::optional fixSuggestion; }; class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLogger @@ -253,7 +271,7 @@ public: */ void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation, bool showContext = true, bool showFileName = true, - const std::optional &suggestion = {}, + const std::optional &suggestion = {}, const QString overrideFileName = QString()) { log(message, id, srcLocation, m_categoryLevels[id.name().toString()], showContext, @@ -281,11 +299,11 @@ private: QMap m_categories; void printContext(const QString &overrideFileName, const QQmlJS::SourceLocation &location); - void printFix(const FixSuggestion &fix); + void printFix(const QQmlJSFixSuggestion &fix); void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext, bool showFileName, - const std::optional &suggestion, const QString overrideFileName); + const std::optional &suggestion, const QString overrideFileName); QString m_fileName; QString m_code; diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 8d99b695a8..6853fb64d4 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -292,7 +292,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM return; } - std::optional suggestion; + std::optional suggestion; auto childScopes = m_function->qmlScope->childScopes(); for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) { @@ -307,9 +307,6 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM const auto jsId = scope->childScopes().first()->findJSIdentifier(name); if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) { - - suggestion = FixSuggestion {}; - const QQmlJSScope::JavaScriptIdentifier id = jsId.value(); QQmlJS::SourceLocation fixLocation = id.location; @@ -328,15 +325,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM fixString += handler.isMultiline ? u") "_s : u") => "_s; - suggestion->fixes << FixSuggestion::Fix { - name - + QString::fromLatin1(" is accessible in this scope because " - "you are handling a signal at %1:%2. Use a " - "function instead.\n") - .arg(id.location.startLine) - .arg(id.location.startColumn), - fixLocation, fixString, QString(), false + suggestion = QQmlJSFixSuggestion { + name + u" is accessible in this scope because you are handling a signal" + " at %1:%2. Use a function instead.\n"_s + .arg(id.location.startLine) + .arg(id.location.startColumn), + fixLocation, + fixString }; + suggestion->setAutoApplicable(); } break; } @@ -354,11 +351,10 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM if (!it->hasObject()) continue; if (it->objectType() == m_function->qmlScope) { - suggestion = FixSuggestion {}; - - suggestion->fixes << FixSuggestion::Fix { - name + u" is implicitly injected into this delegate. Add a required property instead."_s, - m_function->qmlScope->sourceLocation(), QString(), QString(), true + suggestion = QQmlJSFixSuggestion { + name + " is implicitly injected into this delegate." + " Add a required property instead."_L1, + m_function->qmlScope->sourceLocation() }; }; @@ -373,36 +369,34 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM if (scope->hasProperty(name)) { const QString id = m_function->addressableScopes.id(scope); - suggestion = FixSuggestion {}; - QQmlJS::SourceLocation fixLocation = location; fixLocation.length = 0; - suggestion->fixes << FixSuggestion::Fix { - name + QLatin1String(" is a member of a parent element\n") - + QLatin1String(" You can qualify the access with its id " - "to avoid this warning:\n"), - fixLocation, (id.isEmpty() ? u"."_s : (id + u'.')), QString(), id.isEmpty() + suggestion = QQmlJSFixSuggestion { + name + " is a member of a parent element.\n You can qualify the access " + "with its id to avoid this warning:\n"_L1, + fixLocation, + (id.isEmpty() ? u"."_s : (id + u'.')) }; - if (id.isEmpty()) { - suggestion->fixes << FixSuggestion::Fix { - u"You first have to give the element an id"_s, QQmlJS::SourceLocation {}, {} - }; - } + if (id.isEmpty()) + suggestion->setHint("You first have to give the element an id"_L1); + else + suggestion->setAutoApplicable(); } } } if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound() && m_function->addressableScopes.existsAnywhereInDocument(name)) { - FixSuggestion::Fix bindComponents; const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1; - bindComponents.replacementString = replacement + '\n'_L1; - bindComponents.message = "Set \"%1\" in order to use IDs " - "from outer components in nested components."_L1.arg(replacement); - bindComponents.cutLocation = QQmlJS::SourceLocation(0, 0, 1, 1); - bindComponents.isHint = false; - suggestion = FixSuggestion {{ bindComponents }}; + QQmlJSFixSuggestion bindComponents { + "Set \"%1\" in order to use IDs from outer components in nested components."_L1 + .arg(replacement), + QQmlJS::SourceLocation(0, 0, 1, 1), + replacement + '\n'_L1 + }; + bindComponents.setAutoApplicable(); + suggestion = bindComponents; } if (!suggestion.has_value()) { @@ -758,25 +752,19 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) const QString id = m_function->addressableScopes.id(scope); - FixSuggestion suggestion; - QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation(); fixLocation.length = 0; - suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_s, - fixLocation, - id.isEmpty() ? u"."_s : (id + u'.'), - QString(), id.isEmpty() }; + QQmlJSFixSuggestion suggestion { + "Reference it by id instead:"_L1, + fixLocation, + id.isEmpty() ? u"."_s : (id + u'.') + }; - fixLocation = scope->sourceLocation(); - fixLocation.length = 0; - - if (id.isEmpty()) { - suggestion.fixes - << FixSuggestion::Fix { u"You first have to give the element an id"_s, - QQmlJS::SourceLocation {}, - {} }; - } + if (id.isEmpty()) + suggestion.setHint("You first have to give the element an id"_L1); + else + suggestion.setAutoApplicable(); m_logger->log( u"Using attached type %1 already initialized in a parent scope."_s.arg( @@ -832,7 +820,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) if (propertyResolution(baseType, propertyName) != PropertyMissing) return; - std::optional fixSuggestion; + std::optional fixSuggestion; if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(), getCurrentSourceLocation()); @@ -1107,7 +1095,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar if (isRestricted(propertyName)) return; - std::optional fixSuggestion; + std::optional fixSuggestion; const auto baseType = m_typeResolver->containedType(callBase); diff --git a/src/qmlcompiler/qqmljsutils.cpp b/src/qmlcompiler/qqmljsutils.cpp index 410f6b0d14..16b58ee1f4 100644 --- a/src/qmlcompiler/qqmljsutils.cpp +++ b/src/qmlcompiler/qqmljsutils.cpp @@ -97,7 +97,7 @@ QQmlJSUtils::ResolvedAlias QQmlJSUtils::resolveAlias(const QQmlJSScopesById &idS property, owner, visitor); } -std::optional QQmlJSUtils::didYouMean(const QString &userInput, +std::optional QQmlJSUtils::didYouMean(const QString &userInput, QStringList candidates, QQmlJS::SourceLocation location) { @@ -144,10 +144,12 @@ std::optional QQmlJSUtils::didYouMean(const QString &userInput, } if (shortestDistance - < std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) { - return FixSuggestion { { FixSuggestion::Fix { - u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord), location, - shortestDistanceWord } } }; + < std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) { + return QQmlJSFixSuggestion { + u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord), + location, + shortestDistanceWord + }; } else { return {}; } diff --git a/src/qmlcompiler/qqmljsutils_p.h b/src/qmlcompiler/qqmljsutils_p.h index 15425d0cab..775a5292d1 100644 --- a/src/qmlcompiler/qqmljsutils_p.h +++ b/src/qmlcompiler/qqmljsutils_p.h @@ -359,7 +359,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils act(begin->scope, begin->extensionSpecifier); } - static std::optional didYouMean(const QString &userInput, + static std::optional didYouMean(const QString &userInput, QStringList candidates, QQmlJS::SourceLocation location); diff --git a/src/qmlcompiler/qqmlsa.cpp b/src/qmlcompiler/qqmlsa.cpp index 9dde74ad86..1d2e4219ce 100644 --- a/src/qmlcompiler/qqmlsa.cpp +++ b/src/qmlcompiler/qqmlsa.cpp @@ -31,10 +31,17 @@ GenericPass::GenericPass(PassManager *manager) d->manager = manager; } -void GenericPass::emitWarning(QAnyStringView message, LoggerWarningId id, +void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id, QQmlJS::SourceLocation srcLocation) { - d->manager->m_visitor->logger()->log(message.toString(), id, srcLocation); + d->manager->m_visitor->logger()->log(diagnostic.toString(), id, srcLocation); +} + + +void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id, + QQmlJS::SourceLocation srcLocation, const FixSuggestion &fix) +{ + d->manager->m_visitor->logger()->log(diagnostic.toString(), id, srcLocation, true, true, fix); } Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeName) diff --git a/src/qmlcompiler/qqmlsa_p.h b/src/qmlcompiler/qqmlsa_p.h index e43ba9ed4e..21240255ce 100644 --- a/src/qmlcompiler/qqmlsa_p.h +++ b/src/qmlcompiler/qqmlsa_p.h @@ -35,6 +35,7 @@ namespace QQmlSA { // ### FIXME: Replace with a proper PIMPL'd type using Element = QQmlJSScope::ConstPtr; +using FixSuggestion = QQmlJSFixSuggestion; class GenericPassPrivate; class PassManager; @@ -46,8 +47,10 @@ public: GenericPass(PassManager *manager); virtual ~GenericPass(); - void emitWarning(QAnyStringView message, LoggerWarningId id, + void emitWarning(QAnyStringView diagnostic, LoggerWarningId id, QQmlJS::SourceLocation srcLocation = QQmlJS::SourceLocation()); + void emitWarning(QAnyStringView diagnostic, LoggerWarningId id, + QQmlJS::SourceLocation srcLocation, const FixSuggestion &fix); Element resolveType(QAnyStringView moduleName, QAnyStringView typeName); // #### TODO: revisions Element resolveLiteralType(const QQmlJSMetaPropertyBinding &binding); Element resolveId(QAnyStringView id, const Element &context); diff --git a/src/qmlls/qqmllintsuggestions.cpp b/src/qmlls/qqmllintsuggestions.cpp index 9c7321e6b3..ec97a94ea7 100644 --- a/src/qmlls/qqmllintsuggestions.cpp +++ b/src/qmlls/qqmllintsuggestions.cpp @@ -198,28 +198,26 @@ void QmlLintSuggestions::diagnose(const QByteArray &url) // We need to interject the information about where the fix suggestions end // here since we don't have access to the textDocument to calculate it later. QJsonArray fixedSuggestions; - for (const FixSuggestion::Fix &fix : suggestion->fixes) { - QQmlJS::SourceLocation cut = fix.cutLocation; + const QQmlJS::SourceLocation cut = suggestion->location(); - int line = cut.isValid() ? cut.startLine - 1 : 0; - int column = cut.isValid() ? cut.startColumn - 1 : 0; + const int line = cut.isValid() ? cut.startLine - 1 : 0; + const int column = cut.isValid() ? cut.startColumn - 1 : 0; - QJsonObject object; - object[u"lspBeginLine"] = line; - object[u"lspBeginCharacter"] = column; + QJsonObject object; + object.insert("lspBeginLine"_L1, line); + object.insert("lspBeginCharacter"_L1, column); - Position end = { line, column }; + Position end = { line, column }; - addLength(end, srcLoc.isValid() ? cut.offset : 0, - srcLoc.isValid() ? cut.length : 0); - object[u"lspEndLine"] = end.line; - object[u"lspEndCharacter"] = end.character; + addLength(end, srcLoc.isValid() ? cut.offset : 0, + srcLoc.isValid() ? cut.length : 0); + object.insert("lspEndLine"_L1, end.line); + object.insert("lspEndCharacter"_L1, end.character); - object[u"message"] = fix.message; - object[u"replacement"] = fix.replacementString; + object.insert("message"_L1, suggestion->fixDescription()); + object.insert("replacement"_L1, suggestion->replacement()); - fixedSuggestions << object; - } + fixedSuggestions << object; QJsonObject data; data[u"suggestions"] = fixedSuggestions;