CppEditor: Consider #pragma once when inserting includes

Fixes: QTCREATORBUG-30808
Change-Id: Ib9f2ed1e428abfaa608b9dc42bc09dd2d403ee56
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-06-05 17:32:36 +02:00
parent d49934604b
commit ccae4fc93c
13 changed files with 128 additions and 15 deletions

View File

@@ -356,6 +356,15 @@ void Document::addUndefinedMacroUse(const QByteArray &name,
_undefinedMacroUses.append(use); _undefinedMacroUses.append(use);
} }
int Document::pragmaOnceLine() const
{
for (const Pragma &p : _pragmas) {
if (p.tokens.size() == 1 && p.tokens.first() == "once")
return p.line;
}
return -1;
}
/*! /*!
\class Document::MacroUse \class Document::MacroUse
\brief The MacroUse class represents the usage of a macro in a \brief The MacroUse class represents the usage of a macro in a

View File

@@ -11,15 +11,14 @@
#include <utils/filepath.h> #include <utils/filepath.h>
#include <QSharedPointer>
#include <QDateTime>
#include <QHash>
#include <QFuture>
#include <QAtomicInt> #include <QAtomicInt>
#include <QByteArrayList>
#include <QDateTime>
#include <QFuture>
#include <QHash>
#include <QSharedPointer>
namespace CPlusPlus { namespace CPlusPlus {
class Macro;
class MacroArgumentReference; class MacroArgumentReference;
class LookupContext; class LookupContext;
@@ -55,6 +54,9 @@ public:
void addUndefinedMacroUse(const QByteArray &name, void addUndefinedMacroUse(const QByteArray &name,
int bytesOffset, int utf16charsOffset); int bytesOffset, int utf16charsOffset);
void appendPragma(const Pragma &pragma) { _pragmas << pragma; }
int pragmaOnceLine() const;
Control *control() const { return _control; } Control *control() const { return _control; }
Control *swapControl(Control *newControl); Control *swapControl(Control *newControl);
TranslationUnit *translationUnit() const { return _translationUnit; } TranslationUnit *translationUnit() const { return _translationUnit; }
@@ -353,6 +355,8 @@ private:
/// the macro name of the include guard, if there is one. /// the macro name of the include guard, if there is one.
QByteArray _includeGuardMacroName; QByteArray _includeGuardMacroName;
QList<Pragma> _pragmas;
QByteArray m_fingerprint; QByteArray m_fingerprint;
QByteArray _source; QByteArray _source;

View File

@@ -85,6 +85,12 @@ void FastPreprocessor::macroAdded(const Macro &macro)
_currentDoc->appendMacro(macro); _currentDoc->appendMacro(macro);
} }
void FastPreprocessor::pragmaAdded(const Pragma &pragma)
{
Q_ASSERT(_currentDoc);
_currentDoc->appendPragma(pragma);
}
static const Macro revision(const Snapshot &s, const Macro &m) static const Macro revision(const Snapshot &s, const Macro &m)
{ {
if (Document::Ptr d = s.document(m.filePath())) { if (Document::Ptr d = s.document(m.filePath())) {

View File

@@ -40,7 +40,7 @@ public:
const Utils::FilePaths &initialIncludes = {}); const Utils::FilePaths &initialIncludes = {});
virtual void macroAdded(const Macro &); virtual void macroAdded(const Macro &);
virtual void pragmaAdded(const Pragma &pragma);
virtual void passedMacroDefinitionCheck(int, int, int, const Macro &); virtual void passedMacroDefinitionCheck(int, int, int, const Macro &);
virtual void failedMacroDefinitionCheck(int, int, const ByteArrayRef &); virtual void failedMacroDefinitionCheck(int, int, const ByteArrayRef &);

View File

@@ -157,4 +157,11 @@ private:
}; };
}; };
class CPLUSPLUS_EXPORT Pragma
{
public:
QByteArrayList tokens;
int line;
};
} // CPlusPlus } // CPlusPlus

View File

@@ -14,6 +14,7 @@ namespace CPlusPlus {
class ByteArrayRef; class ByteArrayRef;
class Macro; class Macro;
class Pragma;
class CPLUSPLUS_EXPORT MacroArgumentReference class CPLUSPLUS_EXPORT MacroArgumentReference
{ {
@@ -61,6 +62,7 @@ public:
virtual ~Client() = 0; virtual ~Client() = 0;
virtual void macroAdded(const Macro &macro) = 0; virtual void macroAdded(const Macro &macro) = 0;
virtual void pragmaAdded(const Pragma &pragma) = 0;
virtual void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset, virtual void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const Macro &macro) = 0; int line, const Macro &macro) = 0;

View File

@@ -1626,10 +1626,10 @@ void Preprocessor::handlePreprocessorDirective(PPToken *tk)
static const QByteArray ppInclude("include"); static const QByteArray ppInclude("include");
static const QByteArray ppIncludeNext("include_next"); static const QByteArray ppIncludeNext("include_next");
static const QByteArray ppImport("import"); static const QByteArray ppImport("import");
static const QByteArray ppPragma("pragma");
//### TODO: //### TODO:
// line // line
// error // error
// pragma
if (tk->is(T_IDENTIFIER)) { if (tk->is(T_IDENTIFIER)) {
const ByteArrayRef directive = tk->asByteArrayRef(); const ByteArrayRef directive = tk->asByteArrayRef();
@@ -1640,6 +1640,8 @@ void Preprocessor::handlePreprocessorDirective(PPToken *tk)
handleIfDefDirective(true, tk); handleIfDefDirective(true, tk);
} else if (directive == ppEndIf) { } else if (directive == ppEndIf) {
handleEndIfDirective(tk, poundToken); handleEndIfDirective(tk, poundToken);
} else if (directive == ppPragma) {
handlePragmaDirective(tk);
} else { } else {
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken); m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken);
@@ -1866,6 +1868,23 @@ void Preprocessor::handleDefineDirective(PPToken *tk)
m_client->macroAdded(macro); m_client->macroAdded(macro);
} }
void Preprocessor::handlePragmaDirective(PPToken *tk)
{
Pragma pragma;
pragma.line = tk->lineno;
lex(tk); // consume "pragma" token
while (isContinuationToken(*tk)) {
if (!consumeComments(tk))
return;
pragma.tokens << tk->asByteArrayRef().toByteArray();
lex(tk);
}
if (m_client)
m_client->pragmaAdded(pragma);
}
QByteArray Preprocessor::expand(PPToken *tk, PPToken *lastConditionToken) QByteArray Preprocessor::expand(PPToken *tk, PPToken *lastConditionToken)
{ {
unsigned line = tk->lineno; unsigned line = tk->lineno;

View File

@@ -210,6 +210,7 @@ private:
void handlePreprocessorDirective(PPToken *tk); void handlePreprocessorDirective(PPToken *tk);
void handleIncludeDirective(PPToken *tk, bool includeNext); void handleIncludeDirective(PPToken *tk, bool includeNext);
void handleDefineDirective(PPToken *tk); void handleDefineDirective(PPToken *tk);
void handlePragmaDirective(PPToken *tk);
QByteArray expand(PPToken *tk, PPToken *lastConditionToken = nullptr); QByteArray expand(PPToken *tk, PPToken *lastConditionToken = nullptr);
const Internal::PPToken evalExpression(PPToken *tk, Value &result); const Internal::PPToken evalExpression(PPToken *tk, Value &result);
void handleIfDirective(PPToken *tk); void handleIfDirective(PPToken *tk);

View File

@@ -298,6 +298,13 @@ void CppSourceProcessor::macroAdded(const CPlusPlus::Macro &macro)
m_currentDoc->appendMacro(macro); m_currentDoc->appendMacro(macro);
} }
void CppSourceProcessor::pragmaAdded(const CPlusPlus::Pragma &pragma)
{
if (!m_currentDoc)
return;
m_currentDoc->appendPragma(pragma);
}
void CppSourceProcessor::passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset, void CppSourceProcessor::passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const CPlusPlus::Macro &macro) int line, const CPlusPlus::Macro &macro)
{ {

View File

@@ -68,6 +68,7 @@ private:
// Client interface // Client interface
void macroAdded(const CPlusPlus::Macro &macro) override; void macroAdded(const CPlusPlus::Macro &macro) override;
void pragmaAdded(const CPlusPlus::Pragma &pragma) override;
void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset, void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const CPlusPlus::Macro &macro) override; int line, const CPlusPlus::Macro &macro) override;
void failedMacroDefinitionCheck(int bytesOffset, int utf16charOffset, void failedMacroDefinitionCheck(int bytesOffset, int utf16charOffset,

View File

@@ -216,15 +216,22 @@ int LineForNewIncludeDirective::findInsertLineForVeryFirstInclude(unsigned *newL
{ {
int insertLine = 1; int insertLine = 1;
// If there is an include guard, insert right after that one const auto appendAndPrependNewline = [&] {
const QByteArray includeGuardMacroName = m_cppDocument->includeGuardMacroName(); if (newLinesToPrepend)
if (!includeGuardMacroName.isEmpty()) { *newLinesToPrepend = 1;
if (newLinesToAppend)
*newLinesToAppend += 1;
};
// If there is an include guard or a "#pragma once", insert right after that one
if (const int pragmaOnceLine = m_cppDocument->pragmaOnceLine(); pragmaOnceLine != -1) {
appendAndPrependNewline();
insertLine = pragmaOnceLine + 1;
} else if (const QByteArray includeGuardMacroName = m_cppDocument->includeGuardMacroName();
!includeGuardMacroName.isEmpty()) {
for (const Macro &definedMacro : m_cppDocument->definedMacros()) { for (const Macro &definedMacro : m_cppDocument->definedMacros()) {
if (definedMacro.name() == includeGuardMacroName) { if (definedMacro.name() == includeGuardMacroName) {
if (newLinesToPrepend) appendAndPrependNewline();
*newLinesToPrepend = 1;
if (newLinesToAppend)
*newLinesToAppend += 1;
insertLine = definedMacro.line() + 1; insertLine = definedMacro.line() + 1;
} }
} }

View File

@@ -1145,6 +1145,25 @@ private slots:
// ------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------
original =
"#pragma once\n"
"void @f();\n"
;
expected =
"#pragma once\n"
"\n"
"#include \"file.h\"\n"
"\n"
"void f();\n"
;
testDocuments << CppTestDocument::create("file.cpp", original, expected);
QTest::newRow("inserting_onlyPragmaOnce")
<< TestIncludePaths::globalIncludePath()
<< testDocuments << firstRefactoringOperation << "\"file.h\"";
testDocuments.clear();
// -------------------------------------------------------------------------------------------
original = original =
"\n" "\n"
"// comment\n" "// comment\n"

View File

@@ -108,6 +108,11 @@ public:
m_definedMacrosLine.append(macro.line()); m_definedMacrosLine.append(macro.line());
} }
void pragmaAdded(const Pragma &pragma) override
{
m_pragmas.append(pragma);
}
void passedMacroDefinitionCheck(int /*bytesOffset*/, void passedMacroDefinitionCheck(int /*bytesOffset*/,
int /*utf16charsOffset*/, int /*utf16charsOffset*/,
int line, int line,
@@ -264,6 +269,8 @@ public:
const QMap<QByteArray, QVector<MacroArgumentReference >> usedMacros() const const QMap<QByteArray, QVector<MacroArgumentReference >> usedMacros() const
{ return m_usedMacros; } { return m_usedMacros; }
const QList<Pragma> &pragmas() const { return m_pragmas; }
private: private:
Environment *m_env; Environment *m_env;
QByteArray *m_output; QByteArray *m_output;
@@ -282,6 +289,7 @@ private:
QSet<QByteArray> m_unresolvedDefines; QSet<QByteArray> m_unresolvedDefines;
QList<int> m_macroArgsCount; QList<int> m_macroArgsCount;
QMap<QByteArray, QVector<MacroArgumentReference >> m_usedMacros; QMap<QByteArray, QVector<MacroArgumentReference >> m_usedMacros;
QList<Pragma> m_pragmas;
}; };
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -381,6 +389,7 @@ private slots:
void trigraph(); void trigraph();
void nested_arguments_expansion(); void nested_arguments_expansion();
void preprocessorSymbolsAsMacroArguments(); void preprocessorSymbolsAsMacroArguments();
void pragmas();
}; };
// Remove all #... lines, and 'simplify' string, to allow easily comparing the result // Remove all #... lines, and 'simplify' string, to allow easily comparing the result
@@ -2090,6 +2099,28 @@ void tst_Preprocessor::preprocessorSymbolsAsMacroArguments()
QVERIFY(preprocess.run(QLatin1String("<stdin>"), input).startsWith("# 1 \"<stdin>\"\n")); QVERIFY(preprocess.run(QLatin1String("<stdin>"), input).startsWith("# 1 \"<stdin>\"\n"));
} }
void tst_Preprocessor::pragmas()
{
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray input =
"#pragma once\n"
"#include <iostream>\n"
"#pragma pack(/*distraction*/push)\n"
"struct S { bool b1; int i; short s; bool b2; };\n"
"#pragma pack(pop)\n";
preprocess.run(QLatin1String("<stdin>"), input);
QCOMPARE(client.pragmas().size(), 3);
QCOMPARE(client.pragmas().at(0).line, 1);
QCOMPARE(client.pragmas().at(0).tokens, QByteArrayList{"once"});
QCOMPARE(client.pragmas().at(1).line, 3);
QCOMPARE(client.pragmas().at(1).tokens, (QByteArrayList{"pack", "(", "push", ")"}));
QCOMPARE(client.pragmas().at(2).line, 5);
QCOMPARE(client.pragmas().at(2).tokens, (QByteArrayList{"pack", "(", "pop", ")"}));
}
void tst_Preprocessor::excessive_nesting() void tst_Preprocessor::excessive_nesting()
{ {
Environment env; Environment env;