qmllint: Uncruftify FixSuggestion

In this form we can expose it in QQmlSA.

Pick-to: 6.5
Task-number: QTBUG-110834
Change-Id: Ieb0cf31b6e86379c0d80f89bc6c63b129f269798
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2023-02-06 12:14:38 +01:00
parent e3831511de
commit 403f4117e2
10 changed files with 227 additions and 197 deletions

View File

@ -814,7 +814,7 @@ void QQmlJSImportVisitor::checkRequiredProperties()
? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope)
: u"here"_s;
std::optional<FixSuggestion> suggestion;
std::optional<QQmlJSFixSuggestion> 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> fixSuggestion;
std::optional<QQmlJSFixSuggestion> 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<FixSuggestion> fix;
std::optional<QQmlJSFixSuggestion> 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);

View File

@ -323,7 +323,7 @@ void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
}
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message,
QAnyStringView id, const std::optional<FixSuggestion> &suggestion = {})
QAnyStringView id, const std::optional<QQmlJSFixSuggestion> &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<int>(fix.cutLocation.startLine);
jsonFix[u"column"_s] = static_cast<int>(fix.cutLocation.startColumn);
jsonFix[u"charOffset"_s] = static_cast<int>(fix.cutLocation.offset);
jsonFix[u"length"_s] = static_cast<int>(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<FixSuggestion::Fix> fixesToApply;
QList<QQmlJSFixSuggestion> 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;

View File

@ -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<FixSuggestion> &suggestion,
bool showFileName, const std::optional<QQmlJSFixSuggestion> &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

View File

@ -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<Fix> 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> fixSuggestion;
std::optional<QQmlJSFixSuggestion> 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<FixSuggestion> &suggestion = {},
const std::optional<QQmlJSFixSuggestion> &suggestion = {},
const QString overrideFileName = QString())
{
log(message, id, srcLocation, m_categoryLevels[id.name().toString()], showContext,
@ -281,11 +299,11 @@ private:
QMap<QString, Category> 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<FixSuggestion> &suggestion, const QString overrideFileName);
const std::optional<QQmlJSFixSuggestion> &suggestion, const QString overrideFileName);
QString m_fileName;
QString m_code;

View File

@ -292,7 +292,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
return;
}
std::optional<FixSuggestion> suggestion;
std::optional<QQmlJSFixSuggestion> 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"<id>."_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"<id>."_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"<id>."_s : (id + u'.'),
QString(), id.isEmpty() };
QQmlJSFixSuggestion suggestion {
"Reference it by id instead:"_L1,
fixLocation,
id.isEmpty() ? u"<id>."_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> fixSuggestion;
std::optional<QQmlJSFixSuggestion> 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> fixSuggestion;
std::optional<QQmlJSFixSuggestion> fixSuggestion;
const auto baseType = m_typeResolver->containedType(callBase);

View File

@ -97,7 +97,7 @@ QQmlJSUtils::ResolvedAlias QQmlJSUtils::resolveAlias(const QQmlJSScopesById &idS
property, owner, visitor);
}
std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
std::optional<QQmlJSFixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
QStringList candidates,
QQmlJS::SourceLocation location)
{
@ -144,10 +144,12 @@ std::optional<FixSuggestion> 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 {};
}

View File

@ -359,7 +359,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
act(begin->scope, begin->extensionSpecifier);
}
static std::optional<FixSuggestion> didYouMean(const QString &userInput,
static std::optional<QQmlJSFixSuggestion> didYouMean(const QString &userInput,
QStringList candidates,
QQmlJS::SourceLocation location);

View File

@ -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)

View File

@ -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);

View File

@ -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;