qmlcompiler: Suggest fix for multiline strings

We can now suggest a template string to use instead that is properly
escaped. Once auto-fixing becomes available we can automatically replace
deprecated multiline strings with the new suggestion.

Task-number: QTBUG-92448
Change-Id: I4e77820b66ae960cde558a62689c5da328b8df5b
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Maximilian Goldstein 2022-02-25 12:15:06 +01:00
parent 7e7582fc70
commit 31abba8cdc
5 changed files with 84 additions and 9 deletions

View File

@ -1112,10 +1112,36 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
|| s.contains(QChar(0x2029u))) {
QString templateString;
bool escaped = false;
const QChar stringQuote = s[0];
for (qsizetype i = 1; i < s.length() - 1; i++) {
const QChar c = s[i];
if (c == u'\\') {
escaped = !escaped;
} else if (escaped) {
// If we encounter an escaped quote, unescape it since we use backticks here
if (c == stringQuote)
templateString.chop(1);
escaped = false;
} else {
if (c == u'`')
templateString += u'\\';
if (c == u'$' && i + 1 < s.length() - 1 && s[i + 1] == u'{')
templateString += u'\\';
}
templateString += c;
}
const FixSuggestion suggestion = { { { u"Use a template literal instead"_qs,
sl->literalToken, u"`" + templateString + u"`" } } };
m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
"deprecated. Use a template "
"literal instead."),
Log_MultilineString, sl->literalToken);
"deprecated."),
Log_MultilineString, sl->literalToken, true, true, suggestion);
}
return true;

View File

@ -241,6 +241,11 @@ void QQmlJSLogger::printFix(const FixSuggestion &fix)
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 (fixItem.replacementString.contains(u'\n'))
continue;
m_output.write(u" "_qs.repeated(
issueLocationWithContext.beforeText().length() - tabCount)
+ u"\t"_qs.repeated(tabCount)

View File

@ -0,0 +1,10 @@
import QtQml
QtObject {
property string quote: "
quote: \" \\\" \\\\\"
ticks: ` \` \\\` \\\`
singleTicks: ' \' \\' \\\'
expression: \${expr} \${expr} \\\${expr} \\\${expr}
"
}

View File

@ -0,0 +1,10 @@
import QtQml
QtObject {
property string quote: '
quote: " \" \\" \\\"
ticks: \` \` \\\` \\\`
singleTicks: \' \\\' \\\\\'
expression: \${expr} \${expr} \\\${expr} \\\${expr}
'
}

View File

@ -659,9 +659,26 @@ void TestQmllint::dirtyQmlCode_data()
<< QString() << false;
QTest::newRow("multilineString")
<< QStringLiteral("multilineString.qml")
<< QStringLiteral("String contains unescaped line terminator which is deprecated. Use "
"a template literal instead.")
<< QStringLiteral("String contains unescaped line terminator which is deprecated.")
<< QString() << QString() << true;
QTest::newRow("multilineStringTortureQuote")
<< QStringLiteral("multilineStringTortureQuote.qml")
<< QStringLiteral("String contains unescaped line terminator which is deprecated.")
<< QString() << QStringLiteral(R"(`
quote: " \\" \\\\"
ticks: \` \` \\\` \\\`
singleTicks: ' \' \\' \\\'
expression: \${expr} \${expr} \\\${expr} \\\${expr}
`)") << true;
QTest::newRow("multilineStringTortureTick")
<< QStringLiteral("multilineStringTortureTick.qml")
<< QStringLiteral("String contains unescaped line terminator which is deprecated.")
<< QString() << QStringLiteral(R"(`
quote: " \" \\" \\\"
ticks: \` \` \\\` \\\`
singleTicks: ' \\' \\\\'
expression: \${expr} \${expr} \\\${expr} \\\${expr}
`)") << true;
QTest::newRow("unresolvedType")
<< QStringLiteral("unresolvedType.qml")
<< QStringLiteral("UnresolvedType was not found. Did you add all import paths?")
@ -1278,10 +1295,17 @@ void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &subs
contains = true;
break;
}
if (searchReplacements == DoReplacementSearch
&& fix[u"replacement"].toString().contains(substring)) {
contains = true;
break;
if (searchReplacements == DoReplacementSearch) {
QString replacement = fix[u"replacement"].toString();
#ifdef Q_OS_WIN
// Replacements can contain native line endings
// but we need them to be uniform in order for them to conform to our test data
replacement = replacement.replace(u"\r\n"_qs, u"\n"_qs);
#endif
if (replacement.contains(substring)) {
contains = true;
break;
}
}
}
}