forked from qt-creator/qt-creator
CppEditor: Add quickfix to add project dependency from include directive
Change-Id: I87f94c13d2d41bead255977057739db521be5c38 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -95,6 +95,7 @@ add_qtc_plugin(CppEditor
|
||||
insertionpointlocator.cpp insertionpointlocator.h
|
||||
projectinfo.cpp projectinfo.h
|
||||
projectpart.cpp projectpart.h
|
||||
quickfixes/addmodulefrominclude.cpp quickfixes/addmodulefrominclude.h
|
||||
quickfixes/assigntolocalvariable.cpp quickfixes/assigntolocalvariable.h
|
||||
quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
|
||||
quickfixes/completeswitchstatement.cpp quickfixes/completeswitchstatement.h
|
||||
|
@@ -223,6 +223,8 @@ QtcPlugin {
|
||||
name: "Quickfixes"
|
||||
prefix: "quickfixes/"
|
||||
files: [
|
||||
"addmodulefrominclude.cpp",
|
||||
"addmodulefrominclude.h",
|
||||
"assigntolocalvariable.cpp",
|
||||
"assigntolocalvariable.h",
|
||||
"bringidentifierintoscope.cpp",
|
||||
|
172
src/plugins/cppeditor/quickfixes/addmodulefrominclude.cpp
Normal file
172
src/plugins/cppeditor/quickfixes/addmodulefrominclude.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "addmodulefrominclude.h"
|
||||
|
||||
#include "../cppeditortr.h"
|
||||
#include "../cppeditorwidget.h"
|
||||
#include "../cpprefactoringchanges.h"
|
||||
#include "cppquickfix.h"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
#include <QTest>
|
||||
#endif
|
||||
|
||||
using namespace CPlusPlus;
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
using namespace TextEditor;
|
||||
using namespace Utils;
|
||||
|
||||
namespace CppEditor::Internal {
|
||||
|
||||
class AddModuleFromIncludeOp : public CppQuickFixOperation
|
||||
{
|
||||
public:
|
||||
AddModuleFromIncludeOp(const CppQuickFixInterface &interface, const QString &module)
|
||||
: CppQuickFixOperation(interface)
|
||||
, m_module(module)
|
||||
{
|
||||
setDescription(Tr::tr("Add project dependency %1").arg(module));
|
||||
}
|
||||
|
||||
void perform() override
|
||||
{
|
||||
if (Project * const project = ProjectManager::projectForFile(currentFile()->filePath())) {
|
||||
if (ProjectNode * const product = project->productNodeForFilePath(
|
||||
currentFile()->filePath())) {
|
||||
product->addDependencies({m_module});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const QString m_module;
|
||||
};
|
||||
|
||||
class AddModuleFromInclude : public CppQuickFixFactory
|
||||
{
|
||||
#ifdef WITH_TESTS
|
||||
public:
|
||||
static QObject *createTest();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
|
||||
{
|
||||
Kit * const currentKit = activeKitForCurrentProject();
|
||||
if (!currentKit)
|
||||
return;
|
||||
|
||||
const int line = interface.currentFile()->cursor().blockNumber() + 1;
|
||||
for (const Document::Include &incl : interface.semanticInfo().doc->unresolvedIncludes()) {
|
||||
if (line == incl.line()) {
|
||||
const QString fileName = FilePath::fromString(incl.unresolvedFileName()).fileName();
|
||||
if (QString module = currentKit->moduleForHeader(fileName); !module.isEmpty()) {
|
||||
result << new AddModuleFromIncludeOp(interface, module);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
using namespace CppEditor::Tests;
|
||||
|
||||
class AddModuleFromIncludeTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void test()
|
||||
{
|
||||
// Set up project.
|
||||
Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
|
||||
return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
|
||||
});
|
||||
if (!kit)
|
||||
QSKIP("The test requires at least one valid kit with a valid Qt");
|
||||
const auto projectDir = std::make_unique<TemporaryCopiedDir>(
|
||||
":/cppeditor/testcases/add-module-from-include");
|
||||
SourceFilesRefreshGuard refreshGuard;
|
||||
ProjectOpenerAndCloser projectMgr;
|
||||
QVERIFY(projectMgr.open(projectDir->absolutePath("add-module-from-include.pro"), true, kit));
|
||||
QVERIFY(refreshGuard.wait());
|
||||
|
||||
// Open source file and locate the include directive.
|
||||
const FilePath sourceFilePath = projectDir->absolutePath("main.cpp");
|
||||
QVERIFY2(sourceFilePath.exists(), qPrintable(sourceFilePath.toUserOutput()));
|
||||
const auto editor = qobject_cast<BaseTextEditor *>(
|
||||
EditorManager::openEditor(sourceFilePath));
|
||||
QVERIFY(editor);
|
||||
const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
|
||||
QVERIFY(doc);
|
||||
QTextCursor classCursor = doc->document()->find("#include");
|
||||
QVERIFY(!classCursor.isNull());
|
||||
editor->setCursorPosition(classCursor.position());
|
||||
const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget());
|
||||
QVERIFY(editorWidget);
|
||||
QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
|
||||
|
||||
// Query factory.
|
||||
AddModuleFromInclude factory;
|
||||
CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
|
||||
QuickFixOperations operations;
|
||||
factory.match(quickFixInterface, operations);
|
||||
QCOMPARE(operations.size(), qsizetype(1));
|
||||
operations.first()->perform();
|
||||
|
||||
// Compare files.
|
||||
const FileFilter filter({"*_expected"}, QDir::Files);
|
||||
const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
|
||||
QVERIFY(!expectedDocuments.isEmpty());
|
||||
for (const FilePath &expected : expectedDocuments) {
|
||||
static const QString suffix = "_expected";
|
||||
const FilePath actual = expected.parentDir()
|
||||
.pathAppended(expected.fileName().chopped(suffix.length()));
|
||||
QVERIFY(actual.exists());
|
||||
const auto actualContents = actual.fileContents();
|
||||
QVERIFY(actualContents);
|
||||
const auto expectedContents = expected.fileContents();
|
||||
const QByteArrayList actualLines = actualContents->split('\n');
|
||||
const QByteArrayList expectedLines = expectedContents->split('\n');
|
||||
if (actualLines.size() != expectedLines.size()) {
|
||||
qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
|
||||
qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
|
||||
}
|
||||
QCOMPARE(actualLines.size(), expectedLines.size());
|
||||
for (int i = 0; i < actualLines.size(); ++i) {
|
||||
const QByteArray actualLine = actualLines.at(i);
|
||||
const QByteArray expectedLine = expectedLines.at(i);
|
||||
if (actualLine != expectedLine)
|
||||
qDebug() << "Unexpected content in line" << (i + 1) << "of file"
|
||||
<< actual.fileName();
|
||||
QCOMPARE(actualLine, expectedLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QObject *AddModuleFromInclude::createTest() { return new AddModuleFromIncludeTest; }
|
||||
#endif
|
||||
|
||||
void registerAddModuleFromIncludeQuickfix()
|
||||
{
|
||||
CppQuickFixFactory::registerFactory<AddModuleFromInclude>();
|
||||
}
|
||||
|
||||
} // namespace CppEditor::Internal
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
#include <addmodulefrominclude.moc>
|
||||
#endif
|
8
src/plugins/cppeditor/quickfixes/addmodulefrominclude.h
Normal file
8
src/plugins/cppeditor/quickfixes/addmodulefrominclude.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (C) 2025 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 registerAddModuleFromIncludeQuickfix();
|
||||
} // namespace CppEditor::Internal
|
@@ -457,7 +457,7 @@ private:
|
||||
Kit * const currentKit = activeKitForCurrentProject();
|
||||
if (!currentKit)
|
||||
return;
|
||||
if (const QString module = currentKit->moduleForClass(className); !module.isEmpty()) {
|
||||
if (const QString module = currentKit->moduleForHeader(className); !module.isEmpty()) {
|
||||
result << new AddIncludeForUndefinedIdentifierOp(
|
||||
interface, 2, '<' + className + '>', module);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include "../cppeditorwidget.h"
|
||||
#include "../cppfunctiondecldeflink.h"
|
||||
#include "../cpprefactoringchanges.h"
|
||||
#include "addmodulefrominclude.h"
|
||||
#include "assigntolocalvariable.h"
|
||||
#include "bringidentifierintoscope.h"
|
||||
#include "completeswitchstatement.h"
|
||||
@@ -111,6 +112,7 @@ void createCppQuickFixFactories()
|
||||
new ExtraRefactoringOperations;
|
||||
|
||||
registerAssignToLocalVariableQuickfix();
|
||||
registerAddModuleFromIncludeQuickfix();
|
||||
registerBringIdentifierIntoScopeQuickfixes();
|
||||
registerCodeGenerationQuickfixes();
|
||||
registerCompleteSwitchStatementQuickfix();
|
||||
|
@@ -0,0 +1,2 @@
|
||||
CONFIG -= qt
|
||||
SOURCES = main.cpp
|
@@ -0,0 +1,3 @@
|
||||
SOURCES = main.cpp
|
||||
|
||||
QT += concurrent
|
@@ -0,0 +1,5 @@
|
||||
#include <QtConcurrent>
|
||||
|
||||
int main()
|
||||
{
|
||||
}
|
@@ -556,10 +556,10 @@ void Kit::addToRunEnvironment(Environment &env) const
|
||||
factory->addToRunEnvironment(this, env);
|
||||
}
|
||||
|
||||
QString Kit::moduleForClass(const QString &className) const
|
||||
QString Kit::moduleForHeader(const QString &headerFileName) const
|
||||
{
|
||||
for (KitAspectFactory *factory : KitManager::kitAspectFactories()) {
|
||||
if (const QString module = factory->moduleForClass(this, className); !module.isEmpty())
|
||||
if (const QString module = factory->moduleForHeader(this, headerFileName); !module.isEmpty())
|
||||
return module;
|
||||
}
|
||||
return {};
|
||||
|
@@ -102,7 +102,7 @@ public:
|
||||
Utils::Environment runEnvironment() const;
|
||||
|
||||
QList<Utils::OutputLineParser *> createOutputParsers() const;
|
||||
QString moduleForClass(const QString &className) const;
|
||||
QString moduleForHeader(const QString &className) const;
|
||||
|
||||
QString toHtml(const Tasks &additional = Tasks(), const QString &extraText = QString()) const;
|
||||
Kit *clone(bool keepName = false) const;
|
||||
|
@@ -310,7 +310,7 @@ int KitAspectFactory::weight(const Kit *k) const
|
||||
return k->value(id()).isValid() ? 1 : 0;
|
||||
}
|
||||
|
||||
QString KitAspectFactory::moduleForClass(const Kit *k, const QString &className) const
|
||||
QString KitAspectFactory::moduleForHeader(const Kit *k, const QString &className) const
|
||||
{
|
||||
Q_UNUSED(k)
|
||||
Q_UNUSED(className)
|
||||
|
@@ -59,7 +59,7 @@ public:
|
||||
|
||||
virtual int weight(const Kit *k) const;
|
||||
|
||||
virtual QString moduleForClass(const Kit *k, const QString &className) const;
|
||||
virtual QString moduleForHeader(const Kit *k, const QString &className) const;
|
||||
|
||||
virtual ItemList toUserOutput(const Kit *) const = 0;
|
||||
|
||||
|
@@ -281,18 +281,18 @@ QString QtVersion::defaultUnexpandedDisplayName() const
|
||||
return result;
|
||||
}
|
||||
|
||||
QString QtVersion::moduleForClass(const QString &className) const
|
||||
QString QtVersion::moduleForHeader(const QString &headerFileName) const
|
||||
{
|
||||
if (!d->m_classesPerModule) {
|
||||
d->m_classesPerModule.emplace();
|
||||
const FileFilter classesFilter({"Q[A-Z]*"}, QDir::Files);
|
||||
const FileFilter filesFilter({}, QDir::Files);
|
||||
const FileFilter frameworksFilter({"*.framework"}, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
const FilePaths frameworks = libraryPath().dirEntries(frameworksFilter);
|
||||
for (const FilePath &framework : frameworks) {
|
||||
const QString frameworkName = framework.fileName();
|
||||
const QString &moduleName = frameworkName.left(frameworkName.indexOf('.'));
|
||||
const FilePath headersDir = libraryPath().resolvePath(framework.pathAppended("Headers"));
|
||||
const FilePaths headers = headersDir.dirEntries(classesFilter);
|
||||
const FilePaths headers = headersDir.dirEntries(filesFilter);
|
||||
d->m_classesPerModule->insert(moduleName, Utils::transform(headers, &FilePath::fileName));
|
||||
}
|
||||
if (frameworks.isEmpty()) {
|
||||
@@ -300,7 +300,7 @@ QString QtVersion::moduleForClass(const QString &className) const
|
||||
const FilePaths modules = headerPath().dirEntries(modulesFilter);
|
||||
for (const FilePath &module : modules) {
|
||||
const FilePath headersDir = headerPath().resolvePath(module);
|
||||
const FilePaths headers = headersDir.dirEntries(classesFilter);
|
||||
const FilePaths headers = headersDir.dirEntries(filesFilter);
|
||||
d->m_classesPerModule
|
||||
->insert(module.fileName(), Utils::transform(headers, &FilePath::fileName));
|
||||
}
|
||||
@@ -308,7 +308,7 @@ QString QtVersion::moduleForClass(const QString &className) const
|
||||
}
|
||||
|
||||
for (auto it = d->m_classesPerModule->cbegin(); it != d->m_classesPerModule->cend(); ++it) {
|
||||
if (it.value().contains(className)) {
|
||||
if (it.value().contains(headerFileName)) {
|
||||
QTC_ASSERT(it.key().size() > 2, return it.key());
|
||||
return it.key().left(2) + '.' + it.key().mid(2).toLower();
|
||||
}
|
||||
|
@@ -174,7 +174,7 @@ public:
|
||||
Utils::FilePath librarySearchPath() const;
|
||||
|
||||
Utils::FilePaths directoriesToIgnoreInProjectTree() const;
|
||||
QString moduleForClass(const QString &className) const; // Format is "Qt.core"
|
||||
QString moduleForHeader(const QString &className) const; // Format is "Qt.core"
|
||||
|
||||
QString qtNamespace() const;
|
||||
QString qtLibInfix() const;
|
||||
|
@@ -130,7 +130,7 @@ private:
|
||||
QSet<Id> availableFeatures(const Kit *k) const override;
|
||||
|
||||
int weight(const Kit *k) const override;
|
||||
QString moduleForClass(const Kit *k, const QString &className) const override;
|
||||
QString moduleForHeader(const Kit *k, const QString &className) const override;
|
||||
|
||||
void qtVersionsChanged(const QList<int> &addedIds,
|
||||
const QList<int> &removedIds,
|
||||
@@ -472,10 +472,10 @@ int QtKitAspectFactory::weight(const Kit *k) const
|
||||
return qtAbi.isCompatibleWith(tcAbi); }) ? 1 : 0;
|
||||
}
|
||||
|
||||
QString QtKitAspectFactory::moduleForClass(const Kit *k, const QString &className) const
|
||||
QString QtKitAspectFactory::moduleForHeader(const Kit *k, const QString &headerFileName) const
|
||||
{
|
||||
if (const QtVersion * const v = QtKitAspect::qtVersion(k))
|
||||
return v->moduleForClass(className);
|
||||
return v->moduleForHeader(headerFileName);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user