forked from qt-creator/qt-creator
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:
@@ -96,6 +96,8 @@ add_qtc_plugin(CppEditor
|
||||
projectinfo.cpp projectinfo.h
|
||||
projectpart.cpp projectpart.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/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h
|
||||
quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h
|
||||
@@ -107,7 +109,6 @@ add_qtc_plugin(CppEditor
|
||||
quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
|
||||
quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
|
||||
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
|
||||
quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
|
||||
quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h
|
||||
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
|
||||
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h
|
||||
|
@@ -223,6 +223,8 @@ QtcPlugin {
|
||||
"bringidentifierintoscope.h",
|
||||
"convertqt4connect.cpp",
|
||||
"convertqt4connect.h",
|
||||
"convertstringliteral.cpp",
|
||||
"convertstringliteral.h",
|
||||
"cppcodegenerationquickfixes.cpp",
|
||||
"cppcodegenerationquickfixes.h",
|
||||
"cppinsertvirtualmethods.cpp",
|
||||
|
758
src/plugins/cppeditor/quickfixes/convertstringliteral.cpp
Normal file
758
src/plugins/cppeditor/quickfixes/convertstringliteral.cpp
Normal 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
|
8
src/plugins/cppeditor/quickfixes/convertstringliteral.h
Normal file
8
src/plugins/cppeditor/quickfixes/convertstringliteral.h
Normal 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
|
@@ -1238,42 +1238,6 @@ void QuickfixTest::testGeneric_data()
|
||||
<< _("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")
|
||||
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
|
||||
<< _("void foo() {\n"
|
||||
@@ -1602,26 +1566,6 @@ void QuickfixTest::testGeneric_data()
|
||||
<< CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
|
||||
<< _("void @WhAt_TODO_hErE();\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")
|
||||
<< CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier)
|
||||
<< _("void func() {\n"
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "cppquickfixhelpers.h"
|
||||
#include "cppquickfixprojectsettings.h"
|
||||
#include "convertqt4connect.h"
|
||||
#include "convertstringliteral.h"
|
||||
#include "insertfunctiondefinition.h"
|
||||
#include "moveclasstoownfile.h"
|
||||
#include "movefunctiondefinition.h"
|
||||
@@ -131,17 +132,6 @@ namespace Internal {
|
||||
// different quick fixes.
|
||||
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)
|
||||
{
|
||||
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 {
|
||||
|
||||
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,
|
||||
QuickFixOperations &result)
|
||||
{
|
||||
@@ -5075,10 +4498,7 @@ void createCppQuickFixes()
|
||||
|
||||
new ConvertToCamelCase;
|
||||
|
||||
new ConvertCStringToNSString;
|
||||
new ConvertNumericLiteral;
|
||||
new TranslateStringLiteral;
|
||||
new WrapStringLiteral;
|
||||
|
||||
new MoveDeclarationOutOfIf;
|
||||
new MoveDeclarationOutOfWhile;
|
||||
@@ -5109,11 +4529,10 @@ void createCppQuickFixes()
|
||||
registerMoveFunctionDefinitionQuickfixes();
|
||||
registerInsertFunctionDefinitionQuickfixes();
|
||||
registerBringIdentifierIntoScopeQuickfixes();
|
||||
registerConvertStringLiteralQuickfixes();
|
||||
|
||||
new OptimizeForLoop;
|
||||
|
||||
new EscapeStringLiteral;
|
||||
|
||||
new ExtraRefactoringOperations;
|
||||
|
||||
new ConvertCommentStyle;
|
||||
|
@@ -72,22 +72,6 @@ public:
|
||||
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.
|
||||
@@ -114,47 +98,6 @@ public:
|
||||
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
|
||||
"AN_EXAMPLE_SYMBOL" into "AnExampleSymbol".
|
||||
@@ -413,19 +356,6 @@ public:
|
||||
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
|
||||
class ConvertCommentStyle : public CppQuickFixFactory
|
||||
{
|
||||
|
Reference in New Issue
Block a user