Commit 66feceac authored by Takumi Asaki's avatar Takumi Asaki Committed by Nikolai Kosjar
Browse files

CppEditor: Add escape/unescape string literal QuickFix



Change-Id: I32c22dfa32ee0345b76e8c35381bce988d20ed49
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent ed3cdb9c
......@@ -2060,6 +2060,18 @@
\endcode
\li for
\row
\li Escape String Literal as UTF-8
\li Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
String Literals are handled as UTF-8.
\li String literal
\row
\li Unescape String Literal as UTF-8
\li Unescapes octal or hexadecimal escape sequences in a string literal.
String Literals are handled as UTF-8.
\li String literal
\endtable
\section2 Refactoring QML Code
......
......@@ -1271,6 +1271,43 @@ void CppEditorPlugin::test_quickfix_data()
" int m_it;\n"
"};\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");
}
void CppEditorPlugin::test_quickfix()
......
......@@ -63,6 +63,7 @@
#include <QMessageBox>
#include <QSharedPointer>
#include <QTextCursor>
#include <QTextCodec>
#include <cctype>
......@@ -118,6 +119,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new InsertVirtualMethods);
plugIn->addAutoReleasedObject(new OptimizeForLoop);
plugIn->addAutoReleasedObject(new EscapeStringLiteral);
}
// In the following anonymous namespace all functions are collected, which could be of interest for
......@@ -4863,3 +4866,176 @@ void OptimizeForLoop::match(const CppQuickFixInterface &interface, QuickFixOpera
result.append(QuickFixOperation::Ptr(op));
}
}
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(QApplication::translate("CppTools::QuickFix",
"Escape String Literal as UTF-8"));
} else {
setDescription(QApplication::translate("CppTools::QuickFix",
"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 QByteArray escapeString(const QByteArray &contents)
{
QByteArray newContents;
for (int i = 0; i < contents.length(); ++i) {
quint8 c = contents.at(i);
if (isascii(c) && isprint(c)) {
newContents += c;
} else {
newContents += QByteArray("\\x") +
QByteArray::number(c, 16).rightJustified(2, '0');
}
}
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()
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.file(fileName());
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());
QByteArray newContents;
if (m_escape)
newContents = escapeString(oldContents);
else
newContents = unescapeString(oldContents);
if (oldContents != newContents) {
// Check UTF-8 byte array is correct or not.
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
const QString str = decoder->toUnicode(newContents);
const QByteArray utf8buf = str.toUtf8();
if (utf8codec->canEncode(str) && newContents == utf8buf) {
ChangeSet changes;
changes.replace(startPos + 1, endPos - 1, str);
currentFile->setChangeSet(changes);
currentFile->apply();
}
}
}
private:
ExpressionAST *m_literal;
bool m_escape;
};
} // anonymous namespace
void EscapeStringLiteral::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
const QList<AST *> &path = interface->path();
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) {
QuickFixOperation::Ptr op(
new EscapeStringLiteralOperation(interface, literal, true));
result.append(op);
}
if (canUnescape) {
QuickFixOperation::Ptr op(
new EscapeStringLiteralOperation(interface, literal, false));
result.append(op);
}
}
......@@ -524,6 +524,19 @@ public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
/*!
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 match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
} // namespace Internal
} // namespace CppEditor
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment