CppEditor: Move quickfixes for string literals into dedicated files

Change-Id: I60d9d30981a68a6393ba39f566bd174b0f391793
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-15 17:07:03 +02:00
parent c5325effc8
commit b3e4d552d3
7 changed files with 772 additions and 710 deletions

View File

@@ -96,6 +96,8 @@ add_qtc_plugin(CppEditor
projectinfo.cpp projectinfo.h projectinfo.cpp projectinfo.h
projectpart.cpp projectpart.h projectpart.cpp projectpart.h
quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
quickfixes/convertstringliteral.cpp quickfixes/convertstringliteral.h
quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h
quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h
quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h
@@ -107,7 +109,6 @@ add_qtc_plugin(CppEditor
quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h

View File

@@ -223,6 +223,8 @@ QtcPlugin {
"bringidentifierintoscope.h", "bringidentifierintoscope.h",
"convertqt4connect.cpp", "convertqt4connect.cpp",
"convertqt4connect.h", "convertqt4connect.h",
"convertstringliteral.cpp",
"convertstringliteral.h",
"cppcodegenerationquickfixes.cpp", "cppcodegenerationquickfixes.cpp",
"cppcodegenerationquickfixes.h", "cppcodegenerationquickfixes.h",
"cppinsertvirtualmethods.cpp", "cppinsertvirtualmethods.cpp",

View File

@@ -0,0 +1,758 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "convertstringliteral.h"
#include "../cppeditordocument.h"
#include "../cppeditortr.h"
#include "../cppeditorwidget.h"
#include "../cpprefactoringchanges.h"
#include "cppquickfix.h"
#include <QTextDecoder>
#ifdef WITH_TESTS
#include "cppquickfix_test.h"
#include <QtTest>
#endif
using namespace CPlusPlus;
using namespace Utils;
namespace CppEditor::Internal {
namespace {
enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone };
enum ActionFlags {
EncloseInQLatin1CharAction = 0x1,
EncloseInQLatin1StringAction = 0x2,
EncloseInQStringLiteralAction = 0x4,
EncloseInQByteArrayLiteralAction = 0x8,
EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction
| EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction,
TranslateTrAction = 0x10,
TranslateQCoreApplicationAction = 0x20,
TranslateNoopAction = 0x40,
TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction,
RemoveObjectiveCAction = 0x100,
ConvertEscapeSequencesToCharAction = 0x200,
ConvertEscapeSequencesToStringAction = 0x400,
SingleQuoteAction = 0x800,
DoubleQuoteAction = 0x1000
};
static bool isQtStringLiteral(const QByteArray &id)
{
return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral"
|| id == "QByteArrayLiteral";
}
static bool isQtStringTranslation(const QByteArray &id)
{
return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}
/* Convert single-character string literals into character literals with some
* special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */
static QByteArray stringToCharEscapeSequences(const QByteArray &content)
{
if (content.size() == 1)
return content.at(0) == '\'' ? QByteArray("\\'") : content;
if (content.size() == 2 && content.at(0) == '\\')
return content == "\\\"" ? QByteArray(1, '"') : content;
return QByteArray();
}
/* Convert character literal into a string literal with some special cases
* 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */
static QByteArray charToStringEscapeSequences(const QByteArray &content)
{
if (content.size() == 1)
return content.at(0) == '"' ? QByteArray("\\\"") : content;
if (content.size() == 2)
return content == "\\'" ? QByteArray("'") : content;
return QByteArray();
}
static QString msgQtStringLiteralDescription(const QString &replacement)
{
return Tr::tr("Enclose in %1(...)").arg(replacement);
}
static QString stringLiteralReplacement(unsigned actions)
{
if (actions & EncloseInQLatin1CharAction)
return QLatin1String("QLatin1Char");
if (actions & EncloseInQLatin1StringAction)
return QLatin1String("QLatin1String");
if (actions & EncloseInQStringLiteralAction)
return QLatin1String("QStringLiteral");
if (actions & EncloseInQByteArrayLiteralAction)
return QLatin1String("QByteArrayLiteral");
if (actions & TranslateTrAction)
return QLatin1String("tr");
if (actions & TranslateQCoreApplicationAction)
return QLatin1String("QCoreApplication::translate");
if (actions & TranslateNoopAction)
return QLatin1String("QT_TRANSLATE_NOOP");
return QString();
}
static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path,
const CppRefactoringFilePtr &file, StringLiteralType *type,
QByteArray *enclosingFunction = nullptr,
CallAST **enclosingFunctionCall = nullptr)
{
*type = TypeNone;
if (enclosingFunction)
enclosingFunction->clear();
if (enclosingFunctionCall)
*enclosingFunctionCall = nullptr;
if (path.isEmpty())
return nullptr;
ExpressionAST *literal = path.last()->asExpression();
if (literal) {
if (literal->asStringLiteral()) {
// Check for Objective C string (@"bla")
const QChar firstChar = file->charAt(file->startOf(literal));
*type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString;
} else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) {
// character ('c') constants are numeric.
if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL))
*type = TypeChar;
}
}
if (*type != TypeNone && enclosingFunction && path.size() > 1) {
if (CallAST *call = path.at(path.size() - 2)->asCall()) {
if (call->base_expression) {
if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) {
if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) {
*enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars();
if (enclosingFunctionCall)
*enclosingFunctionCall = call;
}
}
}
}
}
return literal;
}
class EscapeStringLiteralOperation: public CppQuickFixOperation
{
public:
EscapeStringLiteralOperation(const CppQuickFixInterface &interface,
ExpressionAST *literal, bool escape)
: CppQuickFixOperation(interface)
, m_literal(literal)
, m_escape(escape)
{
if (m_escape) {
setDescription(Tr::tr("Escape String Literal as UTF-8"));
} else {
setDescription(Tr::tr("Unescape String Literal as UTF-8"));
}
}
private:
static inline bool isDigit(quint8 ch, int base)
{
if (base == 8)
return ch >= '0' && ch < '8';
if (base == 16)
return isxdigit(ch);
return false;
}
static QByteArrayList escapeString(const QByteArray &contents)
{
QByteArrayList newContents;
QByteArray chunk;
bool wasEscaped = false;
for (const quint8 c : contents) {
const bool needsEscape = !isascii(c) || !isprint(c);
if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) {
newContents << chunk;
chunk.clear();
}
if (needsEscape)
chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0');
else
chunk += c;
wasEscaped = needsEscape;
}
if (!chunk.isEmpty())
newContents << chunk;
return newContents;
}
static QByteArray unescapeString(const QByteArray &contents)
{
QByteArray newContents;
const int len = contents.length();
for (int i = 0; i < len; ++i) {
quint8 c = contents.at(i);
if (c == '\\' && i < len - 1) {
int idx = i + 1;
quint8 ch = contents.at(idx);
int base = 0;
int maxlen = 0;
if (isDigit(ch, 8)) {
base = 8;
maxlen = 3;
} else if ((ch == 'x' || ch == 'X') && idx < len - 1) {
base = 16;
maxlen = 2;
ch = contents.at(++idx);
}
if (base > 0) {
QByteArray buf;
while (isDigit(ch, base) && idx < len && buf.length() < maxlen) {
buf += ch;
++idx;
if (idx == len)
break;
ch = contents.at(idx);
}
if (!buf.isEmpty()) {
bool ok;
uint value = buf.toUInt(&ok, base);
// Don't unescape isascii() && !isprint()
if (ok && (!isascii(value) || isprint(value))) {
newContents += value;
i = idx - 1;
continue;
}
}
}
newContents += c;
c = contents.at(++i);
}
newContents += c;
}
return newContents;
}
// QuickFixOperation interface
public:
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
const int startPos = currentFile->startOf(m_literal);
const int endPos = currentFile->endOf(m_literal);
StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
QTC_ASSERT(stringLiteral, return);
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).
identifier->chars());
QByteArrayList newContents;
if (m_escape)
newContents = escapeString(oldContents);
else
newContents = {unescapeString(oldContents)};
if (newContents.isEmpty()
|| (newContents.size() == 1 && newContents.first() == oldContents)) {
return;
}
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
ChangeSet changes;
bool replace = true;
for (const QByteArray &chunk : std::as_const(newContents)) {
const QString str = decoder->toUnicode(chunk);
const QByteArray utf8buf = str.toUtf8();
if (!utf8codec->canEncode(str) || chunk != utf8buf)
return;
if (replace)
changes.replace(startPos + 1, endPos - 1, str);
else
changes.insert(endPos, "\"" + str + "\"");
replace = false;
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
ExpressionAST *m_literal;
bool m_escape;
};
/// Operation performs the operations of type ActionFlags passed in as actions.
class WrapStringLiteralOp : public CppQuickFixOperation
{
public:
WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority,
unsigned actions, const QString &description, ExpressionAST *literal,
const QString &translationContext = QString())
: CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal),
m_translationContext(translationContext)
{
setDescription(description);
}
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
ChangeSet changes;
const int startPos = currentFile->startOf(m_literal);
const int endPos = currentFile->endOf(m_literal);
// kill leading '@'. No need to adapt endPos, that is done by ChangeSet
if (m_actions & RemoveObjectiveCAction)
changes.remove(startPos, startPos + 1);
// Fix quotes
if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) {
const QString newQuote((m_actions & SingleQuoteAction)
? QLatin1Char('\'') : QLatin1Char('"'));
changes.replace(startPos, startPos + 1, newQuote);
changes.replace(endPos - 1, endPos, newQuote);
}
// Convert single character strings into character constants
if (m_actions & ConvertEscapeSequencesToCharAction) {
StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
QTC_ASSERT(stringLiteral, return ;);
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).identifier->chars());
const QByteArray newContents = stringToCharEscapeSequences(oldContents);
QTC_ASSERT(!newContents.isEmpty(), return ;);
if (oldContents != newContents)
changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
}
// Convert character constants into strings constants
if (m_actions & ConvertEscapeSequencesToStringAction) {
NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical.
QTC_ASSERT(charLiteral, return ;);
const QByteArray oldContents(currentFile->tokenAt(charLiteral->literal_token).identifier->chars());
const QByteArray newContents = charToStringEscapeSequences(oldContents);
QTC_ASSERT(!newContents.isEmpty(), return ;);
if (oldContents != newContents)
changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
}
// Enclose in literal or translation function, macro.
if (m_actions & (EncloseActionMask | TranslationMask)) {
changes.insert(endPos, QString(QLatin1Char(')')));
QString leading = stringLiteralReplacement(m_actions);
leading += QLatin1Char('(');
if (m_actions
& (TranslateQCoreApplicationAction | TranslateNoopAction)) {
leading += QLatin1Char('"');
leading += m_translationContext;
leading += QLatin1String("\", ");
}
changes.insert(startPos, leading);
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
const unsigned m_actions;
ExpressionAST *m_literal;
const QString m_translationContext;
};
class ConvertCStringToNSStringOp: public CppQuickFixOperation
{
public:
ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority,
StringLiteralAST *stringLiteral, CallAST *qlatin1Call)
: CppQuickFixOperation(interface, priority)
, stringLiteral(stringLiteral)
, qlatin1Call(qlatin1Call)
{
setDescription(Tr::tr("Convert to Objective-C String Literal"));
}
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
ChangeSet changes;
if (qlatin1Call) {
changes.replace(currentFile->startOf(qlatin1Call), currentFile->startOf(stringLiteral),
QLatin1String("@"));
changes.remove(currentFile->endOf(stringLiteral), currentFile->endOf(qlatin1Call));
} else {
changes.insert(currentFile->startOf(stringLiteral), QLatin1String("@"));
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
StringLiteralAST *stringLiteral;
CallAST *qlatin1Call;
};
/*!
Replace
"abcd"
QLatin1String("abcd")
QLatin1Literal("abcd")
With
@"abcd"
Activates on: the string literal, if the file type is a Objective-C(++) file.
*/
class ConvertCStringToNSString: public CppQuickFixFactory
{
#ifdef WITH_TESTS
public:
static QObject *createTest();
#endif
private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
{
CppRefactoringFilePtr file = interface.currentFile();
if (!interface.editor()->cppEditorDocument()->isObjCEnabled())
return;
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
CallAST *qlatin1Call;
const QList<AST *> &path = interface.path();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction,
&qlatin1Call);
if (!literal || type != TypeString)
return;
if (!isQtStringLiteral(enclosingFunction))
qlatin1Call = nullptr;
result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(),
qlatin1Call);
}
};
/*!
Replace
"abcd"
With
tr("abcd") or
QCoreApplication::translate("CONTEXT", "abcd") or
QT_TRANSLATE_NOOP("GLOBAL", "abcd")
depending on what is available.
Activates on: the string literal
*/
class TranslateStringLiteral: public CppQuickFixFactory
{
#ifdef WITH_TESTS
public:
static QObject *createTest();
#endif
private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
{
// Initialize
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
const QList<AST *> &path = interface.path();
CppRefactoringFilePtr file = interface.currentFile();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
if (!literal || type != TypeString
|| isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction))
return;
QString trContext;
std::shared_ptr<Control> control = interface.context().bindings()->control();
const Name *trName = control->identifier("tr");
// Check whether we are in a function:
const QString description = Tr::tr("Mark as Translatable");
for (int i = path.size() - 1; i >= 0; --i) {
if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) {
Function *function = definition->symbol;
ClassOrNamespace *b = interface.context().lookupType(function);
if (b) {
// Do we have a tr function?
const QList<LookupItem> items = b->find(trName);
for (const LookupItem &r : items) {
Symbol *s = r.declaration();
if (s->type()->asFunctionType()) {
// no context required for tr
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateTrAction,
description, literal);
return;
}
}
}
// We need to do a QCA::translate, so we need a context.
// Use fully qualified class name:
Overview oo;
const QList<const Name *> names = LookupContext::path(function);
for (const Name *n : names) {
if (!trContext.isEmpty())
trContext.append(QLatin1String("::"));
trContext.append(oo.prettyName(n));
}
// ... or global if none available!
if (trContext.isEmpty())
trContext = QLatin1String("GLOBAL");
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateQCoreApplicationAction,
description, literal, trContext);
return;
}
}
// We need to use Q_TRANSLATE_NOOP
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateNoopAction,
description, literal, trContext);
}
};
/*!
Replace
"abcd" -> QLatin1String("abcd")
@"abcd" -> QLatin1String("abcd") (Objective C)
'a' -> QLatin1Char('a')
'a' -> "a"
"a" -> 'a' or QLatin1Char('a') (Single character string constants)
"\n" -> '\n', QLatin1Char('\n')
Except if they are already enclosed in
QLatin1Char, QT_TRANSLATE_NOOP, tr,
trUtf8, QLatin1Literal, QLatin1String
Activates on: the string or character literal
*/
class WrapStringLiteral: public CppQuickFixFactory
{
#ifdef WITH_TESTS
public:
static QObject *createTest();
#endif
private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
{
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
const QList<AST *> &path = interface.path();
CppRefactoringFilePtr file = interface.currentFile();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
if (!literal || type == TypeNone)
return;
if ((type == TypeChar && enclosingFunction == "QLatin1Char")
|| isQtStringLiteral(enclosingFunction)
|| isQtStringTranslation(enclosingFunction))
return;
const int priority = path.size() - 1; // very high priority
if (type == TypeChar) {
unsigned actions = EncloseInQLatin1CharAction;
QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions));
result << new WrapStringLiteralOp(interface, priority, actions, description, literal);
if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) {
const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars());
if (!charToStringEscapeSequences(contents).isEmpty()) {
actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction;
description = Tr::tr("Convert to String Literal");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
}
}
} else {
const unsigned objectiveCActions = type == TypeObjCString ?
unsigned(RemoveObjectiveCAction) : 0u;
unsigned actions = 0;
if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) {
const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
if (!stringToCharEscapeSequences(contents).isEmpty()) {
actions = EncloseInQLatin1CharAction | SingleQuoteAction
| ConvertEscapeSequencesToCharAction | objectiveCActions;
QString description =
Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
actions &= ~EncloseInQLatin1CharAction;
description = Tr::tr("Convert to Character Literal");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
}
}
actions = EncloseInQLatin1StringAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
actions = EncloseInQStringLiteralAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
actions = EncloseInQByteArrayLiteralAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
}
}
};
/*!
Escapes or unescapes a string literal as UTF-8.
Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
Unescapes octal or hexadecimal escape sequences in a string literal.
String literals are handled as UTF-8 even if file's encoding is not UTF-8.
*/
class EscapeStringLiteral : public CppQuickFixFactory
{
#ifdef WITH_TESTS
public:
static QObject *createTest();
#endif
private:
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override
{
const QList<AST *> &path = interface.path();
if (path.isEmpty())
return;
AST * const lastAst = path.last();
ExpressionAST *literal = lastAst->asStringLiteral();
if (!literal)
return;
StringLiteralAST *stringLiteral = literal->asStringLiteral();
CppRefactoringFilePtr file = interface.currentFile();
const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
bool canEscape = false;
bool canUnescape = false;
for (int i = 0; i < contents.length(); ++i) {
quint8 c = contents.at(i);
if (!isascii(c) || !isprint(c)) {
canEscape = true;
} else if (c == '\\' && i < contents.length() - 1) {
c = contents.at(++i);
if ((c >= '0' && c < '8') || c == 'x' || c == 'X')
canUnescape = true;
}
}
if (canEscape)
result << new EscapeStringLiteralOperation(interface, literal, true);
if (canUnescape)
result << new EscapeStringLiteralOperation(interface, literal, false);
}
};
#ifdef WITH_TESTS
using namespace Tests;
class EscapeStringLiteralTest : public QObject
{
Q_OBJECT
private slots:
void test_data()
{
QTest::addColumn<QByteArray>("original");
QTest::addColumn<QByteArray>("expected");
// Escape String Literal as UTF-8 (no-trigger)
QTest::newRow("EscapeStringLiteral_notrigger")
<< QByteArray("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n")
<< QByteArray();
// Escape String Literal as UTF-8
QTest::newRow("EscapeStringLiteral")
<< QByteArray("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n")
<< QByteArray("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n");
// Unescape String Literal as UTF-8 (from hexdecimal escape sequences)
QTest::newRow("UnescapeStringLiteral_hex")
<< QByteArray("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n")
<< QByteArray("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
// Unescape String Literal as UTF-8 (from octal escape sequences)
QTest::newRow("UnescapeStringLiteral_oct")
<< QByteArray("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n")
<< QByteArray("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
// Unescape String Literal as UTF-8 (triggered but no change)
QTest::newRow("UnescapeStringLiteral_noconv")
<< QByteArray("const char *escaped_ascii = \"@\\x1b\";\n")
<< QByteArray("const char *escaped_ascii = \"\\x1b\";\n");
// Unescape String Literal as UTF-8 (no conversion because of invalid utf-8)
QTest::newRow("UnescapeStringLiteral_invalid")
<< QByteArray("const char *escaped = \"@\\xe3\\x81\";\n")
<< QByteArray("const char *escaped = \"\\xe3\\x81\";\n");
QTest::newRow("escape string literal: simple case")
<< QByteArray(R"(const char *str = @"àxyz";)")
<< QByteArray(R"(const char *str = "\xc3\xa0xyz";)");
QTest::newRow("escape string literal: simple case reverse")
<< QByteArray(R"(const char *str = @"\xc3\xa0xyz";)")
<< QByteArray(R"(const char *str = "àxyz";)");
QTest::newRow("escape string literal: raw string literal")
<< QByteArray(R"x(const char *str = @R"(àxyz)";)x")
<< QByteArray(R"x(const char *str = R"(\xc3\xa0xyz)";)x");
QTest::newRow("escape string literal: splitting required")
<< QByteArray(R"(const char *str = @"àf23бgб1";)")
<< QByteArray(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)");
QTest::newRow("escape string literal: unescape adjacent literals")
<< QByteArray(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)")
<< QByteArray(R"(const char *str = "àf23бgб1";)");
}
void test()
{
QFETCH(QByteArray, original);
QFETCH(QByteArray, expected);
EscapeStringLiteral factory;
QuickFixOperationTest(singleDocument(original, expected), &factory);
}
};
QObject *EscapeStringLiteral::createTest() { return new EscapeStringLiteralTest; }
QObject *ConvertCStringToNSString::createTest() { return new QObject; }
QObject *WrapStringLiteral::createTest() { return new QObject; }
QObject *TranslateStringLiteral::createTest() { return new QObject; }
#endif // WITH_TESTS
} // namespace
void registerConvertStringLiteralQuickfixes()
{
CppQuickFixFactory::registerFactory<ConvertCStringToNSString>();
CppQuickFixFactory::registerFactory<EscapeStringLiteral>();
CppQuickFixFactory::registerFactory<TranslateStringLiteral>();
CppQuickFixFactory::registerFactory<WrapStringLiteral>();
}
} // namespace CppEditor::Internal
#ifdef WITH_TESTS
#include <convertstringliteral.moc>
#endif

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace CppEditor::Internal {
void registerConvertStringLiteralQuickfixes();
} // namespace CppEditor::Internal

View File

@@ -1238,42 +1238,6 @@ void QuickfixTest::testGeneric_data()
<< _("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n") << _("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n")
<< _(); << _();
// Escape String Literal as UTF-8 (no-trigger)
QTest::newRow("EscapeStringLiteral_notrigger")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n")
<< _();
// Escape String Literal as UTF-8
QTest::newRow("EscapeStringLiteral")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n")
<< _("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n");
// Unescape String Literal as UTF-8 (from hexdecimal escape sequences)
QTest::newRow("UnescapeStringLiteral_hex")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n")
<< _("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
// Unescape String Literal as UTF-8 (from octal escape sequences)
QTest::newRow("UnescapeStringLiteral_oct")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n")
<< _("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
// Unescape String Literal as UTF-8 (triggered but no change)
QTest::newRow("UnescapeStringLiteral_noconv")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *escaped_ascii = \"@\\x1b\";\n")
<< _("const char *escaped_ascii = \"\\x1b\";\n");
// Unescape String Literal as UTF-8 (no conversion because of invalid utf-8)
QTest::newRow("UnescapeStringLiteral_invalid")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _("const char *escaped = \"@\\xe3\\x81\";\n")
<< _("const char *escaped = \"\\xe3\\x81\";\n");
QTest::newRow("ConvertFromPointer") QTest::newRow("ConvertFromPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer) << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n" << _("void foo() {\n"
@@ -1602,26 +1566,6 @@ void QuickfixTest::testGeneric_data()
<< CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
<< _("void @WhAt_TODO_hErE();\n") << _("void @WhAt_TODO_hErE();\n")
<< _("void WhAtTODOHErE();\n"); << _("void WhAtTODOHErE();\n");
QTest::newRow("escape string literal: simple case")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"àxyz";)")
<< _(R"(const char *str = "\xc3\xa0xyz";)");
QTest::newRow("escape string literal: simple case reverse")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"\xc3\xa0xyz";)")
<< _(R"(const char *str = "àxyz";)");
QTest::newRow("escape string literal: raw string literal")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"x(const char *str = @R"(àxyz)";)x")
<< _(R"x(const char *str = R"(\xc3\xa0xyz)";)x");
QTest::newRow("escape string literal: splitting required")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"àf23бgб1";)")
<< _(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)");
QTest::newRow("escape string literal: unescape adjacent literals")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)")
<< _(R"(const char *str = "àf23бgб1";)");
QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004") QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004")
<< CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier) << CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier)
<< _("void func() {\n" << _("void func() {\n"

View File

@@ -22,6 +22,7 @@
#include "cppquickfixhelpers.h" #include "cppquickfixhelpers.h"
#include "cppquickfixprojectsettings.h" #include "cppquickfixprojectsettings.h"
#include "convertqt4connect.h" #include "convertqt4connect.h"
#include "convertstringliteral.h"
#include "insertfunctiondefinition.h" #include "insertfunctiondefinition.h"
#include "moveclasstoownfile.h" #include "moveclasstoownfile.h"
#include "movefunctiondefinition.h" #include "movefunctiondefinition.h"
@@ -131,17 +132,6 @@ namespace Internal {
// different quick fixes. // different quick fixes.
namespace { namespace {
inline bool isQtStringLiteral(const QByteArray &id)
{
return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral"
|| id == "QByteArrayLiteral";
}
inline bool isQtStringTranslation(const QByteArray &id)
{
return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}
QString nameString(const NameAST *name) QString nameString(const NameAST *name)
{ {
return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name); return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
@@ -1012,389 +1002,6 @@ void SplitIfStatement::doMatch(const CppQuickFixInterface &interface, QuickFixOp
} }
} }
/* Analze a string/character literal like "x", QLatin1String("x") and return the literal
* (StringLiteral or NumericLiteral for characters) and its type
* and the enclosing function (QLatin1String, tr...) */
enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone };
enum ActionFlags {
EncloseInQLatin1CharAction = 0x1,
EncloseInQLatin1StringAction = 0x2,
EncloseInQStringLiteralAction = 0x4,
EncloseInQByteArrayLiteralAction = 0x8,
EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction
| EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction,
TranslateTrAction = 0x10,
TranslateQCoreApplicationAction = 0x20,
TranslateNoopAction = 0x40,
TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction,
RemoveObjectiveCAction = 0x100,
ConvertEscapeSequencesToCharAction = 0x200,
ConvertEscapeSequencesToStringAction = 0x400,
SingleQuoteAction = 0x800,
DoubleQuoteAction = 0x1000
};
/* Convert single-character string literals into character literals with some
* special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */
static QByteArray stringToCharEscapeSequences(const QByteArray &content)
{
if (content.size() == 1)
return content.at(0) == '\'' ? QByteArray("\\'") : content;
if (content.size() == 2 && content.at(0) == '\\')
return content == "\\\"" ? QByteArray(1, '"') : content;
return QByteArray();
}
/* Convert character literal into a string literal with some special cases
* 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */
static QByteArray charToStringEscapeSequences(const QByteArray &content)
{
if (content.size() == 1)
return content.at(0) == '"' ? QByteArray("\\\"") : content;
if (content.size() == 2)
return content == "\\'" ? QByteArray("'") : content;
return QByteArray();
}
static QString msgQtStringLiteralDescription(const QString &replacement)
{
return Tr::tr("Enclose in %1(...)").arg(replacement);
}
static QString stringLiteralReplacement(unsigned actions)
{
if (actions & EncloseInQLatin1CharAction)
return QLatin1String("QLatin1Char");
if (actions & EncloseInQLatin1StringAction)
return QLatin1String("QLatin1String");
if (actions & EncloseInQStringLiteralAction)
return QLatin1String("QStringLiteral");
if (actions & EncloseInQByteArrayLiteralAction)
return QLatin1String("QByteArrayLiteral");
if (actions & TranslateTrAction)
return QLatin1String("tr");
if (actions & TranslateQCoreApplicationAction)
return QLatin1String("QCoreApplication::translate");
if (actions & TranslateNoopAction)
return QLatin1String("QT_TRANSLATE_NOOP");
return QString();
}
static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path,
const CppRefactoringFilePtr &file, StringLiteralType *type,
QByteArray *enclosingFunction = nullptr,
CallAST **enclosingFunctionCall = nullptr)
{
*type = TypeNone;
if (enclosingFunction)
enclosingFunction->clear();
if (enclosingFunctionCall)
*enclosingFunctionCall = nullptr;
if (path.isEmpty())
return nullptr;
ExpressionAST *literal = path.last()->asExpression();
if (literal) {
if (literal->asStringLiteral()) {
// Check for Objective C string (@"bla")
const QChar firstChar = file->charAt(file->startOf(literal));
*type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString;
} else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) {
// character ('c') constants are numeric.
if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL))
*type = TypeChar;
}
}
if (*type != TypeNone && enclosingFunction && path.size() > 1) {
if (CallAST *call = path.at(path.size() - 2)->asCall()) {
if (call->base_expression) {
if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) {
if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) {
*enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars();
if (enclosingFunctionCall)
*enclosingFunctionCall = call;
}
}
}
}
}
return literal;
}
namespace {
/// Operation performs the operations of type ActionFlags passed in as actions.
class WrapStringLiteralOp : public CppQuickFixOperation
{
public:
WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority,
unsigned actions, const QString &description, ExpressionAST *literal,
const QString &translationContext = QString())
: CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal),
m_translationContext(translationContext)
{
setDescription(description);
}
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
ChangeSet changes;
const int startPos = currentFile->startOf(m_literal);
const int endPos = currentFile->endOf(m_literal);
// kill leading '@'. No need to adapt endPos, that is done by ChangeSet
if (m_actions & RemoveObjectiveCAction)
changes.remove(startPos, startPos + 1);
// Fix quotes
if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) {
const QString newQuote((m_actions & SingleQuoteAction)
? QLatin1Char('\'') : QLatin1Char('"'));
changes.replace(startPos, startPos + 1, newQuote);
changes.replace(endPos - 1, endPos, newQuote);
}
// Convert single character strings into character constants
if (m_actions & ConvertEscapeSequencesToCharAction) {
StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
QTC_ASSERT(stringLiteral, return ;);
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).identifier->chars());
const QByteArray newContents = stringToCharEscapeSequences(oldContents);
QTC_ASSERT(!newContents.isEmpty(), return ;);
if (oldContents != newContents)
changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
}
// Convert character constants into strings constants
if (m_actions & ConvertEscapeSequencesToStringAction) {
NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical.
QTC_ASSERT(charLiteral, return ;);
const QByteArray oldContents(currentFile->tokenAt(charLiteral->literal_token).identifier->chars());
const QByteArray newContents = charToStringEscapeSequences(oldContents);
QTC_ASSERT(!newContents.isEmpty(), return ;);
if (oldContents != newContents)
changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
}
// Enclose in literal or translation function, macro.
if (m_actions & (EncloseActionMask | TranslationMask)) {
changes.insert(endPos, QString(QLatin1Char(')')));
QString leading = stringLiteralReplacement(m_actions);
leading += QLatin1Char('(');
if (m_actions
& (TranslateQCoreApplicationAction | TranslateNoopAction)) {
leading += QLatin1Char('"');
leading += m_translationContext;
leading += QLatin1String("\", ");
}
changes.insert(startPos, leading);
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
const unsigned m_actions;
ExpressionAST *m_literal;
const QString m_translationContext;
};
} // anonymous namespace
void WrapStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
const QList<AST *> &path = interface.path();
CppRefactoringFilePtr file = interface.currentFile();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
if (!literal || type == TypeNone)
return;
if ((type == TypeChar && enclosingFunction == "QLatin1Char")
|| isQtStringLiteral(enclosingFunction)
|| isQtStringTranslation(enclosingFunction))
return;
const int priority = path.size() - 1; // very high priority
if (type == TypeChar) {
unsigned actions = EncloseInQLatin1CharAction;
QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions));
result << new WrapStringLiteralOp(interface, priority, actions, description, literal);
if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) {
const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars());
if (!charToStringEscapeSequences(contents).isEmpty()) {
actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction;
description = Tr::tr("Convert to String Literal");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
}
}
} else {
const unsigned objectiveCActions = type == TypeObjCString ?
unsigned(RemoveObjectiveCAction) : 0u;
unsigned actions = 0;
if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) {
const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
if (!stringToCharEscapeSequences(contents).isEmpty()) {
actions = EncloseInQLatin1CharAction | SingleQuoteAction
| ConvertEscapeSequencesToCharAction | objectiveCActions;
QString description =
Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
actions &= ~EncloseInQLatin1CharAction;
description = Tr::tr("Convert to Character Literal");
result << new WrapStringLiteralOp(interface, priority, actions,
description, literal);
}
}
actions = EncloseInQLatin1StringAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
actions = EncloseInQStringLiteralAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
actions = EncloseInQByteArrayLiteralAction | objectiveCActions;
result << new WrapStringLiteralOp(interface, priority, actions,
msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
}
}
void TranslateStringLiteral::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
// Initialize
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
const QList<AST *> &path = interface.path();
CppRefactoringFilePtr file = interface.currentFile();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
if (!literal || type != TypeString
|| isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction))
return;
QString trContext;
std::shared_ptr<Control> control = interface.context().bindings()->control();
const Name *trName = control->identifier("tr");
// Check whether we are in a function:
const QString description = Tr::tr("Mark as Translatable");
for (int i = path.size() - 1; i >= 0; --i) {
if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) {
Function *function = definition->symbol;
ClassOrNamespace *b = interface.context().lookupType(function);
if (b) {
// Do we have a tr function?
const QList<LookupItem> items = b->find(trName);
for (const LookupItem &r : items) {
Symbol *s = r.declaration();
if (s->type()->asFunctionType()) {
// no context required for tr
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateTrAction,
description, literal);
return;
}
}
}
// We need to do a QCA::translate, so we need a context.
// Use fully qualified class name:
Overview oo;
const QList<const Name *> names = LookupContext::path(function);
for (const Name *n : names) {
if (!trContext.isEmpty())
trContext.append(QLatin1String("::"));
trContext.append(oo.prettyName(n));
}
// ... or global if none available!
if (trContext.isEmpty())
trContext = QLatin1String("GLOBAL");
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateQCoreApplicationAction,
description, literal, trContext);
return;
}
}
// We need to use Q_TRANSLATE_NOOP
result << new WrapStringLiteralOp(interface, path.size() - 1,
TranslateNoopAction,
description, literal, trContext);
}
namespace {
class ConvertCStringToNSStringOp: public CppQuickFixOperation
{
public:
ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority,
StringLiteralAST *stringLiteral, CallAST *qlatin1Call)
: CppQuickFixOperation(interface, priority)
, stringLiteral(stringLiteral)
, qlatin1Call(qlatin1Call)
{
setDescription(Tr::tr("Convert to Objective-C String Literal"));
}
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
ChangeSet changes;
if (qlatin1Call) {
changes.replace(currentFile->startOf(qlatin1Call), currentFile->startOf(stringLiteral),
QLatin1String("@"));
changes.remove(currentFile->endOf(stringLiteral), currentFile->endOf(qlatin1Call));
} else {
changes.insert(currentFile->startOf(stringLiteral), QLatin1String("@"));
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
StringLiteralAST *stringLiteral;
CallAST *qlatin1Call;
};
} // anonymous namespace
void ConvertCStringToNSString::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
CppRefactoringFilePtr file = interface.currentFile();
if (!interface.editor()->cppEditorDocument()->isObjCEnabled())
return;
StringLiteralType type = TypeNone;
QByteArray enclosingFunction;
CallAST *qlatin1Call;
const QList<AST *> &path = interface.path();
ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction,
&qlatin1Call);
if (!literal || type != TypeString)
return;
if (!isQtStringLiteral(enclosingFunction))
qlatin1Call = nullptr;
result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(),
qlatin1Call);
}
namespace { namespace {
class ConvertNumericLiteralOp: public CppQuickFixOperation class ConvertNumericLiteralOp: public CppQuickFixOperation
@@ -4315,190 +3922,6 @@ void OptimizeForLoop::doMatch(const CppQuickFixInterface &interface, QuickFixOpe
} }
} }
namespace {
class EscapeStringLiteralOperation: public CppQuickFixOperation
{
public:
EscapeStringLiteralOperation(const CppQuickFixInterface &interface,
ExpressionAST *literal, bool escape)
: CppQuickFixOperation(interface)
, m_literal(literal)
, m_escape(escape)
{
if (m_escape) {
setDescription(Tr::tr("Escape String Literal as UTF-8"));
} else {
setDescription(Tr::tr("Unescape String Literal as UTF-8"));
}
}
private:
static inline bool isDigit(quint8 ch, int base)
{
if (base == 8)
return ch >= '0' && ch < '8';
if (base == 16)
return isxdigit(ch);
return false;
}
static QByteArrayList escapeString(const QByteArray &contents)
{
QByteArrayList newContents;
QByteArray chunk;
bool wasEscaped = false;
for (const quint8 c : contents) {
const bool needsEscape = !isascii(c) || !isprint(c);
if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) {
newContents << chunk;
chunk.clear();
}
if (needsEscape)
chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0');
else
chunk += c;
wasEscaped = needsEscape;
}
if (!chunk.isEmpty())
newContents << chunk;
return newContents;
}
static QByteArray unescapeString(const QByteArray &contents)
{
QByteArray newContents;
const int len = contents.length();
for (int i = 0; i < len; ++i) {
quint8 c = contents.at(i);
if (c == '\\' && i < len - 1) {
int idx = i + 1;
quint8 ch = contents.at(idx);
int base = 0;
int maxlen = 0;
if (isDigit(ch, 8)) {
base = 8;
maxlen = 3;
} else if ((ch == 'x' || ch == 'X') && idx < len - 1) {
base = 16;
maxlen = 2;
ch = contents.at(++idx);
}
if (base > 0) {
QByteArray buf;
while (isDigit(ch, base) && idx < len && buf.length() < maxlen) {
buf += ch;
++idx;
if (idx == len)
break;
ch = contents.at(idx);
}
if (!buf.isEmpty()) {
bool ok;
uint value = buf.toUInt(&ok, base);
// Don't unescape isascii() && !isprint()
if (ok && (!isascii(value) || isprint(value))) {
newContents += value;
i = idx - 1;
continue;
}
}
}
newContents += c;
c = contents.at(++i);
}
newContents += c;
}
return newContents;
}
// QuickFixOperation interface
public:
void perform() override
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
const int startPos = currentFile->startOf(m_literal);
const int endPos = currentFile->endOf(m_literal);
StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
QTC_ASSERT(stringLiteral, return);
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).
identifier->chars());
QByteArrayList newContents;
if (m_escape)
newContents = escapeString(oldContents);
else
newContents = {unescapeString(oldContents)};
if (newContents.isEmpty()
|| (newContents.size() == 1 && newContents.first() == oldContents)) {
return;
}
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
ChangeSet changes;
bool replace = true;
for (const QByteArray &chunk : std::as_const(newContents)) {
const QString str = decoder->toUnicode(chunk);
const QByteArray utf8buf = str.toUtf8();
if (!utf8codec->canEncode(str) || chunk != utf8buf)
return;
if (replace)
changes.replace(startPos + 1, endPos - 1, str);
else
changes.insert(endPos, "\"" + str + "\"");
replace = false;
}
currentFile->setChangeSet(changes);
currentFile->apply();
}
private:
ExpressionAST *m_literal;
bool m_escape;
};
} // anonymous namespace
void EscapeStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
const QList<AST *> &path = interface.path();
if (path.isEmpty())
return;
AST * const lastAst = path.last();
ExpressionAST *literal = lastAst->asStringLiteral();
if (!literal)
return;
StringLiteralAST *stringLiteral = literal->asStringLiteral();
CppRefactoringFilePtr file = interface.currentFile();
const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
bool canEscape = false;
bool canUnescape = false;
for (int i = 0; i < contents.length(); ++i) {
quint8 c = contents.at(i);
if (!isascii(c) || !isprint(c)) {
canEscape = true;
} else if (c == '\\' && i < contents.length() - 1) {
c = contents.at(++i);
if ((c >= '0' && c < '8') || c == 'x' || c == 'X')
canUnescape = true;
}
}
if (canEscape)
result << new EscapeStringLiteralOperation(interface, literal, true);
if (canUnescape)
result << new EscapeStringLiteralOperation(interface, literal, false);
}
void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface, void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result) QuickFixOperations &result)
{ {
@@ -5075,10 +4498,7 @@ void createCppQuickFixes()
new ConvertToCamelCase; new ConvertToCamelCase;
new ConvertCStringToNSString;
new ConvertNumericLiteral; new ConvertNumericLiteral;
new TranslateStringLiteral;
new WrapStringLiteral;
new MoveDeclarationOutOfIf; new MoveDeclarationOutOfIf;
new MoveDeclarationOutOfWhile; new MoveDeclarationOutOfWhile;
@@ -5109,11 +4529,10 @@ void createCppQuickFixes()
registerMoveFunctionDefinitionQuickfixes(); registerMoveFunctionDefinitionQuickfixes();
registerInsertFunctionDefinitionQuickfixes(); registerInsertFunctionDefinitionQuickfixes();
registerBringIdentifierIntoScopeQuickfixes(); registerBringIdentifierIntoScopeQuickfixes();
registerConvertStringLiteralQuickfixes();
new OptimizeForLoop; new OptimizeForLoop;
new EscapeStringLiteral;
new ExtraRefactoringOperations; new ExtraRefactoringOperations;
new ConvertCommentStyle; new ConvertCommentStyle;

View File

@@ -72,22 +72,6 @@ public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
}; };
/*!
Replace
"abcd"
QLatin1String("abcd")
QLatin1Literal("abcd")
With
@"abcd"
Activates on: the string literal, if the file type is a Objective-C(++) file.
*/
class ConvertCStringToNSString: public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
};
/*! /*!
Base class for converting numeric literals between decimal, octal and hex. Base class for converting numeric literals between decimal, octal and hex.
@@ -114,47 +98,6 @@ public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
}; };
/*!
Replace
"abcd"
With
tr("abcd") or
QCoreApplication::translate("CONTEXT", "abcd") or
QT_TRANSLATE_NOOP("GLOBAL", "abcd")
depending on what is available.
Activates on: the string literal
*/
class TranslateStringLiteral: public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
};
/*!
Replace
"abcd" -> QLatin1String("abcd")
@"abcd" -> QLatin1String("abcd") (Objective C)
'a' -> QLatin1Char('a')
'a' -> "a"
"a" -> 'a' or QLatin1Char('a') (Single character string constants)
"\n" -> '\n', QLatin1Char('\n')
Except if they are already enclosed in
QLatin1Char, QT_TRANSLATE_NOOP, tr,
trUtf8, QLatin1Literal, QLatin1String
Activates on: the string or character literal
*/
class WrapStringLiteral: public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
};
/*! /*!
Turns "an_example_symbol" into "anExampleSymbol" and Turns "an_example_symbol" into "anExampleSymbol" and
"AN_EXAMPLE_SYMBOL" into "AnExampleSymbol". "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol".
@@ -413,19 +356,6 @@ public:
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
}; };
/*!
Escapes or unescapes a string literal as UTF-8.
Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
Unescapes octal or hexadecimal escape sequences in a string literal.
String literals are handled as UTF-8 even if file's encoding is not UTF-8.
*/
class EscapeStringLiteral : public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
};
//! Converts C-style to C++-style comments and vice versa //! Converts C-style to C++-style comments and vice versa
class ConvertCommentStyle : public CppQuickFixFactory class ConvertCommentStyle : public CppQuickFixFactory
{ {