diff --git a/doc/src/editors/creator-editors.qdoc b/doc/src/editors/creator-editors.qdoc index 0ba58b85c017c9558404b266d63daacd11634615..975de5d5b14d2998e41dacdb91d2cf174c8abec9 100644 --- a/doc/src/editors/creator-editors.qdoc +++ b/doc/src/editors/creator-editors.qdoc @@ -1827,10 +1827,6 @@ \li String literal - \row - \li #include Header File - \li Adds the matching #include statement for a forward-declared class or struct - \li Forward-declared class or struct \row \li Add Definition in ... \li Inserts a definition stub for a function declaration either in the header file @@ -1957,9 +1953,9 @@ icon appears: \inlineimage qml-toolbar-indicator.png \row - \li Add #include for undeclared identifier + \li Add #include for undeclared or forward declared identifier \li Adds an #include directive to the current file to make the - declaration of a symbol available. + definition of a symbol available. \li Undeclared identifier \row \li Reformat Pointers or References diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index 1dae435824eb06c3d8d13890c0bc7ffcae3d5bfd..6b0817b14c323d2278f929a8c3d24f548dcf1649 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -156,6 +156,9 @@ private slots: void test_quickfix_AddIncludeForUndefinedIdentifier_onBaseOfQualifiedName(); void test_quickfix_AddIncludeForUndefinedIdentifier_onTemplateName(); void test_quickfix_AddIncludeForUndefinedIdentifier_onTemplateNameInsideArguments(); + void test_quickfix_AddIncludeForUndefinedIdentifier_withForwardDeclaration(); + void test_quickfix_AddIncludeForUndefinedIdentifier_withForwardDeclaration2(); + void test_quickfix_AddIncludeForUndefinedIdentifier_withForwardHeader(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_ignoremoc(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_sortingTop(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_sortingMiddle(); diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index e17588048f4aa7425b4274d182a2a5f5cadd2f7b..70d2e1c267a7a20050d1f7054df08b9224585028 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -219,11 +219,11 @@ QuickFixTestCase::~QuickFixTestCase() } void QuickFixTestCase::run(const QList<QuickFixTestDocument::Ptr> &theTestFiles, - CppQuickFixFactory *factory, const QString &incPath) + CppQuickFixFactory *factory, const QString &incPath, int resultIndex) { ProjectPart::HeaderPaths hps; hps += ProjectPart::HeaderPath(incPath, ProjectPart::HeaderPath::IncludePath); - QuickFixTestCase(theTestFiles, factory, hps); + QuickFixTestCase(theTestFiles, factory, hps, resultIndex); } /// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing. @@ -2404,6 +2404,137 @@ void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_onTemplateN QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath()); } +void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_withForwardDeclaration() +{ + QList<QuickFixTestDocument::Ptr> testFiles; + + QByteArray original; + QByteArray expected; + + // Header File + original = "class Foo {};\n"; + expected = original; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/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" + ; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/afile.cpp", original, expected); + + // Do not use the test factory, at least once we want to go through the "full stack". + AddIncludeForUndefinedIdentifier factory; + QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath()); +} + +void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_withForwardDeclaration2() +{ + QList<QuickFixTestDocument::Ptr> testFiles; + + QByteArray original; + QByteArray expected; + + // Header File + original = "template<class T> class Foo {};\n"; + expected = original; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "template<class T> class Foo;\n" + "\n" + "void f()\n" + "{\n" + " @Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "template<class T> class Foo;\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/afile.cpp", original, expected); + + // Do not use the test factory, at least once we want to go through the "full stack". + AddIncludeForUndefinedIdentifier factory; + QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath()); +} + +void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_withForwardHeader() +{ + QList<QuickFixTestDocument::Ptr> testFiles; + + QByteArray original; + QByteArray expected; + + // Header File + original = "template<class T> class QMyClass {};\n"; + expected = original; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/qmyclass.h", original, expected); + + // Forward Header File + original = "#include \"qmyclass.h\"\n"; + expected = original; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/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" + ; + testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8() + + "/afile.cpp", original, expected); + + // Do not use the test factory, at least once we want to go through the "full stack". + AddIncludeForUndefinedIdentifier factory; + QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath(), 1); +} + /// Check: Ignore *.moc includes void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_inserting_ignoremoc() { diff --git a/src/plugins/cppeditor/cppquickfix_test.h b/src/plugins/cppeditor/cppquickfix_test.h index 770d1a15f9e9b51f11046c3201a5b352092c0494..72be61be4d105ed9a9d173f23203045bb9f7a265 100644 --- a/src/plugins/cppeditor/cppquickfix_test.h +++ b/src/plugins/cppeditor/cppquickfix_test.h @@ -82,7 +82,7 @@ public: ~QuickFixTestCase(); static void run(const QList<QuickFixTestDocument::Ptr> &theTestFiles, - CppQuickFixFactory *factory, const QString &incPath); + CppQuickFixFactory *factory, const QString &incPath, int resultIndex = 0); private: QSharedPointer<TextEditor::QuickFixOperation> getFix(CppQuickFixFactory *factory, CppEditorWidget *editorWidget, diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index ae21bd7743ec1d58d4e7f4490487e008056327d3..44e8e882780615c1e521f4dce49726b1526372b0 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -81,7 +81,6 @@ namespace Internal { void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) { plugIn->addAutoReleasedObject(new AddIncludeForUndefinedIdentifier); - plugIn->addAutoReleasedObject(new AddIncludeForForwardDeclaration); plugIn->addAutoReleasedObject(new FlipLogicalOperands); plugIn->addAutoReleasedObject(new InverseLogicalComparison); @@ -1529,123 +1528,6 @@ void ConvertNumericLiteral::match(const CppQuickFixInterface &interface, QuickFi namespace { -class AddIncludeForForwardDeclarationOp: public CppQuickFixOperation -{ -public: - AddIncludeForForwardDeclarationOp(const CppQuickFixInterface &interface, int priority, - Symbol *fwdClass) - : CppQuickFixOperation(interface, priority) - , fwdClass(fwdClass) - { - setDescription(QApplication::translate("CppTools::QuickFix", - "#include Header File")); - } - - void perform() - { - QTC_ASSERT(fwdClass != 0, return); - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.file(fileName()); - - SymbolFinder symbolFinder; - if (Class *k = symbolFinder.findMatchingClassDeclaration(fwdClass, snapshot())) { - const QString headerFile = QString::fromUtf8(k->fileName(), k->fileNameLength()); - - // collect the fwd headers - Snapshot fwdHeaders; - fwdHeaders.insert(snapshot().document(headerFile)); - foreach (Document::Ptr doc, snapshot()) { - QFileInfo headerFileInfo(doc->fileName()); - if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) - fwdHeaders.insert(doc); - else if (headerFileInfo.suffix().isEmpty()) - fwdHeaders.insert(doc); - } - - QStringList candidates = fwdHeaders.filesDependingOn(headerFile); - - const QString className = QString::fromUtf8(k->identifier()->chars()); - - QString best; - foreach (const QString &c, candidates) { - QFileInfo headerFileInfo(c); - if (headerFileInfo.fileName() == className) { - best = c; - break; - } else if (headerFileInfo.fileName().at(0).isUpper()) { - best = c; - // and continue - } else if (!best.isEmpty()) { - if (c.count(QLatin1Char('/')) < best.count(QLatin1Char('/'))) - best = c; - } - } - - if (best.isEmpty()) - best = headerFile; - - const QString include = QString::fromLatin1("<%1>").arg(QFileInfo(best).fileName()); - insertNewIncludeDirective(include, currentFile, semanticInfo().doc); - } - } - - static Symbol *checkName(const CppQuickFixInterface &interface, NameAST *ast) - { - if (ast && interface.isCursorOn(ast)) { - if (const Name *name = ast->name) { - unsigned line, column; - interface.semanticInfo().doc->translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); - - Symbol *fwdClass = 0; - - foreach (const LookupItem &r, - interface.context().lookup(name, interface.semanticInfo().doc->scopeAt(line, column))) { - if (!r.declaration()) - continue; - else if (ForwardClassDeclaration *fwd = r.declaration()->asForwardClassDeclaration()) - fwdClass = fwd; - else if (r.declaration()->isClass()) - return 0; // nothing to do. - } - - return fwdClass; - } - } - - return 0; - } - -private: - Symbol *fwdClass; -}; - -} // anonymous namespace - -void AddIncludeForForwardDeclaration::match(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - for (int index = path.size() - 1; index != -1; --index) { - AST *ast = path.at(index); - if (NamedTypeSpecifierAST *namedTy = ast->asNamedTypeSpecifier()) { - if (Symbol *fwdClass = AddIncludeForForwardDeclarationOp::checkName(interface, - namedTy->name)) { - result.append(new AddIncludeForForwardDeclarationOp(interface, index, fwdClass)); - return; - } - } else if (ElaboratedTypeSpecifierAST *eTy = ast->asElaboratedTypeSpecifier()) { - if (Symbol *fwdClass = AddIncludeForForwardDeclarationOp::checkName(interface, - eTy->name)) { - result.append(new AddIncludeForForwardDeclarationOp(interface, index, fwdClass)); - return; - } - } - } -} - -namespace { - class AddLocalDeclarationOp: public CppQuickFixOperation { public: @@ -1858,28 +1740,21 @@ QString findShortestInclude(const QString currentDocumentFilePath, return result; } -QString findIncludeForQtClass(const QString &className, - const ProjectPart::HeaderPaths &headerPaths, - bool classFoundInLocator) +QString findQtIncludeWithSameName(const QString &className, + const ProjectPart::HeaderPaths &headerPaths) { QString result; - // for QSomething, propose a <QSomething> include -- if such a class was in the locator - if (classFoundInLocator) { - result = QLatin1Char('<') + className + QLatin1Char('>'); - - // otherwise, check for a header file with the same name in the Qt include paths - } else { - foreach (const ProjectPart::HeaderPath &headerPath, headerPaths) { - if (!headerPath.path.contains(QLatin1String("/Qt"))) // "QtCore", "QtGui" etc... - continue; + // Check for a header file with the same name in the Qt include paths + foreach (const ProjectPart::HeaderPath &headerPath, headerPaths) { + if (!headerPath.path.contains(QLatin1String("/Qt"))) // "QtCore", "QtGui" etc... + continue; - const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + className; - const QFileInfo fileInfo(headerPathCandidate); - if (fileInfo.exists() && fileInfo.isFile()) { - result = QLatin1Char('<') + className + QLatin1Char('>'); - break; - } + const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + className; + const QFileInfo fileInfo(headerPathCandidate); + if (fileInfo.exists() && fileInfo.isFile()) { + result = QLatin1Char('<') + className + QLatin1Char('>'); + break; } } @@ -1925,7 +1800,7 @@ NameAST *nameUnderCursor(const QList<AST *> &path) return nameAst; } -bool canLookup(const CppQuickFixInterface &interface, const NameAST *nameAst) +bool canLookupDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) { QTC_ASSERT(nameAst, return false); @@ -1937,10 +1812,22 @@ bool canLookup(const CppQuickFixInterface &interface, const NameAST *nameAst) if (!scope) return false; - // Check if the name resolves to something + // Try to find the class/template definition const Name *name = nameAst->name; - const QList<LookupItem> existingResults = interface.context().lookup(name, scope); - return !existingResults.isEmpty(); + const QList<LookupItem> results = interface.context().lookup(name, scope); + foreach (const LookupItem &item, results) { + if (Symbol *declaration = item.declaration()) { + if (declaration->isClass()) + return true; + if (Template *templ = declaration->asTemplate()) { + Symbol *declaration = templ->declaration(); + if (declaration && declaration->isClass()) + return true; + } + } + } + + return false; } QString templateNameAsString(const TemplateNameId *templateName) @@ -1967,6 +1854,18 @@ QString unqualifiedNameForLocator(const Name *name) } } +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; +} + } // anonymous namespace void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interface, @@ -1976,8 +1875,8 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa if (!nameAst) return; - if (canLookup(interface, nameAst)) - return; // There are results, so include isn't needed + if (canLookupDefinition(interface, nameAst)) + return; const QString className = unqualifiedNameForLocator(nameAst->name); if (className.isEmpty()) @@ -1987,29 +1886,48 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa const ProjectPart::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); // Find an include file through the locator - bool classFoundInLocator = false; - QFutureInterface<Core::LocatorFilterEntry> dummyInterface; if (CppClassesFilter *classesFilter = ExtensionSystem::PluginManager::getObject<CppClassesFilter>()) { - const QList<Core::LocatorFilterEntry> matches - = classesFilter->matchesFor(dummyInterface, className); + QFutureInterface<Core::LocatorFilterEntry> dummy; + const QList<Core::LocatorFilterEntry> matches = classesFilter->matchesFor(dummy, className); + + const Snapshot forwardHeaders = forwardingHeaders(interface); foreach (const Core::LocatorFilterEntry &entry, matches) { IndexItem::Ptr info = entry.internalData.value<IndexItem::Ptr>(); if (info->symbolName() != className) continue; - classFoundInLocator = true; - // Find the shortest way to include fileName given the includePaths - const QString include = findShortestInclude(currentDocumentFilePath, info->fileName(), - headerPaths); - if (!include.isEmpty()) - result.append(new AddIncludeForUndefinedIdentifierOp(interface, 0, include)); + Snapshot localForwardHeaders = forwardHeaders; + localForwardHeaders.insert(interface.snapshot().document(info->fileName())); + QStringList headerAndItsForwardingHeaders; + headerAndItsForwardingHeaders << info->fileName(); + headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->fileName()); + + foreach (const QString &header, headerAndItsForwardingHeaders) { + const QString include = findShortestInclude(currentDocumentFilePath, header, + headerPaths); + if (!include.isEmpty()) { + const QString headerFileName = QFileInfo(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.append(new AddIncludeForUndefinedIdentifierOp(interface, priority, + include)); + } + } } } - // If e.g. QString was found in "<qstring.h>" propose an extra prioritized entry "<QString>". + // The header file we are looking for might not be (yet) included in any file we have parsed. + // As such, it will not be findable via locator. At least for Qt classes, check also for + // headers with the same name. if (className.size() > 2 && className.at(0) == QLatin1Char('Q') && className.at(1).isUpper()) { - const QString include = findIncludeForQtClass(className, headerPaths, classFoundInLocator); + const QString include = findQtIncludeWithSameName(className, headerPaths); if (!include.isEmpty()) result.append(new AddIncludeForUndefinedIdentifierOp(interface, 1, include)); } diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h index 20b75040440f322d83c2927d1a105ac26883d604..b8ba741e4f29b03bfde0e821af13c9cb8721909e 100644 --- a/src/plugins/cppeditor/cppquickfixes.h +++ b/src/plugins/cppeditor/cppquickfixes.h @@ -62,7 +62,7 @@ namespace Internal { void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); /*! - Adds an include for an undefined identifier. + Adds an include for an undefined identifier or only forward declared identifier. Activates on: the undefined identifier */ @@ -84,17 +84,6 @@ private: QString m_include; }; -/*! - Can be triggered on a class forward declaration to add the matching #include. - - Activates on: the name of a forward-declared class or struct -*/ -class AddIncludeForForwardDeclaration: public CppQuickFixFactory -{ -public: - void match(const CppQuickFixInterface &interface, QuickFixOperations &result); -}; - /*! Rewrite a op b