CppEditor: Move ExtractLiteralAsParameter quickfix to its own files

Change-Id: Ib443b2ae2b3b07685471384b0d26c8ef9deac1b2
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-16 15:28:47 +02:00
parent 5b83dd2430
commit a82ad37bde
8 changed files with 576 additions and 514 deletions

View File

@@ -111,6 +111,7 @@ add_qtc_plugin(CppEditor
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
quickfixes/createdeclarationfromuse.cpp quickfixes/createdeclarationfromuse.h quickfixes/createdeclarationfromuse.cpp quickfixes/createdeclarationfromuse.h
quickfixes/extractfunction.cpp quickfixes/extractfunction.h quickfixes/extractfunction.cpp quickfixes/extractfunction.h
quickfixes/extractliteralasparameter.cpp quickfixes/extractliteralasparameter.h
quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h
quickfixes/logicaloperationquickfixes.cpp quickfixes/logicaloperationquickfixes.h quickfixes/logicaloperationquickfixes.cpp quickfixes/logicaloperationquickfixes.h
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h

View File

@@ -251,6 +251,8 @@ QtcPlugin {
"createdeclarationfromuse.h", "createdeclarationfromuse.h",
"extractfunction.cpp", "extractfunction.cpp",
"extractfunction.h", "extractfunction.h",
"extractliteralasparameter.cpp",
"extractliteralasparameter.h",
"insertfunctiondefinition.cpp", "insertfunctiondefinition.cpp",
"insertfunctiondefinition.h", "insertfunctiondefinition.h",
"logicaloperationquickfixes.cpp", "logicaloperationquickfixes.cpp",

View File

@@ -1039,47 +1039,6 @@ void QuickfixTest::testGeneric_data()
"}" "}"
) << _(); ) << _();
QTest::newRow("ExtractLiteralAsParameter_freeFunction")
<< CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
"void foo(const char *a, long b = 1)\n"
"{return 1@56 + 123 + 156;}\n"
) << _(
"void foo(const char *a, long b = 1, int newParameter = 156)\n"
"{return newParameter + 123 + newParameter;}\n"
);
QTest::newRow("ExtractLiteralAsParameter_memberFunction")
<< CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
"class Narf {\n"
"public:\n"
" int zort();\n"
"};\n\n"
"int Narf::zort()\n"
"{ return 15@5 + 1; }\n"
) << _(
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155);\n"
"};\n\n"
"int Narf::zort(int newParameter)\n"
"{ return newParameter + 1; }\n"
);
QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline")
<< CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
"class Narf {\n"
"public:\n"
" int zort()\n"
" { return 15@5 + 1; }\n"
"};\n"
) << _(
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155)\n"
" { return newParameter + 1; }\n"
"};\n"
);
QTest::newRow("ConvertFromPointer") QTest::newRow("ConvertFromPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer) << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n" << _("void foo() {\n"
@@ -1493,145 +1452,6 @@ void QuickfixTest::testAssignToLocalVariableTemplates()
QuickFixOperationTest(testDocuments, &factory); QuickFixOperationTest(testDocuments, &factory);
} }
void QuickfixTest::testExtractLiteralAsParameterTypeDeduction_data()
{
QTest::addColumn<QByteArray>("typeString");
QTest::addColumn<QByteArray>("literal");
QTest::newRow("int")
<< QByteArray("int ") << QByteArray("156");
QTest::newRow("unsigned int")
<< QByteArray("unsigned int ") << QByteArray("156u");
QTest::newRow("long")
<< QByteArray("long ") << QByteArray("156l");
QTest::newRow("unsigned long")
<< QByteArray("unsigned long ") << QByteArray("156ul");
QTest::newRow("long long")
<< QByteArray("long long ") << QByteArray("156ll");
QTest::newRow("unsigned long long")
<< QByteArray("unsigned long long ") << QByteArray("156ull");
QTest::newRow("float")
<< QByteArray("float ") << QByteArray("3.14159f");
QTest::newRow("double")
<< QByteArray("double ") << QByteArray("3.14159");
QTest::newRow("long double")
<< QByteArray("long double ") << QByteArray("3.14159L");
QTest::newRow("bool")
<< QByteArray("bool ") << QByteArray("true");
QTest::newRow("bool")
<< QByteArray("bool ") << QByteArray("false");
QTest::newRow("char")
<< QByteArray("char ") << QByteArray("'X'");
QTest::newRow("wchar_t")
<< QByteArray("wchar_t ") << QByteArray("L'X'");
QTest::newRow("char16_t")
<< QByteArray("char16_t ") << QByteArray("u'X'");
QTest::newRow("char32_t")
<< QByteArray("char32_t ") << QByteArray("U'X'");
QTest::newRow("const char *")
<< QByteArray("const char *") << QByteArray("\"narf\"");
QTest::newRow("const wchar_t *")
<< QByteArray("const wchar_t *") << QByteArray("L\"narf\"");
QTest::newRow("const char16_t *")
<< QByteArray("const char16_t *") << QByteArray("u\"narf\"");
QTest::newRow("const char32_t *")
<< QByteArray("const char32_t *") << QByteArray("U\"narf\"");
}
void QuickfixTest::testExtractLiteralAsParameterTypeDeduction()
{
QFETCH(QByteArray, typeString);
QFETCH(QByteArray, literal);
const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n");
const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ")
+ literal + QByteArray(") {return newParameter;}\n");
if (literal == "3.14159") {
qWarning("Literal 3.14159 is wrongly reported as int. Skipping.");
return;
} else if (literal == "3.14159L") {
qWarning("Literal 3.14159L is wrongly reported as long. Skipping.");
return;
}
ExtractLiteralAsParameter factory;
QuickFixOperationTest(singleDocument(original, expected), &factory);
}
void QuickfixTest::testExtractLiteralAsParameterFreeFunctionSeparateFiles()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
QByteArray expected;
// Header File
original =
"void foo(const char *a, long b = 1);\n";
expected =
"void foo(const char *a, long b = 1, int newParameter = 156);\n";
testDocuments << CppTestDocument::create("file.h", original, expected);
// Source File
original =
"void foo(const char *a, long b)\n"
"{return 1@56 + 123 + 156;}\n";
expected =
"void foo(const char *a, long b, int newParameter)\n"
"{return newParameter + 123 + newParameter;}\n";
testDocuments << CppTestDocument::create("file.cpp", original, expected);
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void QuickfixTest::testExtractLiteralAsParameterMemberFunctionSeparateFiles()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
QByteArray expected;
// Header File
original =
"class Narf {\n"
"public:\n"
" int zort();\n"
"};\n";
expected =
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155);\n"
"};\n";
testDocuments << CppTestDocument::create("file.h", original, expected);
// Source File
original =
"#include \"file.h\"\n\n"
"int Narf::zort()\n"
"{ return 15@5 + 1; }\n";
expected =
"#include \"file.h\"\n\n"
"int Narf::zort(int newParameter)\n"
"{ return newParameter + 1; }\n";
testDocuments << CppTestDocument::create("file.cpp", original, expected);
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
original =
"T(\"test\")\n"
"{\n"
" const int i = @14;\n"
"}\n";
testDocuments << CppTestDocument::create("file.cpp", original, "");
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void QuickfixTest::testConvertToMetaMethodInvocation_data() void QuickfixTest::testConvertToMetaMethodInvocation_data()
{ {
QTest::addColumn<QByteArray>("input"); QTest::addColumn<QByteArray>("input");

View File

@@ -99,12 +99,6 @@ private slots:
void testAssignToLocalVariableTemplates(); void testAssignToLocalVariableTemplates();
void testExtractLiteralAsParameterTypeDeduction_data();
void testExtractLiteralAsParameterTypeDeduction();
void testExtractLiteralAsParameterFreeFunctionSeparateFiles();
void testExtractLiteralAsParameterMemberFunctionSeparateFiles();
void testExtractLiteralAsParameterNotTriggeringForInvalidCode();
void testConvertToMetaMethodInvocation_data(); void testConvertToMetaMethodInvocation_data();
void testConvertToMetaMethodInvocation(); void testConvertToMetaMethodInvocation();
}; };

View File

@@ -11,7 +11,6 @@
#include "../cpppointerdeclarationformatter.h" #include "../cpppointerdeclarationformatter.h"
#include "../cpprefactoringchanges.h" #include "../cpprefactoringchanges.h"
#include "../cpptoolsreuse.h" #include "../cpptoolsreuse.h"
#include "../includeutils.h"
#include "../insertionpointlocator.h" #include "../insertionpointlocator.h"
#include "../symbolfinder.h" #include "../symbolfinder.h"
#include "bringidentifierintoscope.h" #include "bringidentifierintoscope.h"
@@ -24,6 +23,7 @@
#include "convertstringliteral.h" #include "convertstringliteral.h"
#include "createdeclarationfromuse.h" #include "createdeclarationfromuse.h"
#include "extractfunction.h" #include "extractfunction.h"
#include "extractliteralasparameter.h"
#include "insertfunctiondefinition.h" #include "insertfunctiondefinition.h"
#include "logicaloperationquickfixes.h" #include "logicaloperationquickfixes.h"
#include "moveclasstoownfile.h" #include "moveclasstoownfile.h"
@@ -870,321 +870,6 @@ void CompleteSwitchCaseStatement::doMatch(const CppQuickFixInterface &interface,
namespace { namespace {
struct ReplaceLiteralsResult
{
Token token;
QString literalText;
};
template <class T>
class ReplaceLiterals : private ASTVisitor
{
public:
ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal)
: ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes),
m_literal(literal)
{
m_result.token = m_file->tokenAt(literal->firstToken());
m_literalTokenText = m_result.token.spell();
m_result.literalText = QLatin1String(m_literalTokenText);
if (m_result.token.isCharLiteral()) {
m_result.literalText.prepend(QLatin1Char('\''));
m_result.literalText.append(QLatin1Char('\''));
if (m_result.token.kind() == T_WIDE_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('L'));
else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('u'));
else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('U'));
} else if (m_result.token.isStringLiteral()) {
m_result.literalText.prepend(QLatin1Char('"'));
m_result.literalText.append(QLatin1Char('"'));
if (m_result.token.kind() == T_WIDE_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('L'));
else if (m_result.token.kind() == T_UTF16_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('u'));
else if (m_result.token.kind() == T_UTF32_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('U'));
}
}
ReplaceLiteralsResult apply(AST *ast)
{
ast->accept(this);
return m_result;
}
private:
bool visit(T *ast) override
{
if (ast != m_literal
&& strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) {
return true;
}
int start, end;
m_file->startAndEndOf(ast->firstToken(), &start, &end);
m_changes->replace(start, end, QLatin1String("newParameter"));
return true;
}
const CppRefactoringFilePtr &m_file;
ChangeSet *m_changes;
T *m_literal;
const char *m_literalTokenText;
ReplaceLiteralsResult m_result;
};
class ExtractLiteralAsParameterOp : public CppQuickFixOperation
{
public:
ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority,
ExpressionAST *literal, FunctionDefinitionAST *function)
: CppQuickFixOperation(interface, priority),
m_literal(literal),
m_functionDefinition(function)
{
setDescription(Tr::tr("Extract Constant as Function Parameter"));
}
struct FoundDeclaration
{
FunctionDeclaratorAST *ast = nullptr;
CppRefactoringFilePtr file;
};
FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring,
FunctionDefinitionAST *ast)
{
FoundDeclaration result;
Function *func = ast->symbol;
if (Class *matchingClass = isMemberFunction(context(), func)) {
// Dealing with member functions
const QualifiedNameId *qName = func->name()->asQualifiedNameId();
for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) {
if (!s->name()
|| !qName->identifier()->match(s->identifier())
|| !s->type()->asFunctionType()
|| !s->type().match(func->type())
|| s->asFunction()) {
continue;
}
const FilePath declFilePath = matchingClass->filePath();
result.file = refactoring.cppFile(declFilePath);
ASTPath astPath(result.file->cppDocument());
const QList<AST *> path = astPath(s->line(), s->column());
SimpleDeclarationAST *simpleDecl = nullptr;
for (AST *node : path) {
simpleDecl = node->asSimpleDeclaration();
if (simpleDecl) {
if (simpleDecl->symbols && !simpleDecl->symbols->next) {
result.ast = functionDeclarator(simpleDecl);
return result;
}
}
}
if (simpleDecl)
break;
}
} else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) {
// Dealing with free functions and inline member functions.
bool isHeaderFile;
FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile);
if (!declFilePath.exists())
return FoundDeclaration();
result.file = refactoring.cppFile(declFilePath);
if (!result.file)
return FoundDeclaration();
const LookupContext lc(result.file->cppDocument(), snapshot());
const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
for (const LookupItem &candidate : candidates) {
if (Symbol *s = candidate.declaration()) {
if (s->asDeclaration()) {
ASTPath astPath(result.file->cppDocument());
const QList<AST *> path = astPath(s->line(), s->column());
for (AST *node : path) {
SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration();
if (simpleDecl) {
result.ast = functionDeclarator(simpleDecl);
return result;
}
}
}
}
}
}
return result;
}
void perform() override
{
FunctionDeclaratorAST *functionDeclaratorOfDefinition
= functionDeclarator(m_functionDefinition);
const CppRefactoringChanges refactoring(snapshot());
const CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
deduceTypeNameOfLiteral(currentFile->cppDocument());
ChangeSet changes;
if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) {
m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
} else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) {
m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
} else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) {
m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
}
const FoundDeclaration functionDeclaration
= findDeclaration(refactoring, m_functionDefinition);
appendFunctionParameter(functionDeclaratorOfDefinition, currentFile, &changes,
!functionDeclaration.ast);
if (functionDeclaration.ast) {
if (currentFile->filePath() != functionDeclaration.file->filePath()) {
ChangeSet declChanges;
appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges,
true);
functionDeclaration.file->setChangeSet(declChanges);
functionDeclaration.file->apply();
} else {
appendFunctionParameter(functionDeclaration.ast, currentFile, &changes,
true);
}
}
currentFile->setChangeSet(changes);
currentFile->apply();
QTextCursor c = currentFile->cursor();
c.setPosition(c.position() - parameterName().length());
editor()->setTextCursor(c);
editor()->renameSymbolUnderCursor();
}
private:
bool hasParameters(FunctionDeclaratorAST *ast) const
{
return ast->parameter_declaration_clause
&& ast->parameter_declaration_clause->parameter_declaration_list
&& ast->parameter_declaration_clause->parameter_declaration_list->value;
}
void deduceTypeNameOfLiteral(const Document::Ptr &document)
{
TypeOfExpression typeOfExpression;
typeOfExpression.init(document, snapshot());
Overview overview;
Scope *scope = m_functionDefinition->symbol->enclosingScope();
const QList<LookupItem> items = typeOfExpression(m_literal, document, scope);
if (!items.isEmpty())
m_typeName = overview.prettyType(items.first().type());
}
static QString parameterName() { return QLatin1String("newParameter"); }
QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const
{
QString str;
if (hasParameters(ast))
str = QLatin1String(", ");
str += m_typeName;
if (!m_typeName.endsWith(QLatin1Char('*')))
str += QLatin1Char(' ');
str += parameterName();
return str;
}
FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const
{
for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) {
FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value);
if (functionDeclaratorAST)
return functionDeclaratorAST;
}
return nullptr;
}
FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const
{
for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) {
FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator();
if (funcdecl)
return funcdecl;
}
return nullptr;
}
FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const
{
return functionDeclarator(ast->declarator);
}
void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file,
ChangeSet *changes, bool addDefaultValue)
{
if (!ast)
return;
if (m_declarationInsertionString.isEmpty())
m_declarationInsertionString = parameterDeclarationTextToInsert(ast);
QString insertion = m_declarationInsertionString;
if (addDefaultValue)
insertion += QLatin1String(" = ") + m_literalInfo.literalText;
changes->insert(file->startOf(ast->rparen_token), insertion);
}
ExpressionAST *m_literal;
FunctionDefinitionAST *m_functionDefinition;
QString m_typeName;
QString m_declarationInsertionString;
ReplaceLiteralsResult m_literalInfo;
};
} // anonymous namespace
void ExtractLiteralAsParameter::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
const QList<AST *> &path = interface.path();
if (path.count() < 2)
return;
AST * const lastAst = path.last();
ExpressionAST *literal;
if (!((literal = lastAst->asNumericLiteral())
|| (literal = lastAst->asStringLiteral())
|| (literal = lastAst->asBoolLiteral()))) {
return;
}
FunctionDefinitionAST *function;
int i = path.count() - 2;
while (!(function = path.at(i)->asFunctionDefinition())) {
// Ignore literals in lambda expressions for now.
if (path.at(i)->asLambdaExpression())
return;
if (--i < 0)
return;
}
PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list;
if (!declaratorList)
return;
if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) {
if (declarator->parameter_declaration_clause
&& declarator->parameter_declaration_clause->dot_dot_dot_token) {
// Do not handle functions with ellipsis parameter.
return;
}
}
const int priority = path.size() - 1;
result << new ExtractLiteralAsParameterOp(interface, priority, literal, function);
}
namespace {
class ConvertFromAndToPointerOp : public CppQuickFixOperation class ConvertFromAndToPointerOp : public CppQuickFixOperation
{ {
public: public:
@@ -1961,7 +1646,6 @@ void createCppQuickFixes()
new ApplyDeclDefLinkChanges; new ApplyDeclDefLinkChanges;
new ConvertFromAndToPointer; new ConvertFromAndToPointer;
new ExtractLiteralAsParameter;
new AssignToLocalVariable; new AssignToLocalVariable;
@@ -1979,6 +1663,7 @@ void createCppQuickFixes()
registerRewriteControlStatementQuickfixes(); registerRewriteControlStatementQuickfixes();
registerRewriteCommentQuickfixes(); registerRewriteCommentQuickfixes();
registerExtractFunctionQuickfix(); registerExtractFunctionQuickfix();
registerExtractLiteralAsParameterQuickfix();
new ExtraRefactoringOperations; new ExtraRefactoringOperations;

View File

@@ -125,17 +125,6 @@ private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
}; };
/*!
Extracts the selected constant and converts it to a parameter of the current function.
Activates on numeric, bool, character, or string literal in the function body.
*/
class ExtractLiteralAsParameter : public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
};
/*! /*!
Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa. Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa.

View File

@@ -0,0 +1,563 @@
// 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 "extractliteralasparameter.h"
#include "../cppeditortr.h"
#include "../cppeditorwidget.h"
#include "../cpprefactoringchanges.h"
#include "cppquickfix.h"
#include "cppquickfixhelpers.h"
#include <cplusplus/ASTPath.h>
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
#ifdef WITH_TESTS
#include "cppquickfix_test.h"
#include <QtTest>
#endif
using namespace CPlusPlus;
using namespace Utils;
namespace CppEditor::Internal {
namespace {
struct ReplaceLiteralsResult
{
Token token;
QString literalText;
};
template <class T>
class ReplaceLiterals : private ASTVisitor
{
public:
ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal)
: ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes),
m_literal(literal)
{
m_result.token = m_file->tokenAt(literal->firstToken());
m_literalTokenText = m_result.token.spell();
m_result.literalText = QLatin1String(m_literalTokenText);
if (m_result.token.isCharLiteral()) {
m_result.literalText.prepend(QLatin1Char('\''));
m_result.literalText.append(QLatin1Char('\''));
if (m_result.token.kind() == T_WIDE_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('L'));
else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('u'));
else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL)
m_result.literalText.prepend(QLatin1Char('U'));
} else if (m_result.token.isStringLiteral()) {
m_result.literalText.prepend(QLatin1Char('"'));
m_result.literalText.append(QLatin1Char('"'));
if (m_result.token.kind() == T_WIDE_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('L'));
else if (m_result.token.kind() == T_UTF16_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('u'));
else if (m_result.token.kind() == T_UTF32_STRING_LITERAL)
m_result.literalText.prepend(QLatin1Char('U'));
}
}
ReplaceLiteralsResult apply(AST *ast)
{
ast->accept(this);
return m_result;
}
private:
bool visit(T *ast) override
{
if (ast != m_literal
&& strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) {
return true;
}
int start, end;
m_file->startAndEndOf(ast->firstToken(), &start, &end);
m_changes->replace(start, end, QLatin1String("newParameter"));
return true;
}
const CppRefactoringFilePtr &m_file;
ChangeSet *m_changes;
T *m_literal;
const char *m_literalTokenText;
ReplaceLiteralsResult m_result;
};
class ExtractLiteralAsParameterOp : public CppQuickFixOperation
{
public:
ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority,
ExpressionAST *literal, FunctionDefinitionAST *function)
: CppQuickFixOperation(interface, priority),
m_literal(literal),
m_functionDefinition(function)
{
setDescription(Tr::tr("Extract Constant as Function Parameter"));
}
struct FoundDeclaration
{
FunctionDeclaratorAST *ast = nullptr;
CppRefactoringFilePtr file;
};
FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring,
FunctionDefinitionAST *ast)
{
FoundDeclaration result;
Function *func = ast->symbol;
if (Class *matchingClass = isMemberFunction(context(), func)) {
// Dealing with member functions
const QualifiedNameId *qName = func->name()->asQualifiedNameId();
for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) {
if (!s->name()
|| !qName->identifier()->match(s->identifier())
|| !s->type()->asFunctionType()
|| !s->type().match(func->type())
|| s->asFunction()) {
continue;
}
const FilePath declFilePath = matchingClass->filePath();
result.file = refactoring.cppFile(declFilePath);
ASTPath astPath(result.file->cppDocument());
const QList<AST *> path = astPath(s->line(), s->column());
SimpleDeclarationAST *simpleDecl = nullptr;
for (AST *node : path) {
simpleDecl = node->asSimpleDeclaration();
if (simpleDecl) {
if (simpleDecl->symbols && !simpleDecl->symbols->next) {
result.ast = functionDeclarator(simpleDecl);
return result;
}
}
}
if (simpleDecl)
break;
}
} else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) {
// Dealing with free functions and inline member functions.
bool isHeaderFile;
FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile);
if (!declFilePath.exists())
return FoundDeclaration();
result.file = refactoring.cppFile(declFilePath);
if (!result.file)
return FoundDeclaration();
const LookupContext lc(result.file->cppDocument(), snapshot());
const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
for (const LookupItem &candidate : candidates) {
if (Symbol *s = candidate.declaration()) {
if (s->asDeclaration()) {
ASTPath astPath(result.file->cppDocument());
const QList<AST *> path = astPath(s->line(), s->column());
for (AST *node : path) {
SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration();
if (simpleDecl) {
result.ast = functionDeclarator(simpleDecl);
return result;
}
}
}
}
}
}
return result;
}
void perform() override
{
FunctionDeclaratorAST *functionDeclaratorOfDefinition
= functionDeclarator(m_functionDefinition);
const CppRefactoringChanges refactoring(snapshot());
const CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
deduceTypeNameOfLiteral(currentFile->cppDocument());
ChangeSet changes;
if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) {
m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
} else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) {
m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
} else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) {
m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile, &changes,
concreteLiteral)
.apply(m_functionDefinition->function_body);
}
const FoundDeclaration functionDeclaration
= findDeclaration(refactoring, m_functionDefinition);
appendFunctionParameter(functionDeclaratorOfDefinition, currentFile, &changes,
!functionDeclaration.ast);
if (functionDeclaration.ast) {
if (currentFile->filePath() != functionDeclaration.file->filePath()) {
ChangeSet declChanges;
appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges,
true);
functionDeclaration.file->setChangeSet(declChanges);
functionDeclaration.file->apply();
} else {
appendFunctionParameter(functionDeclaration.ast, currentFile, &changes,
true);
}
}
currentFile->setChangeSet(changes);
currentFile->apply();
QTextCursor c = currentFile->cursor();
c.setPosition(c.position() - parameterName().length());
editor()->setTextCursor(c);
editor()->renameSymbolUnderCursor();
}
private:
bool hasParameters(FunctionDeclaratorAST *ast) const
{
return ast->parameter_declaration_clause
&& ast->parameter_declaration_clause->parameter_declaration_list
&& ast->parameter_declaration_clause->parameter_declaration_list->value;
}
void deduceTypeNameOfLiteral(const Document::Ptr &document)
{
TypeOfExpression typeOfExpression;
typeOfExpression.init(document, snapshot());
Overview overview;
Scope *scope = m_functionDefinition->symbol->enclosingScope();
const QList<LookupItem> items = typeOfExpression(m_literal, document, scope);
if (!items.isEmpty())
m_typeName = overview.prettyType(items.first().type());
}
static QString parameterName() { return QLatin1String("newParameter"); }
QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const
{
QString str;
if (hasParameters(ast))
str = QLatin1String(", ");
str += m_typeName;
if (!m_typeName.endsWith(QLatin1Char('*')))
str += QLatin1Char(' ');
str += parameterName();
return str;
}
FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const
{
for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) {
FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value);
if (functionDeclaratorAST)
return functionDeclaratorAST;
}
return nullptr;
}
FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const
{
for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) {
FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator();
if (funcdecl)
return funcdecl;
}
return nullptr;
}
FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const
{
return functionDeclarator(ast->declarator);
}
void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file,
ChangeSet *changes, bool addDefaultValue)
{
if (!ast)
return;
if (m_declarationInsertionString.isEmpty())
m_declarationInsertionString = parameterDeclarationTextToInsert(ast);
QString insertion = m_declarationInsertionString;
if (addDefaultValue)
insertion += QLatin1String(" = ") + m_literalInfo.literalText;
changes->insert(file->startOf(ast->rparen_token), insertion);
}
ExpressionAST *m_literal;
FunctionDefinitionAST *m_functionDefinition;
QString m_typeName;
QString m_declarationInsertionString;
ReplaceLiteralsResult m_literalInfo;
};
/*!
Extracts the selected constant and converts it to a parameter of the current function.
Activates on numeric, bool, character, or string literal in the function body.
*/
class ExtractLiteralAsParameter : public CppQuickFixFactory
{
#ifdef WITH_TESTS
public:
static QObject *createTest();
#endif
private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
{
const QList<AST *> &path = interface.path();
if (path.count() < 2)
return;
AST * const lastAst = path.last();
ExpressionAST *literal;
if (!((literal = lastAst->asNumericLiteral())
|| (literal = lastAst->asStringLiteral())
|| (literal = lastAst->asBoolLiteral()))) {
return;
}
FunctionDefinitionAST *function;
int i = path.count() - 2;
while (!(function = path.at(i)->asFunctionDefinition())) {
// Ignore literals in lambda expressions for now.
if (path.at(i)->asLambdaExpression())
return;
if (--i < 0)
return;
}
PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list;
if (!declaratorList)
return;
if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) {
if (declarator->parameter_declaration_clause
&& declarator->parameter_declaration_clause->dot_dot_dot_token) {
// Do not handle functions with ellipsis parameter.
return;
}
}
const int priority = path.size() - 1;
result << new ExtractLiteralAsParameterOp(interface, priority, literal, function);
}
};
#ifdef WITH_TESTS
using namespace Tests;
class ExtractLiteralAsParameterTest : public QObject
{
Q_OBJECT
private slots:
void testTypeDeduction_data()
{
QTest::addColumn<QByteArray>("typeString");
QTest::addColumn<QByteArray>("literal");
QTest::newRow("int")
<< QByteArray("int ") << QByteArray("156");
QTest::newRow("unsigned int")
<< QByteArray("unsigned int ") << QByteArray("156u");
QTest::newRow("long")
<< QByteArray("long ") << QByteArray("156l");
QTest::newRow("unsigned long")
<< QByteArray("unsigned long ") << QByteArray("156ul");
QTest::newRow("long long")
<< QByteArray("long long ") << QByteArray("156ll");
QTest::newRow("unsigned long long")
<< QByteArray("unsigned long long ") << QByteArray("156ull");
QTest::newRow("float")
<< QByteArray("float ") << QByteArray("3.14159f");
QTest::newRow("double")
<< QByteArray("double ") << QByteArray("3.14159");
QTest::newRow("long double")
<< QByteArray("long double ") << QByteArray("3.14159L");
QTest::newRow("bool")
<< QByteArray("bool ") << QByteArray("true");
QTest::newRow("bool")
<< QByteArray("bool ") << QByteArray("false");
QTest::newRow("char")
<< QByteArray("char ") << QByteArray("'X'");
QTest::newRow("wchar_t")
<< QByteArray("wchar_t ") << QByteArray("L'X'");
QTest::newRow("char16_t")
<< QByteArray("char16_t ") << QByteArray("u'X'");
QTest::newRow("char32_t")
<< QByteArray("char32_t ") << QByteArray("U'X'");
QTest::newRow("const char *")
<< QByteArray("const char *") << QByteArray("\"narf\"");
QTest::newRow("const wchar_t *")
<< QByteArray("const wchar_t *") << QByteArray("L\"narf\"");
QTest::newRow("const char16_t *")
<< QByteArray("const char16_t *") << QByteArray("u\"narf\"");
QTest::newRow("const char32_t *")
<< QByteArray("const char32_t *") << QByteArray("U\"narf\"");
}
void testTypeDeduction()
{
QFETCH(QByteArray, typeString);
QFETCH(QByteArray, literal);
const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n");
const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ")
+ literal + QByteArray(") {return newParameter;}\n");
if (literal == "3.14159") {
qWarning("Literal 3.14159 is wrongly reported as int. Skipping.");
return;
} else if (literal == "3.14159L") {
qWarning("Literal 3.14159L is wrongly reported as long. Skipping.");
return;
}
ExtractLiteralAsParameter factory;
QuickFixOperationTest(singleDocument(original, expected), &factory);
}
void testFreeFunctionSeparateFiles()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
QByteArray expected;
// Header File
original =
"void foo(const char *a, long b = 1);\n";
expected =
"void foo(const char *a, long b = 1, int newParameter = 156);\n";
testDocuments << CppTestDocument::create("file.h", original, expected);
// Source File
original =
"void foo(const char *a, long b)\n"
"{return 1@56 + 123 + 156;}\n";
expected =
"void foo(const char *a, long b, int newParameter)\n"
"{return newParameter + 123 + newParameter;}\n";
testDocuments << CppTestDocument::create("file.cpp", original, expected);
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void testMemberFunctionSeparateFiles()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
QByteArray expected;
// Header File
original =
"class Narf {\n"
"public:\n"
" int zort();\n"
"};\n";
expected =
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155);\n"
"};\n";
testDocuments << CppTestDocument::create("file.h", original, expected);
// Source File
original =
"#include \"file.h\"\n\n"
"int Narf::zort()\n"
"{ return 15@5 + 1; }\n";
expected =
"#include \"file.h\"\n\n"
"int Narf::zort(int newParameter)\n"
"{ return newParameter + 1; }\n";
testDocuments << CppTestDocument::create("file.cpp", original, expected);
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void testNotTriggeringForInvalidCode()
{
QList<TestDocumentPtr> testDocuments;
QByteArray original;
original =
"T(\"test\")\n"
"{\n"
" const int i = @14;\n"
"}\n";
testDocuments << CppTestDocument::create("file.cpp", original, "");
ExtractLiteralAsParameter factory;
QuickFixOperationTest(testDocuments, &factory);
}
void test_data()
{
QTest::addColumn<QByteArray>("original");
QTest::addColumn<QByteArray>("expected");
QTest::newRow("ExtractLiteralAsParameter_freeFunction")
<< QByteArray(
"void foo(const char *a, long b = 1)\n"
"{return 1@56 + 123 + 156;}\n")
<< QByteArray(
"void foo(const char *a, long b = 1, int newParameter = 156)\n"
"{return newParameter + 123 + newParameter;}\n");
QTest::newRow("ExtractLiteralAsParameter_memberFunction")
<< QByteArray(
"class Narf {\n"
"public:\n"
" int zort();\n"
"};\n\n"
"int Narf::zort()\n"
"{ return 15@5 + 1; }\n")
<< QByteArray(
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155);\n"
"};\n\n"
"int Narf::zort(int newParameter)\n"
"{ return newParameter + 1; }\n");
QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline")
<< QByteArray(
"class Narf {\n"
"public:\n"
" int zort()\n"
" { return 15@5 + 1; }\n"
"};\n")
<< QByteArray(
"class Narf {\n"
"public:\n"
" int zort(int newParameter = 155)\n"
" { return newParameter + 1; }\n"
"};\n");
}
void test()
{
QFETCH(QByteArray, original);
QFETCH(QByteArray, expected);
ExtractLiteralAsParameter factory;
QuickFixOperationTest(singleDocument(original, expected), &factory);
}
};
QObject *ExtractLiteralAsParameter::createTest() { return new ExtractLiteralAsParameterTest; }
#endif // WITH_TESTS
} // namespace
void registerExtractLiteralAsParameterQuickfix()
{
CppQuickFixFactory::registerFactory<ExtractLiteralAsParameter>();
}
} // namespace CppEditor::Internal
#ifdef WITH_TESTS
#include <extractliteralasparameter.moc>
#endif

View File

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