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
|
insertionpointlocator.cpp insertionpointlocator.h
|
||||||
projectinfo.cpp projectinfo.h
|
projectinfo.cpp projectinfo.h
|
||||||
projectpart.cpp projectpart.h
|
projectpart.cpp projectpart.h
|
||||||
|
quickfixes/addmodulefrominclude.cpp quickfixes/addmodulefrominclude.h
|
||||||
quickfixes/assigntolocalvariable.cpp quickfixes/assigntolocalvariable.h
|
quickfixes/assigntolocalvariable.cpp quickfixes/assigntolocalvariable.h
|
||||||
quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
|
quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
|
||||||
quickfixes/completeswitchstatement.cpp quickfixes/completeswitchstatement.h
|
quickfixes/completeswitchstatement.cpp quickfixes/completeswitchstatement.h
|
||||||
|
@@ -223,6 +223,8 @@ QtcPlugin {
|
|||||||
name: "Quickfixes"
|
name: "Quickfixes"
|
||||||
prefix: "quickfixes/"
|
prefix: "quickfixes/"
|
||||||
files: [
|
files: [
|
||||||
|
"addmodulefrominclude.cpp",
|
||||||
|
"addmodulefrominclude.h",
|
||||||
"assigntolocalvariable.cpp",
|
"assigntolocalvariable.cpp",
|
||||||
"assigntolocalvariable.h",
|
"assigntolocalvariable.h",
|
||||||
"bringidentifierintoscope.cpp",
|
"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();
|
Kit * const currentKit = activeKitForCurrentProject();
|
||||||
if (!currentKit)
|
if (!currentKit)
|
||||||
return;
|
return;
|
||||||
if (const QString module = currentKit->moduleForClass(className); !module.isEmpty()) {
|
if (const QString module = currentKit->moduleForHeader(className); !module.isEmpty()) {
|
||||||
result << new AddIncludeForUndefinedIdentifierOp(
|
result << new AddIncludeForUndefinedIdentifierOp(
|
||||||
interface, 2, '<' + className + '>', module);
|
interface, 2, '<' + className + '>', module);
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include "../cppeditorwidget.h"
|
#include "../cppeditorwidget.h"
|
||||||
#include "../cppfunctiondecldeflink.h"
|
#include "../cppfunctiondecldeflink.h"
|
||||||
#include "../cpprefactoringchanges.h"
|
#include "../cpprefactoringchanges.h"
|
||||||
|
#include "addmodulefrominclude.h"
|
||||||
#include "assigntolocalvariable.h"
|
#include "assigntolocalvariable.h"
|
||||||
#include "bringidentifierintoscope.h"
|
#include "bringidentifierintoscope.h"
|
||||||
#include "completeswitchstatement.h"
|
#include "completeswitchstatement.h"
|
||||||
@@ -111,6 +112,7 @@ void createCppQuickFixFactories()
|
|||||||
new ExtraRefactoringOperations;
|
new ExtraRefactoringOperations;
|
||||||
|
|
||||||
registerAssignToLocalVariableQuickfix();
|
registerAssignToLocalVariableQuickfix();
|
||||||
|
registerAddModuleFromIncludeQuickfix();
|
||||||
registerBringIdentifierIntoScopeQuickfixes();
|
registerBringIdentifierIntoScopeQuickfixes();
|
||||||
registerCodeGenerationQuickfixes();
|
registerCodeGenerationQuickfixes();
|
||||||
registerCompleteSwitchStatementQuickfix();
|
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);
|
factory->addToRunEnvironment(this, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Kit::moduleForClass(const QString &className) const
|
QString Kit::moduleForHeader(const QString &headerFileName) const
|
||||||
{
|
{
|
||||||
for (KitAspectFactory *factory : KitManager::kitAspectFactories()) {
|
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 module;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@@ -102,7 +102,7 @@ public:
|
|||||||
Utils::Environment runEnvironment() const;
|
Utils::Environment runEnvironment() const;
|
||||||
|
|
||||||
QList<Utils::OutputLineParser *> createOutputParsers() 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;
|
QString toHtml(const Tasks &additional = Tasks(), const QString &extraText = QString()) const;
|
||||||
Kit *clone(bool keepName = false) 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;
|
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(k)
|
||||||
Q_UNUSED(className)
|
Q_UNUSED(className)
|
||||||
|
@@ -59,7 +59,7 @@ public:
|
|||||||
|
|
||||||
virtual int weight(const Kit *k) const;
|
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;
|
virtual ItemList toUserOutput(const Kit *) const = 0;
|
||||||
|
|
||||||
|
@@ -281,18 +281,18 @@ QString QtVersion::defaultUnexpandedDisplayName() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QtVersion::moduleForClass(const QString &className) const
|
QString QtVersion::moduleForHeader(const QString &headerFileName) const
|
||||||
{
|
{
|
||||||
if (!d->m_classesPerModule) {
|
if (!d->m_classesPerModule) {
|
||||||
d->m_classesPerModule.emplace();
|
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 FileFilter frameworksFilter({"*.framework"}, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
const FilePaths frameworks = libraryPath().dirEntries(frameworksFilter);
|
const FilePaths frameworks = libraryPath().dirEntries(frameworksFilter);
|
||||||
for (const FilePath &framework : frameworks) {
|
for (const FilePath &framework : frameworks) {
|
||||||
const QString frameworkName = framework.fileName();
|
const QString frameworkName = framework.fileName();
|
||||||
const QString &moduleName = frameworkName.left(frameworkName.indexOf('.'));
|
const QString &moduleName = frameworkName.left(frameworkName.indexOf('.'));
|
||||||
const FilePath headersDir = libraryPath().resolvePath(framework.pathAppended("Headers"));
|
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));
|
d->m_classesPerModule->insert(moduleName, Utils::transform(headers, &FilePath::fileName));
|
||||||
}
|
}
|
||||||
if (frameworks.isEmpty()) {
|
if (frameworks.isEmpty()) {
|
||||||
@@ -300,7 +300,7 @@ QString QtVersion::moduleForClass(const QString &className) const
|
|||||||
const FilePaths modules = headerPath().dirEntries(modulesFilter);
|
const FilePaths modules = headerPath().dirEntries(modulesFilter);
|
||||||
for (const FilePath &module : modules) {
|
for (const FilePath &module : modules) {
|
||||||
const FilePath headersDir = headerPath().resolvePath(module);
|
const FilePath headersDir = headerPath().resolvePath(module);
|
||||||
const FilePaths headers = headersDir.dirEntries(classesFilter);
|
const FilePaths headers = headersDir.dirEntries(filesFilter);
|
||||||
d->m_classesPerModule
|
d->m_classesPerModule
|
||||||
->insert(module.fileName(), Utils::transform(headers, &FilePath::fileName));
|
->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) {
|
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());
|
QTC_ASSERT(it.key().size() > 2, return it.key());
|
||||||
return it.key().left(2) + '.' + it.key().mid(2).toLower();
|
return it.key().left(2) + '.' + it.key().mid(2).toLower();
|
||||||
}
|
}
|
||||||
|
@@ -174,7 +174,7 @@ public:
|
|||||||
Utils::FilePath librarySearchPath() const;
|
Utils::FilePath librarySearchPath() const;
|
||||||
|
|
||||||
Utils::FilePaths directoriesToIgnoreInProjectTree() 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 qtNamespace() const;
|
||||||
QString qtLibInfix() const;
|
QString qtLibInfix() const;
|
||||||
|
@@ -130,7 +130,7 @@ private:
|
|||||||
QSet<Id> availableFeatures(const Kit *k) const override;
|
QSet<Id> availableFeatures(const Kit *k) const override;
|
||||||
|
|
||||||
int weight(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,
|
void qtVersionsChanged(const QList<int> &addedIds,
|
||||||
const QList<int> &removedIds,
|
const QList<int> &removedIds,
|
||||||
@@ -472,10 +472,10 @@ int QtKitAspectFactory::weight(const Kit *k) const
|
|||||||
return qtAbi.isCompatibleWith(tcAbi); }) ? 1 : 0;
|
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))
|
if (const QtVersion * const v = QtKitAspect::qtVersion(k))
|
||||||
return v->moduleForClass(className);
|
return v->moduleForHeader(headerFileName);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user