diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index 584fd07490f..507ed0d0468 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -95,6 +95,7 @@ add_qtc_plugin(CppEditor insertionpointlocator.cpp insertionpointlocator.h projectinfo.cpp projectinfo.h projectpart.cpp projectpart.h + quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index 6dd13b3fcb5..4ea91a8a04a 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -219,6 +219,8 @@ QtcPlugin { name: "Quickfixes" prefix: "quickfixes/" files: [ + "bringidentifierintoscope.cpp", + "bringidentifierintoscope.h", "convertqt4connect.cpp", "convertqt4connect.h", "cppcodegenerationquickfixes.cpp", diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp new file mode 100644 index 00000000000..f316ba2adac --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp @@ -0,0 +1,1503 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "bringidentifierintoscope.h" + +#include "../cppeditortr.h" +#include "../cpplocatordata.h" +#include "../cppprojectfile.h" +#include "../cpprefactoringchanges.h" +#include "../indexitem.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include +#include +#include +#include + +#ifdef WITH_TESTS +#include "../cppsourceprocessertesthelper.h" +#include "../cpptoolstestcase.h" +#include "cppquickfix_test.h" +#include +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace Utils; + +#ifdef WITH_TESTS +namespace CppEditor::Internal::Tests { +typedef QList QuickFixTestDocuments; +} +Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments) +#endif + +namespace CppEditor::Internal { +namespace { + +static QString findShortestInclude( + const QString currentDocumentFilePath, + const QString candidateFilePath, + const HeaderPaths &headerPaths) +{ + QString result; + + const QFileInfo fileInfo(candidateFilePath); + + if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) { + result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"'); + } else { + for (const 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; +} + +static QString findMatchingInclude(const QString &className, const 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 HeaderPath &headerPath : headerPaths) { + const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName; + const QFileInfo fileInfo(headerPathCandidate); + if (fileInfo.exists() && fileInfo.isFile()) + return '<' + fileName + '>'; + } + } + return {}; +} + +static HeaderPaths relevantHeaderPaths(const QString &filePath) +{ + HeaderPaths headerPaths; + + const QList projectParts = CppModelManager::projectPart(filePath); + if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none + headerPaths += CppModelManager::headerPaths(); + } else { + for (const ProjectPart::ConstPtr &part : projectParts) + headerPaths += part->headerPaths; + } + + return headerPaths; +} + +static 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 { Declared, ForwardDeclared, NotDeclared }; +static LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) +{ + QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared); + + // Find the enclosing scope + int line, column; + const Document::Ptr doc = interface.semanticInfo().doc; + doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column); + Scope *scope = doc->scopeAt(line, column); + if (!scope) + return LookupResult::NotDeclared; + + // Try to find the class/template definition + const Name *name = nameAst->name; + const QList results = interface.context().lookup(name, scope); + LookupResult best = LookupResult::NotDeclared; + for (const LookupItem &item : results) { + if (Symbol *declaration = item.declaration()) { + if (declaration->asClass()) + return LookupResult::Declared; + if (declaration->asForwardClassDeclaration()) { + best = LookupResult::ForwardDeclared; + continue; + } + if (Template *templ = declaration->asTemplate()) { + if (Symbol *declaration = templ->declaration()) { + if (declaration->asClass()) + return LookupResult::Declared; + if (declaration->asForwardClassDeclaration()) { + best = LookupResult::ForwardDeclared; + continue; + } + } + } + return LookupResult::Declared; + } + } + + return best; +} + +static QString templateNameAsString(const TemplateNameId *templateName) +{ + const Identifier *id = templateName->identifier(); + return QString::fromUtf8(id->chars(), id->size()); +} + +static Snapshot forwardingHeaders(const CppQuickFixInterface &interface) +{ + Snapshot result; + + const Snapshot docs = interface.snapshot(); + for (Document::Ptr doc : docs) { + if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) + result.insert(doc); + } + + return result; +} + +static QList matchName(const Name *name, QString *className) +{ + if (!name) + return {}; + + QString simpleName; + QList matches; + CppLocatorData *locatorData = CppModelManager::locatorData(); + 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 = locatorData->findSymbols(IndexItem::Class, *className); + if (matches.isEmpty()) { + 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.isEmpty()) + matches = locatorData->findSymbols(IndexItem::Class, *className); + + if (matches.isEmpty() && !simpleName.isEmpty()) + *className = simpleName; + + return matches; +} + +class AddIncludeForUndefinedIdentifierOp : public CppQuickFixOperation +{ +public: + AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, + const QString &include); + void perform() override; + + QString include() const { return m_include; } + +private: + QString m_include; +}; + +class AddForwardDeclForUndefinedIdentifierOp : public CppQuickFixOperation +{ +public: + AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, + const QString &fqClassName, int symbolPos); +private: + void perform() override + { + 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.cppFile(filePath()); + + 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(); + } + + const QString m_className; + const int m_symbolPos; +}; + +AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp( + const CppQuickFixInterface &interface, int priority, const QString &include) + : CppQuickFixOperation(interface, priority) + , m_include(include) +{ + setDescription(Tr::tr("Add #include %1").arg(m_include)); +} + +void AddIncludeForUndefinedIdentifierOp::perform() +{ + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr file = refactoring.cppFile(filePath()); + + ChangeSet changes; + insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes); + file->setChangeSet(changes); + file->apply(); +} + +AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( + const CppQuickFixInterface &interface, + int priority, + const QString &fqClassName, + int symbolPos) + : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) +{ + setDescription(Tr::tr("Add Forward Declaration for %1").arg(m_className)); +} + +/*! + Adds an include for an undefined identifier or only forward declared identifier. + Activates on: the undefined identifier +*/ +class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const NameAST *nameAst = nameUnderCursor(interface.path()); + if (!nameAst || !nameAst->name) + return; + + const LookupResult lookupResult = lookUpDefinition(interface, nameAst); + if (lookupResult == LookupResult::Declared) + return; + + QString className; + const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString(); + const HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); + FilePaths headers; + + const QList matches = matchName(nameAst->name, &className); + // Find an include file through the locator + if (!matches.isEmpty()) { + QList indexItems; + const Snapshot forwardHeaders = forwardingHeaders(interface); + for (const IndexItem::Ptr &info : matches) { + if (!info || info->symbolName() != className) + continue; + indexItems << info; + + Snapshot localForwardHeaders = forwardHeaders; + localForwardHeaders.insert(interface.snapshot().document(info->filePath())); + FilePaths headerAndItsForwardingHeaders; + headerAndItsForwardingHeaders << info->filePath(); + headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath()); + + for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) { + const QString include = findShortestInclude(currentDocumentFilePath, + header.toString(), + headerPaths); + if (include.size() > 2) { + const QString headerFileName = info->filePath().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::NotDeclared && indexItems.size() == 1) { + QString qualifiedName = Overview().prettyName(nameAst->name); + if (qualifiedName.startsWith("::")) + qualifiedName.remove(0, 2); + if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) { + const Node * const node = ProjectTree::nodeForFile(interface.filePath()); + FileType fileType = node && node->asFileNode() ? node->asFileNode()->fileType() + : FileType::Unknown; + if (fileType == FileType::Unknown + && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) { + fileType = FileType::Header; + } + if (fileType == 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 FilePath &fp) { return fp.fileName() == className; })) { + const QString include = findMatchingInclude(className, headerPaths); + const auto matcher = [&include](const TextEditor::QuickFixOperation::Ptr &o) { + const auto includeOp = o.dynamicCast(); + return includeOp && includeOp->include() == include; + }; + if (!include.isEmpty() && !contains(result, matcher)) + result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include); + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; +using namespace ::CppEditor::Tests; +using namespace ::CppEditor::Tests::Internal; // FIXME: Get rid of this nonsense namespace + +/// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing. +class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory +{ +public: + AddIncludeForUndefinedIdentifierTestFactory(const QString &include) + : m_include(include) {} + + void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override + { + result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include); + } + +private: + const QString m_include; +}; + +class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory +{ +public: + AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos) + : m_className(className), m_symbolPos(symbolPos) {} + + void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override + { + result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0, + m_className, m_symbolPos); + } + +private: + const QString m_className; + const int m_symbolPos; +}; + +class BringIdentifierIntoScopeTest : public QObject +{ + Q_OBJECT + +private slots: + void testAddIncludeForUndefinedIdentifier_data() + { + QTest::addColumn("headerPath"); + QTest::addColumn("testDocuments"); + QTest::addColumn("refactoringOperationIndex"); + QTest::addColumn("includeForTestFactory"); + + const int firstRefactoringOperation = 0; + const int secondRefactoringOperation = 1; + + QList testDocuments; + QByteArray original; + QByteArray expected; + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Fo@o foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onSimpleName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Fo@o foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onNameOfQualifiedName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @N::Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo { static void bar() {} };\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @Foo::bar();\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo::bar();\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedClassName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template class Foo { static void bar() {} };\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @Foo::bar();\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo::bar();\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedTemplateClassName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { template class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @N::Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onTemplateName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { template class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Bar<@Foo> foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Bar foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onTemplateNameInsideArguments") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "class Foo;\n" + "\n" + "void f()\n" + "{\n" + " @Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "class Foo;\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardDeclaration") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "template class Foo;\n" + "\n" + "void f()\n" + "{\n" + " @Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "template class Foo;\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardDeclaration2") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template class QMyClass {};\n"; + expected = original; + testDocuments << CppTestDocument::create("qmyclass.h", original, expected); + + // Forward Header File + original = "#include \"qmyclass.h\"\n"; + expected = original; + testDocuments << CppTestDocument::create("QMyClass", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @QMyClass c;\n" + "}\n" + ; + expected = + "#include \"QMyClass\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " QMyClass c;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardHeader") + << TestIncludePaths::globalIncludePath() + << testDocuments << secondRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "void @f();\n" + "#include \"file.moc\";\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void f();\n" + "#include \"file.moc\";\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingIgnoreMoc") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"y.h\"\n" + "#include \"z.h\"\n" + "\n@" + ; + expected = + "#include \"file.h\"\n" + "#include \"y.h\"\n" + "#include \"z.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"file.h\"\n" + "#include \"z.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingMiddle") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingBottom") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"b.h\"\n" + "#include \"a.h\"\n" + "\n@" + ; + expected = + "#include \"b.h\"\n" + "#include \"a.h\"\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_appendToUnsorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \n" + "#include \n" + "\n@" + ; + expected = + "#include \"file.h\"\n" + "\n" + "#include \n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_firstLocalIncludeAtFront") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n" + "void @f();\n" + ; + expected = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n" + "#include \n" + "\n" + "void f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("firstGlobalIncludeAtBack") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"prefixa.h\"\n" + "#include \"prefixb.h\"\n" + "\n" + "#include \"foo.h\"\n" + "\n@" + ; + expected = + "#include \"prefixa.h\"\n" + "#include \"prefixb.h\"\n" + "#include \"prefixc.h\"\n" + "\n" + "#include \"foo.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"prefixc.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"lib/file.h\"\n" + "#include \"lib/fileother.h\"\n" + "\n@" + ; + expected = + "#include \"lib/file.h\"\n" + "#include \"lib/fileother.h\"\n" + "\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \n" + "#include \n" + "#include \n" + "\n@" + ; + expected = + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedDirsSorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \n" + "#include \n" + "#include \n" + "\n@" + ; + expected = + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedDirsUnsorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes1") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"z.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include \n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes2") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"a.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include \n" + "\n@" + ; + expected = + "#include \"z.h\"\n" + "#include \"lib/file.h\"\n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes3") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"lib/file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include \n" + "\n@" + ; + expected = + "#include \"z.h\"\n" + "#include \n" + "#include \n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes4") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "void @f();\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_noinclude") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#ifndef FOO_H\n" + "#define FOO_H\n" + "void @f();\n" + "#endif\n" + ; + expected = + "#ifndef FOO_H\n" + "#define FOO_H\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void f();\n" + "#endif\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_onlyIncludeGuard") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "\n" + "// comment\n" + "\n" + "void @f();\n" + ; + expected = + "\n" + "// comment\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void @f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "\n" + "/*\n" + " comment\n" + " */\n" + "\n" + "void @f();\n" + ; + expected = + "\n" + "/*\n" + " comment\n" + " */\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void @f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "@QDir dir;\n" + ; + expected = + "#include \n" + "\n" + "QDir dir;\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_checkQSomethingInQtIncludePaths") + << TestIncludePaths::globalQtCoreIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = + "std::s@tring s;\n" + ; + expected = + "#include \n" + "\n" + "std::string s;\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_std::string") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = "class A{};"; + testDocuments << CppTestDocument::create("a.h", original, original); + original = "class B{};"; + testDocuments << CppTestDocument::create("b.h", original, original); + original = + "#include \"b.h\"\n" + "@A a;\n" + "B b;"; + expected = + "#include \"b.h\"\n\n" + "#include \"a.h\"\n" + "A a;\n" + "B b;"; + testDocuments << CppTestDocument::create("b.cpp", original, expected); + QTest::newRow("preserve first header") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = "class C{};"; + testDocuments << CppTestDocument::create("c.h", original, original); + original = "class B{};"; + testDocuments << CppTestDocument::create("b.h", original, original); + original = + "#include \"c.h\"\n" + "C c;\n" + "@B b;"; + expected = + "#include \"b.h\"\n" + "#include \"c.h\"\n" + "C c;\n" + "B b;"; + testDocuments << CppTestDocument::create("x.cpp", original, expected); + QTest::newRow("do not preserve first header") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + } + + void testAddIncludeForUndefinedIdentifier() + { + QFETCH(QString, headerPath); + QFETCH(QuickFixTestDocuments, testDocuments); + QFETCH(int, refactoringOperationIndex); + QFETCH(QString, includeForTestFactory); + + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + for (const TestDocumentPtr &testDocument : std::as_const(testDocuments)) + testDocument->setBaseDirectory(temporaryDir.path()); + + QScopedPointer factory; + if (includeForTestFactory.isEmpty()) + factory.reset(new AddIncludeForUndefinedIdentifier); + else + factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory)); + + QuickFixOperationTest::run(testDocuments, factory.data(), headerPath, + refactoringOperationIndex); + } + + void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude() + { + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + + QList testDocuments; + QByteArray original; + QByteArray expected; + + const QByteArray base = temporaryDir.path().toUtf8(); + + // This file makes the QDir definition available so that locator finds it. + original = expected = "#include \n" + "void avoidBeingRecognizedAsForwardingHeader();"; + testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected); + + original = expected = "@QDir dir;\n"; + testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected); + + AddIncludeForUndefinedIdentifier factory; + const QStringList expectedOperations = QStringList("Add #include "); + QuickFixOfferedOperationsTest( + testDocuments, + &factory, + ProjectExplorer::toUserHeaderPaths( + QStringList{TestIncludePaths::globalQtCoreIncludePath()}), + expectedOperations); + } + + void testAddForwardDeclForUndefinedIdentifier_data() + { + QTest::addColumn("testDocuments"); + QTest::addColumn("symbol"); + QTest::addColumn("symbolPos"); + + QByteArray original; + QByteArray expected; + + original = + "#pragma once\n" + "\n" + "void f(const Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "class Blubb;\n" + "void f(const Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("unqualified symbol") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "namespace NS {\n" + "\n" + "class Blubb;\n" + "class C;\n" + "}\n" + "void f(const NS::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, full namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS::NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "namespace NS {\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "class C;\n" + "}\n" + "void f(const NS::NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, partial namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS::NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, other namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, no namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + "namespace NS2 {}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + "namespace NS2 {}\n" + ; + QTest::newRow("qualified symbol, existing namespace after symbol") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + } + + void testAddForwardDeclForUndefinedIdentifier() + { + QFETCH(QuickFixTestDocuments, testDocuments); + QFETCH(QString, symbol); + QFETCH(int, symbolPos); + + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + testDocuments.first()->setBaseDirectory(temporaryDir.path()); + + QScopedPointer factory( + new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos)); + QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0); + } +}; + +QObject *AddIncludeForUndefinedIdentifier::createTest() +{ + return new BringIdentifierIntoScopeTest; +} + +#endif // WITH_TESTS +} // namespace + +void registerBringIdentifierIntoScopeQuickfixes() +{ + CppQuickFixFactory::registerFactory(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include +#endif diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h new file mode 100644 index 00000000000..b2f8bbe5d61 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h @@ -0,0 +1,8 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace CppEditor::Internal { +void registerBringIdentifierIntoScopeQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp index 4d29b03be4f..04b9020b660 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp @@ -34,11 +34,6 @@ using CppEditor::Tests::Internal::TestIncludePaths; typedef QByteArray _; -namespace CppEditor::Internal::Tests { -typedef QList QuickFixTestDocuments; -} -Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments) - namespace CppEditor { namespace Internal { namespace Tests { @@ -253,39 +248,6 @@ void QuickFixOperationTest::run(const QList &testDocuments, QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex); } -/// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing. -class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory -{ -public: - AddIncludeForUndefinedIdentifierTestFactory(const QString &include) - : m_include(include) {} - - void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override - { - result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include); - } - -private: - const QString m_include; -}; - -class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory -{ -public: - AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos) - : m_className(className), m_symbolPos(symbolPos) {} - - void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override - { - result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0, - m_className, m_symbolPos); - } - -private: - const QString m_className; - const int m_symbolPos; -}; - } // namespace Tests } // namespace Internal @@ -2190,1008 +2152,6 @@ void QuickfixTest::testInsertDeclFromDefNotTriggeredForTemplateFunc() QuickFixOperationTest(singleDocument(contents, ""), &factory); } -void QuickfixTest::testAddIncludeForUndefinedIdentifier_data() -{ - QTest::addColumn("headerPath"); - QTest::addColumn("testDocuments"); - QTest::addColumn("refactoringOperationIndex"); - QTest::addColumn("includeForTestFactory"); - - const int firstRefactoringOperation = 0; - const int secondRefactoringOperation = 1; - - QList testDocuments; - QByteArray original; - QByteArray expected; - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Fo@o foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onSimpleName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Fo@o foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onNameOfQualifiedName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @N::Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo { static void bar() {} };\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @Foo::bar();\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo::bar();\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedClassName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template class Foo { static void bar() {} };\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @Foo::bar();\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo::bar();\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedTemplateClassName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { template class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @N::Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onTemplateName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { template class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Bar<@Foo> foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Bar foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onTemplateNameInsideArguments") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "class Foo;\n" - "\n" - "void f()\n" - "{\n" - " @Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "class Foo;\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardDeclaration") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "template class Foo;\n" - "\n" - "void f()\n" - "{\n" - " @Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "template class Foo;\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardDeclaration2") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template class QMyClass {};\n"; - expected = original; - testDocuments << CppTestDocument::create("qmyclass.h", original, expected); - - // Forward Header File - original = "#include \"qmyclass.h\"\n"; - expected = original; - testDocuments << CppTestDocument::create("QMyClass", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @QMyClass c;\n" - "}\n" - ; - expected = - "#include \"QMyClass\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " QMyClass c;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardHeader") - << TestIncludePaths::globalIncludePath() - << testDocuments << secondRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "void @f();\n" - "#include \"file.moc\";\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void f();\n" - "#include \"file.moc\";\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingIgnoreMoc") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"y.h\"\n" - "#include \"z.h\"\n" - "\n@" - ; - expected = - "#include \"file.h\"\n" - "#include \"y.h\"\n" - "#include \"z.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"file.h\"\n" - "#include \"z.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingMiddle") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingBottom") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"b.h\"\n" - "#include \"a.h\"\n" - "\n@" - ; - expected = - "#include \"b.h\"\n" - "#include \"a.h\"\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_appendToUnsorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \n" - "#include \n" - "\n@" - ; - expected = - "#include \"file.h\"\n" - "\n" - "#include \n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_firstLocalIncludeAtFront") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n" - "void @f();\n" - ; - expected = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n" - "#include \n" - "\n" - "void f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("firstGlobalIncludeAtBack") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"prefixa.h\"\n" - "#include \"prefixb.h\"\n" - "\n" - "#include \"foo.h\"\n" - "\n@" - ; - expected = - "#include \"prefixa.h\"\n" - "#include \"prefixb.h\"\n" - "#include \"prefixc.h\"\n" - "\n" - "#include \"foo.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"prefixc.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"lib/file.h\"\n" - "#include \"lib/fileother.h\"\n" - "\n@" - ; - expected = - "#include \"lib/file.h\"\n" - "#include \"lib/fileother.h\"\n" - "\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \n" - "#include \n" - "#include \n" - "\n@" - ; - expected = - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedDirsSorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \n" - "#include \n" - "#include \n" - "\n@" - ; - expected = - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedDirsUnsorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes1") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"z.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include \n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes2") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"a.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include \n" - "\n@" - ; - expected = - "#include \"z.h\"\n" - "#include \"lib/file.h\"\n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes3") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"lib/file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include \n" - "\n@" - ; - expected = - "#include \"z.h\"\n" - "#include \n" - "#include \n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes4") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "void @f();\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_noinclude") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#ifndef FOO_H\n" - "#define FOO_H\n" - "void @f();\n" - "#endif\n" - ; - expected = - "#ifndef FOO_H\n" - "#define FOO_H\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void f();\n" - "#endif\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_onlyIncludeGuard") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "\n" - "// comment\n" - "\n" - "void @f();\n" - ; - expected = - "\n" - "// comment\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void @f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "\n" - "/*\n" - " comment\n" - " */\n" - "\n" - "void @f();\n" - ; - expected = - "\n" - "/*\n" - " comment\n" - " */\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void @f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "@QDir dir;\n" - ; - expected = - "#include \n" - "\n" - "QDir dir;\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_checkQSomethingInQtIncludePaths") - << TestIncludePaths::globalQtCoreIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = - "std::s@tring s;\n" - ; - expected = - "#include \n" - "\n" - "std::string s;\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_std::string") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = "class A{};"; - testDocuments << CppTestDocument::create("a.h", original, original); - original = "class B{};"; - testDocuments << CppTestDocument::create("b.h", original, original); - original = - "#include \"b.h\"\n" - "@A a;\n" - "B b;"; - expected = - "#include \"b.h\"\n\n" - "#include \"a.h\"\n" - "A a;\n" - "B b;"; - testDocuments << CppTestDocument::create("b.cpp", original, expected); - QTest::newRow("preserve first header") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = "class C{};"; - testDocuments << CppTestDocument::create("c.h", original, original); - original = "class B{};"; - testDocuments << CppTestDocument::create("b.h", original, original); - original = - "#include \"c.h\"\n" - "C c;\n" - "@B b;"; - expected = - "#include \"b.h\"\n" - "#include \"c.h\"\n" - "C c;\n" - "B b;"; - testDocuments << CppTestDocument::create("x.cpp", original, expected); - QTest::newRow("do not preserve first header") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); -} - -void QuickfixTest::testAddIncludeForUndefinedIdentifier() -{ - QFETCH(QString, headerPath); - QFETCH(QuickFixTestDocuments, testDocuments); - QFETCH(int, refactoringOperationIndex); - QFETCH(QString, includeForTestFactory); - - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - for (const TestDocumentPtr &testDocument : std::as_const(testDocuments)) - testDocument->setBaseDirectory(temporaryDir.path()); - - QScopedPointer factory; - if (includeForTestFactory.isEmpty()) - factory.reset(new AddIncludeForUndefinedIdentifier); - else - factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory)); - - QuickFixOperationTest::run(testDocuments, factory.data(), headerPath, - refactoringOperationIndex); -} - -void QuickfixTest::testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude() -{ - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - - QList testDocuments; - QByteArray original; - QByteArray expected; - - const QByteArray base = temporaryDir.path().toUtf8(); - - // This file makes the QDir definition available so that locator finds it. - original = expected = "#include \n" - "void avoidBeingRecognizedAsForwardingHeader();"; - testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected); - - original = expected = "@QDir dir;\n"; - testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected); - - AddIncludeForUndefinedIdentifier factory; - const QStringList expectedOperations = QStringList("Add #include "); - QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::toUserHeaderPaths( - QStringList{TestIncludePaths::globalQtCoreIncludePath()}), expectedOperations); -} - -void QuickfixTest::testAddForwardDeclForUndefinedIdentifier_data() -{ - QTest::addColumn("testDocuments"); - QTest::addColumn("symbol"); - QTest::addColumn("symbolPos"); - - QByteArray original; - QByteArray expected; - - original = - "#pragma once\n" - "\n" - "void f(const Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "class Blubb;\n" - "void f(const Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("unqualified symbol") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "namespace NS {\n" - "\n" - "class Blubb;\n" - "class C;\n" - "}\n" - "void f(const NS::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, full namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS::NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "namespace NS {\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "class C;\n" - "}\n" - "void f(const NS::NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, partial namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS::NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, other namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, no namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - "namespace NS2 {}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - "namespace NS2 {}\n" - ; - QTest::newRow("qualified symbol, existing namespace after symbol") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); -} - -void QuickfixTest::testAddForwardDeclForUndefinedIdentifier() -{ - QFETCH(QuickFixTestDocuments, testDocuments); - QFETCH(QString, symbol); - QFETCH(int, symbolPos); - - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - testDocuments.first()->setBaseDirectory(temporaryDir.path()); - - QScopedPointer factory( - new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos)); - QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0); -} - void QuickfixTest::testAssignToLocalVariableTemplates() { diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h index 289a8ed0a86..b5a9e30d9d3 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h @@ -106,13 +106,6 @@ private slots: void testInsertDeclFromDefTemplateReturnType(); void testInsertDeclFromDefNotTriggeredForTemplateFunc(); - void testAddIncludeForUndefinedIdentifier_data(); - void testAddIncludeForUndefinedIdentifier(); - void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude(); - - void testAddForwardDeclForUndefinedIdentifier_data(); - void testAddForwardDeclForUndefinedIdentifier(); - void testAssignToLocalVariableTemplates(); void testExtractFunction_data(); diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp index ff361714ff6..02b5ce716f2 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp @@ -9,14 +9,13 @@ #include "../cppeditortr.h" #include "../cppeditorwidget.h" #include "../cppfunctiondecldeflink.h" -#include "../cpplocatordata.h" #include "../cpppointerdeclarationformatter.h" #include "../cpprefactoringchanges.h" #include "../cpptoolsreuse.h" #include "../includeutils.h" -#include "../indexitem.h" #include "../insertionpointlocator.h" #include "../symbolfinder.h" +#include "bringidentifierintoscope.h" #include "cppcodegenerationquickfixes.h" #include "cppinsertvirtualmethods.h" #include "cppquickfixassistant.h" @@ -1695,367 +1694,6 @@ void ConvertToCamelCase::doMatch(const CppQuickFixInterface &interface, QuickFix } } -AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp( - const CppQuickFixInterface &interface, int priority, const QString &include) - : CppQuickFixOperation(interface, priority) - , m_include(include) -{ - setDescription(Tr::tr("Add #include %1").arg(m_include)); -} - -void AddIncludeForUndefinedIdentifierOp::perform() -{ - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr file = refactoring.cppFile(filePath()); - - ChangeSet changes; - insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes); - file->setChangeSet(changes); - file->apply(); -} - -AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( - const CppQuickFixInterface &interface, - int priority, - const QString &fqClassName, - int symbolPos) - : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) -{ - setDescription(Tr::tr("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.cppFile(filePath()); - - 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 { - for (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; - - const QList projectParts = CppModelManager::projectPart(filePath); - if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none - headerPaths += CppModelManager::headerPaths(); - } else { - for (const ProjectPart::ConstPtr &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 { Declared, ForwardDeclared, NotDeclared }; -LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) -{ - QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared); - - // Find the enclosing scope - int line, column; - const Document::Ptr doc = interface.semanticInfo().doc; - doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column); - Scope *scope = doc->scopeAt(line, column); - if (!scope) - return LookupResult::NotDeclared; - - // Try to find the class/template definition - const Name *name = nameAst->name; - const QList results = interface.context().lookup(name, scope); - LookupResult best = LookupResult::NotDeclared; - for (const LookupItem &item : results) { - if (Symbol *declaration = item.declaration()) { - if (declaration->asClass()) - return LookupResult::Declared; - if (declaration->asForwardClassDeclaration()) { - best = LookupResult::ForwardDeclared; - continue; - } - if (Template *templ = declaration->asTemplate()) { - if (Symbol *declaration = templ->declaration()) { - if (declaration->asClass()) - return LookupResult::Declared; - if (declaration->asForwardClassDeclaration()) { - best = LookupResult::ForwardDeclared; - continue; - } - } - } - return LookupResult::Declared; - } - } - - return best; -} - -QString templateNameAsString(const TemplateNameId *templateName) -{ - const Identifier *id = templateName->identifier(); - return QString::fromUtf8(id->chars(), id->size()); -} - -Snapshot forwardingHeaders(const CppQuickFixInterface &interface) -{ - Snapshot result; - - const Snapshot docs = interface.snapshot(); - for (Document::Ptr doc : docs) { - if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) - result.insert(doc); - } - - return result; -} - -QList matchName(const Name *name, QString *className) -{ - if (!name) - return {}; - - QString simpleName; - QList matches; - CppLocatorData *locatorData = CppModelManager::locatorData(); - 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 = locatorData->findSymbols(IndexItem::Class, *className); - if (matches.isEmpty()) { - 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.isEmpty()) - matches = locatorData->findSymbols(IndexItem::Class, *className); - - if (matches.isEmpty() && !simpleName.isEmpty()) - *className = simpleName; - - return matches; -} - -} // anonymous namespace - -void AddIncludeForUndefinedIdentifier::doMatch(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::Declared) - return; - - QString className; - const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString(); - const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); - FilePaths headers; - - const QList matches = matchName(nameAst->name, &className); - // Find an include file through the locator - if (!matches.isEmpty()) { - QList indexItems; - const Snapshot forwardHeaders = forwardingHeaders(interface); - for (const IndexItem::Ptr &info : matches) { - if (!info || info->symbolName() != className) - continue; - indexItems << info; - - Snapshot localForwardHeaders = forwardHeaders; - localForwardHeaders.insert(interface.snapshot().document(info->filePath())); - FilePaths headerAndItsForwardingHeaders; - headerAndItsForwardingHeaders << info->filePath(); - headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath()); - - for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) { - const QString include = findShortestInclude(currentDocumentFilePath, - header.toString(), - headerPaths); - if (include.size() > 2) { - const QString headerFileName = info->filePath().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::NotDeclared && 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 @@ -5431,8 +5069,6 @@ void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface, void createCppQuickFixes() { - new AddIncludeForUndefinedIdentifier; - new FlipLogicalOperands; new InverseLogicalComparison; new RewriteLogicalAnd; @@ -5472,6 +5108,7 @@ void createCppQuickFixes() registerConvertQt4ConnectQuickfix(); registerMoveFunctionDefinitionQuickfixes(); registerInsertFunctionDefinitionQuickfixes(); + registerBringIdentifierIntoScopeQuickfixes(); new OptimizeForLoop; diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.h b/src/plugins/cppeditor/quickfixes/cppquickfixes.h index ce64252adf0..845ed8d0e26 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.h @@ -28,43 +28,6 @@ public: void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; }; -/*! - Adds an include for an undefined identifier or only forward declared identifier. - - Activates on: the undefined identifier -*/ -class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -// Exposed for tests -class AddIncludeForUndefinedIdentifierOp: public CppQuickFixOperation -{ -public: - AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, - const QString &include); - void perform() override; - - QString include() const { return m_include; } - -private: - QString m_include; -}; - -class AddForwardDeclForUndefinedIdentifierOp: public CppQuickFixOperation -{ -public: - AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, - const QString &fqClassName, int symbolPos); -private: - void perform() override; - - const QString m_className; - const int m_symbolPos; -}; - /*! Rewrite a op b