CppEditor: Add very first include after include guard

...when adding an include for an undefined identifier.

Change-Id: Ia338e924901262a847d3bd7ed9733d8e66c631dd
Task-number: QTCREATORBUG-10391
Reviewed-by: Christian Stenger <christian.stenger@digia.com>
This commit is contained in:
Nikolai Kosjar
2014-10-07 17:20:25 +02:00
parent eefcd23cee
commit a366429766
7 changed files with 132 additions and 52 deletions

View File

@@ -151,3 +151,11 @@ void FastPreprocessor::startExpandingMacro(unsigned bytesOffset, unsigned utf16c
utf16charsOffset, macro.nameToQString().size(), utf16charsOffset, macro.nameToQString().size(),
line, actuals); line, actuals);
} }
void FastPreprocessor::markAsIncludeGuard(const QByteArray &macroName)
{
if (!_currentDoc)
return;
_currentDoc->setIncludeGuardMacroName(macroName);
}

View File

@@ -73,7 +73,7 @@ public:
const Macro &, const Macro &,
const QVector<MacroArgumentReference> &); const QVector<MacroArgumentReference> &);
virtual void stopExpandingMacro(unsigned, const Macro &) {} virtual void stopExpandingMacro(unsigned, const Macro &) {}
virtual void markAsIncludeGuard(const QByteArray &) {} virtual void markAsIncludeGuard(const QByteArray &macroName);
virtual void startSkippingBlocks(unsigned) {} virtual void startSkippingBlocks(unsigned) {}
virtual void stopSkippingBlocks(unsigned) {} virtual void stopSkippingBlocks(unsigned) {}

View File

@@ -169,6 +169,7 @@ private slots:
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_mixedIncludeTypes3(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_mixedIncludeTypes3();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_mixedIncludeTypes4(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_mixedIncludeTypes4();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_noinclude(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_noinclude();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_onlyIncludeGuard();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCppStyleCommentOnTop(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCppStyleCommentOnTop();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCStyleCommentOnTop(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCStyleCommentOnTop();
void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_checkQSomethingInQtIncludePaths(); void test_quickfix_AddIncludeForUndefinedIdentifier_inserting_checkQSomethingInQtIncludePaths();

View File

@@ -2828,6 +2828,36 @@ void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_inserting_n
QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath()); QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath());
} }
/// Check: Insert very first include after include guard
void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_inserting_onlyIncludeGuard()
{
QList<QuickFixTestDocument::Ptr> testFiles;
QByteArray original;
QByteArray expected;
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"
;
testFiles << QuickFixTestDocument::create(TestIncludePaths::directoryOfTestFile().toUtf8()
+ "/file.cpp", original, expected);
AddIncludeForUndefinedIdentifierTestFactory factory(QLatin1String("\"file.h\""));
QuickFixTestCase::run(testFiles, &factory, TestIncludePaths::globalIncludePath());
}
/// Check: Insert very first include if there is a c++ style comment on top /// Check: Insert very first include if there is a c++ style comment on top
void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCppStyleCommentOnTop() void CppEditorPlugin::test_quickfix_AddIncludeForUndefinedIdentifier_inserting_veryFirstIncludeCppStyleCommentOnTop()
{ {

View File

@@ -259,11 +259,12 @@ Namespace *isNamespaceFunction(const LookupContext &context, Function *function)
} }
// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!). // Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file) void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file,
const CPlusPlus::Document::Ptr &cppDocument)
{ {
// Find optimal position // Find optimal position
using namespace IncludeUtils; using namespace IncludeUtils;
LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(), LineForNewIncludeDirective finder(file->document(), cppDocument,
LineForNewIncludeDirective::IgnoreMocIncludes, LineForNewIncludeDirective::IgnoreMocIncludes,
LineForNewIncludeDirective::AutoDetect); LineForNewIncludeDirective::AutoDetect);
unsigned newLinesToPrepend = 0; unsigned newLinesToPrepend = 0;
@@ -1583,7 +1584,7 @@ public:
best = headerFile; best = headerFile;
const QString include = QString::fromLatin1("<%1>").arg(QFileInfo(best).fileName()); const QString include = QString::fromLatin1("<%1>").arg(QFileInfo(best).fileName());
insertNewIncludeDirective(include, currentFile); insertNewIncludeDirective(include, currentFile, semanticInfo().doc);
} }
} }
@@ -1826,7 +1827,7 @@ void AddIncludeForUndefinedIdentifierOp::perform()
CppRefactoringChanges refactoring(snapshot()); CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr file = refactoring.file(fileName()); CppRefactoringFilePtr file = refactoring.file(fileName());
insertNewIncludeDirective(m_include, file); insertNewIncludeDirective(m_include, file, semanticInfo().doc);
} }
namespace { namespace {

View File

@@ -35,6 +35,7 @@
#include <cplusplus/PreprocessorEnvironment.h> #include <cplusplus/PreprocessorEnvironment.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <QDir> #include <QDir>
@@ -80,15 +81,57 @@ QString includeDir(const QString &include)
return dirPrefix; return dirPrefix;
} }
int lineAfterFirstComment(const QTextDocument *textDocument)
{
int insertLine = -1;
QTextBlock block = textDocument->firstBlock();
while (block.isValid()) {
const QString trimmedText = block.text().trimmed();
// Only skip the first comment!
if (trimmedText.startsWith(QLatin1String("/*"))) {
do {
const int pos = block.text().indexOf(QLatin1String("*/"));
if (pos > -1) {
insertLine = block.blockNumber() + 2;
break;
}
block = block.next();
} while (block.isValid());
break;
} else if (trimmedText.startsWith(QLatin1String("//"))) {
block = block.next();
while (block.isValid()) {
if (!block.text().trimmed().startsWith(QLatin1String("//"))) {
insertLine = block.blockNumber() + 1;
break;
}
block = block.next();
}
break;
}
if (!trimmedText.isEmpty())
break;
block = block.next();
}
return insertLine;
}
} // anonymous namespace } // anonymous namespace
LineForNewIncludeDirective::LineForNewIncludeDirective(const QTextDocument *textDocument, LineForNewIncludeDirective::LineForNewIncludeDirective(const QTextDocument *textDocument,
QList<Document::Include> includes, const Document::Ptr cppDocument,
MocIncludeMode mocIncludeMode, MocIncludeMode mocIncludeMode,
IncludeStyle includeStyle) IncludeStyle includeStyle)
: m_textDocument(textDocument) : m_textDocument(textDocument)
, m_cppDocument(cppDocument)
, m_includeStyle(includeStyle) , m_includeStyle(includeStyle)
{ {
const QList<Document::Include> includes = cppDocument->resolvedIncludes();
// Ignore *.moc includes if requested // Ignore *.moc includes if requested
if (mocIncludeMode == IgnoreMocIncludes) { if (mocIncludeMode == IgnoreMocIncludes) {
foreach (const Document::Include &include, includes) { foreach (const Document::Include &include, includes) {
@@ -129,6 +172,43 @@ LineForNewIncludeDirective::LineForNewIncludeDirective(const QTextDocument *text
} }
} }
int LineForNewIncludeDirective::findInsertLineForVeryFirstInclude(unsigned *newLinesToPrepend,
unsigned *newLinesToAppend)
{
int insertLine = 1;
// If there is an include guard, insert right after that one
const QByteArray includeGuardMacroName = m_cppDocument->includeGuardMacroName();
if (!includeGuardMacroName.isEmpty()) {
const QList<Macro> definedMacros = m_cppDocument->definedMacros();
foreach (const Macro &definedMacro, definedMacros) {
if (definedMacro.name() == includeGuardMacroName) {
if (newLinesToPrepend)
*newLinesToPrepend = 1;
if (newLinesToAppend)
*newLinesToAppend += 1;
insertLine = definedMacro.line() + 1;
}
}
QTC_CHECK(insertLine != 1);
} else {
// Otherwise, if there is a comment, insert right after it
insertLine = lineAfterFirstComment(m_textDocument);
if (insertLine != -1) {
if (newLinesToPrepend)
*newLinesToPrepend = 1;
// Otherwise, insert at top of file
} else {
if (newLinesToAppend)
*newLinesToAppend += 1;
insertLine = 1;
}
}
return insertLine;
}
int LineForNewIncludeDirective::operator()(const QString &newIncludeFileName, int LineForNewIncludeDirective::operator()(const QString &newIncludeFileName,
unsigned *newLinesToPrepend, unsigned *newLinesToPrepend,
unsigned *newLinesToAppend) unsigned *newLinesToAppend)
@@ -144,51 +224,8 @@ int LineForNewIncludeDirective::operator()(const QString &newIncludeFileName,
: Client::IncludeGlobal; : Client::IncludeGlobal;
// Handle no includes // Handle no includes
if (m_includes.empty()) { if (m_includes.empty())
unsigned insertLine = 0; return findInsertLineForVeryFirstInclude(newLinesToPrepend, newLinesToAppend);
QTextBlock block = m_textDocument->firstBlock();
while (block.isValid()) {
const QString trimmedText = block.text().trimmed();
// Only skip the first comment!
if (trimmedText.startsWith(QLatin1String("/*"))) {
do {
const int pos = block.text().indexOf(QLatin1String("*/"));
if (pos > -1) {
insertLine = block.blockNumber() + 2;
break;
}
block = block.next();
} while (block.isValid());
break;
} else if (trimmedText.startsWith(QLatin1String("//"))) {
block = block.next();
while (block.isValid()) {
if (!block.text().trimmed().startsWith(QLatin1String("//"))) {
insertLine = block.blockNumber() + 1;
break;
}
block = block.next();
}
break;
}
if (!trimmedText.isEmpty())
break;
block = block.next();
}
if (insertLine == 0) {
if (newLinesToAppend)
*newLinesToAppend += 1;
insertLine = 1;
} else {
if (newLinesToPrepend)
*newLinesToPrepend = 1;
}
return insertLine;
}
typedef QList<IncludeGroup> IncludeGroups; typedef QList<IncludeGroup> IncludeGroups;

View File

@@ -90,7 +90,7 @@ public:
enum IncludeStyle { LocalBeforeGlobal, GlobalBeforeLocal, AutoDetect }; enum IncludeStyle { LocalBeforeGlobal, GlobalBeforeLocal, AutoDetect };
LineForNewIncludeDirective(const QTextDocument *textDocument, LineForNewIncludeDirective(const QTextDocument *textDocument,
QList<Include> includes, const CPlusPlus::Document::Ptr cppDocument,
MocIncludeMode mocIncludeMode = IgnoreMocIncludes, MocIncludeMode mocIncludeMode = IgnoreMocIncludes,
IncludeStyle includeStyle = AutoDetect); IncludeStyle includeStyle = AutoDetect);
@@ -100,10 +100,13 @@ public:
unsigned *newLinesToAppend = 0); unsigned *newLinesToAppend = 0);
private: private:
int findInsertLineForVeryFirstInclude(unsigned *newLinesToPrepend, unsigned *newLinesToAppend);
QList<IncludeGroup> getGroupsByIncludeType(const QList<IncludeGroup> &groups, QList<IncludeGroup> getGroupsByIncludeType(const QList<IncludeGroup> &groups,
IncludeType includeType); IncludeType includeType);
const QTextDocument *m_textDocument; const QTextDocument *m_textDocument;
const CPlusPlus::Document::Ptr m_cppDocument;
IncludeStyle m_includeStyle; IncludeStyle m_includeStyle;
QList<Include> m_includes; QList<Include> m_includes;
}; };