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)
|
? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope)
|
||||||
: u"here"_s;
|
: u"here"_s;
|
||||||
|
|
||||||
std::optional<FixSuggestion> suggestion;
|
std::optional<QQmlJSFixSuggestion> suggestion;
|
||||||
|
|
||||||
QString message =
|
QString message =
|
||||||
QStringLiteral(
|
QStringLiteral(
|
||||||
|
@ -824,15 +824,15 @@ void QQmlJSImportVisitor::checkRequiredProperties()
|
||||||
if (requiredScope != scope) {
|
if (requiredScope != scope) {
|
||||||
if (!prevRequiredScope.isNull()) {
|
if (!prevRequiredScope.isNull()) {
|
||||||
auto sourceScope = prevRequiredScope->baseType();
|
auto sourceScope = prevRequiredScope->baseType();
|
||||||
suggestion = FixSuggestion {
|
suggestion = QQmlJSFixSuggestion {
|
||||||
{ { u"%1:%2:%3: Property marked as required in %4"_s
|
"%1:%2:%3: Property marked as required in %4"_L1
|
||||||
.arg(sourceScope->filePath())
|
.arg(sourceScope->filePath())
|
||||||
.arg(sourceScope->sourceLocation().startLine)
|
.arg(sourceScope->sourceLocation().startLine)
|
||||||
.arg(sourceScope->sourceLocation().startColumn)
|
.arg(sourceScope->sourceLocation().startColumn)
|
||||||
.arg(requiredScopeName),
|
.arg(requiredScopeName),
|
||||||
sourceScope->sourceLocation(), QString(),
|
sourceScope->sourceLocation()
|
||||||
sourceScope->filePath() } }
|
|
||||||
};
|
};
|
||||||
|
suggestion->setFilename(sourceScope->filePath());
|
||||||
} else {
|
} else {
|
||||||
message += QStringLiteral(" (marked as required by %1)")
|
message += QStringLiteral(" (marked as required by %1)")
|
||||||
.arg(requiredScopeName);
|
.arg(requiredScopeName);
|
||||||
|
@ -863,7 +863,7 @@ void QQmlJSImportVisitor::processPropertyBindings()
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO: Can this be in a better suited category?
|
// TODO: Can this be in a better suited category?
|
||||||
std::optional<FixSuggestion> fixSuggestion;
|
std::optional<QQmlJSFixSuggestion> fixSuggestion;
|
||||||
|
|
||||||
for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
|
for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
|
||||||
baseScope = baseScope->baseType()) {
|
baseScope = baseScope->baseType()) {
|
||||||
|
@ -945,7 +945,7 @@ void QQmlJSImportVisitor::checkSignal(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!signalMethod.has_value()) { // haven't found anything
|
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
|
// 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
|
// 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())
|
const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end())
|
||||||
- location.offset;
|
- location.offset;
|
||||||
|
|
||||||
fix = FixSuggestion { { FixSuggestion::Fix {
|
fix = QQmlJSFixSuggestion {
|
||||||
QStringLiteral("Implicitly defining %1 as signal handler in "
|
"Implicitly defining %1 as signal handler in Connections is deprecated. "
|
||||||
"Connections is deprecated. Create a function "
|
"Create a function instead"_L1.arg(handlerName),
|
||||||
"instead")
|
|
||||||
.arg(handlerName),
|
|
||||||
QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
|
QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
|
||||||
location.startColumn),
|
location.startColumn),
|
||||||
QStringLiteral("function %1(%2) { ... }")
|
"function %1(%2) { ... }"_L1.arg(handlerName, handlerParameters.join(u", "))
|
||||||
.arg(handlerName, handlerParameters.join(u", ")) } } };
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
m_logger->log(QStringLiteral("no matching signal found for handler \"%1\"")
|
m_logger->log(QStringLiteral("no matching signal found for handler \"%1\"")
|
||||||
|
@ -1366,9 +1364,12 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
|
||||||
templateString += c;
|
templateString += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FixSuggestion suggestion = { { { u"Use a template literal instead"_s,
|
QQmlJSFixSuggestion suggestion = {
|
||||||
sl->literalToken, u"`" % templateString % u"`",
|
"Use a template literal instead"_L1,
|
||||||
QString(), false } } };
|
sl->literalToken,
|
||||||
|
u"`" % templateString % u"`"
|
||||||
|
};
|
||||||
|
suggestion.setAutoApplicable();
|
||||||
m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
|
m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
|
||||||
"deprecated."),
|
"deprecated."),
|
||||||
qmlMultilineStrings, sl->literalToken, true, true, suggestion);
|
qmlMultilineStrings, sl->literalToken, true, true, suggestion);
|
||||||
|
|
|
@ -323,7 +323,7 @@ void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message,
|
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;
|
QJsonObject jsonMessage;
|
||||||
|
|
||||||
|
@ -362,19 +362,35 @@ static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage
|
||||||
jsonMessage[u"message"_s] = message.message;
|
jsonMessage[u"message"_s] = message.message;
|
||||||
|
|
||||||
QJsonArray suggestions;
|
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()) {
|
if (suggestion.has_value()) {
|
||||||
for (const auto &fix : suggestion->fixes) {
|
QJsonObject jsonFix {
|
||||||
QJsonObject jsonFix;
|
{ "message"_L1, suggestion->fixDescription() },
|
||||||
jsonFix[u"message"] = fix.message;
|
{ "replacement"_L1, suggestion->replacement() },
|
||||||
jsonFix[u"line"_s] = static_cast<int>(fix.cutLocation.startLine);
|
{ "isHint"_L1, !suggestion->isAutoApplicable() },
|
||||||
jsonFix[u"column"_s] = static_cast<int>(fix.cutLocation.startColumn);
|
};
|
||||||
jsonFix[u"charOffset"_s] = static_cast<int>(fix.cutLocation.offset);
|
convertLocation(suggestion->location(), &jsonFix);
|
||||||
jsonFix[u"length"_s] = static_cast<int>(fix.cutLocation.length);
|
const QString filename = suggestion->filename();
|
||||||
jsonFix[u"replacement"_s] = fix.replacementString;
|
if (!filename.isEmpty())
|
||||||
jsonFix[u"isHint"] = fix.isHint;
|
jsonFix.insert("fileName"_L1, filename);
|
||||||
if (!fix.fileName.isEmpty())
|
suggestions << jsonFix;
|
||||||
jsonFix[u"fileName"] = fix.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;
|
jsonMessage[u"suggestions"] = suggestions;
|
||||||
|
@ -778,7 +794,7 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent
|
||||||
|
|
||||||
QString code = m_fileContents;
|
QString code = m_fileContents;
|
||||||
|
|
||||||
QList<FixSuggestion::Fix> fixesToApply;
|
QList<QQmlJSFixSuggestion> fixesToApply;
|
||||||
|
|
||||||
QFileInfo info(m_logger->fileName());
|
QFileInfo info(m_logger->fileName());
|
||||||
const QString currentFileAbsolutePath = info.absoluteFilePath();
|
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 auto &messages : { m_logger->infos(), m_logger->warnings(), m_logger->errors() })
|
||||||
for (const Message &msg : messages) {
|
for (const Message &msg : messages) {
|
||||||
if (!msg.fixSuggestion.has_value())
|
if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const auto &fix : msg.fixSuggestion->fixes) {
|
// Ignore fix suggestions for other files
|
||||||
if (fix.isHint)
|
const QString filename = msg.fixSuggestion->filename();
|
||||||
continue;
|
if (!filename.isEmpty()
|
||||||
|
&& QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
|
||||||
// Ignore fix suggestions for other files
|
continue;
|
||||||
if (!fix.fileName.isEmpty()
|
|
||||||
&& QFileInfo(fix.fileName).absoluteFilePath() != currentFileAbsolutePath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixesToApply << fix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fixesToApply << msg.fixSuggestion.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixesToApply.isEmpty())
|
if (fixesToApply.isEmpty())
|
||||||
return NothingToFix;
|
return NothingToFix;
|
||||||
|
|
||||||
std::sort(fixesToApply.begin(), fixesToApply.end(),
|
std::sort(fixesToApply.begin(), fixesToApply.end(),
|
||||||
[](FixSuggestion::Fix &a, FixSuggestion::Fix &b) {
|
[](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) {
|
||||||
return a.cutLocation.offset < b.cutLocation.offset;
|
return a.location().offset < b.location().offset;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
|
for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
|
||||||
QQmlJS::SourceLocation srcLocA = it->cutLocation;
|
const QQmlJS::SourceLocation srcLocA = it->location();
|
||||||
QQmlJS::SourceLocation srcLocB = (it + 1)->cutLocation;
|
const QQmlJS::SourceLocation srcLocB = (it + 1)->location();
|
||||||
if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
|
if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
|
||||||
if (!silent)
|
if (!silent)
|
||||||
qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug "
|
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;
|
int offsetChange = 0;
|
||||||
|
|
||||||
for (const auto &fix : fixesToApply) {
|
for (const auto &fix : fixesToApply) {
|
||||||
qsizetype cutLocation = fix.cutLocation.offset + offsetChange;
|
const QQmlJS::SourceLocation fixLocation = fix.location();
|
||||||
QString before = code.left(cutLocation);
|
qsizetype cutLocation = fixLocation.offset + offsetChange;
|
||||||
QString after = code.mid(cutLocation + fix.cutLocation.length);
|
const QString before = code.left(cutLocation);
|
||||||
|
const QString after = code.mid(cutLocation + fixLocation.length);
|
||||||
|
|
||||||
code = before + fix.replacementString + after;
|
const QString replacement = fix.replacement();
|
||||||
offsetChange += fix.replacementString.size() - fix.cutLocation.length;
|
code = before + replacement + after;
|
||||||
|
offsetChange += replacement.size() - fixLocation.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlJS::Engine engine;
|
QQmlJS::Engine engine;
|
||||||
|
|
|
@ -219,7 +219,7 @@ static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
|
||||||
|
|
||||||
void QQmlJSLogger::log(const QString &message, LoggerWarningId id,
|
void QQmlJSLogger::log(const QString &message, LoggerWarningId id,
|
||||||
const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
|
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)
|
const QString overrideFileName)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
|
Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
|
||||||
|
@ -324,59 +324,58 @@ void QQmlJSLogger::printContext(const QString &overrideFileName,
|
||||||
+ QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
|
+ 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();
|
const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath();
|
||||||
QString code = m_code;
|
QString code = m_code;
|
||||||
QString currentFile;
|
QString currentFile;
|
||||||
for (const auto &fixItem : fix.fixes) {
|
m_output.writePrefixedMessage(fixItem.fixDescription(), QtInfoMsg);
|
||||||
m_output.writePrefixedMessage(fixItem.message, QtInfoMsg);
|
|
||||||
|
|
||||||
if (!fixItem.cutLocation.isValid())
|
if (!fixItem.location().isValid())
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
if (fixItem.fileName == currentFile) {
|
const QString filename = fixItem.filename();
|
||||||
// Nothing to do in this case, we've already read the code
|
if (filename == currentFile) {
|
||||||
} else if (fixItem.fileName.isEmpty() || fixItem.fileName == currentFileAbsPath) {
|
// Nothing to do in this case, we've already read the code
|
||||||
code = m_code;
|
} else if (filename.isEmpty() || filename == currentFileAbsPath) {
|
||||||
} else {
|
code = m_code;
|
||||||
QFile file(fixItem.fileName);
|
} else {
|
||||||
const bool success = file.open(QFile::ReadOnly);
|
QFile file(filename);
|
||||||
Q_ASSERT(success);
|
const bool success = file.open(QFile::ReadOnly);
|
||||||
code = QString::fromUtf8(file.readAll());
|
Q_ASSERT(success);
|
||||||
currentFile = fixItem.fileName;
|
code = QString::fromUtf8(file.readAll());
|
||||||
}
|
currentFile = 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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -70,19 +70,37 @@ private:
|
||||||
QStringView m_afterText;
|
QStringView m_afterText;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Q_QMLCOMPILER_PRIVATE_EXPORT FixSuggestion
|
class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSFixSuggestion
|
||||||
{
|
{
|
||||||
struct Fix
|
public:
|
||||||
{
|
QQmlJSFixSuggestion() = default;
|
||||||
QString message;
|
QQmlJSFixSuggestion(const QString &fixDescription, const QQmlJS::SourceLocation &location,
|
||||||
QQmlJS::SourceLocation cutLocation = QQmlJS::SourceLocation();
|
const QString &replacement = QString())
|
||||||
QString replacementString = QString();
|
: m_location(location)
|
||||||
QString fileName = QString();
|
, m_fixDescription(fixDescription)
|
||||||
// A Fix is a hint if it can not be automatically applied to fix an issue or only points out
|
, m_replacement(replacement)
|
||||||
// its origin
|
{}
|
||||||
bool isHint = true;
|
|
||||||
};
|
QString fixDescription() const { return m_fixDescription; }
|
||||||
QList<Fix> fixes;
|
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
|
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
|
// 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.
|
// Message object by virtue of coming from a LoggerWarningId.
|
||||||
QAnyStringView id;
|
QAnyStringView id;
|
||||||
std::optional<FixSuggestion> fixSuggestion;
|
std::optional<QQmlJSFixSuggestion> fixSuggestion;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLogger
|
class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLogger
|
||||||
|
@ -253,7 +271,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation,
|
void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation,
|
||||||
bool showContext = true, bool showFileName = true,
|
bool showContext = true, bool showFileName = true,
|
||||||
const std::optional<FixSuggestion> &suggestion = {},
|
const std::optional<QQmlJSFixSuggestion> &suggestion = {},
|
||||||
const QString overrideFileName = QString())
|
const QString overrideFileName = QString())
|
||||||
{
|
{
|
||||||
log(message, id, srcLocation, m_categoryLevels[id.name().toString()], showContext,
|
log(message, id, srcLocation, m_categoryLevels[id.name().toString()], showContext,
|
||||||
|
@ -281,11 +299,11 @@ private:
|
||||||
QMap<QString, Category> m_categories;
|
QMap<QString, Category> m_categories;
|
||||||
|
|
||||||
void printContext(const QString &overrideFileName, const QQmlJS::SourceLocation &location);
|
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,
|
void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation,
|
||||||
QtMsgType type, bool showContext, bool showFileName,
|
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_fileName;
|
||||||
QString m_code;
|
QString m_code;
|
||||||
|
|
|
@ -292,7 +292,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FixSuggestion> suggestion;
|
std::optional<QQmlJSFixSuggestion> suggestion;
|
||||||
|
|
||||||
auto childScopes = m_function->qmlScope->childScopes();
|
auto childScopes = m_function->qmlScope->childScopes();
|
||||||
for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) {
|
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);
|
const auto jsId = scope->childScopes().first()->findJSIdentifier(name);
|
||||||
|
|
||||||
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
|
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
|
||||||
|
|
||||||
suggestion = FixSuggestion {};
|
|
||||||
|
|
||||||
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
|
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
|
||||||
|
|
||||||
QQmlJS::SourceLocation fixLocation = id.location;
|
QQmlJS::SourceLocation fixLocation = id.location;
|
||||||
|
@ -328,15 +325,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
|
||||||
|
|
||||||
fixString += handler.isMultiline ? u") "_s : u") => "_s;
|
fixString += handler.isMultiline ? u") "_s : u") => "_s;
|
||||||
|
|
||||||
suggestion->fixes << FixSuggestion::Fix {
|
suggestion = QQmlJSFixSuggestion {
|
||||||
name
|
name + u" is accessible in this scope because you are handling a signal"
|
||||||
+ QString::fromLatin1(" is accessible in this scope because "
|
" at %1:%2. Use a function instead.\n"_s
|
||||||
"you are handling a signal at %1:%2. Use a "
|
.arg(id.location.startLine)
|
||||||
"function instead.\n")
|
.arg(id.location.startColumn),
|
||||||
.arg(id.location.startLine)
|
fixLocation,
|
||||||
.arg(id.location.startColumn),
|
fixString
|
||||||
fixLocation, fixString, QString(), false
|
|
||||||
};
|
};
|
||||||
|
suggestion->setAutoApplicable();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -354,11 +351,10 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
|
||||||
if (!it->hasObject())
|
if (!it->hasObject())
|
||||||
continue;
|
continue;
|
||||||
if (it->objectType() == m_function->qmlScope) {
|
if (it->objectType() == m_function->qmlScope) {
|
||||||
suggestion = FixSuggestion {};
|
suggestion = QQmlJSFixSuggestion {
|
||||||
|
name + " is implicitly injected into this delegate."
|
||||||
suggestion->fixes << FixSuggestion::Fix {
|
" Add a required property instead."_L1,
|
||||||
name + u" is implicitly injected into this delegate. Add a required property instead."_s,
|
m_function->qmlScope->sourceLocation()
|
||||||
m_function->qmlScope->sourceLocation(), QString(), QString(), true
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -373,36 +369,34 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
|
||||||
if (scope->hasProperty(name)) {
|
if (scope->hasProperty(name)) {
|
||||||
const QString id = m_function->addressableScopes.id(scope);
|
const QString id = m_function->addressableScopes.id(scope);
|
||||||
|
|
||||||
suggestion = FixSuggestion {};
|
|
||||||
|
|
||||||
QQmlJS::SourceLocation fixLocation = location;
|
QQmlJS::SourceLocation fixLocation = location;
|
||||||
fixLocation.length = 0;
|
fixLocation.length = 0;
|
||||||
suggestion->fixes << FixSuggestion::Fix {
|
suggestion = QQmlJSFixSuggestion {
|
||||||
name + QLatin1String(" is a member of a parent element\n")
|
name + " is a member of a parent element.\n You can qualify the access "
|
||||||
+ QLatin1String(" You can qualify the access with its id "
|
"with its id to avoid this warning:\n"_L1,
|
||||||
"to avoid this warning:\n"),
|
fixLocation,
|
||||||
fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.')), QString(), id.isEmpty()
|
(id.isEmpty() ? u"<id>."_s : (id + u'.'))
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id.isEmpty()) {
|
if (id.isEmpty())
|
||||||
suggestion->fixes << FixSuggestion::Fix {
|
suggestion->setHint("You first have to give the element an id"_L1);
|
||||||
u"You first have to give the element an id"_s, QQmlJS::SourceLocation {}, {}
|
else
|
||||||
};
|
suggestion->setAutoApplicable();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
|
if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
|
||||||
&& m_function->addressableScopes.existsAnywhereInDocument(name)) {
|
&& m_function->addressableScopes.existsAnywhereInDocument(name)) {
|
||||||
FixSuggestion::Fix bindComponents;
|
|
||||||
const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
|
const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
|
||||||
bindComponents.replacementString = replacement + '\n'_L1;
|
QQmlJSFixSuggestion bindComponents {
|
||||||
bindComponents.message = "Set \"%1\" in order to use IDs "
|
"Set \"%1\" in order to use IDs from outer components in nested components."_L1
|
||||||
"from outer components in nested components."_L1.arg(replacement);
|
.arg(replacement),
|
||||||
bindComponents.cutLocation = QQmlJS::SourceLocation(0, 0, 1, 1);
|
QQmlJS::SourceLocation(0, 0, 1, 1),
|
||||||
bindComponents.isHint = false;
|
replacement + '\n'_L1
|
||||||
suggestion = FixSuggestion {{ bindComponents }};
|
};
|
||||||
|
bindComponents.setAutoApplicable();
|
||||||
|
suggestion = bindComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suggestion.has_value()) {
|
if (!suggestion.has_value()) {
|
||||||
|
@ -758,25 +752,19 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
|
||||||
|
|
||||||
const QString id = m_function->addressableScopes.id(scope);
|
const QString id = m_function->addressableScopes.id(scope);
|
||||||
|
|
||||||
FixSuggestion suggestion;
|
|
||||||
|
|
||||||
QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation();
|
QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation();
|
||||||
fixLocation.length = 0;
|
fixLocation.length = 0;
|
||||||
|
|
||||||
suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_s,
|
QQmlJSFixSuggestion suggestion {
|
||||||
fixLocation,
|
"Reference it by id instead:"_L1,
|
||||||
id.isEmpty() ? u"<id>."_s : (id + u'.'),
|
fixLocation,
|
||||||
QString(), id.isEmpty() };
|
id.isEmpty() ? u"<id>."_s : (id + u'.')
|
||||||
|
};
|
||||||
|
|
||||||
fixLocation = scope->sourceLocation();
|
if (id.isEmpty())
|
||||||
fixLocation.length = 0;
|
suggestion.setHint("You first have to give the element an id"_L1);
|
||||||
|
else
|
||||||
if (id.isEmpty()) {
|
suggestion.setAutoApplicable();
|
||||||
suggestion.fixes
|
|
||||||
<< FixSuggestion::Fix { u"You first have to give the element an id"_s,
|
|
||||||
QQmlJS::SourceLocation {},
|
|
||||||
{} };
|
|
||||||
}
|
|
||||||
|
|
||||||
m_logger->log(
|
m_logger->log(
|
||||||
u"Using attached type %1 already initialized in a parent scope."_s.arg(
|
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)
|
if (propertyResolution(baseType, propertyName) != PropertyMissing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::optional<FixSuggestion> fixSuggestion;
|
std::optional<QQmlJSFixSuggestion> fixSuggestion;
|
||||||
|
|
||||||
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
|
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
|
||||||
getCurrentSourceLocation());
|
getCurrentSourceLocation());
|
||||||
|
@ -1107,7 +1095,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
|
||||||
if (isRestricted(propertyName))
|
if (isRestricted(propertyName))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::optional<FixSuggestion> fixSuggestion;
|
std::optional<QQmlJSFixSuggestion> fixSuggestion;
|
||||||
|
|
||||||
const auto baseType = m_typeResolver->containedType(callBase);
|
const auto baseType = m_typeResolver->containedType(callBase);
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ QQmlJSUtils::ResolvedAlias QQmlJSUtils::resolveAlias(const QQmlJSScopesById &idS
|
||||||
property, owner, visitor);
|
property, owner, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
|
std::optional<QQmlJSFixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
|
||||||
QStringList candidates,
|
QStringList candidates,
|
||||||
QQmlJS::SourceLocation location)
|
QQmlJS::SourceLocation location)
|
||||||
{
|
{
|
||||||
|
@ -144,10 +144,12 @@ std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shortestDistance
|
if (shortestDistance
|
||||||
< std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) {
|
< std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) {
|
||||||
return FixSuggestion { { FixSuggestion::Fix {
|
return QQmlJSFixSuggestion {
|
||||||
u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord), location,
|
u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord),
|
||||||
shortestDistanceWord } } };
|
location,
|
||||||
|
shortestDistanceWord
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,7 +359,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
|
||||||
act(begin->scope, begin->extensionSpecifier);
|
act(begin->scope, begin->extensionSpecifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<FixSuggestion> didYouMean(const QString &userInput,
|
static std::optional<QQmlJSFixSuggestion> didYouMean(const QString &userInput,
|
||||||
QStringList candidates,
|
QStringList candidates,
|
||||||
QQmlJS::SourceLocation location);
|
QQmlJS::SourceLocation location);
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,17 @@ GenericPass::GenericPass(PassManager *manager)
|
||||||
d->manager = manager;
|
d->manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericPass::emitWarning(QAnyStringView message, LoggerWarningId id,
|
void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
|
||||||
QQmlJS::SourceLocation srcLocation)
|
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)
|
Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeName)
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace QQmlSA {
|
||||||
|
|
||||||
// ### FIXME: Replace with a proper PIMPL'd type
|
// ### FIXME: Replace with a proper PIMPL'd type
|
||||||
using Element = QQmlJSScope::ConstPtr;
|
using Element = QQmlJSScope::ConstPtr;
|
||||||
|
using FixSuggestion = QQmlJSFixSuggestion;
|
||||||
|
|
||||||
class GenericPassPrivate;
|
class GenericPassPrivate;
|
||||||
class PassManager;
|
class PassManager;
|
||||||
|
@ -46,8 +47,10 @@ public:
|
||||||
GenericPass(PassManager *manager);
|
GenericPass(PassManager *manager);
|
||||||
virtual ~GenericPass();
|
virtual ~GenericPass();
|
||||||
|
|
||||||
void emitWarning(QAnyStringView message, LoggerWarningId id,
|
void emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
|
||||||
QQmlJS::SourceLocation srcLocation = QQmlJS::SourceLocation());
|
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 resolveType(QAnyStringView moduleName, QAnyStringView typeName); // #### TODO: revisions
|
||||||
Element resolveLiteralType(const QQmlJSMetaPropertyBinding &binding);
|
Element resolveLiteralType(const QQmlJSMetaPropertyBinding &binding);
|
||||||
Element resolveId(QAnyStringView id, const Element &context);
|
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
|
// 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.
|
// here since we don't have access to the textDocument to calculate it later.
|
||||||
QJsonArray fixedSuggestions;
|
QJsonArray fixedSuggestions;
|
||||||
for (const FixSuggestion::Fix &fix : suggestion->fixes) {
|
const QQmlJS::SourceLocation cut = suggestion->location();
|
||||||
QQmlJS::SourceLocation cut = fix.cutLocation;
|
|
||||||
|
|
||||||
int line = cut.isValid() ? cut.startLine - 1 : 0;
|
const int line = cut.isValid() ? cut.startLine - 1 : 0;
|
||||||
int column = cut.isValid() ? cut.startColumn - 1 : 0;
|
const int column = cut.isValid() ? cut.startColumn - 1 : 0;
|
||||||
|
|
||||||
QJsonObject object;
|
QJsonObject object;
|
||||||
object[u"lspBeginLine"] = line;
|
object.insert("lspBeginLine"_L1, line);
|
||||||
object[u"lspBeginCharacter"] = column;
|
object.insert("lspBeginCharacter"_L1, column);
|
||||||
|
|
||||||
Position end = { line, column };
|
Position end = { line, column };
|
||||||
|
|
||||||
addLength(end, srcLoc.isValid() ? cut.offset : 0,
|
addLength(end, srcLoc.isValid() ? cut.offset : 0,
|
||||||
srcLoc.isValid() ? cut.length : 0);
|
srcLoc.isValid() ? cut.length : 0);
|
||||||
object[u"lspEndLine"] = end.line;
|
object.insert("lspEndLine"_L1, end.line);
|
||||||
object[u"lspEndCharacter"] = end.character;
|
object.insert("lspEndCharacter"_L1, end.character);
|
||||||
|
|
||||||
object[u"message"] = fix.message;
|
object.insert("message"_L1, suggestion->fixDescription());
|
||||||
object[u"replacement"] = fix.replacementString;
|
object.insert("replacement"_L1, suggestion->replacement());
|
||||||
|
|
||||||
fixedSuggestions << object;
|
fixedSuggestions << object;
|
||||||
}
|
|
||||||
QJsonObject data;
|
QJsonObject data;
|
||||||
data[u"suggestions"] = fixedSuggestions;
|
data[u"suggestions"] = fixedSuggestions;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue