/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "cppquickfixes.h" #include "cppeditorwidget.h" #include "cppeditordocument.h" #include "cppfunctiondecldeflink.h" #include "cppquickfixassistant.h" #include "cppinsertvirtualmethods.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace CPlusPlus; using namespace CppTools; using namespace TextEditor; using Utils::ChangeSet; namespace CppEditor { static QList g_cppQuickFixFactories; CppQuickFixFactory::CppQuickFixFactory() { g_cppQuickFixFactories.append(this); } CppQuickFixFactory::~CppQuickFixFactory() { g_cppQuickFixFactories.removeOne(this); } const QList &CppQuickFixFactory::cppQuickFixFactories() { return g_cppQuickFixFactories; } namespace Internal { QString inlinePrefix(const QString &targetFile, const std::function &extraCondition = {}) { if (ProjectFile::isHeader(ProjectFile::classify(targetFile)) && (!extraCondition || extraCondition())) { return "inline "; } return {}; } // In the following anonymous namespace all functions are collected, which could be of interest for // different quick fixes. namespace { class NSVisitor : public ASTVisitor { public: NSVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) : ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_remainingNamespaces(namespaces), m_symbolPos(symbolPos) {} const QStringList remainingNamespaces() const { return m_remainingNamespaces; } const NamespaceAST *firstNamespace() const { return m_firstNamespace; } const AST *firstToken() const { return m_firstToken; } const NamespaceAST *enclosingNamespace() const { return m_enclosingNamespace; } private: bool preVisit(AST *ast) override { if (!m_firstToken) m_firstToken = ast; if (m_file->startOf(ast) >= m_symbolPos) m_done = true; return !m_done; } bool visit(NamespaceAST *ns) override { if (!m_firstNamespace) m_firstNamespace = ns; if (m_remainingNamespaces.isEmpty()) { m_done = true; return false; } QString name; const Identifier * const id = translationUnit()->identifier(ns->identifier_token); if (id) name = QString::fromUtf8(id->chars(), id->size()); if (name != m_remainingNamespaces.first()) return name.isEmpty(); if (!ns->linkage_body) { m_done = true; return false; } m_enclosingNamespace = ns; m_remainingNamespaces.removeFirst(); return !m_remainingNamespaces.isEmpty(); } void postVisit(AST *ast) override { if (ast == m_enclosingNamespace) m_done = true; } const CppRefactoringFile * const m_file; const NamespaceAST *m_enclosingNamespace = nullptr; const NamespaceAST *m_firstNamespace = nullptr; const AST *m_firstToken = nullptr; QStringList m_remainingNamespaces; const int m_symbolPos; bool m_done = false; }; /** * @brief The NSCheckerVisitor class checks which namespaces are missing for a given list * of enclosing namespaces at a given position */ class NSCheckerVisitor : public ASTVisitor { public: NSCheckerVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) : ASTVisitor(file->cppDocument()->translationUnit()) , m_file(file) , m_remainingNamespaces(namespaces) , m_symbolPos(symbolPos) {} /** * @brief returns the names of the namespaces that are additionally needed at the symbolPos * @return A list of namespace names, the outermost namespace at index 0 and the innermost * at the last index */ const QStringList remainingNamespaces() const { return m_remainingNamespaces; } private: bool preVisit(AST *ast) override { if (m_file->startOf(ast) >= m_symbolPos) m_done = true; return !m_done; } void postVisit(AST *ast) override { if (!m_done && m_file->endOf(ast) > m_symbolPos) m_done = true; } bool visit(NamespaceAST *ns) override { if (m_remainingNamespaces.isEmpty()) return false; QString name = getName(ns); if (name != m_remainingNamespaces.first()) return false; m_enteredNamespaces.push_back(ns); m_remainingNamespaces.removeFirst(); // if we reached the searched namespace we don't have to search deeper return !m_remainingNamespaces.isEmpty(); } bool visit(UsingDirectiveAST *usingNS) override { // example: we search foo::bar and get 'using namespace foo;using namespace foo::bar;' const QString fullName = Overview{}.prettyName(usingNS->name->name); const QStringList namespaces = fullName.split("::"); if (namespaces.length() > m_remainingNamespaces.length()) return false; // from other using namespace statements const auto curList = m_usingsPerNamespace.find(currentNamespace()); const bool isCurListValid = curList != m_usingsPerNamespace.end(); const bool startEqual = std::equal(namespaces.cbegin(), namespaces.cend(), m_remainingNamespaces.cbegin()); if (startEqual) { if (isCurListValid) { if (namespaces.length() > curList->second.length()) { // eg. we already have 'using namespace foo;' and // now get 'using namespace foo::bar;' curList->second = namespaces; } // the other case: first 'using namespace foo::bar;' and now 'using namespace foo;' } else m_usingsPerNamespace.emplace(currentNamespace(), namespaces); } else if (isCurListValid) { // ex: we have already 'using namespace foo;' and get 'using namespace bar;' now QStringList newlist = curList->second; newlist.append(namespaces); if (newlist.length() <= m_remainingNamespaces.length()) { const bool startEqual = std::equal(newlist.cbegin(), newlist.cend(), m_remainingNamespaces.cbegin()); if (startEqual) curList->second.append(namespaces); } } return false; } void endVisit(NamespaceAST *ns) override { // if the symbolPos was in the namespace and the // namespace has no children, m_done should be true postVisit(ns); if (!m_done && currentNamespace() == ns) { // we were not succesfull in this namespace, so undo all changes m_remainingNamespaces.push_front(getName(currentNamespace())); m_usingsPerNamespace.erase(currentNamespace()); m_enteredNamespaces.pop_back(); } } void endVisit(TranslationUnitAST *) override { // the last node, create the final result // we must handle like the following: We search for foo::bar and have: // using namespace foo::bar; // namespace foo { // // cursor/symbolPos here // } if (m_remainingNamespaces.empty()) { // we are already finished return; } // find the longest combination of normal namespaces + using statements int longestNamespaceList = 0; int enteredNamespaceCount = 0; // check 'using namespace ...;' statements in the global scope const auto namespaces = m_usingsPerNamespace.find(nullptr); if (namespaces != m_usingsPerNamespace.end()) longestNamespaceList = namespaces->second.length(); for (auto ns : m_enteredNamespaces) { ++enteredNamespaceCount; const auto namespaces = m_usingsPerNamespace.find(ns); int newListLength = enteredNamespaceCount; if (namespaces != m_usingsPerNamespace.end()) newListLength += namespaces->second.length(); longestNamespaceList = std::max(newListLength, longestNamespaceList); } m_remainingNamespaces.erase(m_remainingNamespaces.begin(), m_remainingNamespaces.begin() + longestNamespaceList - m_enteredNamespaces.size()); } QString getName(NamespaceAST *ns) { const Identifier *const id = translationUnit()->identifier(ns->identifier_token); if (id) return QString::fromUtf8(id->chars(), id->size()); return {}; } NamespaceAST *currentNamespace() { return m_enteredNamespaces.empty() ? nullptr : m_enteredNamespaces.back(); } const CppRefactoringFile *const m_file; QStringList m_remainingNamespaces; const int m_symbolPos; std::vector m_enteredNamespaces; // track 'using namespace ...' statements std::unordered_map m_usingsPerNamespace; bool m_done = false; }; /** * @brief getListOfMissingNamespacesForLocation checks which namespaces are present at a given * location and returns a list of namespace names that are needed to get the wanted namespace * @param file The file of the location * @param wantedNamespaces the namespace as list that should exists at the insert location * @param loc The location that should be checked (the namespaces should be available there) * @return A list of namespaces that are missing to reach the wanted namespaces. */ QStringList getListOfMissingNamespacesForLocation(const CppRefactoringFile *file, const QStringList &wantedNamespaces, InsertionLocation loc) { NSCheckerVisitor visitor(file, wantedNamespaces, file->position(loc.line(), loc.column())); visitor.accept(file->cppDocument()->translationUnit()->ast()); return visitor.remainingNamespaces(); } enum DefPos { DefPosInsideClass, DefPosOutsideClass, DefPosImplementationFile }; /** * @brief getNamespaceNames Returns a list of namespaces for an enclosing namespaces of a * namespace (contains the namespace itself) * @param firstNamespace the starting namespace (included in the list) * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost * at the last index */ QStringList getNamespaceNames(const Namespace *firstNamespace) { QStringList namespaces; for (const Namespace *scope = firstNamespace; scope; scope = scope->enclosingNamespace()) { if (scope->name() && scope->name()->identifier()) { namespaces.prepend(QString::fromUtf8(scope->name()->identifier()->chars(), scope->name()->identifier()->size())); } } return namespaces; } /** * @brief getNamespaceNames Returns a list of enclosing namespaces for a symbol * @param symbol a symbol from which we want the enclosing namespaces * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost * at the last index */ QStringList getNamespaceNames(const Symbol *symbol) { return getNamespaceNames(symbol->enclosingNamespace()); } // TODO: We should use the "CreateMissing" approach everywhere. enum class NamespaceHandling { CreateMissing, Ignore }; InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder, NamespaceHandling namespaceHandling, CppRefactoringChanges& refactoring, const QString& fileName) { QTC_ASSERT(symbol, return InsertionLocation()); CppRefactoringFilePtr file = refactoring.file(fileName); QStringList requiredNamespaces; if (namespaceHandling == NamespaceHandling::CreateMissing) { requiredNamespaces = getNamespaceNames(symbol); } // Try to find optimal location // FIXME: The locator should not return a valid location if the namespaces don't match // (or provide enough context). const InsertionPointLocator locator(refactoring); const QList list = locator.methodDefinition(symbol, useSymbolFinder, fileName); const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(fileName)); const bool hasIncludeGuard = isHeader && !file->cppDocument()->includeGuardMacroName().isEmpty(); int lastLine; if (hasIncludeGuard) { const TranslationUnit * const tu = file->cppDocument()->translationUnit(); tu->getTokenStartPosition(tu->ast()->lastToken(), &lastLine); } int i = 0; for ( ; i < list.count(); ++i) { InsertionLocation location = list.at(i); if (!location.isValid() || location.fileName() != fileName) continue; if (hasIncludeGuard && location.line() == lastLine) continue; if (!requiredNamespaces.isEmpty()) { QStringList missing = getListOfMissingNamespacesForLocation(file.get(), requiredNamespaces, location); if (!missing.isEmpty()) continue; } return location; } // ...failed, // if class member try to get position right after class int line = 0, column = 0; if (Class *clazz = symbol->enclosingClass()) { if (symbol->fileName() == fileName.toUtf8()) { file->cppDocument()->translationUnit()->getPosition(clazz->endOffset(), &line, &column); if (line != 0) { ++column; // Skipping the ";" return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String(""), line, column); } } } // fall through: position at end of file, unless we find a matching namespace const QTextDocument *doc = file->document(); int pos = qMax(0, doc->characterCount() - 1); QString prefix = "\n\n"; QString suffix = "\n\n"; NSVisitor visitor(file.data(), requiredNamespaces, pos); visitor.accept(file->cppDocument()->translationUnit()->ast()); if (visitor.enclosingNamespace()) pos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; for (const QString &ns : visitor.remainingNamespaces()) { prefix += "namespace " + ns + " {\n"; suffix += "}\n"; } //TODO watch for moc-includes file->lineAndColumn(pos, &line, &column); return InsertionLocation(fileName, prefix, suffix, line, column); } inline bool isQtStringLiteral(const QByteArray &id) { return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral"; } inline bool isQtStringTranslation(const QByteArray &id) { return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP"; } inline bool isQtFuzzyComparable(const QString &typeName) { return typeName == QLatin1String("double") || typeName == QLatin1String("float") || typeName == QLatin1String("qreal"); } Class *isMemberFunction(const LookupContext &context, Function *function) { QTC_ASSERT(function, return nullptr); Scope *enclosingScope = function->enclosingScope(); while (!(enclosingScope->isNamespace() || enclosingScope->isClass())) enclosingScope = enclosingScope->enclosingScope(); QTC_ASSERT(enclosingScope != nullptr, return nullptr); const Name *functionName = function->name(); if (!functionName) return nullptr; if (!functionName->isQualifiedNameId()) return nullptr; // trying to add a declaration for a global function const QualifiedNameId *q = functionName->asQualifiedNameId(); if (!q->base()) return nullptr; if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { foreach (Symbol *s, binding->symbols()) { if (Class *matchingClass = s->asClass()) return matchingClass; } } return nullptr; } Namespace *isNamespaceFunction(const LookupContext &context, Function *function) { QTC_ASSERT(function, return nullptr); if (isMemberFunction(context, function)) return nullptr; Scope *enclosingScope = function->enclosingScope(); while (!(enclosingScope->isNamespace() || enclosingScope->isClass())) enclosingScope = enclosingScope->enclosingScope(); QTC_ASSERT(enclosingScope != nullptr, return nullptr); const Name *functionName = function->name(); if (!functionName) return nullptr; // global namespace if (!functionName->isQualifiedNameId()) { foreach (Symbol *s, context.globalNamespace()->symbols()) { if (Namespace *matchingNamespace = s->asNamespace()) return matchingNamespace; } return nullptr; } const QualifiedNameId *q = functionName->asQualifiedNameId(); if (!q->base()) return nullptr; if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { foreach (Symbol *s, binding->symbols()) { if (Namespace *matchingNamespace = s->asNamespace()) return matchingNamespace; } } return nullptr; } // Given include is e.g. "afile.h" or (quotes/angle brackets included!). void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file, const Document::Ptr &cppDocument) { // Find optimal position using namespace IncludeUtils; LineForNewIncludeDirective finder(file->document(), cppDocument, LineForNewIncludeDirective::IgnoreMocIncludes, LineForNewIncludeDirective::AutoDetect); unsigned newLinesToPrepend = 0; unsigned newLinesToAppend = 0; const int insertLine = finder(include, &newLinesToPrepend, &newLinesToAppend); QTC_ASSERT(insertLine >= 1, return); const int insertPosition = file->position(insertLine, 1); QTC_ASSERT(insertPosition >= 0, return); // Construct text to insert const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n'); QString prependedNewLines, appendedNewLines; while (newLinesToAppend--) appendedNewLines += QLatin1String("\n"); while (newLinesToPrepend--) prependedNewLines += QLatin1String("\n"); const QString textToInsert = prependedNewLines + includeLine + appendedNewLines; // Insert ChangeSet changes; changes.insert(insertPosition, textToInsert); file->setChangeSet(changes); file->apply(); } bool nameIncludesOperatorName(const Name *name) { return name->isOperatorNameId() || (name->isQualifiedNameId() && name->asQualifiedNameId()->name()->isOperatorNameId()); } QString memberBaseName(const QString &name) { QString baseName = name; // Remove leading and trailing "_" while (baseName.startsWith(QLatin1Char('_'))) baseName.remove(0, 1); while (baseName.endsWith(QLatin1Char('_'))) baseName.chop(1); if (baseName != name) return baseName; // If no leading/trailing "_": remove "m_" and "m" prefix if (baseName.startsWith(QLatin1String("m_"))) { baseName.remove(0, 2); } else if (baseName.startsWith(QLatin1Char('m')) && baseName.length() > 1 && baseName.at(1).isUpper()) { baseName.remove(0, 1); baseName[0] = baseName.at(0).toLower(); } return baseName; } } // anonymous namespace namespace { class InverseLogicalComparisonOp: public CppQuickFixOperation { public: InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority, BinaryExpressionAST *binary, Kind invertToken) : CppQuickFixOperation(interface, priority) , binary(binary) { Token tok; tok.f.kind = invertToken; replacement = QLatin1String(tok.spell()); // check for enclosing nested expression if (priority - 1 >= 0) nested = interface.path()[priority - 1]->asNestedExpression(); // check for ! before parentheses if (nested && priority - 2 >= 0) { negation = interface.path()[priority - 2]->asUnaryExpression(); if (negation && !interface.currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) negation = nullptr; } } QString description() const override { return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; if (negation) { // can't remove parentheses since that might break precedence changes.remove(currentFile->range(negation->unary_op_token)); } else if (nested) { changes.insert(currentFile->startOf(nested), QLatin1String("!")); } else { changes.insert(currentFile->startOf(binary), QLatin1String("!(")); changes.insert(currentFile->endOf(binary), QLatin1String(")")); } changes.replace(currentFile->range(binary->binary_op_token), replacement); currentFile->setChangeSet(changes); currentFile->apply(); } private: BinaryExpressionAST *binary = nullptr; NestedExpressionAST *nested = nullptr; UnaryExpressionAST *negation = nullptr; QString replacement; }; } // anonymous namespace void InverseLogicalComparison::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { CppRefactoringFilePtr file = interface.currentFile(); const QList &path = interface.path(); int index = path.size() - 1; BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); if (!binary) return; if (!interface.isCursorOn(binary->binary_op_token)) return; Kind invertToken; switch (file->tokenAt(binary->binary_op_token).kind()) { case T_LESS_EQUAL: invertToken = T_GREATER; break; case T_LESS: invertToken = T_GREATER_EQUAL; break; case T_GREATER: invertToken = T_LESS_EQUAL; break; case T_GREATER_EQUAL: invertToken = T_LESS; break; case T_EQUAL_EQUAL: invertToken = T_EXCLAIM_EQUAL; break; case T_EXCLAIM_EQUAL: invertToken = T_EQUAL_EQUAL; break; default: return; } result << new InverseLogicalComparisonOp(interface, index, binary, invertToken); } namespace { class FlipLogicalOperandsOp: public CppQuickFixOperation { public: FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority, BinaryExpressionAST *binary, QString replacement) : CppQuickFixOperation(interface) , binary(binary) , replacement(replacement) { setPriority(priority); } QString description() const override { if (replacement.isEmpty()) return QApplication::translate("CppTools::QuickFix", "Swap Operands"); else return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; changes.flip(currentFile->range(binary->left_expression), currentFile->range(binary->right_expression)); if (!replacement.isEmpty()) changes.replace(currentFile->range(binary->binary_op_token), replacement); currentFile->setChangeSet(changes); currentFile->apply(); } private: BinaryExpressionAST *binary; QString replacement; }; } // anonymous namespace void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); int index = path.size() - 1; BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); if (!binary) return; if (!interface.isCursorOn(binary->binary_op_token)) return; Kind flipToken; switch (file->tokenAt(binary->binary_op_token).kind()) { case T_LESS_EQUAL: flipToken = T_GREATER_EQUAL; break; case T_LESS: flipToken = T_GREATER; break; case T_GREATER: flipToken = T_LESS; break; case T_GREATER_EQUAL: flipToken = T_LESS_EQUAL; break; case T_EQUAL_EQUAL: case T_EXCLAIM_EQUAL: case T_AMPER_AMPER: case T_PIPE_PIPE: flipToken = T_EOF_SYMBOL; break; default: return; } QString replacement; if (flipToken != T_EOF_SYMBOL) { Token tok; tok.f.kind = flipToken; replacement = QLatin1String(tok.spell()); } result << new FlipLogicalOperandsOp(interface, index, binary, replacement); } namespace { class RewriteLogicalAndOp: public CppQuickFixOperation { public: QSharedPointer mk; UnaryExpressionAST *left; UnaryExpressionAST *right; BinaryExpressionAST *pattern; RewriteLogicalAndOp(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) , mk(new ASTPatternBuilder) { left = mk->UnaryExpression(); right = mk->UnaryExpression(); pattern = mk->BinaryExpression(left, right); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; changes.replace(currentFile->range(pattern->binary_op_token), QLatin1String("||")); changes.remove(currentFile->range(left->unary_op_token)); changes.remove(currentFile->range(right->unary_op_token)); const int start = currentFile->startOf(pattern); const int end = currentFile->endOf(pattern); changes.insert(start, QLatin1String("!(")); changes.insert(end, QLatin1String(")")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(pattern)); currentFile->apply(); } }; } // anonymous namespace void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { BinaryExpressionAST *expression = nullptr; const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); int index = path.size() - 1; for (; index != -1; --index) { expression = path.at(index)->asBinaryExpression(); if (expression) break; } if (!expression) return; if (!interface.isCursorOn(expression->binary_op_token)) return; QSharedPointer op(new RewriteLogicalAndOp(interface)); ASTMatcher matcher; if (expression->match(op->pattern, &matcher) && file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) && file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) && file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) { op->setDescription(QApplication::translate("CppTools::QuickFix", "Rewrite Condition Using ||")); op->setPriority(index); result.append(op); } } static bool checkDeclarationForSplit(SimpleDeclarationAST *declaration) { if (!declaration->semicolon_token) return false; if (!declaration->decl_specifier_list) return false; for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) { SpecifierAST *specifier = it->value; if (specifier->asEnumSpecifier() || specifier->asClassSpecifier()) return false; } return declaration->declarator_list && declaration->declarator_list->next; } namespace { class SplitSimpleDeclarationOp: public CppQuickFixOperation { public: SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority, SimpleDeclarationAST *decl) : CppQuickFixOperation(interface, priority) , declaration(decl) { setDescription(QApplication::translate("CppTools::QuickFix", "Split Declaration")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; SpecifierListAST *specifiers = declaration->decl_specifier_list; int declSpecifiersStart = currentFile->startOf(specifiers->firstToken()); int declSpecifiersEnd = currentFile->endOf(specifiers->lastToken() - 1); int insertPos = currentFile->endOf(declaration->semicolon_token); DeclaratorAST *prevDeclarator = declaration->declarator_list->value; for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) { DeclaratorAST *declarator = it->value; changes.insert(insertPos, QLatin1String("\n")); changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos); changes.insert(insertPos, QLatin1String(" ")); changes.move(currentFile->range(declarator), insertPos); changes.insert(insertPos, QLatin1String(";")); const int prevDeclEnd = currentFile->endOf(prevDeclarator); changes.remove(prevDeclEnd, currentFile->startOf(declarator)); prevDeclarator = declarator; } currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(declaration)); currentFile->apply(); } private: SimpleDeclarationAST *declaration; }; } // anonymous namespace void SplitSimpleDeclaration::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { CoreDeclaratorAST *core_declarator = nullptr; const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); const int cursorPosition = file->cursor().selectionStart(); for (int index = path.size() - 1; index != -1; --index) { AST *node = path.at(index); if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) { core_declarator = coreDecl; } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { if (checkDeclarationForSplit(simpleDecl)) { SimpleDeclarationAST *declaration = simpleDecl; const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken()); const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1); if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) { // the AST node under cursor is a specifier. result << new SplitSimpleDeclarationOp(interface, index, declaration); return; } if (core_declarator && interface.isCursorOn(core_declarator)) { // got a core-declarator under the text cursor. result << new SplitSimpleDeclarationOp(interface, index, declaration); return; } } return; } } } namespace { class AddBracesToIfOp: public CppQuickFixOperation { public: AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, const IfStatementAST *statement) : CppQuickFixOperation(interface, priority) , _statement(statement) { setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; const int start = currentFile->endOf(_statement->rparen_token); changes.insert(start, QLatin1String(" {")); const int end = currentFile->endOf(_statement->statement->lastToken() - 1); changes.insert(end, QLatin1String("\n}")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(ChangeSet::Range(start, end)); currentFile->apply(); } private: const IfStatementAST * const _statement; }; } // anonymous namespace void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); // show when we're on the 'if' of an if statement int index = path.size() - 1; IfStatementAST *ifStatement = path.at(index)->asIfStatement(); if (ifStatement && interface.isCursorOn(ifStatement->if_token) && ifStatement->statement && !ifStatement->statement->asCompoundStatement()) { result << new AddBracesToIfOp(interface, index, ifStatement); return; } // or if we're on the statement contained in the if // ### This may not be such a good idea, consider nested ifs... for (; index != -1; --index) { IfStatementAST *ifStatement = path.at(index)->asIfStatement(); if (ifStatement && ifStatement->statement && interface.isCursorOn(ifStatement->statement) && !ifStatement->statement->asCompoundStatement()) { result << new AddBracesToIfOp(interface, index, ifStatement); return; } } // ### This could very well be extended to the else branch // and other nodes entirely. } namespace { class MoveDeclarationOutOfIfOp: public CppQuickFixOperation { public: MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) { setDescription(QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition")); reset(); } void reset() { condition = mk.Condition(); pattern = mk.IfStatement(condition); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; changes.copy(currentFile->range(core), currentFile->startOf(condition)); int insertPos = currentFile->startOf(pattern); changes.move(currentFile->range(condition), insertPos); changes.insert(insertPos, QLatin1String(";\n")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(pattern)); currentFile->apply(); } ASTMatcher matcher; ASTPatternBuilder mk; ConditionAST *condition = nullptr; IfStatementAST *pattern = nullptr; CoreDeclaratorAST *core = nullptr; }; } // anonymous namespace void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); using Ptr = QSharedPointer; Ptr op(new MoveDeclarationOutOfIfOp(interface)); int index = path.size() - 1; for (; index != -1; --index) { if (IfStatementAST *statement = path.at(index)->asIfStatement()) { if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { DeclaratorAST *declarator = op->condition->declarator; op->core = declarator->core_declarator; if (!op->core) return; if (interface.isCursorOn(op->core)) { op->setPriority(index); result.append(op); return; } op->reset(); } } } } namespace { class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation { public: MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) { setDescription(QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition")); reset(); } void reset() { condition = mk.Condition(); pattern = mk.WhileStatement(condition); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; changes.insert(currentFile->startOf(condition), QLatin1String("(")); changes.insert(currentFile->endOf(condition), QLatin1String(") != 0")); int insertPos = currentFile->startOf(pattern); const int conditionStart = currentFile->startOf(condition); changes.move(conditionStart, currentFile->startOf(core), insertPos); changes.copy(currentFile->range(core), insertPos); changes.insert(insertPos, QLatin1String(";\n")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(pattern)); currentFile->apply(); } ASTMatcher matcher; ASTPatternBuilder mk; ConditionAST *condition = nullptr; WhileStatementAST *pattern = nullptr; CoreDeclaratorAST *core = nullptr; }; } // anonymous namespace void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); QSharedPointer op(new MoveDeclarationOutOfWhileOp(interface)); int index = path.size() - 1; for (; index != -1; --index) { if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) { if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { DeclaratorAST *declarator = op->condition->declarator; op->core = declarator->core_declarator; if (!op->core) return; if (!declarator->equal_token) return; if (!declarator->initializer) return; if (interface.isCursorOn(op->core)) { op->setPriority(index); result.append(op); return; } op->reset(); } } } } namespace { class SplitIfStatementOp: public CppQuickFixOperation { public: SplitIfStatementOp(const CppQuickFixInterface &interface, int priority, IfStatementAST *pattern, BinaryExpressionAST *condition) : CppQuickFixOperation(interface, priority) , pattern(pattern) , condition(condition) { setDescription(QApplication::translate("CppTools::QuickFix", "Split if Statement")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); const Token binaryToken = currentFile->tokenAt(condition->binary_op_token); if (binaryToken.is(T_AMPER_AMPER)) splitAndCondition(currentFile); else splitOrCondition(currentFile); } void splitAndCondition(CppRefactoringFilePtr currentFile) const { ChangeSet changes; int startPos = currentFile->startOf(pattern); changes.insert(startPos, QLatin1String("if (")); changes.move(currentFile->range(condition->left_expression), startPos); changes.insert(startPos, QLatin1String(") {\n")); const int lExprEnd = currentFile->endOf(condition->left_expression); changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); changes.insert(currentFile->endOf(pattern), QLatin1String("\n}")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(pattern)); currentFile->apply(); } void splitOrCondition(CppRefactoringFilePtr currentFile) const { ChangeSet changes; StatementAST *ifTrueStatement = pattern->statement; CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement(); int insertPos = currentFile->endOf(ifTrueStatement); if (compoundStatement) changes.insert(insertPos, QLatin1String(" ")); else changes.insert(insertPos, QLatin1String("\n")); changes.insert(insertPos, QLatin1String("else if (")); const int rExprStart = currentFile->startOf(condition->right_expression); changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos); changes.insert(insertPos, QLatin1String(")")); const int rParenEnd = currentFile->endOf(pattern->rparen_token); changes.copy(rParenEnd, currentFile->endOf(pattern->statement), insertPos); const int lExprEnd = currentFile->endOf(condition->left_expression); changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); currentFile->setChangeSet(changes); currentFile->appendIndentRange(currentFile->range(pattern)); currentFile->apply(); } private: IfStatementAST *pattern; BinaryExpressionAST *condition; }; } // anonymous namespace void SplitIfStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { IfStatementAST *pattern = nullptr; const QList &path = interface.path(); int index = path.size() - 1; for (; index != -1; --index) { AST *node = path.at(index); if (IfStatementAST *stmt = node->asIfStatement()) { pattern = stmt; break; } } if (!pattern || !pattern->statement) return; unsigned splitKind = 0; for (++index; index < path.size(); ++index) { AST *node = path.at(index); BinaryExpressionAST *condition = node->asBinaryExpression(); if (!condition) return; Token binaryToken = interface.currentFile()->tokenAt(condition->binary_op_token); // only accept a chain of ||s or &&s - no mixing if (!splitKind) { splitKind = binaryToken.kind(); if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE) return; // we can't reliably split &&s in ifs with an else branch if (splitKind == T_AMPER_AMPER && pattern->else_statement) return; } else if (splitKind != binaryToken.kind()) { return; } if (interface.isCursorOn(condition->binary_op_token)) { result << new SplitIfStatementOp(interface, index, pattern, condition); return; } } } /* 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, EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction | EncloseInQStringLiteralAction, TranslateTrAction = 0x8, TranslateQCoreApplicationAction = 0x10, TranslateNoopAction = 0x20, TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction, RemoveObjectiveCAction = 0x40, ConvertEscapeSequencesToCharAction = 0x100, ConvertEscapeSequencesToStringAction = 0x200, SingleQuoteAction = 0x400, DoubleQuoteAction = 0x800 }; /* 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 QApplication::translate("CppTools::QuickFix", "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 & 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 &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.file(filePath().toString()); 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::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { StringLiteralType type = TypeNone; QByteArray enclosingFunction; const QList &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 = QApplication::translate("CppTools::QuickFix", "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 = QApplication::translate("CppTools::QuickFix", "Convert to Character Literal and Enclose in QLatin1Char(...)"); result << new WrapStringLiteralOp(interface, priority, actions, description, literal); actions &= ~EncloseInQLatin1CharAction; description = QApplication::translate("CppTools::QuickFix", "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); } } void TranslateStringLiteral::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { // Initialize StringLiteralType type = TypeNone; QByteArray enclosingFunction; const QList &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; QSharedPointer control = interface.context().bindings()->control(); const Name *trName = control->identifier("tr"); // Check whether we are in a function: const QString description = QApplication::translate("CppTools::QuickFix", "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? foreach (const LookupItem &r, b->find(trName)) { Symbol *s = r.declaration(); if (s->type()->isFunctionType()) { // 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; foreach (const Name *n, LookupContext::path(function)) { 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(QApplication::translate("CppTools::QuickFix", "Convert to Objective-C String Literal")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); 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::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { CppRefactoringFilePtr file = interface.currentFile(); if (!interface.editor()->cppEditorDocument()->isObjCEnabled()) return; StringLiteralType type = TypeNone; QByteArray enclosingFunction; CallAST *qlatin1Call; const QList &path = interface.path(); ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction, &qlatin1Call); if (!literal || type != TypeString) return; if (!isQtStringLiteral(enclosingFunction)) qlatin1Call = nullptr; result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(), qlatin1Call); } namespace { class ConvertNumericLiteralOp: public CppQuickFixOperation { public: ConvertNumericLiteralOp(const CppQuickFixInterface &interface, int start, int end, const QString &replacement) : CppQuickFixOperation(interface) , start(start) , end(end) , replacement(replacement) {} void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; changes.replace(start, end, replacement); currentFile->setChangeSet(changes); currentFile->apply(); } private: int start, end; QString replacement; }; } // anonymous namespace void ConvertNumericLiteral::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); if (path.isEmpty()) return; NumericLiteralAST *literal = path.last()->asNumericLiteral(); if (!literal) return; Token token = file->tokenAt(literal->asNumericLiteral()->literal_token); if (!token.is(T_NUMERIC_LITERAL)) return; const NumericLiteral *numeric = token.number; if (numeric->isDouble() || numeric->isFloat()) return; // remove trailing L or U and stuff const char * const spell = numeric->chars(); int numberLength = numeric->size(); while (numberLength > 0 && !std::isxdigit(spell[numberLength - 1])) --numberLength; if (numberLength < 1) return; // convert to number bool valid; ulong value = 0; const QString x = QString::fromUtf8(spell).left(numberLength); if (x.startsWith("0b", Qt::CaseInsensitive)) value = x.mid(2).toULong(&valid, 2); else value = x.toULong(&valid, 0); if (!valid) return; const int priority = path.size() - 1; // very high priority const int start = file->startOf(literal); const char * const str = numeric->chars(); const bool isBinary = numberLength > 2 && str[0] == '0' && tolower(str[1]) == 'b'; const bool isOctal = numberLength >= 2 && str[0] == '0' && str[1] >= '0' && str[1] <= '7'; const bool isDecimal = !(isBinary || isOctal || numeric->isHex()); if (!numeric->isHex()) { /* Convert integer literal to hex representation. Replace 0b100000 32 040 With 0x20 */ const QString replacement = QString::asprintf("0x%lX", value); auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Hexadecimal")); op->setPriority(priority); result << op; } if (!isOctal) { /* Convert integer literal to octal representation. Replace 0b100000 32 0x20 With 040 */ const QString replacement = QString::asprintf("0%lo", value); auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Octal")); op->setPriority(priority); result << op; } if (!isDecimal) { /* Convert integer literal to decimal representation. Replace 0b100000 0x20 040 With 32 */ const QString replacement = QString::asprintf("%lu", value); auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Decimal")); op->setPriority(priority); result << op; } if (!isBinary) { /* Convert integer literal to binary representation. Replace 32 0x20 040 With 0b100000 */ QString replacement = "0b"; if (value == 0) { replacement.append('0'); } else { std::bitset::digits> b(value); QRegularExpression re("^[0]*"); replacement.append(QString::fromStdString(b.to_string()).remove(re)); } auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Binary")); op->setPriority(priority); result << op; } } namespace { class AddLocalDeclarationOp: public CppQuickFixOperation { public: AddLocalDeclarationOp(const CppQuickFixInterface &interface, int priority, const BinaryExpressionAST *binaryAST, const SimpleNameAST *simpleNameAST) : CppQuickFixOperation(interface, priority) , binaryAST(binaryAST) , simpleNameAST(simpleNameAST) { setDescription(QApplication::translate("CppTools::QuickFix", "Add Local Declaration")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); TypeOfExpression typeOfExpression; typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings()); Scope *scope = currentFile->scopeAt(binaryAST->firstToken()); const QList result = typeOfExpression(currentFile->textOf(binaryAST->right_expression).toUtf8(), scope, TypeOfExpression::Preprocess); if (!result.isEmpty()) { SubstitutionEnvironment env; env.setContext(context()); env.switchScope(result.first().scope()); ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); if (!con) con = typeOfExpression.context().globalNamespace(); UseMinimalNames q(con); env.enter(&q); Control *control = context().bindings()->control().data(); FullySpecifiedType tn = rewriteType(result.first().type(), &env, control); Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); QString ty = oo.prettyType(tn, simpleNameAST->name); if (!ty.isEmpty()) { ChangeSet changes; changes.replace(currentFile->startOf(binaryAST), currentFile->endOf(simpleNameAST), ty); currentFile->setChangeSet(changes); currentFile->apply(); } } } private: const BinaryExpressionAST *binaryAST; const SimpleNameAST *simpleNameAST; }; } // anonymous namespace void AddLocalDeclaration::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); for (int index = path.size() - 1; index != -1; --index) { if (BinaryExpressionAST *binary = path.at(index)->asBinaryExpression()) { if (binary->left_expression && binary->right_expression && file->tokenAt(binary->binary_op_token).is(T_EQUAL)) { IdExpressionAST *idExpr = binary->left_expression->asIdExpression(); if (interface.isCursorOn(binary->left_expression) && idExpr && idExpr->name->asSimpleName() != nullptr) { SimpleNameAST *nameAST = idExpr->name->asSimpleName(); const QList results = interface.context().lookup(nameAST->name, file->scopeAt(nameAST->firstToken())); Declaration *decl = nullptr; foreach (const LookupItem &r, results) { if (!r.declaration()) continue; if (Declaration *d = r.declaration()->asDeclaration()) { if (!d->type()->isFunctionType()) { decl = d; break; } } } if (!decl) { result << new AddLocalDeclarationOp(interface, index, binary, nameAST); return; } } } } } } namespace { class ConvertToCamelCaseOp: public CppQuickFixOperation { public: ConvertToCamelCaseOp(const CppQuickFixInterface &interface, const QString &name, const AST *nameAst, bool test) : CppQuickFixOperation(interface, -1) , m_name(name) , m_nameAst(nameAst) , m_isAllUpper(name.isUpper()) , m_test(test) { setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Camel Case")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); QString newName = m_isAllUpper ? m_name.toLower() : m_name; for (int i = 1; i < newName.length(); ++i) { const QChar c = newName.at(i); if (c.isUpper() && m_isAllUpper) { newName[i] = c.toLower(); } else if (i < newName.length() - 1 && isConvertibleUnderscore(newName, i)) { newName.remove(i, 1); newName[i] = newName.at(i).toUpper(); } } if (m_test) { ChangeSet changeSet; changeSet.replace(currentFile->range(m_nameAst), newName); currentFile->setChangeSet(changeSet); currentFile->apply(); } else { editor()->renameUsages(newName); } } static bool isConvertibleUnderscore(const QString &name, int pos) { return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter() && !(pos == 1 && name.at(0) == QLatin1Char('m')); } private: const QString m_name; const AST * const m_nameAst; const bool m_isAllUpper; const bool m_test; }; } // anonymous namespace void ConvertToCamelCase::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); if (path.isEmpty()) return; AST * const ast = path.last(); const Name *name = nullptr; const AST *astForName = nullptr; if (const NameAST * const nameAst = ast->asName()) { if (nameAst->name && nameAst->name->asNameId()) { astForName = nameAst; name = nameAst->name; } } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) { astForName = namespaceAst; name = namespaceAst->symbol->name(); } if (!name) return; QString nameString = QString::fromUtf8(name->identifier()->chars()); if (nameString.length() < 3) return; for (int i = 1; i < nameString.length() - 1; ++i) { if (ConvertToCamelCaseOp::isConvertibleUnderscore(nameString, i)) { result << new ConvertToCamelCaseOp(interface, nameString, astForName, m_test); return; } } } AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp( const CppQuickFixInterface &interface, int priority, const QString &include) : CppQuickFixOperation(interface, priority) , m_include(include) { setDescription(QApplication::translate("CppTools::QuickFix", "Add #include %1").arg(m_include)); } void AddIncludeForUndefinedIdentifierOp::perform() { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr file = refactoring.file(filePath().toString()); insertNewIncludeDirective(m_include, file, semanticInfo().doc); } AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( const CppQuickFixInterface &interface, int priority, const QString &fqClassName, int symbolPos) : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) { setDescription(QApplication::translate("CppTools::QuickFix", "Add forward declaration for %1").arg(m_className)); } void AddForwardDeclForUndefinedIdentifierOp::perform() { const QStringList parts = m_className.split("::"); QTC_ASSERT(!parts.isEmpty(), return); const QStringList namespaces = parts.mid(0, parts.length() - 1); CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr file = refactoring.file(filePath().toString()); NSVisitor visitor(file.data(), namespaces, m_symbolPos); visitor.accept(file->cppDocument()->translationUnit()->ast()); const auto stringToInsert = [&visitor, symbol = parts.last()] { QString s = "\n"; for (const QString &ns : visitor.remainingNamespaces()) s += "namespace " + ns + " { "; s += "class " + symbol + ';'; for (int i = 0; i < visitor.remainingNamespaces().size(); ++i) s += " }"; return s; }; int insertPos = 0; // Find the position to insert: // If we have a matching namespace, we do the insertion there. // If we don't have a matching namespace, but there is another namespace in the file, // we assume that to be a good position for our insertion. // Otherwise, do the insertion after the last include that comes before the use of the symbol. // If there is no such include, do the insertion before the first token. if (visitor.enclosingNamespace()) { insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; } else if (visitor.firstNamespace()) { insertPos = file->startOf(visitor.firstNamespace()); } else { const QTextCursor tc = file->document()->find( QRegularExpression("^\\s*#include .*$"), m_symbolPos, QTextDocument::FindBackward | QTextDocument::FindCaseSensitively); if (!tc.isNull()) insertPos = tc.position() + 1; else if (visitor.firstToken()) insertPos = file->startOf(visitor.firstToken()); } QString insertion = stringToInsert(); if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator) insertion.prepend('\n'); if (file->charAt(insertPos) != QChar::ParagraphSeparator) insertion.append('\n'); ChangeSet s; s.insert(insertPos, insertion); file->setChangeSet(s); file->apply(); } namespace { QString findShortestInclude(const QString currentDocumentFilePath, const QString candidateFilePath, const ProjectExplorer::HeaderPaths &headerPaths) { QString result; const QFileInfo fileInfo(candidateFilePath); if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) { result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"'); } else { foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) { if (!candidateFilePath.startsWith(headerPath.path)) continue; QString relativePath = candidateFilePath.mid(headerPath.path.size()); if (!relativePath.isEmpty() && relativePath.at(0) == QLatin1Char('/')) relativePath = relativePath.mid(1); if (result.isEmpty() || relativePath.size() + 2 < result.size()) result = QLatin1Char('<') + relativePath + QLatin1Char('>'); } } return result; } QString findMatchingInclude(const QString &className, const ProjectExplorer::HeaderPaths &headerPaths) { const QStringList candidateFileNames{className, className + ".h", className + ".hpp", className.toLower(), className.toLower() + ".h", className.toLower() + ".hpp"}; for (const QString &fileName : candidateFileNames) { for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) { const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName; const QFileInfo fileInfo(headerPathCandidate); if (fileInfo.exists() && fileInfo.isFile()) return '<' + fileName + '>'; } } return {}; } ProjectExplorer::HeaderPaths relevantHeaderPaths(const QString &filePath) { ProjectExplorer::HeaderPaths headerPaths; CppModelManager *modelManager = CppModelManager::instance(); const QList projectParts = modelManager->projectPart(filePath); if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none headerPaths += modelManager->headerPaths(); } else { foreach (const ProjectPart::Ptr &part, projectParts) headerPaths += part->headerPaths; } return headerPaths; } NameAST *nameUnderCursor(const QList &path) { if (path.isEmpty()) return nullptr; NameAST *nameAst = nullptr; for (int i = path.size() - 1; i >= 0; --i) { AST * const ast = path.at(i); if (SimpleNameAST *simpleName = ast->asSimpleName()) { nameAst = simpleName; } else if (TemplateIdAST *templateId = ast->asTemplateId()) { nameAst = templateId; } else if (nameAst && ast->asNamedTypeSpecifier()) { break; // Stop at "Foo" for "N::Bar<@Foo>" } else if (QualifiedNameAST *qualifiedName = ast->asQualifiedName()) { nameAst = qualifiedName; break; } } return nameAst; } enum class LookupResult { Definition, Declaration, None }; LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) { QTC_ASSERT(nameAst && nameAst->name, return LookupResult::None); // Find the enclosing scope int line, column; const Document::Ptr doc = interface.semanticInfo().doc; doc->translationUnit()->getTokenStartPosition(nameAst->firstToken(), &line, &column); Scope *scope = doc->scopeAt(line, column); if (!scope) return LookupResult::None; // Try to find the class/template definition const Name *name = nameAst->name; const QList results = interface.context().lookup(name, scope); foreach (const LookupItem &item, results) { if (Symbol *declaration = item.declaration()) { if (declaration->isClass()) return LookupResult::Definition; if (declaration->isForwardClassDeclaration()) return LookupResult::Declaration; if (Template *templ = declaration->asTemplate()) { if (Symbol *declaration = templ->declaration()) { if (declaration->isClass()) return LookupResult::Definition; if (declaration->isForwardClassDeclaration()) return LookupResult::Declaration; } } } } return LookupResult::None; } QString templateNameAsString(const TemplateNameId *templateName) { const Identifier *id = templateName->identifier(); return QString::fromUtf8(id->chars(), id->size()); } Snapshot forwardingHeaders(const CppQuickFixInterface &interface) { Snapshot result; foreach (Document::Ptr doc, interface.snapshot()) { if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) result.insert(doc); } return result; } bool matchName(const Name *name, QList *matches, QString *className) { if (!name) return false; QString simpleName; if (Core::ILocatorFilter *classesFilter = CppTools::CppModelManager::instance()->classesFilter()) { QFutureInterface dummy; const Overview oo; if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) { const Name *name = qualifiedName->name(); if (const TemplateNameId *templateName = name->asTemplateNameId()) { *className = templateNameAsString(templateName); } else { simpleName = oo.prettyName(name); *className = simpleName; *matches = classesFilter->matchesFor(dummy, *className); if (matches->empty()) { if (const Name *name = qualifiedName->base()) { if (const TemplateNameId *templateName = name->asTemplateNameId()) *className = templateNameAsString(templateName); else *className = oo.prettyName(name); } } } } else if (const TemplateNameId *templateName = name->asTemplateNameId()) { *className = templateNameAsString(templateName); } else { *className = oo.prettyName(name); } if (matches->empty()) *matches = classesFilter->matchesFor(dummy, *className); if (matches->empty() && !simpleName.isEmpty()) *className = simpleName; } return !matches->empty(); } } // anonymous namespace void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const NameAST *nameAst = nameUnderCursor(interface.path()); if (!nameAst || !nameAst->name) return; const LookupResult lookupResult = lookUpDefinition(interface, nameAst); if (lookupResult == LookupResult::Definition) return; QString className; QList matches; const QString currentDocumentFilePath = interface.semanticInfo().doc->fileName(); const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); QList headers; // Find an include file through the locator if (matchName(nameAst->name, &matches, &className)) { QList indexItems; const Snapshot forwardHeaders = forwardingHeaders(interface); foreach (const Core::LocatorFilterEntry &entry, matches) { IndexItem::Ptr info = entry.internalData.value(); if (info->symbolName() != className) continue; indexItems << info; Snapshot localForwardHeaders = forwardHeaders; localForwardHeaders.insert(interface.snapshot().document(info->fileName())); Utils::FilePaths headerAndItsForwardingHeaders; headerAndItsForwardingHeaders << Utils::FilePath::fromString(info->fileName()); headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->fileName()); foreach (const Utils::FilePath &header, headerAndItsForwardingHeaders) { const QString include = findShortestInclude(currentDocumentFilePath, header.toString(), headerPaths); if (include.size() > 2) { const QString headerFileName = Utils::FilePath::fromString(info->fileName()).fileName(); QTC_ASSERT(!headerFileName.isEmpty(), break); int priority = 0; if (headerFileName == className) priority = 2; else if (headerFileName.at(1).isUpper()) priority = 1; result << new AddIncludeForUndefinedIdentifierOp(interface, priority, include); headers << header; } } } if (lookupResult == LookupResult::None && indexItems.size() == 1) { QString qualifiedName = Overview().prettyName(nameAst->name); if (qualifiedName.startsWith("::")) qualifiedName.remove(0, 2); if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) { const ProjectExplorer::Node * const node = ProjectExplorer::ProjectTree ::nodeForFile(interface.filePath()); ProjectExplorer::FileType fileType = node && node->asFileNode() ? node->asFileNode()->fileType() : ProjectExplorer::FileType::Unknown; if (fileType == ProjectExplorer::FileType::Unknown && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) { fileType = ProjectExplorer::FileType::Header; } if (fileType == ProjectExplorer::FileType::Header) { result << new AddForwardDeclForUndefinedIdentifierOp( interface, 0, indexItems.first()->scopedSymbolName(), interface.currentFile()->startOf(nameAst)); } } } } if (className.isEmpty()) return; // Fallback: Check the include paths for files that look like candidates // for the given name. if (!Utils::contains(headers, [&className](const Utils::FilePath &fp) { return fp.fileName() == className; })) { const QString include = findMatchingInclude(className, headerPaths); const auto matcher = [&include](const QuickFixOperation::Ptr &o) { const auto includeOp = o.dynamicCast(); return includeOp && includeOp->include() == include; }; if (!include.isEmpty() && !Utils::contains(result, matcher)) result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include); } } namespace { class RearrangeParamDeclarationListOp: public CppQuickFixOperation { public: enum Target { TargetPrevious, TargetNext }; RearrangeParamDeclarationListOp(const CppQuickFixInterface &interface, AST *currentParam, AST *targetParam, Target target) : CppQuickFixOperation(interface) , m_currentParam(currentParam) , m_targetParam(targetParam) { QString targetString; if (target == TargetPrevious) targetString = QApplication::translate("CppTools::QuickFix", "Switch with Previous Parameter"); else targetString = QApplication::translate("CppTools::QuickFix", "Switch with Next Parameter"); setDescription(targetString); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); int targetEndPos = currentFile->endOf(m_targetParam); ChangeSet changes; changes.flip(currentFile->startOf(m_currentParam), currentFile->endOf(m_currentParam), currentFile->startOf(m_targetParam), targetEndPos); currentFile->setChangeSet(changes); currentFile->setOpenEditor(false, targetEndPos); currentFile->apply(); } private: AST *m_currentParam; AST *m_targetParam; }; } // anonymous namespace void RearrangeParamDeclarationList::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList path = interface.path(); ParameterDeclarationAST *paramDecl = nullptr; int index = path.size() - 1; for (; index != -1; --index) { paramDecl = path.at(index)->asParameterDeclaration(); if (paramDecl) break; } if (index < 1) return; ParameterDeclarationClauseAST *paramDeclClause = path.at(index-1)->asParameterDeclarationClause(); QTC_ASSERT(paramDeclClause && paramDeclClause->parameter_declaration_list, return); ParameterDeclarationListAST *paramListNode = paramDeclClause->parameter_declaration_list; ParameterDeclarationListAST *prevParamListNode = nullptr; while (paramListNode) { if (paramDecl == paramListNode->value) break; prevParamListNode = paramListNode; paramListNode = paramListNode->next; } if (!paramListNode) return; if (prevParamListNode) result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, prevParamListNode->value, RearrangeParamDeclarationListOp::TargetPrevious); if (paramListNode->next) result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, paramListNode->next->value, RearrangeParamDeclarationListOp::TargetNext); } namespace { class ReformatPointerDeclarationOp: public CppQuickFixOperation { public: ReformatPointerDeclarationOp(const CppQuickFixInterface &interface, const ChangeSet change) : CppQuickFixOperation(interface) , m_change(change) { QString description; if (m_change.operationList().size() == 1) { description = QApplication::translate("CppTools::QuickFix", "Reformat to \"%1\"").arg(m_change.operationList().constFirst().text); } else { // > 1 description = QApplication::translate("CppTools::QuickFix", "Reformat Pointers or References"); } setDescription(description); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); currentFile->setChangeSet(m_change); currentFile->apply(); } private: ChangeSet m_change; }; /// Filter the results of ASTPath. /// The resulting list contains the supported AST types only once. /// For this, the results of ASTPath are iterated in reverse order. class ReformatPointerDeclarationASTPathResultsFilter { public: QList filter(const QList &astPathList) { QList filtered; for (int i = astPathList.size() - 1; i >= 0; --i) { AST *ast = astPathList.at(i); if (!m_hasSimpleDeclaration && ast->asSimpleDeclaration()) { m_hasSimpleDeclaration = true; filtered.append(ast); } else if (!m_hasFunctionDefinition && ast->asFunctionDefinition()) { m_hasFunctionDefinition = true; filtered.append(ast); } else if (!m_hasParameterDeclaration && ast->asParameterDeclaration()) { m_hasParameterDeclaration = true; filtered.append(ast); } else if (!m_hasIfStatement && ast->asIfStatement()) { m_hasIfStatement = true; filtered.append(ast); } else if (!m_hasWhileStatement && ast->asWhileStatement()) { m_hasWhileStatement = true; filtered.append(ast); } else if (!m_hasForStatement && ast->asForStatement()) { m_hasForStatement = true; filtered.append(ast); } else if (!m_hasForeachStatement && ast->asForeachStatement()) { m_hasForeachStatement = true; filtered.append(ast); } } return filtered; } private: bool m_hasSimpleDeclaration = false; bool m_hasFunctionDefinition = false; bool m_hasParameterDeclaration = false; bool m_hasIfStatement = false; bool m_hasWhileStatement = false; bool m_hasForStatement = false; bool m_hasForeachStatement = false; }; } // anonymous namespace void ReformatPointerDeclaration::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); overview.showArgumentNames = true; overview.showReturnTypes = true; const QTextCursor cursor = file->cursor(); ChangeSet change; PointerDeclarationFormatter formatter(file, overview, PointerDeclarationFormatter::RespectCursor); if (cursor.hasSelection()) { // This will no work always as expected since this function is only called if // interface-path() is not empty. If the user selects the whole document via // ctrl-a and there is an empty line in the end, then the cursor is not on // any AST and therefore no quick fix will be triggered. change = formatter.format(file->cppDocument()->translationUnit()->ast()); if (!change.isEmpty()) result << new ReformatPointerDeclarationOp(interface, change); } else { const QList suitableASTs = ReformatPointerDeclarationASTPathResultsFilter().filter(path); foreach (AST *ast, suitableASTs) { change = formatter.format(ast); if (!change.isEmpty()) { result << new ReformatPointerDeclarationOp(interface, change); return; } } } } namespace { class CaseStatementCollector : public ASTVisitor { public: CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot, Scope *scope) : ASTVisitor(document->translationUnit()), document(document), scope(scope) { typeOfExpression.init(document, snapshot); } QStringList operator ()(AST *ast) { values.clear(); foundCaseStatementLevel = false; accept(ast); return values; } bool preVisit(AST *ast) override { if (CaseStatementAST *cs = ast->asCaseStatement()) { foundCaseStatementLevel = true; if (ExpressionAST *csExpression = cs->expression) { if (ExpressionAST *expression = csExpression->asIdExpression()) { QList candidates = typeOfExpression(expression, document, scope); if (!candidates.isEmpty() && candidates.first().declaration()) { Symbol *decl = candidates.first().declaration(); values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); } } } return true; } else if (foundCaseStatementLevel) { return false; } return true; } Overview prettyPrint; bool foundCaseStatementLevel = false; QStringList values; TypeOfExpression typeOfExpression; Document::Ptr document; Scope *scope; }; class CompleteSwitchCaseStatementOp: public CppQuickFixOperation { public: CompleteSwitchCaseStatementOp(const CppQuickFixInterface &interface, int priority, CompoundStatementAST *compoundStatement, const QStringList &values) : CppQuickFixOperation(interface, priority) , compoundStatement(compoundStatement) , values(values) { setDescription(QApplication::translate("CppTools::QuickFix", "Complete Switch Statement")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ChangeSet changes; int start = currentFile->endOf(compoundStatement->lbrace_token); changes.insert(start, QLatin1String("\ncase ") + values.join(QLatin1String(":\nbreak;\ncase ")) + QLatin1String(":\nbreak;")); currentFile->setChangeSet(changes); currentFile->appendIndentRange(ChangeSet::Range(start, start + 1)); currentFile->apply(); } CompoundStatementAST *compoundStatement; QStringList values; }; static Enum *findEnum(const QList &results, const LookupContext &ctxt) { foreach (const LookupItem &result, results) { const FullySpecifiedType fst = result.type(); Type *type = result.declaration() ? result.declaration()->type().type() : fst.type(); if (!type) continue; if (Enum *e = type->asEnumType()) return e; if (const NamedType *namedType = type->asNamedType()) { if (ClassOrNamespace *con = ctxt.lookupType(namedType->name(), result.scope())) { const QList enums = con->unscopedEnums(); const Name *referenceName = namedType->name(); if (const QualifiedNameId *qualifiedName = referenceName->asQualifiedNameId()) referenceName = qualifiedName->name(); foreach (Enum *e, enums) { if (const Name *candidateName = e->name()) { if (candidateName->match(referenceName)) return e; } } } } } return nullptr; } Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *statement) { Block *block = statement->symbol; Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column()); TypeOfExpression typeOfExpression; typeOfExpression.setExpandTemplates(true); typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); const QList results = typeOfExpression(statement->condition, interface.semanticInfo().doc, scope); return findEnum(results, typeOfExpression.context()); } } // anonymous namespace void CompleteSwitchCaseStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); if (path.isEmpty()) return; // look for switch statement for (int depth = path.size() - 1; depth >= 0; --depth) { AST *ast = path.at(depth); SwitchStatementAST *switchStatement = ast->asSwitchStatement(); if (switchStatement) { if (!interface.isCursorOn(switchStatement->switch_token) || !switchStatement->statement) return; CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement(); if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" return; // look if the condition's type is an enum if (Enum *e = conditionEnum(interface, switchStatement)) { // check the possible enum values QStringList values; Overview prettyPrint; for (int i = 0; i < e->memberCount(); ++i) { if (Declaration *decl = e->memberAt(i)->asDeclaration()) values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); } // Get the used values Block *block = switchStatement->symbol; CaseStatementCollector caseValues(interface.semanticInfo().doc, interface.snapshot(), interface.semanticInfo().doc->scopeAt(block->line(), block->column())); QStringList usedValues = caseValues(switchStatement); // save the values that would be added foreach (const QString &usedValue, usedValues) values.removeAll(usedValue); if (!values.isEmpty()) result << new CompleteSwitchCaseStatementOp(interface, depth, compoundStatement, values); return; } return; } } } namespace { class InsertDeclOperation: public CppQuickFixOperation { public: InsertDeclOperation(const CppQuickFixInterface &interface, const QString &targetFileName, const Class *targetSymbol, InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority) : CppQuickFixOperation(interface, priority) , m_targetFileName(targetFileName) , m_targetSymbol(targetSymbol) , m_xsSpec(xsSpec) , m_decl(decl) { setDescription(QCoreApplication::translate("CppEditor::InsertDeclOperation", "Add %1 Declaration") .arg(InsertionPointLocator::accessSpecToString(xsSpec))); } void perform() override { CppRefactoringChanges refactoring(snapshot()); InsertionPointLocator locator(refactoring); const InsertionLocation loc = locator.methodDeclarationInClass( m_targetFileName, m_targetSymbol, m_xsSpec); QTC_ASSERT(loc.isValid(), return); CppRefactoringFilePtr targetFile = refactoring.file(m_targetFileName); int targetPosition1 = targetFile->position(loc.line(), loc.column()); int targetPosition2 = qMax(0, targetFile->position(loc.line(), 1) - 1); ChangeSet target; target.insert(targetPosition1, loc.prefix() + m_decl); targetFile->setChangeSet(target); targetFile->appendIndentRange(ChangeSet::Range(targetPosition2, targetPosition1)); targetFile->setOpenEditor(true, targetPosition1); targetFile->apply(); } static QString generateDeclaration(const Function *function); private: QString m_targetFileName; const Class *m_targetSymbol; InsertionPointLocator::AccessSpec m_xsSpec; QString m_decl; }; class DeclOperationFactory { public: DeclOperationFactory(const CppQuickFixInterface &interface, const QString &fileName, const Class *matchingClass, const QString &decl) : m_interface(interface) , m_fileName(fileName) , m_matchingClass(matchingClass) , m_decl(decl) {} QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority) { return new InsertDeclOperation(m_interface, m_fileName, m_matchingClass, xsSpec, m_decl, priority); } private: const CppQuickFixInterface &m_interface; const QString &m_fileName; const Class *m_matchingClass; const QString &m_decl; }; } // anonymous namespace void InsertDeclFromDef::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); CppRefactoringFilePtr file = interface.currentFile(); FunctionDefinitionAST *funDef = nullptr; int idx = 0; for (; idx < path.size(); ++idx) { AST *node = path.at(idx); if (idx > 1) { if (DeclaratorIdAST *declId = node->asDeclaratorId()) { if (file->isCursorOn(declId)) { if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) { funDef = candidate; break; } } } } if (node->asClassSpecifier()) return; } if (!funDef || !funDef->symbol) return; Function *fun = funDef->symbol; if (Class *matchingClass = isMemberFunction(interface.context(), fun)) { const QualifiedNameId *qName = fun->name()->asQualifiedNameId(); for (Symbol *symbol = matchingClass->find(qName->identifier()); symbol; symbol = symbol->next()) { Symbol *s = symbol; if (fun->enclosingScope()->isTemplate()) { if (const Template *templ = s->type()->asTemplateType()) { if (Symbol *decl = templ->declaration()) { if (decl->type()->isFunctionType()) s = decl; } } } if (!s->name() || !qName->identifier()->match(s->identifier()) || !s->type()->isFunctionType()) continue; if (s->type().match(fun->type())) { // Declaration exists. return; } } QString fileName = QString::fromUtf8(matchingClass->fileName(), matchingClass->fileNameLength()); const QString decl = InsertDeclOperation::generateDeclaration(fun); // Add several possible insertion locations for declaration DeclOperationFactory operation(interface, fileName, matchingClass, decl); result << operation(InsertionPointLocator::Public, 5) << operation(InsertionPointLocator::PublicSlot, 4) << operation(InsertionPointLocator::Protected, 3) << operation(InsertionPointLocator::ProtectedSlot, 2) << operation(InsertionPointLocator::Private, 1) << operation(InsertionPointLocator::PrivateSlot, 0); } } QString InsertDeclOperation::generateDeclaration(const Function *function) { Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; oo.showReturnTypes = true; oo.showArgumentNames = true; oo.showEnclosingTemplate = true; QString decl; decl += oo.prettyType(function->type(), function->unqualifiedName()); decl += QLatin1String(";\n"); return decl; } namespace { class InsertDefOperation: public CppQuickFixOperation { public: // Make sure that either loc is valid or targetFileName is not empty. InsertDefOperation(const CppQuickFixInterface &interface, Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc, const DefPos defpos, const QString &targetFileName = QString(), bool freeFunction = false) : CppQuickFixOperation(interface, 0) , m_decl(decl) , m_declAST(declAST) , m_loc(loc) , m_defpos(defpos) , m_targetFileName(targetFileName) { if (m_defpos == DefPosImplementationFile) { const QString declFile = QString::fromUtf8(decl->fileName(), decl->fileNameLength()); const QDir dir = QFileInfo(declFile).dir(); setPriority(2); setDescription(QCoreApplication::translate("CppEditor::InsertDefOperation", "Add Definition in %1") .arg(dir.relativeFilePath(m_loc.isValid() ? m_loc.fileName() : m_targetFileName))); } else if (freeFunction) { setDescription(QCoreApplication::translate("CppEditor::InsertDefOperation", "Add Definition Here")); } else if (m_defpos == DefPosInsideClass) { setDescription(QCoreApplication::translate("CppEditor::InsertDefOperation", "Add Definition Inside Class")); } else if (m_defpos == DefPosOutsideClass) { setPriority(1); setDescription(QCoreApplication::translate("CppEditor::InsertDefOperation", "Add Definition Outside Class")); } } static void insertDefinition( const CppQuickFixOperation *op, InsertionLocation loc, DefPos defPos, DeclaratorAST *declAST, Declaration *decl, const QString &targetFilePath, ChangeSet *changeSet = nullptr, QList *indentRanges = nullptr) { CppRefactoringChanges refactoring(op->snapshot()); if (!loc.isValid()) loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore, refactoring, targetFilePath); QTC_ASSERT(loc.isValid(), return); CppRefactoringFilePtr targetFile = refactoring.file(loc.fileName()); Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; oo.showReturnTypes = true; oo.showArgumentNames = true; oo.showEnclosingTemplate = true; if (defPos == DefPosInsideClass) { const int targetPos = targetFile->position(loc.line(), loc.column()); ChangeSet localChangeSet; ChangeSet * const target = changeSet ? changeSet : &localChangeSet; target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';' const ChangeSet::Range indentRange(targetPos, targetPos + 4); if (indentRanges) indentRanges->append(indentRange); else targetFile->appendIndentRange(indentRange); if (!changeSet) { targetFile->setChangeSet(*target); targetFile->setOpenEditor(true, targetPos); targetFile->apply(); // Move cursor inside definition QTextCursor c = targetFile->cursor(); c.setPosition(targetPos); c.movePosition(QTextCursor::Down); c.movePosition(QTextCursor::EndOfLine); op->editor()->setTextCursor(c); } } else { // make target lookup context Document::Ptr targetDoc = targetFile->cppDocument(); Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column()); LookupContext targetContext(targetDoc, op->snapshot()); ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); if (!targetCoN) targetCoN = targetContext.globalNamespace(); // setup rewriting to get minimally qualified names SubstitutionEnvironment env; env.setContext(op->context()); env.switchScope(decl->enclosingScope()); UseMinimalNames q(targetCoN); env.enter(&q); Control *control = op->context().bindings()->control().data(); // rewrite the function type const FullySpecifiedType tn = rewriteType(decl->type(), &env, control); // rewrite the function name if (nameIncludesOperatorName(decl->name())) { CppRefactoringFilePtr file = refactoring.file(op->filePath().toString()); const QString operatorNameText = file->textOf(declAST->core_declarator); oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); } const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN, control)); const QString defText = inlinePrefix( targetFilePath, [defPos] { return defPos == DefPosOutsideClass; }) + oo.prettyType(tn, name) + QLatin1String("\n{\n\n}"); const int targetPos = targetFile->position(loc.line(), loc.column()); const int targetPos2 = qMax(0, targetFile->position(loc.line(), 1) - 1); ChangeSet localChangeSet; ChangeSet * const target = changeSet ? changeSet : &localChangeSet; target->insert(targetPos, loc.prefix() + defText + loc.suffix()); const ChangeSet::Range indentRange(targetPos2, targetPos); if (indentRanges) indentRanges->append(indentRange); else targetFile->appendIndentRange(indentRange); if (!changeSet) { targetFile->setChangeSet(*target); targetFile->setOpenEditor(true, targetPos); targetFile->apply(); // Move cursor inside definition QTextCursor c = targetFile->cursor(); c.setPosition(targetPos); c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, loc.prefix().count(QLatin1String("\n")) + 2); c.movePosition(QTextCursor::EndOfLine); if (defPos == DefPosImplementationFile) { if (targetFile->editor()) targetFile->editor()->setTextCursor(c); } else { op->editor()->setTextCursor(c); } } } } private: void perform() override { insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFileName); } Declaration *m_decl; DeclaratorAST *m_declAST; InsertionLocation m_loc; const DefPos m_defpos; const QString m_targetFileName; }; } // anonymous namespace void InsertDefFromDecl::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); int idx = path.size() - 1; for (; idx >= 0; --idx) { AST *node = path.at(idx); if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { if (idx > 0 && path.at(idx - 1)->asStatement()) return; if (simpleDecl->symbols && !simpleDecl->symbols->next) { if (Symbol *symbol = simpleDecl->symbols->value) { if (Declaration *decl = symbol->asDeclaration()) { if (Function *func = decl->type()->asFunctionType()) { if (func->isSignal() || func->isPureVirtual() || func->isFriend()) return; // Check if there is already a definition SymbolFinder symbolFinder; if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(), true)) { return; } // Insert Position: Implementation File DeclaratorAST *declAST = simpleDecl->declarator_list->value; InsertDefOperation *op = nullptr; ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString()); const bool isHeaderFile = ProjectFile::isHeader(kind); if (isHeaderFile) { CppRefactoringChanges refactoring(interface.snapshot()); InsertionPointLocator locator(refactoring); // find appropriate implementation file, but do not use this // location, because insertLocationForMethodDefinition() should // be used in perform() to get consistent insert positions. foreach (const InsertionLocation &location, locator.methodDefinition(decl, false, QString())) { if (!location.isValid()) continue; const QString fileName = location.fileName(); if (ProjectFile::isHeader(ProjectFile::classify(fileName))) { const QString source = CppTools::correspondingHeaderOrSource(fileName); if (!source.isEmpty()) { op = new InsertDefOperation(interface, decl, declAST, InsertionLocation(), DefPosImplementationFile, source); } } else { op = new InsertDefOperation(interface, decl, declAST, InsertionLocation(), DefPosImplementationFile, fileName); } if (op) result << op; break; } } // Determine if we are dealing with a free function const bool isFreeFunction = func->enclosingClass() == nullptr; // Insert Position: Outside Class if (!isFreeFunction) { result << new InsertDefOperation(interface, decl, declAST, InsertionLocation(), DefPosOutsideClass, interface.filePath().toString()); } // Insert Position: Inside Class // Determine insert location direct after the declaration. int line, column; const CppRefactoringFilePtr file = interface.currentFile(); file->lineAndColumn(file->endOf(simpleDecl), &line, &column); const InsertionLocation loc = InsertionLocation(interface.filePath().toString(), QString(), QString(), line, column); result << new InsertDefOperation(interface, decl, declAST, loc, DefPosInsideClass, QString(), isFreeFunction); return; } } } } break; } } } class InsertMemberFromInitializationOp : public CppQuickFixOperation { public: InsertMemberFromInitializationOp( const CppQuickFixInterface &interface, const Class *theClass, const QString &member, const QString &type) : CppQuickFixOperation(interface), m_class(theClass), m_member(member), m_type(type) { setDescription(QCoreApplication::translate("CppTools::Quickfix", "Add Class Member \"%1\"").arg(m_member)); } private: void perform() override { QString type = m_type; if (type.isEmpty()) { type = QInputDialog::getText( Core::ICore::dialogParent(), QCoreApplication::translate("CppTools::Quickfix","Provide the type"), QCoreApplication::translate("CppTools::Quickfix","Data type:"), QLineEdit::Normal); } if (type.isEmpty()) return; const CppRefactoringChanges refactoring(snapshot()); const InsertionPointLocator locator(refactoring); const QString filePath = QString::fromUtf8(m_class->fileName()); const InsertionLocation loc = locator.methodDeclarationInClass( filePath, m_class, InsertionPointLocator::Private); QTC_ASSERT(loc.isValid(), return); CppRefactoringFilePtr targetFile = refactoring.file(filePath); const int targetPosition1 = targetFile->position(loc.line(), loc.column()); const int targetPosition2 = qMax(0, targetFile->position(loc.line(), 1) - 1); ChangeSet target; target.insert(targetPosition1, loc.prefix() + type + ' ' + m_member + ";\n"); targetFile->setChangeSet(target); targetFile->appendIndentRange(ChangeSet::Range(targetPosition2, targetPosition1)); targetFile->apply(); } const Class * const m_class; const QString m_member; const QString m_type; }; void InsertMemberFromInitialization::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { // First check whether we are on a member initialization. const QList path = interface.path(); const int size = path.size(); if (size < 4) return; const SimpleNameAST * const name = path.at(size - 1)->asSimpleName(); if (!name) return; const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer(); if (!memInitializer) return; if (!path.at(size - 3)->asCtorInitializer()) return; const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition(); if (!ctor) return; // Now find the class. const Class *theClass = nullptr; if (size > 4) { const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier(); if (classSpec) // Inline constructor. We get the class directly. theClass = classSpec->symbol; } if (!theClass) { // Out-of-line constructor. We need to find the class. SymbolFinder finder; const QList matches = finder.findMatchingDeclaration( LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()), ctor->symbol); if (!matches.isEmpty()) theClass = matches.first()->enclosingClass(); } if (!theClass) return; // Check whether the member exists already. if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier( name->identifier_token))) { return; } const QString type = getType(interface, memInitializer, ctor); const Identifier * const memberId = interface.currentFile()->cppDocument() ->translationUnit()->identifier(name->identifier_token); const QString member = QString::fromUtf8(memberId->chars(), memberId->size()); result << new InsertMemberFromInitializationOp(interface, theClass, member, type); } QString InsertMemberFromInitialization::getType( const CppQuickFixInterface &interface, const MemInitializerAST *memInitializer, const FunctionDefinitionAST *ctor) const { // Try to deduce the type: If the initialization expression is just a name // (e.g. a constructor argument) or a function call, we don't bother the user. if (!memInitializer->expression) return {}; const ExpressionListParenAST * const lParenAst = memInitializer->expression->asExpressionListParen(); if (!lParenAst || !lParenAst->expression_list || !lParenAst->expression_list->value) return {}; const IdExpressionAST *idExpr = lParenAst->expression_list->value->asIdExpression(); if (!idExpr) { // Not a variable, so check for function call. const CallAST * const call = lParenAst->expression_list->value->asCall(); if (!call || !call->base_expression) return {}; idExpr = call->base_expression->asIdExpression(); } if (!idExpr || !idExpr->name) return {}; LookupContext context(interface.currentFile()->cppDocument(), interface.snapshot()); const QList matches = context.lookup(idExpr->name->name, ctor->symbol); if (matches.isEmpty()) return {}; Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview(); TypePrettyPrinter tpp(&o); FullySpecifiedType type = matches.first().type(); if (!type.type()) return {}; const Function * const funcType = type.type()->asFunctionType(); if (funcType) type = funcType->returnType(); return tpp(type); } class MemberFunctionImplSetting { public: Symbol *func = nullptr; DefPos defPos = DefPosImplementationFile; }; using MemberFunctionImplSettings = QList; class AddImplementationsDialog : public QDialog { Q_DECLARE_TR_FUNCTIONS(AddImplementationsDialog) public: AddImplementationsDialog(const QList &candidates, const Utils::FilePath &implFile) : QDialog(Core::ICore::dialogParent()), m_candidates(candidates) { setWindowTitle(tr("Member Function Implementations")); const auto defaultImplTargetComboBox = new QComboBox; QStringList implTargetStrings{tr("None"), tr("Inline"), tr("Outside Class")}; if (!implFile.isEmpty()) implTargetStrings.append(implFile.fileName()); defaultImplTargetComboBox->insertItems(0, implTargetStrings); connect(defaultImplTargetComboBox, qOverload(&QComboBox::currentIndexChanged), this, [this](int index) { for (QComboBox * const cb : m_implTargetBoxes) cb->setCurrentIndex(index); }); const auto defaultImplTargetLayout = new QHBoxLayout; defaultImplTargetLayout->addWidget(new QLabel(tr("Default Implementation Location:"))); defaultImplTargetLayout->addWidget(defaultImplTargetComboBox); const auto candidatesLayout = new QGridLayout; Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; oo.showReturnTypes = true; for (int i = 0; i < m_candidates.size(); ++i) { const auto implTargetComboBox = new QComboBox; m_implTargetBoxes.append(implTargetComboBox); implTargetComboBox->insertItems(0, implTargetStrings); const Symbol * const func = m_candidates.at(i); candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())), i, 0); candidatesLayout->addWidget(implTargetComboBox, i, 1); } const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1); const auto mainLayout = new QVBoxLayout(this); mainLayout->addLayout(defaultImplTargetLayout); const auto separator = new QFrame(); separator->setFrameShape(QFrame::HLine); mainLayout->addWidget(separator); mainLayout->addLayout(candidatesLayout); mainLayout->addWidget(buttonBox); } MemberFunctionImplSettings settings() const { QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {}); MemberFunctionImplSettings settings; for (int i = 0; i < m_candidates.size(); ++i) { MemberFunctionImplSetting setting; const int index = m_implTargetBoxes.at(i)->currentIndex(); const bool addImplementation = index != 0; if (!addImplementation) continue; setting.func = m_candidates.at(i); setting.defPos = static_cast(index - 1); settings << setting; } return settings; } private: const QList m_candidates; QList m_implTargetBoxes; }; class InsertDefsOperation: public CppQuickFixOperation { public: InsertDefsOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) { setDescription(CppQuickFixFactory::tr("Create Implementations for Member Functions")); const QList &path = interface.path(); if (path.size() < 2) return; // Determine if cursor is on a class const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName(); if (!nameAST || !interface.isCursorOn(nameAST)) return; m_classAST = path.at(path.size() - 2)->asClassSpecifier(); if (!m_classAST) return; const Class * const theClass = m_classAST->symbol; if (!theClass) return; // Collect all member functions without an implementation. for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { Symbol * const s = *it; if (!s->identifier() || !s->type() || !s->isDeclaration() || s->asFunction()) continue; Function * const func = s->type()->asFunctionType(); if (!func || func->isSignal() || func->isFriend()) continue; if (SymbolFinder().findMatchingDefinition(s, interface.snapshot())) continue; m_declarations << s; } } bool isApplicable() const { return !m_declarations.isEmpty(); } void setMode(InsertDefsFromDecls::Mode mode) { m_mode = mode; } private: void perform() override { QTC_ASSERT(!m_declarations.isEmpty(), return); CppRefactoringChanges refactoring(snapshot()); const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString())); QString cppFile; // Only set if the class is defined in a header file. if (isHeaderFile) { InsertionPointLocator locator(refactoring); for (const InsertionLocation &location : locator.methodDefinition(m_declarations.first(), false, {})) { if (!location.isValid()) continue; const QString fileName = location.fileName(); if (ProjectFile::isHeader(ProjectFile::classify(fileName))) { const QString source = CppTools::correspondingHeaderOrSource(fileName); if (!source.isEmpty()) cppFile = source; } else { cppFile = fileName; } break; } } MemberFunctionImplSettings settings; switch (m_mode) { case InsertDefsFromDecls::Mode::User: { AddImplementationsDialog dlg(m_declarations, Utils::FilePath::fromString(cppFile)); if (dlg.exec() == QDialog::Accepted) settings = dlg.settings(); break; } case InsertDefsFromDecls::Mode::Alternating: { int defPos = DefPosImplementationFile; const auto incDefPos = [&defPos] { defPos = (defPos + 1) % (DefPosImplementationFile + 2); }; for (Symbol * const func : qAsConst(m_declarations)) { incDefPos(); if (defPos > DefPosImplementationFile) continue; MemberFunctionImplSetting setting; setting.func = func; setting.defPos = static_cast(defPos); settings << setting; } break; } case InsertDefsFromDecls::Mode::Off: break; } if (settings.isEmpty()) return; class DeclFinder : public ASTVisitor { public: DeclFinder(const CppRefactoringFile *file, const Symbol *func) : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {} SimpleDeclarationAST *decl() const { return m_decl; } private: bool visit(SimpleDeclarationAST *decl) override { if (m_decl) return false; if (decl->symbols && decl->symbols->value == m_func) m_decl = decl; return !m_decl; } const Symbol * const m_func; SimpleDeclarationAST *m_decl = nullptr; }; QHash>> changeSets; for (const MemberFunctionImplSetting &setting : qAsConst(settings)) { DeclFinder finder(currentFile().data(), setting.func); finder.accept(m_classAST); QTC_ASSERT(finder.decl(), continue); InsertionLocation loc; const QString targetFilePath = setting.defPos == DefPosImplementationFile ? cppFile : filePath().toString(); QTC_ASSERT(!targetFilePath.isEmpty(), continue); if (setting.defPos == DefPosInsideClass) { int line, column; currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column); loc = InsertionLocation(filePath().toString(), QString(), QString(), line, column); } auto &changeSet = changeSets[targetFilePath]; InsertDefOperation::insertDefinition( this, loc, setting.defPos, finder.decl()->declarator_list->value, setting.func->asDeclaration(),targetFilePath, &changeSet.first, &changeSet.second); } for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) { const CppRefactoringFilePtr file = refactoring.file(it.key()); for (const ChangeSet::Range &r : it.value().second) file->appendIndentRange(r); file->setChangeSet(it.value().first); file->apply(); } } ClassSpecifierAST *m_classAST = nullptr; QList m_declarations; InsertDefsFromDecls::Mode m_mode; }; void InsertDefsFromDecls::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const auto op = QSharedPointer::create(interface); op->setMode(m_mode); if (op->isApplicable()) result << op; } namespace { bool hasClassMemberWithGetPrefix(const Class *klass) { if (!klass) return false; for (int i = 0; i < klass->memberCount(); ++i) { const Symbol *symbol = klass->memberAt(i); if (symbol->isFunction() || symbol->isDeclaration()) { if (const Name *symbolName = symbol->name()) { if (const Identifier *id = symbolName->identifier()) { if (!strncmp(id->chars(), "get", 3)) return true; } } } } return false; } class GenerateGetterSetterOperation : public CppQuickFixOperation { public: enum OperationType { InvalidType, GetterSetterType, GetterType, SetterType }; GenerateGetterSetterOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) { const QList &path = interface.path(); // We expect something like // [0] TranslationUnitAST // [1] NamespaceAST // [2] LinkageBodyAST // [3] SimpleDeclarationAST // [4] ClassSpecifierAST // [5] SimpleDeclarationAST // [6] DeclaratorAST // [7] DeclaratorIdAST // [8] SimpleNameAST const int n = path.size(); if (n < 6) return; int i = 1; m_variableName = path.at(n - i++)->asSimpleName(); m_declaratorId = path.at(n - i++)->asDeclaratorId(); // DeclaratorAST might be preceded by PointerAST, e.g. for the case // "class C { char *@s; };", where '@' denotes the text cursor position. if (!(m_declarator = path.at(n - i++)->asDeclarator())) { --i; if (path.at(n - i++)->asPointer()) { if (n < 7) return; m_declarator = path.at(n - i++)->asDeclarator(); } } m_variableDecl = path.at(n - i++)->asSimpleDeclaration(); m_classSpecifier = path.at(n - i++)->asClassSpecifier(); m_classDecl = path.at(n - i++)->asSimpleDeclaration(); if (!isValid()) return; // Do not get triggered on member functions and arrays if (m_declarator->postfix_declarator_list) { m_offerQuickFix = false; return; } // Construct getter and setter names const Name *variableName = m_variableName->name; if (!variableName) { m_offerQuickFix = false; return; } const Identifier *variableId = variableName->identifier(); if (!variableId) { m_offerQuickFix = false; return; } m_variableString = QString::fromUtf8(variableId->chars(), variableId->size()); determineGetterSetterNames(); // Check if the class has already a getter and/or a setter. // This is only a simple check which should suffice not triggering the // same quick fix again. Limitations: // 1) It only checks in the current class, but not in base classes. // 2) It compares only names instead of types/signatures. // 3) Symbols in Qt property declarations are ignored. bool hasGetter = false; bool hasSetter = false; if (Class *klass = m_classSpecifier->symbol) { for (int i = 0; i < klass->memberCount(); ++i) { Symbol *symbol = klass->memberAt(i); if (symbol->isQtPropertyDeclaration()) continue; if (const Name *symbolName = symbol->name()) { if (const Identifier *id = symbolName->identifier()) { const QString memberName = QString::fromUtf8(id->chars(), id->size()); if (memberName == m_getterName) hasGetter = true; if (memberName == m_setterName) hasSetter = true; if (hasGetter && hasSetter) break; } } } // for } if (hasGetter && hasSetter) { m_offerQuickFix = false; return; } // Find the right symbol in the simple declaration const List *symbols = m_variableDecl->symbols; QTC_ASSERT(symbols, return); for (; symbols; symbols = symbols->next) { Symbol *s = symbols->value; if (const Name *name = s->name()) { if (const Identifier *id = name->identifier()) { const QString symbolName = QString::fromUtf8(id->chars(), id->size()); if (symbolName == m_variableString) { m_symbol = s; break; } } } } QTC_ASSERT(m_symbol, return); if (hasSetter) { // hasGetter is false in this case m_type = GetterType; } else { // no setter if (hasGetter) { if (m_symbol->type().isConst()) { m_offerQuickFix = false; return; } else { m_type = SetterType; } } else { m_type = (m_symbol->type().isConst()) ? GetterType : GetterSetterType; } } updateDescriptionAndPriority(); } // Clones "other" in order to prevent all the initial detection made in the ctor. GenerateGetterSetterOperation(const CppQuickFixInterface &interface, GenerateGetterSetterOperation *other, OperationType type) : CppQuickFixOperation(interface) , m_type(type) , m_variableName(other->m_variableName) , m_declaratorId(other->m_declaratorId) , m_declarator(other->m_declarator) , m_variableDecl(other->m_variableDecl) , m_classSpecifier(other->m_classSpecifier) , m_classDecl(other->m_classDecl) , m_symbol(other->m_symbol) , m_baseName(other->m_baseName) , m_getterName(other->m_getterName) , m_setterName(other->m_setterName) , m_variableString(other->m_variableString) , m_offerQuickFix(other->m_offerQuickFix) { QTC_ASSERT(isValid(), return); updateDescriptionAndPriority(); } void determineGetterSetterNames() { m_baseName = memberBaseName(m_variableString); if (m_baseName.isEmpty()) m_baseName = QLatin1String("value"); // Getter Name const Utils::optional codeStyleSettings = CppCodeStyleSettings::currentProjectCodeStyle(); const CppCodeStyleSettings settings = codeStyleSettings.value_or(CppCodeStyleSettings::currentGlobalCodeStyle()); const bool hasValidBaseName = m_baseName != m_variableString; const bool getPrefixIsAlreadyUsed = hasClassMemberWithGetPrefix(m_classSpecifier->symbol); if (settings.preferGetterNameWithoutGetPrefix && hasValidBaseName && !getPrefixIsAlreadyUsed) { m_getterName = m_baseName; } else { const QString baseNameWithCapital = m_baseName.left(1).toUpper() + m_baseName.mid(1); m_getterName = QLatin1String("get") + baseNameWithCapital; } // Setter Name const QString baseNameWithCapital = m_baseName.left(1).toUpper() + m_baseName.mid(1); m_setterName = QLatin1String("set") + baseNameWithCapital; } void updateDescriptionAndPriority() { switch (m_type) { case GetterSetterType: setPriority(5); setDescription(CppQuickFixFactory::tr("Create Getter and Setter Member Functions")); break; case GetterType: setPriority(4); setDescription(CppQuickFixFactory::tr("Create Getter Member Function")); break; case SetterType: setPriority(3); setDescription(CppQuickFixFactory::tr("Create Setter Member Function")); break; default: break; } } bool isValid() const { return m_variableName && m_declaratorId && m_declarator && m_variableDecl && m_classSpecifier && m_classDecl && m_offerQuickFix; } static void addGetterAndOrSetter( const CppQuickFixInterface *quickFix, Symbol *symbol, const ClassSpecifierAST *classSpecifier, const QString &rawName, const QString &baseName, const QString &getterName, const QString &setterName, OperationType op) { CppRefactoringChanges refactoring(quickFix->snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(quickFix->filePath().toString()); QTC_ASSERT(symbol, return); FullySpecifiedType fullySpecifiedType = symbol->type(); Type *type = fullySpecifiedType.type(); QTC_ASSERT(type, return); Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; oo.showReturnTypes = true; oo.showArgumentNames = true; const QString typeString = oo.prettyType(fullySpecifiedType); const NameAST *classNameAST = classSpecifier->name; QTC_ASSERT(classNameAST, return); const Name *className = classNameAST->name; QTC_ASSERT(className, return); const Identifier *classId = className->identifier(); QTC_ASSERT(classId, return); QString classString = QString::fromUtf8(classId->chars(), classId->size()); bool wasHeader = true; QString declFileName = currentFile->fileName(); QString implFileName = correspondingHeaderOrSource(declFileName, &wasHeader); const bool sameFile = !wasHeader || !QFile::exists(implFileName); if (sameFile) implFileName = declFileName; InsertionPointLocator locator(refactoring); InsertionLocation declLocation = locator.methodDeclarationInClass (declFileName, classSpecifier->symbol->asClass(), InsertionPointLocator::Public); const bool passByValue = type->isIntegerType() || type->isFloatType() || type->isPointerType() || type->isEnumType(); const QString paramName = baseName != rawName ? baseName : QLatin1String("value"); QString paramString; if (passByValue) { paramString = oo.prettyType(fullySpecifiedType, paramName); } else { const ReferenceType *refType = type->asReferenceType(); FullySpecifiedType constParamType(refType ? refType->elementType() : fullySpecifiedType); constParamType.setConst(true); QScopedPointer referenceType(new ReferenceType(constParamType, false)); const FullySpecifiedType referenceToConstParamType(referenceType.data()); paramString = oo.prettyType(referenceToConstParamType, paramName); } const bool isStatic = symbol->storage() == Symbol::Static; // Construct declaration strings QString declaration = declLocation.prefix(); QString getterTypeString = typeString; FullySpecifiedType getterType(fullySpecifiedType); if (fullySpecifiedType.isConst()) { getterType.setConst(false); getterTypeString = oo.prettyType(getterType); } const QString declarationGetterTypeAndNameString = oo.prettyType(getterType, getterName); const QString declarationGetter = QString::fromLatin1("%1%2()%3;\n") .arg(isStatic ? QLatin1String("static ") : QString(), declarationGetterTypeAndNameString, isStatic ? QString() : QLatin1String(" const")); const QString declarationSetter = QString::fromLatin1("%1void %2(%3);\n") .arg(isStatic ? QLatin1String("static ") : QString(), setterName, paramString); const auto generateGetter = [op] { return op == GetterSetterType || op == GetterType; }; const auto generateSetter = [op] { return op == GetterSetterType || op == SetterType; }; if (generateGetter()) declaration += declarationGetter; if (generateSetter()) declaration += declarationSetter; declaration += declLocation.suffix(); // Construct implementation strings const QString implementationGetterTypeAndNameString = oo.prettyType( getterType, QString::fromLatin1("%1::%2").arg(classString, getterName)); const QString inlineSpecifier = sameFile && wasHeader ? QString("inline") : QString(); const QString implementationGetter = QString::fromLatin1("%4 %1()%2\n" "{\n" "return %3;\n" "}") .arg(implementationGetterTypeAndNameString, isStatic ? QString() : QLatin1String(" const"), rawName, inlineSpecifier); const QString implementationSetter = QString::fromLatin1("%6 void %1::%2(%3)\n" "{\n" "%4 = %5;\n" "}") .arg(classString, setterName, paramString, rawName, paramName, inlineSpecifier); QString implementation; if (generateGetter()) implementation += implementationGetter; if (generateSetter() && !fullySpecifiedType.isConst()) { if (!implementation.isEmpty()) implementation += QLatin1String("\n\n"); implementation += implementationSetter; } // Create and apply changes ChangeSet currChanges; int declInsertPos = currentFile->position(qMax(1, declLocation.line()), declLocation.column()); currChanges.insert(declInsertPos, declaration); if (sameFile) { InsertionLocation loc = insertLocationForMethodDefinition( symbol, false, NamespaceHandling::CreateMissing, refactoring, currentFile->fileName()); implementation = loc.prefix() + implementation + loc.suffix(); const int implInsertPos = currentFile->position(loc.line(), loc.column()); currChanges.insert(implInsertPos, implementation); currentFile->appendIndentRange( ChangeSet::Range(implInsertPos, implInsertPos + implementation.size())); } else { CppRefactoringChanges implRef(quickFix->snapshot()); CppRefactoringFilePtr implFile = implRef.file(implFileName); ChangeSet implChanges; InsertionLocation loc = insertLocationForMethodDefinition( symbol, false, NamespaceHandling::CreateMissing, implRef, implFileName); implementation = loc.prefix() + implementation + loc.suffix(); const int implInsertPos = implFile->position(loc.line(), loc.column()); implChanges.insert(implInsertPos, implementation); implFile->setChangeSet(implChanges); implFile->appendIndentRange( ChangeSet::Range(implInsertPos, implInsertPos + implementation.size())); implFile->apply(); } currentFile->setChangeSet(currChanges); currentFile->appendIndentRange( ChangeSet::Range(declInsertPos, declInsertPos + declaration.size())); currentFile->apply(); } void perform() override { addGetterAndOrSetter(this, m_symbol, m_classSpecifier, m_variableString, m_baseName, m_getterName, m_setterName, m_type); } OperationType m_type = InvalidType; SimpleNameAST *m_variableName = nullptr; DeclaratorIdAST *m_declaratorId = nullptr; DeclaratorAST *m_declarator = nullptr; SimpleDeclarationAST *m_variableDecl = nullptr; ClassSpecifierAST *m_classSpecifier = nullptr; SimpleDeclarationAST *m_classDecl = nullptr; Symbol *m_symbol = nullptr; QString m_baseName; QString m_getterName; QString m_setterName; QString m_variableString; bool m_offerQuickFix = true; }; } // anonymous namespace void GenerateGetterSetter::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { auto op = new GenerateGetterSetterOperation(interface); if (op->m_type != GenerateGetterSetterOperation::InvalidType) { result << op; if (op->m_type == GenerateGetterSetterOperation::GetterSetterType) { result << new GenerateGetterSetterOperation( interface, op, GenerateGetterSetterOperation::GetterType); result << new GenerateGetterSetterOperation( interface, op, GenerateGetterSetterOperation::SetterType); } } else { delete op; } } class MemberInfo { public: MemberInfo(Symbol *m, const QString &r, const QString &b, bool g, bool s) : member(m), rawName(r), baseName(b), hasGetter(g), hasSetter(s) {} Symbol *member = nullptr; QString rawName; QString baseName; bool hasGetter = false; bool hasSetter = false; bool getterRequested = false; bool setterRequested = false; }; using GetterSetterCandidates = std::vector; class CandidateTreeItem : public Utils::TreeItem { public: static const int NameColumn = 0; static const int GetterColumn = 1; static const int SetterColumn = 2; CandidateTreeItem(MemberInfo *candidate) : m_candidate(candidate) { } private: QVariant data(int column, int role) const override { switch (column) { case NameColumn: if (role == Qt::DisplayRole) return m_candidate->rawName; break; case GetterColumn: if (role == Qt::CheckStateRole) return m_candidate->hasGetter || m_candidate->getterRequested ? Qt::Checked : Qt::Unchecked; break; case SetterColumn: if (role == Qt::CheckStateRole) return m_candidate->hasSetter || m_candidate->setterRequested ? Qt::Checked : Qt::Unchecked; break; } return {}; } bool setData(int column, const QVariant &data, int role) override { switch (column) { case GetterColumn: if (role == Qt::CheckStateRole && !m_candidate->hasGetter) { m_candidate->getterRequested = data.toInt() == Qt::Checked; return true; } break; case SetterColumn: if (role == Qt::CheckStateRole && !m_candidate->hasSetter) { m_candidate->setterRequested = data.toInt() == Qt::Checked; return true; } break; default: break; } return false; } Qt::ItemFlags flags(int column) const override { switch (column) { case NameColumn: return Qt::ItemIsEnabled; case GetterColumn: if (m_candidate->hasGetter) return {}; return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; case SetterColumn: if (m_candidate->hasSetter) return {}; return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } return {}; } MemberInfo * const m_candidate; }; class GenerateGettersSettersDialog : public QDialog { Q_DECLARE_TR_FUNCTIONS(GenerateGettersSettersDialog) public: GenerateGettersSettersDialog(const GetterSetterCandidates &candidates) : QDialog(), m_candidates(candidates) { setWindowTitle(tr("Getters and Setters")); const auto model = new Utils::TreeModel(this); model->setHeader(QStringList({tr("Member"), tr("Getter"), tr("Setter")})); for (MemberInfo &candidate : m_candidates) model->rootItem()->appendChild(new CandidateTreeItem(&candidate)); const auto view = new Utils::BaseTreeView(this); view->setModel(model); const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); const auto setCheckStateForAll = [model](int column, int checkState) { for (int i = 0; i < model->rowCount(); ++i) { model->setData(model->index(i, column), checkState, Qt::CheckStateRole); } }; const auto preventPartiallyChecked = [](QCheckBox *checkbox) { if (checkbox->checkState() == Qt::PartiallyChecked) checkbox->setCheckState(Qt::Checked); }; QCheckBox *allGettersCheckbox = nullptr; if (Utils::anyOf(candidates, [](const MemberInfo &mi) { return !mi.hasGetter; })) { allGettersCheckbox = new QCheckBox(tr("Create getters for all members")); connect(allGettersCheckbox, &QCheckBox::stateChanged, [setCheckStateForAll](int state) { if (state != Qt::PartiallyChecked) setCheckStateForAll(CandidateTreeItem::GetterColumn, state); }); connect(allGettersCheckbox, &QCheckBox::clicked, this, [allGettersCheckbox, preventPartiallyChecked] { preventPartiallyChecked(allGettersCheckbox); }); } QCheckBox *allSettersCheckbox = nullptr; if (Utils::anyOf(candidates, [](const MemberInfo &mi) { return !mi.hasSetter; })) { allSettersCheckbox = new QCheckBox(tr("Create setters for all members")); connect(allSettersCheckbox, &QCheckBox::stateChanged, [setCheckStateForAll](int state) { if (state != Qt::PartiallyChecked) setCheckStateForAll(CandidateTreeItem::SetterColumn, state); }); connect(allSettersCheckbox, &QCheckBox::clicked, this, [allSettersCheckbox, preventPartiallyChecked] { preventPartiallyChecked(allSettersCheckbox); }); } const auto hasGetterCount = Utils::count(m_candidates, [](const MemberInfo &mi) { return mi.hasGetter; }); const auto hasSetterCount = Utils::count(m_candidates, [](const MemberInfo &mi) { return mi.hasSetter; }); connect(model, &QAbstractItemModel::dataChanged, this, [this, allGettersCheckbox, allSettersCheckbox, hasGetterCount, hasSetterCount] { const int getterRequestedCount = Utils::count(m_candidates, [](const MemberInfo &mi) { return mi.getterRequested; }); const int setterRequestedCount = Utils::count(m_candidates, [](const MemberInfo &mi) { return mi.setterRequested; }); const auto countToState = [this](int requestedCount, int alreadyExistsCount) { if (requestedCount == 0) return Qt::Unchecked; if (int(m_candidates.size()) - requestedCount == alreadyExistsCount) return Qt::Checked; return Qt::PartiallyChecked; }; if (allGettersCheckbox) { allGettersCheckbox->setCheckState(countToState(getterRequestedCount, hasGetterCount)); } if (allSettersCheckbox) { allSettersCheckbox->setCheckState(countToState(setterRequestedCount, hasSetterCount)); } }); const auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(new QLabel(tr("Please select the getters and/or setters " "to be created."))); if (allGettersCheckbox) mainLayout->addWidget(allGettersCheckbox); if (allSettersCheckbox) mainLayout->addWidget(allSettersCheckbox); mainLayout->addWidget(view); mainLayout->addWidget(buttonBox); } GetterSetterCandidates candidates() const { return m_candidates; } private: GetterSetterCandidates m_candidates; }; class GenerateGettersSettersOperation : public CppQuickFixOperation { public: GenerateGettersSettersOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) { setDescription(CppQuickFixFactory::tr("Create Getter and Setter Member Functions")); const QList &path = interface.path(); if (path.size() < 2) return; // Determine if cursor is on a class const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName(); if (!nameAST || !interface.isCursorOn(nameAST)) return; m_classAST = path.at(path.size() - 2)->asClassSpecifier(); if (!m_classAST) return; const Class * const theClass = m_classAST->symbol; if (!theClass) return; // Go through all data members and try to find out whether they have getters and/or setters. QList dataMembers; QList memberFunctions; for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { Symbol * const s = *it; if (!s->identifier() || !s->type()) continue; if ((s->isDeclaration() && s->type()->asFunctionType()) || s->asFunction()) memberFunctions << s; else if (s->isDeclaration() && (s->isPrivate() || s->isProtected())) dataMembers << s; } for (Symbol * const member : dataMembers) { const QString rawName = QString::fromUtf8(member->identifier()->chars(), member->identifier()->size()); const QString semanticName = memberBaseName(rawName); const QString capitalizedSemanticName = semanticName.at(0).toUpper() + semanticName.mid(1); const QStringList getterNames{semanticName, "get_" + semanticName, "get" + capitalizedSemanticName, "is_" + semanticName, "is" + capitalizedSemanticName}; const QStringList setterNames{"set_" + semanticName, "set" + capitalizedSemanticName}; const bool hasGetter = Utils::anyOf(memberFunctions, [&getterNames](const Symbol *s) { const Identifier * const id = s->identifier(); const auto funcName = QString::fromUtf8(id->chars(), id->size()); return getterNames.contains(funcName); }); const bool hasSetter = Utils::anyOf(memberFunctions, [&setterNames](const Symbol *s) { const Identifier * const id = s->identifier(); const auto funcName = QString::fromUtf8(id->chars(), id->size()); return setterNames.contains(funcName); }); if (!hasGetter || !hasSetter) m_candidates.emplace_back(member, rawName, semanticName, hasGetter, hasSetter); } } GetterSetterCandidates candidates() const { return m_candidates; } bool isApplicable() const { return !m_candidates.empty(); } void setGetterSetterData(const GetterSetterCandidates &data) { m_candidates = data; m_hasData = true; } private: void perform() override { if (!m_hasData) { GenerateGettersSettersDialog dlg(m_candidates); if (dlg.exec() == QDialog::Rejected) return; m_candidates = dlg.candidates(); } for (const MemberInfo &mi : m_candidates) { GenerateGetterSetterOperation::OperationType op = GenerateGetterSetterOperation::InvalidType; if (mi.getterRequested) { if (mi.setterRequested) op = GenerateGetterSetterOperation::GetterSetterType; else op = GenerateGetterSetterOperation::GetterType; } else if (mi.setterRequested) { op = GenerateGetterSetterOperation::SetterType; } if (op == GenerateGetterSetterOperation::InvalidType) continue; const Utils::optional codeStyleSettings = CppCodeStyleSettings::currentProjectCodeStyle(); const CppCodeStyleSettings settings = codeStyleSettings.value_or(CppCodeStyleSettings::currentGlobalCodeStyle()); const QString capitalizedBaseName = mi.baseName.at(0).toUpper() + mi.baseName.mid(1); const QString getterName = settings.preferGetterNameWithoutGetPrefix && mi.baseName != mi.rawName ? mi.baseName : "get" + capitalizedBaseName; const QString setterName = "set" + capitalizedBaseName; GenerateGetterSetterOperation::addGetterAndOrSetter( this, mi.member, m_classAST, mi.rawName, mi.baseName, getterName, setterName, op); } } GetterSetterCandidates m_candidates; const ClassSpecifierAST *m_classAST = nullptr; bool m_hasData = false; }; void GenerateGettersSettersForClass::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const auto op = QSharedPointer::create(interface); if (!op->isApplicable()) return; if (m_test) { GetterSetterCandidates candidates = op->candidates(); for (MemberInfo &mi : candidates) { if (!mi.hasGetter) mi.getterRequested = true; if (!mi.hasSetter) mi.setterRequested = true; } op->setGetterSetterData(candidates); } result << op; } namespace { class ExtractFunctionOptions { public: static bool isValidFunctionName(const QString &name) { return !name.isEmpty() && isValidIdentifier(name); } bool hasValidFunctionName() const { return isValidFunctionName(funcName); } QString funcName; InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public; }; class ExtractFunctionOperation : public CppQuickFixOperation { public: ExtractFunctionOperation(const CppQuickFixInterface &interface, int extractionStart, int extractionEnd, FunctionDefinitionAST *refFuncDef, Symbol *funcReturn, QList > relevantDecls, ExtractFunction::FunctionNameGetter functionNameGetter = ExtractFunction::FunctionNameGetter()) : CppQuickFixOperation(interface) , m_extractionStart(extractionStart) , m_extractionEnd(extractionEnd) , m_refFuncDef(refFuncDef) , m_funcReturn(funcReturn) , m_relevantDecls(relevantDecls) , m_functionNameGetter(functionNameGetter) { setDescription(QCoreApplication::translate("QuickFix::ExtractFunction", "Extract Function")); } void perform() override { QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return); CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); ExtractFunctionOptions options; if (m_functionNameGetter) options.funcName = m_functionNameGetter(); else options = getOptions(); if (!options.hasValidFunctionName()) return; const QString &funcName = options.funcName; Function *refFunc = m_refFuncDef->symbol; // We don't need to rewrite the type for declarations made inside the reference function, // since their scope will remain the same. Then we preserve the original spelling style. // However, we must do so for the return type in the definition. SubstitutionEnvironment env; env.setContext(context()); env.switchScope(refFunc); ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope()); if (!targetCoN) targetCoN = context().globalNamespace(); UseMinimalNames subs(targetCoN); env.enter(&subs); Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); Control *control = context().bindings()->control().data(); QString funcDef; QString funcDecl; // We generate a declaration only in the case of a member function. QString funcCall; Class *matchingClass = isMemberFunction(context(), refFunc); // Write return type. if (!m_funcReturn) { funcDef.append(QLatin1String("void ")); if (matchingClass) funcDecl.append(QLatin1String("void ")); } else { const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control); funcDef.append(printer.prettyType(fullType) + QLatin1Char(' ')); funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' ')); } // Write class qualification, if any. if (matchingClass) { const Scope *current = matchingClass; QVector classes{matchingClass->name()}; while (current->enclosingScope()->asClass()) { current = current->enclosingScope()->asClass(); classes.prepend(current->name()); } while (current->enclosingScope() && current->enclosingScope()->asNamespace()) { current = current->enclosingScope()->asNamespace(); if (current->name()) classes.prepend(current->name()); } for (const Name *n : classes) { const Name *name = rewriteName(n, &env, control); funcDef.append(printer.prettyName(name)); funcDef.append(QLatin1String("::")); } } // Write the extracted function itself and its call. funcDef.append(funcName); if (matchingClass) funcDecl.append(funcName); funcCall.append(funcName); funcDef.append(QLatin1Char('(')); if (matchingClass) funcDecl.append(QLatin1Char('(')); funcCall.append(QLatin1Char('(')); for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) { QPair p = m_relevantDecls.at(i); funcCall.append(p.first); funcDef.append(p.second); if (matchingClass) funcDecl.append(p.second); if (i < m_relevantDecls.length() - 1) { funcCall.append(QLatin1String(", ")); funcDef.append(QLatin1String(", ")); if (matchingClass) funcDecl.append(QLatin1String(", ")); } } funcDef.append(QLatin1Char(')')); if (matchingClass) funcDecl.append(QLatin1Char(')')); funcCall.append(QLatin1Char(')')); if (refFunc->isConst()) { funcDef.append(QLatin1String(" const")); funcDecl.append(QLatin1String(" const")); } funcDef.append(QLatin1String("\n{")); if (matchingClass) funcDecl.append(QLatin1String(";\n")); if (m_funcReturn) { funcDef.append(QLatin1String("\nreturn ") + m_relevantDecls.at(0).first + QLatin1Char(';')); funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = ")); } funcDef.append(QLatin1String("\n}\n\n")); funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n")); funcDef.prepend(inlinePrefix(currentFile->fileName())); funcCall.append(QLatin1Char(';')); // Get starting indentation from original code. int indentedExtractionStart = m_extractionStart; QChar current = currentFile->document()->characterAt(indentedExtractionStart - 1); while (current == QLatin1Char(' ') || current == QLatin1Char('\t')) { --indentedExtractionStart; current = currentFile->document()->characterAt(indentedExtractionStart - 1); } QString extract = currentFile->textOf(indentedExtractionStart, m_extractionEnd); extract.replace(QChar::ParagraphSeparator, QLatin1String("\n")); if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn) extract.append(QLatin1Char('\n')); // Since we need an indent range and a nested reindent range (based on the original // formatting) it's simpler to have two different change sets. ChangeSet change; int position = currentFile->startOf(m_refFuncDef); change.insert(position, funcDef); change.replace(m_extractionStart, m_extractionEnd, funcCall); currentFile->setChangeSet(change); currentFile->appendIndentRange(ChangeSet::Range(position, position + 1)); currentFile->apply(); QTextCursor tc = currentFile->document()->find(QLatin1String("{"), position); QTC_ASSERT(tc.hasSelection(), return); position = tc.selectionEnd() + 1; change.clear(); change.insert(position, extract + '\n'); currentFile->setChangeSet(change); currentFile->appendReindentRange(ChangeSet::Range(position, position + 1)); currentFile->apply(); // Write declaration, if necessary. if (matchingClass) { InsertionPointLocator locator(refactoring); const QString fileName = QLatin1String(matchingClass->fileName()); const InsertionLocation &location = locator.methodDeclarationInClass(fileName, matchingClass, options.access); CppRefactoringFilePtr declFile = refactoring.file(fileName); change.clear(); position = declFile->position(location.line(), location.column()); change.insert(position, location.prefix() + funcDecl + location.suffix()); declFile->setChangeSet(change); declFile->appendIndentRange(ChangeSet::Range(position, position + 1)); declFile->apply(); } } ExtractFunctionOptions getOptions() const { QDialog dlg(Core::ICore::dialogParent()); dlg.setWindowTitle(QCoreApplication::translate("QuickFix::ExtractFunction", "Extract Function Refactoring")); auto layout = new QFormLayout(&dlg); auto funcNameEdit = new Utils::FancyLineEdit; funcNameEdit->setValidationFunction([](Utils::FancyLineEdit *edit, QString *) { return ExtractFunctionOptions::isValidFunctionName(edit->text()); }); layout->addRow(QCoreApplication::translate("QuickFix::ExtractFunction", "Function name"), funcNameEdit); auto accessCombo = new QComboBox; accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public), InsertionPointLocator::Public); accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot), InsertionPointLocator::PublicSlot); accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected), InsertionPointLocator::Protected); accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot), InsertionPointLocator::ProtectedSlot); accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private), InsertionPointLocator::Private); accessCombo->addItem( InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot), InsertionPointLocator::PrivateSlot); layout->addRow(QCoreApplication::translate("QuickFix::ExtractFunction", "Access"), accessCombo); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok); ok->setEnabled(false); QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged, ok, &QPushButton::setEnabled); layout->addWidget(buttonBox); if (dlg.exec() == QDialog::Accepted) { ExtractFunctionOptions options; options.funcName = funcNameEdit->text(); options.access = static_cast(accessCombo-> currentData().toInt()); return options; } return ExtractFunctionOptions(); } int m_extractionStart; int m_extractionEnd; FunctionDefinitionAST *m_refFuncDef; Symbol *m_funcReturn; QList > m_relevantDecls; ExtractFunction::FunctionNameGetter m_functionNameGetter; }; QPair assembleDeclarationData(const QString &specifiers, DeclaratorAST *decltr, const CppRefactoringFilePtr &file, const Overview &printer) { QTC_ASSERT(decltr, return (QPair())); if (decltr->core_declarator && decltr->core_declarator->asDeclaratorId() && decltr->core_declarator->asDeclaratorId()->name) { QString decltrText = file->textOf(file->startOf(decltr), file->endOf(decltr->core_declarator)); if (!decltrText.isEmpty()) { const QString &name = printer.prettyName( decltr->core_declarator->asDeclaratorId()->name->name); QString completeDecl = specifiers; if (!decltrText.contains(QLatin1Char(' '))) completeDecl.append(QLatin1Char(' ') + decltrText); else completeDecl.append(decltrText); return qMakePair(name, completeDecl); } } return QPair(); } class FunctionExtractionAnalyser : public ASTVisitor { public: FunctionExtractionAnalyser(TranslationUnit *unit, const int selStart, const int selEnd, const CppRefactoringFilePtr &file, const Overview &printer) : ASTVisitor(unit) , m_done(false) , m_failed(false) , m_selStart(selStart) , m_selEnd(selEnd) , m_extractionStart(0) , m_extractionEnd(0) , m_file(file) , m_printer(printer) {} bool operator()(FunctionDefinitionAST *refFunDef) { accept(refFunDef); if (!m_failed && m_extractionStart == m_extractionEnd) m_failed = true; return !m_failed; } bool preVisit(AST *) override { return !m_done; } void statement(StatementAST *stmt) { if (!stmt) return; const int stmtStart = m_file->startOf(stmt); const int stmtEnd = m_file->endOf(stmt); if (stmtStart >= m_selEnd || (m_extractionStart && stmtEnd > m_selEnd)) { m_done = true; return; } if (stmtStart >= m_selStart && !m_extractionStart) m_extractionStart = stmtStart; if (stmtEnd > m_extractionEnd && m_extractionStart) m_extractionEnd = stmtEnd; accept(stmt); } bool visit(CaseStatementAST *stmt) override { statement(stmt->statement); return false; } bool visit(CompoundStatementAST *stmt) override { for (StatementListAST *it = stmt->statement_list; it; it = it->next) { statement(it->value); if (m_done) break; } return false; } bool visit(DoStatementAST *stmt) override { statement(stmt->statement); return false; } bool visit(ForeachStatementAST *stmt) override { statement(stmt->statement); return false; } bool visit(RangeBasedForStatementAST *stmt) override { statement(stmt->statement); return false; } bool visit(ForStatementAST *stmt) override { statement(stmt->initializer); if (!m_done) statement(stmt->statement); return false; } bool visit(IfStatementAST *stmt) override { statement(stmt->statement); if (!m_done) statement(stmt->else_statement); return false; } bool visit(TryBlockStatementAST *stmt) override { statement(stmt->statement); for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) { statement(it->value); if (m_done) break; } return false; } bool visit(WhileStatementAST *stmt) override { statement(stmt->statement); return false; } bool visit(DeclarationStatementAST *declStmt) override { // We need to collect the declarations we see before the extraction or even inside it. // They might need to be used as either a parameter or return value. Actually, we could // still obtain their types from the local uses, but it's good to preserve the original // typing style. if (declStmt && declStmt->declaration && declStmt->declaration->asSimpleDeclaration()) { SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration(); if (simpleDecl->decl_specifier_list && simpleDecl->declarator_list) { const QString &specifiers = m_file->textOf(m_file->startOf(simpleDecl), m_file->endOf(simpleDecl->decl_specifier_list->lastValue())); for (DeclaratorListAST *decltrList = simpleDecl->declarator_list; decltrList; decltrList = decltrList->next) { const QPair p = assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer); if (!p.first.isEmpty()) m_knownDecls.insert(p.first, p.second); } } } return false; } bool visit(ReturnStatementAST *) override { if (m_extractionStart) { m_done = true; m_failed = true; } return false; } bool m_done; bool m_failed; const int m_selStart; const int m_selEnd; int m_extractionStart; int m_extractionEnd; QHash m_knownDecls; CppRefactoringFilePtr m_file; const Overview &m_printer; }; } // anonymous namespace ExtractFunction::ExtractFunction(FunctionNameGetter functionNameGetter) : m_functionNameGetter(functionNameGetter) { } void ExtractFunction::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { CppRefactoringFilePtr file = interface.currentFile(); QTextCursor cursor = file->cursor(); if (!cursor.hasSelection()) return; const QList &path = interface.path(); FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from. for (int i = path.size() - 1; i >= 0; --i) { refFuncDef = path.at(i)->asFunctionDefinition(); if (refFuncDef) break; } if (!refFuncDef || !refFuncDef->function_body || !refFuncDef->function_body->asCompoundStatement() || !refFuncDef->function_body->asCompoundStatement()->statement_list || !refFuncDef->symbol || !refFuncDef->symbol->name() || refFuncDef->symbol->enclosingScope()->isTemplate() /* TODO: Templates... */) { return; } // Adjust selection ends. int selStart = cursor.selectionStart(); int selEnd = cursor.selectionEnd(); if (selStart > selEnd) std::swap(selStart, selEnd); Overview printer; // Analyze the content to be extracted, which consists of determining the statements // which are complete and collecting the declarations seen. FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(), selStart, selEnd, file, printer); if (!analyser(refFuncDef)) return; // We also need to collect the declarations of the parameters from the reference function. QSet refFuncParams; if (refFuncDef->declarator->postfix_declarator_list && refFuncDef->declarator->postfix_declarator_list->value && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) { FunctionDeclaratorAST *funcDecltr = refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator(); if (funcDecltr->parameter_declaration_clause && funcDecltr->parameter_declaration_clause->parameter_declaration_list) { for (ParameterDeclarationListAST *it = funcDecltr->parameter_declaration_clause->parameter_declaration_list; it; it = it->next) { ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration(); if (paramDecl->declarator) { const QString &specifiers = file->textOf(file->startOf(paramDecl), file->endOf(paramDecl->type_specifier_list->lastValue())); const QPair &p = assembleDeclarationData(specifiers, paramDecl->declarator, file, printer); if (!p.first.isEmpty()) { analyser.m_knownDecls.insert(p.first, p.second); refFuncParams.insert(p.first); } } } } } // Identify what would be parameters for the new function and its return value, if any. Symbol *funcReturn = nullptr; QList > relevantDecls; const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses; for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) { bool usedBeforeExtraction = false; bool usedAfterExtraction = false; bool usedInsideExtraction = false; const QList &uses = it.value(); foreach (const SemanticInfo::Use &use, uses) { if (use.isInvalid()) continue; const int position = file->position(use.line, use.column); if (position < analyser.m_extractionStart) usedBeforeExtraction = true; else if (position >= analyser.m_extractionEnd) usedAfterExtraction = true; else usedInsideExtraction = true; } const QString &name = printer.prettyName(it.key()->name()); if ((usedBeforeExtraction && usedInsideExtraction) || (usedInsideExtraction && refFuncParams.contains(name))) { QTC_ASSERT(analyser.m_knownDecls.contains(name), return); relevantDecls.append(qMakePair(name, analyser.m_knownDecls.value(name))); } // We assume that the first use of a local corresponds to its declaration. if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) { if (!funcReturn) { QTC_ASSERT(analyser.m_knownDecls.contains(name), return); // The return, if any, is stored as the first item in the list. relevantDecls.prepend(qMakePair(name, analyser.m_knownDecls.value(name))); funcReturn = it.key(); } else { // Would require multiple returns. (Unless we do fancy things, as pointed below.) return; } } } // The current implementation doesn't try to be too smart since it preserves the original form // of the declarations. This might be or not the desired effect. An improvement would be to // let the user somehow customize the function interface. result << new ExtractFunctionOperation(interface, analyser.m_extractionStart, analyser.m_extractionEnd, refFuncDef, funcReturn, relevantDecls, m_functionNameGetter); } namespace { struct ReplaceLiteralsResult { Token token; QString literalText; }; template 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(QApplication::translate("CppTools::QuickFix", "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; QString declFileName; 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()->isFunctionType() || !s->type().match(func->type()) || s->isFunction()) { continue; } declFileName = QString::fromUtf8(matchingClass->fileName(), matchingClass->fileNameLength()); result.file = refactoring.file(declFileName); ASTPath astPath(result.file->cppDocument()); const QList 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; declFileName = correspondingHeaderOrSource(filePath().toString(), &isHeaderFile); if (!QFile::exists(declFileName)) return FoundDeclaration(); result.file = refactoring.file(declFileName); if (!result.file) return FoundDeclaration(); const LookupContext lc(result.file->cppDocument(), snapshot()); const QList 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 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.file(filePath().toString()); deduceTypeNameOfLiteral(currentFile->cppDocument()); ChangeSet changes; if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) { m_literalInfo = ReplaceLiterals(currentFile, &changes, concreteLiteral) .apply(m_functionDefinition->function_body); } else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) { m_literalInfo = ReplaceLiterals(currentFile, &changes, concreteLiteral) .apply(m_functionDefinition->function_body); } else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) { m_literalInfo = ReplaceLiterals(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->fileName() != functionDeclaration.file->fileName()) { 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(); } 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 items = typeOfExpression(m_literal, document, scope); if (!items.isEmpty()) m_typeName = overview.prettyType(items.first().type()); } QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const { QString str; if (hasParameters(ast)) str = QLatin1String(", "); str += m_typeName; if (!m_typeName.endsWith(QLatin1Char('*'))) str += QLatin1Char(' '); str += QLatin1String("newParameter"); 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::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &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 { public: enum Mode { FromPointer, FromVariable, FromReference }; ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode, bool isAutoDeclaration, const SimpleDeclarationAST *simpleDeclaration, const DeclaratorAST *declaratorAST, const SimpleNameAST *identifierAST, Symbol *symbol) : CppQuickFixOperation(interface, priority) , m_mode(mode) , m_isAutoDeclaration(isAutoDeclaration) , m_simpleDeclaration(simpleDeclaration) , m_declaratorAST(declaratorAST) , m_identifierAST(identifierAST) , m_symbol(symbol) , m_refactoring(snapshot()) , m_file(m_refactoring.file(filePath().toString())) , m_document(interface.semanticInfo().doc) { setDescription( mode == FromPointer ? CppQuickFixFactory::tr("Convert to Stack Variable") : CppQuickFixFactory::tr("Convert to Pointer")); } void perform() override { ChangeSet changes; switch (m_mode) { case FromPointer: removePointerOperator(changes); convertToStackVariable(changes); break; case FromReference: removeReferenceOperator(changes); Q_FALLTHROUGH(); case FromVariable: convertToPointer(changes); break; } m_file->setChangeSet(changes); m_file->apply(); } private: void removePointerOperator(ChangeSet &changes) const { if (!m_declaratorAST->ptr_operator_list) return; PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer(); QTC_ASSERT(ptrAST, return); const int pos = m_file->startOf(ptrAST->star_token); changes.remove(pos, pos + 1); } void removeReferenceOperator(ChangeSet &changes) const { ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference(); QTC_ASSERT(refAST, return); const int pos = m_file->startOf(refAST->reference_token); changes.remove(pos, pos + 1); } void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const { ExpressionListAST *exprlist = nullptr; if (newExprAST->new_initializer) { if (ExpressionListParenAST *ast = newExprAST->new_initializer->asExpressionListParen()) exprlist = ast->expression_list; else if (BracedInitializerAST *ast = newExprAST->new_initializer->asBracedInitializer()) exprlist = ast->expression_list; } if (exprlist) { // remove 'new' keyword and type before initializer changes.remove(m_file->startOf(newExprAST->new_token), m_file->startOf(newExprAST->new_initializer)); changes.remove(m_file->endOf(m_declaratorAST->equal_token - 1), m_file->startOf(m_declaratorAST->equal_token + 1)); } else { // remove the whole new expression changes.remove(m_file->endOf(m_identifierAST->firstToken()), m_file->startOf(newExprAST->lastToken())); } } void removeNewKeyword(ChangeSet &changes, NewExpressionAST *newExprAST) const { // remove 'new' keyword before initializer changes.remove(m_file->startOf(newExprAST->new_token), m_file->startOf(newExprAST->new_type_id)); } void convertToStackVariable(ChangeSet &changes) const { // Handle the initializer. if (m_declaratorAST->initializer) { if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression()) { if (m_isAutoDeclaration) { if (!newExpression->new_initializer) changes.insert(m_file->endOf(newExpression), QStringLiteral("()")); removeNewKeyword(changes, newExpression); } else { removeNewExpression(changes, newExpression); } } } // Fix all occurrences of the identifier in this function. ASTPath astPath(m_document); foreach (const SemanticInfo::Use &use, semanticInfo().localUses.value(m_symbol)) { const QList path = astPath(use.line, use.column); AST *idAST = path.last(); bool declarationFound = false; bool starFound = false; int ampersandPos = 0; bool memberAccess = false; bool deleteCall = false; for (int i = path.count() - 2; i >= 0; --i) { if (path.at(i) == m_declaratorAST) { declarationFound = true; break; } if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW) continue; int pos = m_file->startOf(memberAccessAST->access_token); changes.replace(pos, pos + 2, QLatin1String(".")); memberAccess = true; break; } else if (DeleteExpressionAST *deleteAST = path.at(i)->asDeleteExpression()) { const int pos = m_file->startOf(deleteAST->delete_token); changes.insert(pos, QLatin1String("// ")); deleteCall = true; break; } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token); if (tk.kind() == T_STAR) { if (!starFound) { int pos = m_file->startOf(unaryExprAST->unary_op_token); changes.remove(pos, pos + 1); } starFound = true; } else if (tk.kind() == T_AMPER) { ampersandPos = m_file->startOf(unaryExprAST->unary_op_token); } } else if (PointerAST *ptrAST = path.at(i)->asPointer()) { if (!starFound) { const int pos = m_file->startOf(ptrAST->star_token); changes.remove(pos, pos); } starFound = true; } else if (path.at(i)->asFunctionDefinition()) { break; } } if (!declarationFound && !starFound && !memberAccess && !deleteCall) { if (ampersandPos) { changes.insert(ampersandPos, QLatin1String("&(")); changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")")); } else { changes.insert(m_file->startOf(idAST), QLatin1String("&")); } } } } QString typeNameOfDeclaration() const { if (!m_simpleDeclaration || !m_simpleDeclaration->decl_specifier_list || !m_simpleDeclaration->decl_specifier_list->value) { return QString(); } NamedTypeSpecifierAST *namedType = m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier(); if (!namedType) return QString(); Overview overview; return overview.prettyName(namedType->name->name); } void insertNewExpression(ChangeSet &changes, ExpressionAST *ast) const { const QString typeName = typeNameOfDeclaration(); if (CallAST *callAST = ast->asCall()) { if (typeName.isEmpty()) { changes.insert(m_file->startOf(callAST), QLatin1String("new ")); } else { changes.insert(m_file->startOf(callAST), QLatin1String("new ") + typeName + QLatin1Char('(')); changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")")); } } else { if (typeName.isEmpty()) return; changes.insert(m_file->startOf(ast), QLatin1String(" = new ") + typeName); } } void insertNewExpression(ChangeSet &changes) const { const QString typeName = typeNameOfDeclaration(); if (typeName.isEmpty()) return; changes.insert(m_file->endOf(m_identifierAST->firstToken()), QLatin1String(" = new ") + typeName); } void convertToPointer(ChangeSet &changes) const { // Handle initializer. if (m_declaratorAST->initializer) { if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) { changes.insert(m_file->startOf(idExprAST), QLatin1String("&")); } else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) { insertNewExpression(changes, callAST); } else if (ExpressionListParenAST *exprListAST = m_declaratorAST->initializer ->asExpressionListParen()) { insertNewExpression(changes, exprListAST); } else if (BracedInitializerAST *bracedInitializerAST = m_declaratorAST->initializer ->asBracedInitializer()) { insertNewExpression(changes, bracedInitializerAST); } } else { insertNewExpression(changes); } // Fix all occurrences of the identifier in this function. ASTPath astPath(m_document); foreach (const SemanticInfo::Use &use, semanticInfo().localUses.value(m_symbol)) { const QList path = astPath(use.line, use.column); AST *idAST = path.last(); bool insertStar = true; for (int i = path.count() - 2; i >= 0; --i) { if (m_isAutoDeclaration && path.at(i) == m_declaratorAST) { insertStar = false; break; } if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { const int pos = m_file->startOf(memberAccessAST->access_token); changes.replace(pos, pos + 1, QLatin1String("->")); insertStar = false; break; } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) { const int pos = m_file->startOf(unaryExprAST->unary_op_token); changes.remove(pos, pos + 1); insertStar = false; break; } } else if (path.at(i)->asFunctionDefinition()) { break; } } if (insertStar) changes.insert(m_file->startOf(idAST), QLatin1String("*")); } } const Mode m_mode; const bool m_isAutoDeclaration; const SimpleDeclarationAST * const m_simpleDeclaration; const DeclaratorAST * const m_declaratorAST; const SimpleNameAST * const m_identifierAST; Symbol * const m_symbol; const CppRefactoringChanges m_refactoring; const CppRefactoringFilePtr m_file; const Document::Ptr m_document; }; } // anonymous namespace void ConvertFromAndToPointer::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); if (path.count() < 2) return; SimpleNameAST *identifier = path.last()->asSimpleName(); if (!identifier) return; SimpleDeclarationAST *simpleDeclaration = nullptr; DeclaratorAST *declarator = nullptr; bool isFunctionLocal = false; bool isClassLocal = false; ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable; for (int i = path.count() - 2; i >= 0; --i) { AST *ast = path.at(i); if (!declarator && (declarator = ast->asDeclarator())) continue; if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration())) continue; if (declarator && simpleDeclaration) { if (ast->asClassSpecifier()) { isClassLocal = true; } else if (ast->asFunctionDefinition() && !isClassLocal) { isFunctionLocal = true; break; } } } if (!isFunctionLocal || !simpleDeclaration || !declarator) return; Symbol *symbol = nullptr; for (List *lst = simpleDeclaration->symbols; lst; lst = lst->next) { if (lst->value->name() == identifier->name) { symbol = lst->value; break; } } if (!symbol) return; bool isAutoDeclaration = false; if (symbol->storage() == Symbol::Auto) { // For auto variables we must deduce the type from the initializer. if (!declarator->initializer) return; isAutoDeclaration = true; TypeOfExpression typeOfExpression; typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); typeOfExpression.setExpandTemplates(true); CppRefactoringFilePtr file = interface.currentFile(); Scope *scope = file->scopeAt(declarator->firstToken()); QList result = typeOfExpression(file->textOf(declarator->initializer).toUtf8(), scope, TypeOfExpression::Preprocess); if (!result.isEmpty() && result.first().type()->isPointerType()) mode = ConvertFromAndToPointerOp::FromPointer; } else if (declarator->ptr_operator_list) { for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) { if (ops != declarator->ptr_operator_list) { // Bail out on more complex pointer types (e.g. pointer of pointer, // or reference of pointer). return; } if (ops->value->asPointer()) mode = ConvertFromAndToPointerOp::FromPointer; else if (ops->value->asReference()) mode = ConvertFromAndToPointerOp::FromReference; } } const int priority = path.size() - 1; result << new ConvertFromAndToPointerOp(interface, priority, mode, isAutoDeclaration, simpleDeclaration, declarator, identifier, symbol); } namespace { class InsertQtPropertyMembersOp: public CppQuickFixOperation { public: enum GenerateFlag { GenerateGetter = 1 << 0, GenerateSetter = 1 << 1, GenerateSignal = 1 << 2, GenerateStorage = 1 << 3, GenerateReset = 1 << 4 }; InsertQtPropertyMembersOp(const CppQuickFixInterface &interface, int priority, QtPropertyDeclarationAST *declaration, Class *klass, int generateFlags, const QString &getterName, const QString &setterName, const QString &resetName, const QString &signalName, const QString &storageName) : CppQuickFixOperation(interface, priority) , m_declaration(declaration) , m_class(klass) , m_generateFlags(generateFlags) , m_getterName(getterName) , m_setterName(setterName) , m_resetName(resetName) , m_signalName(signalName) , m_storageName(storageName) { setDescription(CppQuickFixFactory::tr("Generate Missing Q_PROPERTY Members")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr file = refactoring.file(filePath().toString()); InsertionPointLocator locator(refactoring); ChangeSet declarations; const QString typeName = file->textOf(m_declaration->type_id); const QString propertyName = file->textOf(m_declaration->property_name); QString baseName = memberBaseName(m_storageName); if (baseName.isEmpty() || baseName == m_storageName) baseName = QStringLiteral("arg"); // getter declaration if (m_generateFlags & GenerateGetter) { const QString getterDeclaration = typeName + QLatin1Char(' ') + m_getterName + QLatin1String("() const\n{\nreturn ") + m_storageName + QLatin1String(";\n}\n"); InsertionLocation getterLoc = locator.methodDeclarationInClass(file->fileName(), m_class, InsertionPointLocator::Public); QTC_ASSERT(getterLoc.isValid(), return); insertAndIndent(file, &declarations, getterLoc, getterDeclaration); } // setter declaration InsertionLocation setterLoc; if (m_generateFlags & GenerateSetter) { QString setterDeclaration; QTextStream setter(&setterDeclaration); setter << "void " << m_setterName << '(' << typeName << ' ' << baseName << ")\n{\n"; if (m_signalName.isEmpty()) { setter << m_storageName << " = " << baseName << ";\n}\n"; } else { if (isQtFuzzyComparable(typeName)) { setter << "qWarning(\"Floating point comparison needs context sanity check\");\n"; setter << "if (qFuzzyCompare(" << m_storageName << ", " << baseName << "))\nreturn;\n\n"; } else setter << "if (" << m_storageName << " == " << baseName << ")\nreturn;\n\n"; setter << m_storageName << " = " << baseName << ";\nemit " << m_signalName << '(' << m_storageName << ");\n}\n"; } setterLoc = locator.methodDeclarationInClass(file->fileName(), m_class, InsertionPointLocator::PublicSlot); QTC_ASSERT(setterLoc.isValid(), return); insertAndIndent(file, &declarations, setterLoc, setterDeclaration); } // reset declaration if (m_generateFlags & GenerateReset) { QString declaration; QTextStream stream(&declaration); stream << "void " << m_resetName << "()\n{\n"; if (m_generateFlags & GenerateSetter) { stream << m_setterName << "({}); // TODO: Adapt to use your actual default value\n"; } else { stream << "static const " << typeName << " defaultValue{}; " "// TODO: Adapt to use your actual default value\n"; if (!m_signalName.isEmpty()) stream << "if (" << m_storageName << " == defaultValue)\nreturn;\n\n"; stream << m_storageName << " = defaultValue;\n"; if (!m_signalName.isEmpty()) stream << "emit " << m_signalName << '(' << m_storageName << ");\n"; } stream << "}\n"; const InsertionLocation loc = setterLoc.isValid() ? InsertionLocation(setterLoc.fileName(), {}, {}, setterLoc.line(), 1) : locator.methodDeclarationInClass(file->fileName(), m_class, InsertionPointLocator::PublicSlot); QTC_ASSERT(loc.isValid(), return); insertAndIndent(file, &declarations, loc, declaration); } // signal declaration if (m_generateFlags & GenerateSignal) { const QString declaration = QLatin1String("void ") + m_signalName + QLatin1Char('(') + typeName + QLatin1Char(' ') + baseName + QLatin1String(");\n"); InsertionLocation loc = locator.methodDeclarationInClass(file->fileName(), m_class, InsertionPointLocator::Signals); QTC_ASSERT(loc.isValid(), return); insertAndIndent(file, &declarations, loc, declaration); } // storage if (m_generateFlags & GenerateStorage) { const QString storageDeclaration = typeName + QLatin1String(" m_") + propertyName + QLatin1String(";\n"); InsertionLocation storageLoc = locator.methodDeclarationInClass(file->fileName(), m_class, InsertionPointLocator::Private); QTC_ASSERT(storageLoc.isValid(), return); insertAndIndent(file, &declarations, storageLoc, storageDeclaration); } file->setChangeSet(declarations); file->apply(); } private: void insertAndIndent(const RefactoringFilePtr &file, ChangeSet *changeSet, const InsertionLocation &loc, const QString &text) { int targetPosition1 = file->position(loc.line(), loc.column()); int targetPosition2 = qMax(0, file->position(loc.line(), 1) - 1); changeSet->insert(targetPosition1, loc.prefix() + text + loc.suffix()); file->appendIndentRange(ChangeSet::Range(targetPosition2, targetPosition1)); } QtPropertyDeclarationAST *m_declaration; Class *m_class; int m_generateFlags; QString m_getterName; QString m_setterName; QString m_resetName; QString m_signalName; QString m_storageName; }; } // anonymous namespace void InsertQtPropertyMembers::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); if (path.isEmpty()) return; AST * const ast = path.last(); QtPropertyDeclarationAST *qtPropertyDeclaration = ast->asQtPropertyDeclaration(); if (!qtPropertyDeclaration || !qtPropertyDeclaration->type_id) return; ClassSpecifierAST *klass = nullptr; for (int i = path.size() - 2; i >= 0; --i) { klass = path.at(i)->asClassSpecifier(); if (klass) break; } if (!klass) return; CppRefactoringFilePtr file = interface.currentFile(); const QString propertyName = file->textOf(qtPropertyDeclaration->property_name); QString getterName; QString setterName; QString resetName; QString signalName; int generateFlags = 0; QtPropertyDeclarationItemListAST *it = qtPropertyDeclaration->property_declaration_item_list; for (; it; it = it->next) { const char *tokenString = file->tokenAt(it->value->item_name_token).spell(); if (!qstrcmp(tokenString, "READ")) { getterName = file->textOf(it->value->expression); generateFlags |= InsertQtPropertyMembersOp::GenerateGetter; } else if (!qstrcmp(tokenString, "WRITE")) { setterName = file->textOf(it->value->expression); generateFlags |= InsertQtPropertyMembersOp::GenerateSetter; } else if (!qstrcmp(tokenString, "RESET")) { resetName = file->textOf(it->value->expression); generateFlags |= InsertQtPropertyMembersOp::GenerateReset; } else if (!qstrcmp(tokenString, "NOTIFY")) { signalName = file->textOf(it->value->expression); generateFlags |= InsertQtPropertyMembersOp::GenerateSignal; } } const QString storageName = QLatin1String("m_") + propertyName; generateFlags |= InsertQtPropertyMembersOp::GenerateStorage; Class *c = klass->symbol; Overview overview; for (int i = 0; i < c->memberCount(); ++i) { Symbol *member = c->memberAt(i); FullySpecifiedType type = member->type(); if (member->asFunction() || (type.isValid() && type->asFunctionType())) { const QString name = overview.prettyName(member->name()); if (name == getterName) generateFlags &= ~InsertQtPropertyMembersOp::GenerateGetter; else if (name == setterName) generateFlags &= ~InsertQtPropertyMembersOp::GenerateSetter; else if (name == resetName) generateFlags &= ~InsertQtPropertyMembersOp::GenerateReset; else if (name == signalName) generateFlags &= ~InsertQtPropertyMembersOp::GenerateSignal; } else if (member->asDeclaration()) { const QString name = overview.prettyName(member->name()); if (name == storageName) generateFlags &= ~InsertQtPropertyMembersOp::GenerateStorage; } } if (getterName.isEmpty() && setterName.isEmpty() && signalName.isEmpty()) return; result << new InsertQtPropertyMembersOp(interface, path.size() - 1, qtPropertyDeclaration, c, generateFlags, getterName, setterName, resetName, signalName, storageName); } namespace { class ApplyDeclDefLinkOperation : public CppQuickFixOperation { public: explicit ApplyDeclDefLinkOperation(const CppQuickFixInterface &interface, const QSharedPointer &link) : CppQuickFixOperation(interface, 100) , m_link(link) {} void perform() override { if (editor()->declDefLink() == m_link) editor()->applyDeclDefLinkChanges(/*don't jump*/false); } protected: virtual void performChanges(const CppRefactoringFilePtr &, const CppRefactoringChanges &) { /* never called since perform is overridden */ } private: QSharedPointer m_link; }; } // anonymous namespace void ApplyDeclDefLinkChanges::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { QSharedPointer link = interface.editor()->declDefLink(); if (!link || !link->isMarkerVisible()) return; auto op = new ApplyDeclDefLinkOperation(interface, link); op->setDescription(FunctionDeclDefLink::tr("Apply Function Signature Changes")); result << op; } namespace { QString definitionSignature(const CppQuickFixInterface *assist, FunctionDefinitionAST *functionDefinitionAST, CppRefactoringFilePtr &baseFile, CppRefactoringFilePtr &targetFile, Scope *scope) { QTC_ASSERT(assist, return QString()); QTC_ASSERT(functionDefinitionAST, return QString()); QTC_ASSERT(scope, return QString()); Function *func = functionDefinitionAST->symbol; QTC_ASSERT(func, return QString()); LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); ClassOrNamespace *cppCoN = cppContext.lookupType(scope); if (!cppCoN) cppCoN = cppContext.globalNamespace(); SubstitutionEnvironment env; env.setContext(assist->context()); env.switchScope(func->enclosingScope()); UseMinimalNames q(cppCoN); env.enter(&q); Control *control = assist->context().bindings()->control().data(); Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; oo.showReturnTypes = true; oo.showArgumentNames = true; oo.showEnclosingTemplate = true; const Name *name = func->name(); if (name && nameIncludesOperatorName(name)) { CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; const QString operatorNameText = baseFile->textOf(coreDeclarator); oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); } const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); const FullySpecifiedType tn = rewriteType(func->type(), &env, control); return oo.prettyType(tn, nameText); } class MoveFuncDefRefactoringHelper { public: enum MoveType { MoveOutside, MoveToCppFile, MoveOutsideMemberToCppFile }; MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, const QString &fromFile, const QString &toFile) : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) { m_fromFile = m_changes.file(fromFile); m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.file(toFile); } void performMove(FunctionDefinitionAST *funcAST) { // Determine file, insert position and scope InsertionLocation l = insertLocationForMethodDefinition( funcAST->symbol, false, NamespaceHandling::Ignore, m_changes, m_toFile->fileName()); const QString prefix = l.prefix(); const QString suffix = l.suffix(); const int insertPos = m_toFile->position(l.line(), l.column()); Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); // construct definition const QString funcDec = inlinePrefix( m_toFile->fileName(), [this] { return m_type == MoveOutside; }) + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, scopeAtInsertPos); QString funcDef = prefix + funcDec; const int startPosition = m_fromFile->endOf(funcAST->declarator); const int endPosition = m_fromFile->endOf(funcAST); funcDef += m_fromFile->textOf(startPosition, endPosition); funcDef += suffix; // insert definition at new position m_toFileChangeSet.insert(insertPos, funcDef); m_toFile->appendIndentRange(ChangeSet::Range(insertPos, insertPos + funcDef.size())); m_toFile->setOpenEditor(true, insertPos); // remove definition from fromFile if (m_type == MoveOutsideMemberToCppFile) { m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); } else { QString textFuncDecl = m_fromFile->textOf(funcAST); textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); } } void applyChanges() { if (!m_toFileChangeSet.isEmpty()) { m_toFile->setChangeSet(m_toFileChangeSet); m_toFile->apply(); } if (!m_fromFileChangeSet.isEmpty()) { m_fromFile->setChangeSet(m_fromFileChangeSet); m_fromFile->apply(); } } private: CppQuickFixOperation *m_operation; MoveType m_type; CppRefactoringChanges m_changes; CppRefactoringFilePtr m_fromFile; CppRefactoringFilePtr m_toFile; ChangeSet m_fromFileChangeSet; ChangeSet m_toFileChangeSet; }; class MoveFuncDefOutsideOp : public CppQuickFixOperation { public: MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, MoveFuncDefRefactoringHelper::MoveType type, FunctionDefinitionAST *funcDef, const QString &cppFileName) : CppQuickFixOperation(interface, 0) , m_funcDef(funcDef) , m_type(type) , m_cppFileName(cppFileName) , m_headerFileName(QString::fromUtf8(funcDef->symbol->fileName(), funcDef->symbol->fileNameLength())) { if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move Definition Outside Class")); } else { const QDir dir = QFileInfo(m_headerFileName).dir(); setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move Definition to %1") .arg(dir.relativeFilePath(m_cppFileName))); } } void perform() override { MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFileName, m_cppFileName); helper.performMove(m_funcDef); helper.applyChanges(); } private: FunctionDefinitionAST *m_funcDef; MoveFuncDefRefactoringHelper::MoveType m_type; const QString m_cppFileName; const QString m_headerFileName; }; } // anonymous namespace void MoveFuncDefOutside::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); SimpleDeclarationAST *classAST = nullptr; FunctionDefinitionAST *funcAST = nullptr; bool moveOutsideMemberDefinition = false; const int pathSize = path.size(); for (int idx = 1; idx < pathSize; ++idx) { if ((funcAST = path.at(idx)->asFunctionDefinition())) { // check cursor position if (idx != pathSize - 1 // Do not allow "void a() @ {..." && funcAST->function_body && !interface.isCursorOn(funcAST->function_body)) { if (path.at(idx - 1)->asTranslationUnit()) { // normal function if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member moveOutsideMemberDefinition = true; // definition break; } if (idx > 1) { if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function break; if (path.at(idx - 2)->asNamespace()) // normal function in namespace break; } } funcAST = nullptr; } } if (!funcAST || !funcAST->symbol) return; bool isHeaderFile = false; const QString cppFileName = correspondingHeaderOrSource(interface.filePath().toString(), &isHeaderFile); if (isHeaderFile && !cppFileName.isEmpty()) { const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile : MoveFuncDefRefactoringHelper::MoveToCppFile; result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); } if (classAST) result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, funcAST, QLatin1String("")); return; } namespace { class MoveAllFuncDefOutsideOp : public CppQuickFixOperation { public: MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, MoveFuncDefRefactoringHelper::MoveType type, ClassSpecifierAST *classDef, const QString &cppFileName) : CppQuickFixOperation(interface, 0) , m_type(type) , m_classDef(classDef) , m_cppFileName(cppFileName) , m_headerFileName(QString::fromUtf8(classDef->symbol->fileName(), classDef->symbol->fileNameLength())) { if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move All Function " "Definitions Outside Class")); } else { const QDir dir = QFileInfo(m_headerFileName).dir(); setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move All Function Definitions to %1") .arg(dir.relativeFilePath(m_cppFileName))); } } void perform() override { MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFileName, m_cppFileName); for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { if (funcAST->symbol && !funcAST->symbol->isGenerated()) helper.performMove(funcAST); } } helper.applyChanges(); } private: MoveFuncDefRefactoringHelper::MoveType m_type; ClassSpecifierAST *m_classDef; const QString m_cppFileName; const QString m_headerFileName; }; } // anonymous namespace void MoveAllFuncDefOutside::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); const int pathSize = path.size(); if (pathSize < 2) return; // Determine if cursor is on a class which is not a base class ClassSpecifierAST *classAST = nullptr; if (SimpleNameAST *nameAST = path.at(pathSize - 1)->asSimpleName()) { if (!interface.isCursorOn(nameAST)) return; classAST = path.at(pathSize - 2)->asClassSpecifier(); } if (!classAST) return; // Determine if the class has at least one function definition bool classContainsFunctions = false; for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { if (funcAST->symbol && !funcAST->symbol->isGenerated()) { classContainsFunctions = true; break; } } } if (!classContainsFunctions) return; bool isHeaderFile = false; const QString cppFileName = correspondingHeaderOrSource(interface.filePath().toString(), &isHeaderFile); if (isHeaderFile && !cppFileName.isEmpty()) { result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveToCppFile, classAST, cppFileName); } result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, classAST, QLatin1String("")); } namespace { class MoveFuncDefToDeclOp : public CppQuickFixOperation { public: MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, const QString &fromFileName, const QString &toFileName, FunctionDefinitionAST *funcDef, const QString &declText, const ChangeSet::Range &fromRange, const ChangeSet::Range &toRange) : CppQuickFixOperation(interface, 0) , m_fromFileName(fromFileName) , m_toFileName(toFileName) , m_funcAST(funcDef) , m_declarationText(declText) , m_fromRange(fromRange) , m_toRange(toRange) { if (m_toFileName == m_fromFileName) { setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move Definition to Class")); } else { const QDir dir = QFileInfo(m_fromFileName).dir(); setDescription(QCoreApplication::translate("CppEditor::QuickFix", "Move Definition to %1") .arg(dir.relativeFilePath(m_toFileName))); } } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr fromFile = refactoring.file(m_fromFileName); CppRefactoringFilePtr toFile = refactoring.file(m_toFileName); const QString wholeFunctionText = m_declarationText + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), fromFile->endOf(m_funcAST->function_body)); // Replace declaration with function and delete old definition ChangeSet toTarget; toTarget.replace(m_toRange, wholeFunctionText); if (m_toFileName == m_fromFileName) toTarget.remove(m_fromRange); toFile->setChangeSet(toTarget); toFile->appendIndentRange(m_toRange); toFile->setOpenEditor(true, m_toRange.start); toFile->apply(); if (m_toFileName != m_fromFileName) { ChangeSet fromTarget; fromTarget.remove(m_fromRange); fromFile->setChangeSet(fromTarget); fromFile->apply(); } } private: const QString m_fromFileName; const QString m_toFileName; FunctionDefinitionAST *m_funcAST; const QString m_declarationText; const ChangeSet::Range m_fromRange; const ChangeSet::Range m_toRange; }; } // anonymous namespace void MoveFuncDefToDecl::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); AST *completeDefAST = nullptr; FunctionDefinitionAST *funcAST = nullptr; const int pathSize = path.size(); for (int idx = 1; idx < pathSize; ++idx) { if ((funcAST = path.at(idx)->asFunctionDefinition())) { AST *enclosingAST = path.at(idx - 1); if (enclosingAST->asClassSpecifier()) return; // check cursor position if (idx != pathSize - 1 // Do not allow "void a() @ {..." && funcAST->function_body && !interface.isCursorOn(funcAST->function_body)) { completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; break; } funcAST = nullptr; } } if (!funcAST || !funcAST->symbol) return; const CppRefactoringChanges refactoring(interface.snapshot()); const CppRefactoringFilePtr defFile = refactoring.file(interface.filePath().toString()); const ChangeSet::Range defRange = defFile->range(completeDefAST); // Determine declaration (file, range, text); QString declFileName; ChangeSet::Range declRange; QString declText; Function *func = funcAST->symbol; if (Class *matchingClass = isMemberFunction(interface.context(), func)) { // Dealing with member functions const QualifiedNameId *qName = func->name()->asQualifiedNameId(); for (Symbol *symbol = matchingClass->find(qName->identifier()); symbol; symbol = symbol->next()) { Symbol *s = symbol; if (func->enclosingScope()->isTemplate()) { if (const Template *templ = s->type()->asTemplateType()) { if (Symbol *decl = templ->declaration()) { if (decl->type()->isFunctionType()) s = decl; } } } if (!s->name() || !qName->identifier()->match(s->identifier()) || !s->type()->isFunctionType() || !s->type().match(func->type()) || s->isFunction()) { continue; } declFileName = QString::fromUtf8(matchingClass->fileName(), matchingClass->fileNameLength()); const CppRefactoringFilePtr declFile = refactoring.file(declFileName); ASTPath astPath(declFile->cppDocument()); const QList path = astPath(s->line(), s->column()); for (int idx = path.size() - 1; idx > 0; --idx) { AST *node = path.at(idx); if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { if (simpleDecl->symbols && !simpleDecl->symbols->next) { declRange = declFile->range(simpleDecl); declText = declFile->textOf(simpleDecl); declText.remove(-1, 1); // remove ';' from declaration text break; } } } if (!declText.isEmpty()) break; } } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { // Dealing with free functions bool isHeaderFile = false; declFileName = correspondingHeaderOrSource(interface.filePath().toString(), &isHeaderFile); if (isHeaderFile) return; const CppRefactoringFilePtr declFile = refactoring.file(declFileName); const LookupContext lc(declFile->cppDocument(), interface.snapshot()); const QList candidates = lc.lookup(func->name(), matchingNamespace); for (const LookupItem &candidate : candidates) { if (Symbol *s = candidate.declaration()) { if (s->asDeclaration()) { ASTPath astPath(declFile->cppDocument()); const QList path = astPath(s->line(), s->column()); for (AST *node : path) { if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { declRange = declFile->range(simpleDecl); declText = declFile->textOf(simpleDecl); declText.remove(-1, 1); // remove ';' from declaration text break; } } } } if (!declText.isEmpty()) { declText.prepend(inlinePrefix(declFileName)); break; } } } if (!declFileName.isEmpty() && !declText.isEmpty()) result << new MoveFuncDefToDeclOp(interface, interface.filePath().toString(), declFileName, funcAST, declText, defRange, declRange); } namespace { class AssignToLocalVariableOperation : public CppQuickFixOperation { public: explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface, const int insertPos, const AST *ast, const Name *name) : CppQuickFixOperation(interface) , m_insertPos(insertPos) , m_ast(ast) , m_name(name) { setDescription(QApplication::translate("CppTools::QuickFix", "Assign to Local Variable")); } void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr file = refactoring.file(filePath().toString()); // Determine return type and new variable name TypeOfExpression typeOfExpression; typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings()); typeOfExpression.setExpandTemplates(true); Scope *scope = file->scopeAt(m_ast->firstToken()); const QList result = typeOfExpression(file->textOf(m_ast).toUtf8(), scope, TypeOfExpression::Preprocess); if (!result.isEmpty()) { SubstitutionEnvironment env; env.setContext(context()); env.switchScope(result.first().scope()); ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); if (!con) con = typeOfExpression.context().globalNamespace(); UseMinimalNames q(con); env.enter(&q); Control *control = context().bindings()->control().data(); FullySpecifiedType type = rewriteType(result.first().type(), &env, control); Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); QString originalName = oo.prettyName(m_name); QString newName = originalName; if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive) && newName.length() > 3 && newName.at(3).isUpper()) { newName.remove(0, 3); newName.replace(0, 1, newName.at(0).toLower()); } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive) && newName.length() > 2 && newName.at(2).isUpper()) { newName.remove(0, 2); newName.replace(0, 1, newName.at(0).toLower()); } else { newName.replace(0, 1, newName.at(0).toUpper()); newName.prepend(QLatin1String("local")); } const int nameLength = originalName.length(); QString tempType = oo.prettyType(type, m_name); const QString insertString = tempType.replace( tempType.length() - nameLength, nameLength, newName + QLatin1String(" = ")); if (!tempType.isEmpty()) { ChangeSet changes; changes.insert(m_insertPos, insertString); file->setChangeSet(changes); file->apply(); // move cursor to new variable name QTextCursor c = file->cursor(); c.setPosition(m_insertPos + insertString.length() - newName.length() - 3); c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); editor()->setTextCursor(c); } } } private: const int m_insertPos; const AST *m_ast; const Name *m_name; }; } // anonymous namespace void AssignToLocalVariable::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); AST *outerAST = nullptr; SimpleNameAST *nameAST = nullptr; for (int i = path.size() - 3; i >= 0; --i) { if (CallAST *callAST = path.at(i)->asCall()) { if (!interface.isCursorOn(callAST)) return; if (i - 2 >= 0) { const int idx = i - 2; if (path.at(idx)->asSimpleDeclaration()) return; if (path.at(idx)->asExpressionStatement()) return; if (path.at(idx)->asMemInitializer()) return; if (path.at(idx)->asCall()) { // Fallback if we have a->b()->c()... --i; continue; } } for (int a = i - 1; a > 0; --a) { if (path.at(a)->asBinaryExpression()) return; if (path.at(a)->asReturnStatement()) return; if (path.at(a)->asCall()) return; } if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member if (NameAST *name = member->member_name) nameAST = name->asSimpleName(); } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or nameAST = qname->unqualified_name->asSimpleName(); // func in ns } else { // normal nameAST = path.at(i + 2)->asSimpleName(); } if (nameAST) { outerAST = callAST; break; } } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) { if (!interface.isCursorOn(newexp)) return; if (i - 2 >= 0) { const int idx = i - 2; if (path.at(idx)->asSimpleDeclaration()) return; if (path.at(idx)->asExpressionStatement()) return; if (path.at(idx)->asMemInitializer()) return; } for (int a = i - 1; a > 0; --a) { if (path.at(a)->asReturnStatement()) return; if (path.at(a)->asCall()) return; } if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) { nameAST = ts->name->asSimpleName(); outerAST = newexp; break; } } } if (outerAST && nameAST) { const CppRefactoringFilePtr file = interface.currentFile(); QList items; TypeOfExpression typeOfExpression; typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), interface.context().bindings()); typeOfExpression.setExpandTemplates(true); // If items are empty, AssignToLocalVariableOperation will fail. items = typeOfExpression(file->textOf(outerAST).toUtf8(), file->scopeAt(outerAST->firstToken()), TypeOfExpression::Preprocess); if (items.isEmpty()) return; if (CallAST *callAST = outerAST->asCall()) { items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(), file->scopeAt(callAST->base_expression->firstToken()), TypeOfExpression::Preprocess); } else { items = typeOfExpression(file->textOf(nameAST).toUtf8(), file->scopeAt(nameAST->firstToken()), TypeOfExpression::Preprocess); } foreach (const LookupItem &item, items) { if (!item.declaration()) continue; if (Function *func = item.declaration()->asFunction()) { if (func->isSignal() || func->returnType()->isVoidType()) return; } else if (Declaration *dec = item.declaration()->asDeclaration()) { if (Function *func = dec->type()->asFunctionType()) { if (func->isSignal() || func->returnType()->isVoidType()) return; } } const Name *name = nameAST->name; const int insertPos = interface.currentFile()->startOf(outerAST); result << new AssignToLocalVariableOperation(interface, insertPos, outerAST, name); return; } } } namespace { class OptimizeForLoopOperation: public CppQuickFixOperation { public: OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst, const bool optimizePostcrement, const ExpressionAST *expression, const FullySpecifiedType &type) : CppQuickFixOperation(interface) , m_forAst(forAst) , m_optimizePostcrement(optimizePostcrement) , m_expression(expression) , m_type(type) { setDescription(QApplication::translate("CppTools::QuickFix", "Optimize for-Loop")); } void perform() override { QTC_ASSERT(m_forAst, return); const QString filename = currentFile()->fileName(); const CppRefactoringChanges refactoring(snapshot()); const CppRefactoringFilePtr file = refactoring.file(filename); ChangeSet change; // Optimize post (in|de)crement operator to pre (in|de)crement operator if (m_optimizePostcrement && m_forAst->expression) { PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr(); if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) { change.flip(file->range(incrdecr->base_expression), file->range(incrdecr->incr_decr_token)); } } // Optimize Condition int renamePos = -1; if (m_expression) { QString varName = QLatin1String("total"); if (file->textOf(m_forAst->initializer).length() == 1) { Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); const QString typeAndName = oo.prettyType(m_type, varName); renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length(); change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" typeAndName + QLatin1String(" = ") + file->textOf(m_expression)); } else { // Check if varName is already used if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) { if (DeclarationAST *decl = ds->declaration) { if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) { for (;;) { bool match = false; for (DeclaratorListAST *it = sdecl->declarator_list; it; it = it->next) { if (file->textOf(it->value->core_declarator) == varName) { varName += QLatin1Char('X'); match = true; break; } } if (!match) break; } } } } renamePos = file->endOf(m_forAst->initializer) + 1 + varName.length(); change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" QLatin1String(", ") + varName + QLatin1String(" = ") + file->textOf(m_expression)); } ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression)); change.replace(exprRange, varName); } file->setChangeSet(change); file->apply(); // Select variable name and trigger symbol rename if (renamePos != -1) { QTextCursor c = file->cursor(); c.setPosition(renamePos); editor()->setTextCursor(c); editor()->renameSymbolUnderCursor(); c.select(QTextCursor::WordUnderCursor); editor()->setTextCursor(c); } } private: const ForStatementAST *m_forAst; const bool m_optimizePostcrement; const ExpressionAST *m_expression; const FullySpecifiedType m_type; }; } // anonymous namespace void OptimizeForLoop::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList path = interface.path(); ForStatementAST *forAst = nullptr; if (!path.isEmpty()) forAst = path.last()->asForStatement(); if (!forAst || !interface.isCursorOn(forAst)) return; // Check for optimizing a postcrement const CppRefactoringFilePtr file = interface.currentFile(); bool optimizePostcrement = false; if (forAst->expression) { if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) { const Token t = file->tokenAt(incrdecr->incr_decr_token); if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS)) optimizePostcrement = true; } } // Check for optimizing condition bool optimizeCondition = false; FullySpecifiedType conditionType; ExpressionAST *conditionExpression = nullptr; if (forAst->initializer && forAst->condition) { if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) { // Get the expression against which we should evaluate IdExpressionAST *conditionId = binary->left_expression->asIdExpression(); if (conditionId) { conditionExpression = binary->right_expression; } else { conditionId = binary->right_expression->asIdExpression(); conditionExpression = binary->left_expression; } if (conditionId && conditionExpression && !(conditionExpression->asNumericLiteral() || conditionExpression->asStringLiteral() || conditionExpression->asIdExpression() || conditionExpression->asUnaryExpression())) { // Determine type of for initializer FullySpecifiedType initializerType; if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) { if (stmt->declaration) { if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) { if (decl->symbols) { if (Symbol *symbol = decl->symbols->value) initializerType = symbol->type(); } } } } // Determine type of for condition TypeOfExpression typeOfExpression; typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), interface.context().bindings()); typeOfExpression.setExpandTemplates(true); Scope *scope = file->scopeAt(conditionId->firstToken()); const QList conditionItems = typeOfExpression( conditionId, interface.semanticInfo().doc, scope); if (!conditionItems.isEmpty()) conditionType = conditionItems.first().type(); if (conditionType.isValid() && (file->textOf(forAst->initializer) == QLatin1String(";") || initializerType == conditionType)) { optimizeCondition = true; } } } } if (optimizePostcrement || optimizeCondition) { result << new OptimizeForLoopOperation(interface, forAst, optimizePostcrement, optimizeCondition ? conditionExpression : nullptr, conditionType); } } 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 (const quint8 c : contents) { 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() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); 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 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 &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) result << new EscapeStringLiteralOperation(interface, literal, true); if (canUnescape) result << new EscapeStringLiteralOperation(interface, literal, false); } namespace { class ConvertQt4ConnectOperation: public CppQuickFixOperation { public: ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes) : CppQuickFixOperation(interface, 1), m_changes(changes) { setDescription(QApplication::translate("CppTools::QuickFix", "Convert connect() to Qt 5 Style")); } private: void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); currentFile->setChangeSet(m_changes); currentFile->apply(); } const ChangeSet m_changes; }; Symbol *skipForwardDeclarations(const QList &symbols) { foreach (Symbol *symbol, symbols) { if (!symbol->type()->isForwardClassDeclarationType()) return symbol; } return nullptr; } bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction) { QList candidates; for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { if (Function *func = (*it)->asFunction()) { const Name *funcName = func->name(); if (!funcName->isOperatorNameId() && !funcName->isConversionNameId() && func->returnType().type() == pointerType && func->isConst() && func->argumentCount() == 0) { candidates << func; } } } const Name *funcName = nullptr; switch (candidates.size()) { case 0: return false; case 1: funcName = candidates.first()->name(); break; default: // Multiple candidates - prefer a function named data foreach (Function *func, candidates) { if (!strcmp(func->name()->identifier()->chars(), "data")) { funcName = func->name(); break; } } if (!funcName) funcName = candidates.first()->name(); } const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); *objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()"); return true; } PointerType *determineConvertedType(NamedType *namedType, const LookupContext &context, Scope *scope, QString *objAccessFunction) { if (!namedType) return nullptr; if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) { if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) { if (Class *klass = objectClassSymbol->asClass()) { for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { if (Function *func = (*it)->asFunction()) { if (const ConversionNameId *conversionName = func->name()->asConversionNameId()) { if (PointerType *type = conversionName->type()->asPointerType()) { if (findRawAccessFunction(klass, type, objAccessFunction)) return type; } } } } } } } return nullptr; } Class *senderOrReceiverClass(const CppQuickFixInterface &interface, const CppRefactoringFilePtr &file, const ExpressionAST *objectPointerAST, Scope *objectPointerScope, QString *objAccessFunction) { const LookupContext &context = interface.context(); QByteArray objectPointerExpression; if (objectPointerAST) objectPointerExpression = file->textOf(objectPointerAST).toUtf8(); else objectPointerExpression = "this"; TypeOfExpression toe; toe.setExpandTemplates(true); toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings()); const QList objectPointerExpressions = toe(objectPointerExpression, objectPointerScope, TypeOfExpression::Preprocess); QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr); Type *objectPointerTypeBase = objectPointerExpressions.first().type().type(); QTC_ASSERT(objectPointerTypeBase, return nullptr); PointerType *objectPointerType = objectPointerTypeBase->asPointerType(); if (!objectPointerType) { objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context, objectPointerScope, objAccessFunction); } QTC_ASSERT(objectPointerType, return nullptr); Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference QTC_ASSERT(objectTypeBase, return nullptr); NamedType *objectType = objectTypeBase->asNamedType(); QTC_ASSERT(objectType, return nullptr); ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope); if (!objectClassCON) { objectClassCON = objectPointerExpressions.first().binding(); QTC_ASSERT(objectClassCON, return nullptr); } QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr); Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols()); QTC_ASSERT(objectClassSymbol, return nullptr); return objectClassSymbol->asClass(); } bool findConnectReplacement(const CppQuickFixInterface &interface, const ExpressionAST *objectPointerAST, const QtMethodAST *methodAST, const CppRefactoringFilePtr &file, QString *replacement, QString *objAccessFunction) { // Get name of method if (!methodAST->declarator || !methodAST->declarator->core_declarator) return false; DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId(); if (!methodDeclIdAST) return false; NameAST *methodNameAST = methodDeclIdAST->name; if (!methodNameAST) return false; // Lookup object pointer type Scope *scope = file->scopeAt(methodAST->firstToken()); Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope, objAccessFunction); QTC_ASSERT(objectClass, return false); // Look up member function in call, including base class members. const LookupContext &context = interface.context(); const QList methodResults = context.lookup(methodNameAST->name, objectClass); if (methodResults.isEmpty()) return false; // Maybe mis-spelled signal/slot name Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads QTC_ASSERT(baseClassScope, return false); Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot QTC_ASSERT(classOfMethod, return false); Symbol *method = methodResults.at(0).declaration(); QTC_ASSERT(method, return false); // Minimize qualification Control *control = context.bindings()->control().data(); ClassOrNamespace *functionCON = context.lookupParent(scope); const Name *shortName = LookupContext::minimalName(method, functionCON, control); if (!shortName->asQualifiedNameId()) shortName = control->qualifiedNameId(classOfMethod->name(), shortName); const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); *replacement = QLatin1Char('&') + oo.prettyName(shortName); return true; } bool onConnectOrDisconnectCall(AST *ast, const ExpressionListAST **arguments) { if (!ast) return false; CallAST *call = ast->asCall(); if (!call) return false; if (!call->base_expression) return false; const IdExpressionAST *idExpr = call->base_expression->asIdExpression(); if (!idExpr || !idExpr->name || !idExpr->name->name) return false; const ExpressionListAST *args = call->expression_list; if (!arguments) return false; const Identifier *id = idExpr->name->name->identifier(); if (!id) return false; const QByteArray name(id->chars(), id->size()); if (name != "connect" && name != "disconnect") return false; if (arguments) *arguments = args; return true; } // Might modify arg* output arguments even if false is returned. bool collectConnectArguments(const ExpressionListAST *arguments, const ExpressionAST **arg1, const QtMethodAST **arg2, const ExpressionAST **arg3, const QtMethodAST **arg4) { if (!arguments || !arg1 || !arg2 || !arg3 || !arg4) return false; *arg1 = arguments->value; arguments = arguments->next; if (!arg1 || !arguments) return false; *arg2 = arguments->value->asQtMethod(); arguments = arguments->next; if (!*arg2 || !arguments) return false; *arg3 = arguments->value; if (!*arg3) return false; // Take care of three-arg version, with 'this' receiver. if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) { *arg3 = nullptr; // Means 'this' *arg4 = receiverMethod; return true; } arguments = arguments->next; if (!arguments) return false; *arg4 = arguments->value->asQtMethod(); if (!*arg4) return false; return true; } } // anonynomous namespace void ConvertQt4Connect::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); for (int i = path.size(); --i >= 0; ) { const ExpressionListAST *arguments; if (!onConnectOrDisconnectCall(path.at(i), &arguments)) continue; const ExpressionAST *arg1, *arg3; const QtMethodAST *arg2, *arg4; if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4)) continue; const CppRefactoringFilePtr file = interface.currentFile(); QString newSignal; QString senderAccessFunc; if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc)) continue; QString newMethod; QString receiverAccessFunc; if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc)) continue; ChangeSet changes; changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc); changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal); if (!arg3) newMethod.prepend(QLatin1String("this, ")); else changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc); changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod); result << new ConvertQt4ConnectOperation(interface, changes); return; } } void ExtraRefactoringOperations::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const auto processor = CppTools::CppToolsBridge::baseEditorDocumentProcessor(interface.filePath().toString()); if (processor) { const auto clangFixItOperations = processor->extraRefactoringOperations(interface); result.append(clangFixItOperations); } } namespace { /** * @brief The NameCounter class counts the parts of a name. E.g. 2 for std::vector or 1 for variant */ class NameCounter : private NameVisitor { public: int count(const Name *name) { counter = 0; accept(name); return counter; } private: void visit(const Identifier *) override { ++counter; } void visit(const DestructorNameId *) override { ++counter; } void visit(const TemplateNameId *) override { ++counter; } void visit(const QualifiedNameId *name) override { if (name->base()) accept(name->base()); accept(name->name()); } int counter; }; /** * @brief getBaseName returns the base name of a qualified name or nullptr. * E.g.: foo::bar => foo; bar => bar * @param name The Name, maybe qualified * @return The base name of the qualified name or nullptr */ const Identifier *getBaseName(const Name *name) { class GetBaseName : public NameVisitor { void visit(const Identifier *name) override { baseName = name; } void visit(const QualifiedNameId *name) override { if (name->base()) accept(name->base()); else accept(name->name()); } public: const Identifier *baseName = nullptr; }; GetBaseName getter; getter.accept(name); return getter.baseName; } /** * @brief countNames counts the parts of the Name. * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1 * @param name The name that should be counted * @return the number of parts of the name */ int countNames(const Name *name) { return NameCounter{}.count(name); } /** * @brief removeLine removes the whole line in which the ast node is located if there are otherwise only whitespaces * @param file The file in which the AST node is located * @param ast The ast node * @param changeSet The ChangeSet of the file */ void removeLine(const CppRefactoringFile *file, AST *ast, ChangeSet &changeSet) { RefactoringFile::Range range = file->range(ast); --range.start; while (range.start >= 0) { QChar current = file->charAt(range.start); if (!current.isSpace()) { ++range.start; break; } if (current == QChar::ParagraphSeparator) break; --range.start; } range.start = std::max(0, range.start); while (range.end < file->document()->characterCount()) { QChar current = file->charAt(range.end); if (!current.isSpace()) break; if (current == QChar::ParagraphSeparator) break; ++range.end; } range.end = std::min(file->document()->characterCount(), range.end); const bool newLineStart = file->charAt(range.start) == QChar::ParagraphSeparator; const bool newLineEnd = file->charAt(range.end) == QChar::ParagraphSeparator; if (!newLineEnd && newLineStart) ++range.start; changeSet.remove(range); } /** * @brief The RemoveNamespaceVisitor class removes a using namespace and rewrites all types that * are in the namespace if needed */ class RemoveNamespaceVisitor : public ASTVisitor { public: RemoveNamespaceVisitor(const CppRefactoringFile *file, const Snapshot &snapshot, const Name *namespace_, int symbolPos, bool removeAllAtGlobalScope) : ASTVisitor(file->cppDocument()->translationUnit()) , m_file(file) , m_snapshot(snapshot) , m_namespace(namespace_) , m_missingNamespace(toString(namespace_) + "::") , m_context(m_file->cppDocument(), m_snapshot) , m_symbolPos(symbolPos) , m_removeAllAtGlobalScope(removeAllAtGlobalScope) {} const ChangeSet &getChanges() { return m_changeSet; } /** * @brief isGlobalUsingNamespace return true if the using namespace that should be removed * is not scoped and other files that include this file will also use the using namespace * @return true if using namespace statement is global and not scoped, false otherwise */ bool isGlobalUsingNamespace() const { return m_parentNode == nullptr; } /** * @brief foundGlobalUsingNamespace return true if removeAllAtGlobalScope is false and * another using namespace is found at the global scope, so that other files that include this * file don't have to be processed * @return true if there was a 'global' second using namespace in this file and * removeAllAtGlobalScope is false */ bool foundGlobalUsingNamespace() const { return m_foundNamespace; } private: bool preVisit(AST *ast) override { if (!m_start) { if (UsingDirectiveAST *usingDirective = ast->asUsingDirective()) { if (nameEqual(usingDirective->name->name, m_namespace)) { // ignore the using namespace that should be removed if (m_file->endOf(ast) != m_symbolPos) { if (m_removeAllAtGlobalScope) removeLine(m_file, ast, m_changeSet); else m_done = true; } } } // if the end of the ast is before we should start, we are not interested in the node if (m_file->endOf(ast) <= m_symbolPos) return false; if (m_file->startOf(ast) > m_symbolPos) m_start = true; } return !m_foundNamespace && !m_done; } bool visit(NamespaceAST *ast) override { if (m_start && nameEqual(m_namespace, ast->symbol->name())) return false; return m_start; } // scopes for using namespace statements: bool visit(LinkageBodyAST *ast) override { return visitNamespaceScope(ast); } bool visit(CompoundStatementAST *ast) override { return visitNamespaceScope(ast); } bool visitNamespaceScope(AST *ast) { ++m_namespaceScopeCounter; if (!m_start) m_parentNode = ast; return true; } void endVisit(LinkageBodyAST *ast) override { endVisitNamespaceScope(ast); } void endVisit(CompoundStatementAST *ast) override { endVisitNamespaceScope(ast); } void endVisitNamespaceScope(AST *ast) { --m_namespaceScopeCounter; m_foundNamespace = false; // if we exit the scope of the using namespace we are done if (ast == m_parentNode) m_done = true; } bool visit(UsingDirectiveAST *ast) override { if (nameEqual(ast->name->name, m_namespace)) { if (m_removeAllAtGlobalScope && m_namespaceScopeCounter == 0) removeLine(m_file, ast, m_changeSet); else m_foundNamespace = true; return false; } return handleAstWithLongestName(ast); } bool visit(DeclaratorIdAST *ast) override { // e.g. we have the following code and get the following Lookup items: // namespace test { // struct foo { // 1. item with test::foo // foo(); // 2. item with test::foo::foo // }; // } // using namespace foo; // foo::foo() { ... } // 3. item with foo::foo // Our current name is foo::foo so we have to match with the 2. item / longest name return handleAstWithLongestName(ast); } template bool handleAstWithLongestName(AST *ast) { if (m_start) { Scope *scope = m_file->scopeAt(ast->firstToken()); const QList localLookup = m_context.lookup(ast->name->name, scope); QList longestName; for (const LookupItem &item : localLookup) { QList names = m_context.fullyQualifiedName(item.declaration()); if (names.length() > longestName.length()) longestName = names; } const int currentNameCount = countNames(ast->name->name); const bool needNew = needMissingNamespaces(std::move(longestName), currentNameCount); if (needNew) insertMissingNamespace(ast); } return false; } bool visit(NamedTypeSpecifierAST *ast) override { return handleAstWithName(ast); } bool visit(IdExpressionAST *ast) override { return handleAstWithName(ast); } template bool handleAstWithName(AST *ast) { if (m_start) { Scope *scope = m_file->scopeAt(ast->firstToken()); const Name *wantToLookup = ast->name->name; // first check if the base name is a typedef. Consider the following example: // using namespace std; // using vec = std::vector; // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in // std::vector::iterator => std::vec::iterator, which is wrong) const Name *baseName = getBaseName(wantToLookup); QList typedefCandidates = m_context.lookup(baseName, scope); if (!typedefCandidates.isEmpty()) { if (typedefCandidates.front().declaration()->isTypedef()) wantToLookup = baseName; } const QList lookups = m_context.lookup(wantToLookup, scope); if (!lookups.empty()) { QList fullName = m_context.fullyQualifiedName( lookups.first().declaration()); const int currentNameCount = countNames(wantToLookup); const bool needNamespace = needMissingNamespaces(std::move(fullName), currentNameCount); if (needNamespace) insertMissingNamespace(ast); } } return true; } template void insertMissingNamespace(AST *ast) { DestructorNameAST *destructorName = ast->name->asDestructorName(); if (destructorName) m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace); else m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace); } bool needMissingNamespaces(QList &&fullName, int currentNameCount) { if (currentNameCount > fullName.length()) return false; // eg. fullName = std::vector, currentName = vector => result should be std fullName.erase(fullName.end() - currentNameCount, fullName.end()); if (fullName.empty()) return false; return nameEqual(m_namespace, fullName.last()); } static bool nameEqual(const Name *name1, const Name *name2) { return Matcher::match(name1, name2); } QString toString(const Name *id) { const Identifier *identifier = id->asNameId(); QTC_ASSERT(identifier, return {}); return QString::fromUtf8(identifier->chars(), identifier->size()); } const CppRefactoringFile *const m_file; const Snapshot &m_snapshot; const Name *m_namespace; // the name of the namespace that should be removed const QString m_missingNamespace; // that should be added if a type was using the namespace LookupContext m_context; ChangeSet m_changeSet; const int m_symbolPos; // the end position of the start symbol bool m_done = false; bool m_start = false; // true if a using namespace was found at a scope and the scope should be left bool m_foundNamespace = false; bool m_removeAllAtGlobalScope; // the scope where the using namespace that should be removed is valid AST *m_parentNode = nullptr; int m_namespaceScopeCounter = 0; }; class RemoveUsingNamespaceOperation : public CppQuickFixOperation { public: RemoveUsingNamespaceOperation(const CppQuickFixInterface &interface, UsingDirectiveAST *usingDirective, bool removeAllAtGlobalScope) : CppQuickFixOperation(interface, 1) , m_usingDirective(usingDirective) , m_removeAllAtGlobalScope(removeAllAtGlobalScope) { const QString name = Overview{}.prettyName(usingDirective->name->name); if (m_removeAllAtGlobalScope) { setDescription(QApplication::translate( "CppTools::QuickFix", "Remove All Occurrences of \"using namespace %1\" in Global Scope " "and Adjust Type Names Accordingly") .arg(name)); } else { setDescription(QApplication::translate("CppTools::QuickFix", "Remove \"using namespace %1\" and " "Adjust Type Names Accordingly") .arg(name)); } } private: void perform() override { CppRefactoringChanges refactoring(snapshot()); CppRefactoringFilePtr currentFile = refactoring.file(filePath().toString()); if (refactorFile(currentFile, refactoring.snapshot(), currentFile->endOf(m_usingDirective), true)) processIncludes(refactoring, filePath().toString()); for (auto &file : m_changes) file->apply(); } /** * @brief refactorFile remove using namespace xyz in the given file and rewrite types * @param file The file that should be processed * @param snapshot The snapshot to work on * @param startSymbol start processing after this index * @param removeUsing if the using directive is in this file, remove it * @return true if the using statement is global and there is no other global using namespace */ bool refactorFile(CppRefactoringFilePtr &file, const Snapshot &snapshot, int startSymbol, bool removeUsing = false) { RemoveNamespaceVisitor visitor(file.get(), snapshot, m_usingDirective->name->name, startSymbol, m_removeAllAtGlobalScope); visitor.accept(file->cppDocument()->translationUnit()->ast()); Utils::ChangeSet changes = visitor.getChanges(); if (removeUsing) removeLine(file.get(), m_usingDirective, changes); file->setChangeSet(changes); // apply changes at the end, otherwise the symbol finder will fail to resolve symbols if // the using namespace is missing m_changes.insert(file); return visitor.isGlobalUsingNamespace() && !visitor.foundGlobalUsingNamespace(); } void processIncludes(CppRefactoringChanges &refactoring, const QString &fileName) { QList includeLocationsOfDocument = refactoring.snapshot().includeLocationsOfDocument(fileName); for (Snapshot::IncludeLocation &loc : includeLocationsOfDocument) { if (m_processed.contains(loc.first)) continue; CppRefactoringFilePtr file = refactoring.file(loc.first->fileName()); const bool noGlobalUsing = refactorFile(file, refactoring.snapshot(), file->position(loc.second, 1)); m_processed.insert(loc.first); if (noGlobalUsing) processIncludes(refactoring, loc.first->fileName()); } } QSet m_processed; QSet m_changes; UsingDirectiveAST *m_usingDirective; bool m_removeAllAtGlobalScope; }; } // namespace void RemoveUsingNamespace::match(const CppQuickFixInterface &interface, QuickFixOperations &result) { const QList &path = interface.path(); // We expect something like // [0] TranslationUnitAST // ... // [] UsingDirectiveAST : if activated at 'using namespace' // [] NameAST (optional): if activated at the name e.g. 'std' int n = path.size() - 1; if (n <= 0) return; if (path.last()->asName()) --n; UsingDirectiveAST *usingDirective = path.at(n)->asUsingDirective(); if (usingDirective && usingDirective->name->name->isNameId()) { result << new RemoveUsingNamespaceOperation(interface, usingDirective, false); const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString())); if (isHeader && path.at(n - 1)->asTranslationUnit()) // using namespace at global scope result << new RemoveUsingNamespaceOperation(interface, usingDirective, true); } } void createCppQuickFixes() { new AddIncludeForUndefinedIdentifier; new FlipLogicalOperands; new InverseLogicalComparison; new RewriteLogicalAnd; new ConvertToCamelCase; new ConvertCStringToNSString; new ConvertNumericLiteral; new TranslateStringLiteral; new WrapStringLiteral; new MoveDeclarationOutOfIf; new MoveDeclarationOutOfWhile; new SplitIfStatement; new SplitSimpleDeclaration; new AddLocalDeclaration; new AddBracesToIf; new RearrangeParamDeclarationList; new ReformatPointerDeclaration; new CompleteSwitchCaseStatement; new InsertQtPropertyMembers; new ConvertQt4Connect; new ApplyDeclDefLinkChanges; new ConvertFromAndToPointer; new ExtractFunction; new ExtractLiteralAsParameter; new GenerateGetterSetter; new GenerateGettersSettersForClass; new InsertDeclFromDef; new InsertDefFromDecl; new InsertMemberFromInitialization; new InsertDefsFromDecls; new MoveFuncDefOutside; new MoveAllFuncDefOutside; new MoveFuncDefToDecl; new AssignToLocalVariable; new InsertVirtualMethods; new OptimizeForLoop; new EscapeStringLiteral; new ExtraRefactoringOperations; new RemoveUsingNamespace; } void destroyCppQuickFixes() { for (int i = g_cppQuickFixFactories.size(); --i >= 0; ) delete g_cppQuickFixFactories.at(i); } } // namespace Internal } // namespace CppEditor