CppEditor: Move "add include" and "add forward decl" quickfixes

... to dedicated files.

Change-Id: Ifb2c00241b3e77c33fdfc79227486e431ecab5d7
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-15 16:21:36 +02:00
parent b0f8fe63cc
commit c5325effc8
8 changed files with 1516 additions and 1449 deletions

View File

@@ -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/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h
quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h
quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h

View File

@@ -219,6 +219,8 @@ QtcPlugin {
name: "Quickfixes" name: "Quickfixes"
prefix: "quickfixes/" prefix: "quickfixes/"
files: [ files: [
"bringidentifierintoscope.cpp",
"bringidentifierintoscope.h",
"convertqt4connect.cpp", "convertqt4connect.cpp",
"convertqt4connect.h", "convertqt4connect.h",
"cppcodegenerationquickfixes.cpp", "cppcodegenerationquickfixes.cpp",

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -106,13 +106,6 @@ private slots:
void testInsertDeclFromDefTemplateReturnType(); void testInsertDeclFromDefTemplateReturnType();
void testInsertDeclFromDefNotTriggeredForTemplateFunc(); void testInsertDeclFromDefNotTriggeredForTemplateFunc();
void testAddIncludeForUndefinedIdentifier_data();
void testAddIncludeForUndefinedIdentifier();
void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude();
void testAddForwardDeclForUndefinedIdentifier_data();
void testAddForwardDeclForUndefinedIdentifier();
void testAssignToLocalVariableTemplates(); void testAssignToLocalVariableTemplates();
void testExtractFunction_data(); void testExtractFunction_data();

View File

@@ -9,14 +9,13 @@
#include "../cppeditortr.h" #include "../cppeditortr.h"
#include "../cppeditorwidget.h" #include "../cppeditorwidget.h"
#include "../cppfunctiondecldeflink.h" #include "../cppfunctiondecldeflink.h"
#include "../cpplocatordata.h"
#include "../cpppointerdeclarationformatter.h" #include "../cpppointerdeclarationformatter.h"
#include "../cpprefactoringchanges.h" #include "../cpprefactoringchanges.h"
#include "../cpptoolsreuse.h" #include "../cpptoolsreuse.h"
#include "../includeutils.h" #include "../includeutils.h"
#include "../indexitem.h"
#include "../insertionpointlocator.h" #include "../insertionpointlocator.h"
#include "../symbolfinder.h" #include "../symbolfinder.h"
#include "bringidentifierintoscope.h"
#include "cppcodegenerationquickfixes.h" #include "cppcodegenerationquickfixes.h"
#include "cppinsertvirtualmethods.h" #include "cppinsertvirtualmethods.h"
#include "cppquickfixassistant.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<ProjectPart::ConstPtr> 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<AST *> &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<LookupItem> 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<IndexItem::Ptr> matchName(const Name *name, QString *className)
{
if (!name)
return {};
QString simpleName;
QList<IndexItem::Ptr> 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<IndexItem::Ptr> matches = matchName(nameAst->name, &className);
// Find an include file through the locator
if (!matches.isEmpty()) {
QList<IndexItem::Ptr> 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<AddIncludeForUndefinedIdentifierOp>();
return includeOp && includeOp->include() == include;
};
if (!include.isEmpty() && !Utils::contains(result, matcher))
result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include);
}
}
namespace { namespace {
class RearrangeParamDeclarationListOp: public CppQuickFixOperation class RearrangeParamDeclarationListOp: public CppQuickFixOperation
@@ -5431,8 +5069,6 @@ void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface,
void createCppQuickFixes() void createCppQuickFixes()
{ {
new AddIncludeForUndefinedIdentifier;
new FlipLogicalOperands; new FlipLogicalOperands;
new InverseLogicalComparison; new InverseLogicalComparison;
new RewriteLogicalAnd; new RewriteLogicalAnd;
@@ -5472,6 +5108,7 @@ void createCppQuickFixes()
registerConvertQt4ConnectQuickfix(); registerConvertQt4ConnectQuickfix();
registerMoveFunctionDefinitionQuickfixes(); registerMoveFunctionDefinitionQuickfixes();
registerInsertFunctionDefinitionQuickfixes(); registerInsertFunctionDefinitionQuickfixes();
registerBringIdentifierIntoScopeQuickfixes();
new OptimizeForLoop; new OptimizeForLoop;

View File

@@ -28,43 +28,6 @@ public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; 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 Rewrite
a op b a op b