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
|
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
|
||||||
|
@@ -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",
|
||||||
|
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")
|
<< _("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"
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user