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:
parent
e3831511de
commit
403f4117e2
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue