Вопрос или проблема
Я хотел бы написать проверку для clang-tidy
, которая находит объявления полей с копированием инициализации (ICIS_CopyInit
) и может изменить их на прямую инициализацию списком (ICIS_ListInit
).
Существует способ точно найти объявления полей с копированием инициализации:
- найти
FieldDecl
- пропустить, если
!hasInClassInitializer()
- пропустить, если
ICIS_CopyInit != getInClassInitStyle()
Затем я могу получить объявление в виде строки согласно тому ответу: https://stackoverflow.com/a/61510968/2492801 и заменить знак =
на открывающую фигурную скобку, добавив закрывающую фигурную скобку в конце.
Но это выглядит неуклюже. Это было бы нормально для int test = 3;
. Но такой подход, например, превратит копированную инициализацию MyStruct m_data = MyStruct(1, 3);
в MyStruct m_data{MyStruct(1, 3)};
, что не лучше, так как это все равно позволяет узкие преобразования аргументов конструктора MyStruct
.
Вместо этого я хотел бы получить MyStruct m_data{1, 3};
. Является ли создание строки замены кода для FixItHint::CreateReplacement(...)
единственным, что я могу сделать, выполняя поиск и замену в строке объявления? Или я могу использовать методы clang AST для создания такой замены кода сам?
Ответ или решение
Создание проверки clang-tidy для преобразования инициализации копированием в инициализацию списком – это интересная задача, которую можно решить, используя AST (Abstract Syntax Tree) в Clang. Давайте рассмотрим, как можно реализовать такую проверку шаг за шагом.
1. Настройка clang-tidy
Первое, что вам нужно сделать, это создать новый clang-tidy проверочный инструмент. Для этого создайте новый класс, который наследует от ClangTidyCheck
.
#include "ClangTidyCheck.h"
#include "ClangTidyContext.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace clang::tidy;
class CopyInitToListInitCheck : public ClangTidyCheck {
public:
CopyInitToListInitCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(MatchFinder *Finder) override {
// Находим FieldDecl с инициализацией копированием
Finder->addMatcher(
fieldDecl(hasInClassInitializer(),
isEqualTo(ICIS_CopyInit)).bind("field"),
this);
}
void check(const MatchFinder::MatchResult &Result) override {
const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");
// Получаем информацию об инициализации
if (const auto *InitExpr = Field->getInClassInitializer()) {
if (const auto *ConstructExpr = dyn_cast<ConstructExpr>(InitExpr)) {
// Получаем аргументы конструктора
SmallVector<Expr *, 4> Args;
for (auto Arg : ConstructExpr->arguments()) {
Args.push_back(Arg);
}
// Создаем новый AST узел для Инициализации списком
auto *ListInit = new (Result.Context) InitListExpr(
Result.Context, SourceLocation(), Args, SourceLocation());
// Здесь необходимо создать замену
auto FixitHint = FixItHint::CreateReplacement(
Field->getSourceRange(),
printNewListInit(Field, ListInit));
// Выдаем предупреждение с возможностью исправления
diagnoseAndSuggest(Field, FixitHint);
}
}
}
private:
std::string printNewListInit(const FieldDecl *Field, InitListExpr *ListInit) {
// Конструируем строку из нового инициализатора
std::string FieldName = Field->getName().str();
std::string ArgsStr;
llvm::raw_string_ostream Stream(ArgsStr);
// Добавляем аргументы
for (size_t i = 0; i < ListInit->getNumInits(); ++i) {
if (i > 0)
Stream << ", ";
ListInit->getInit(i)->printPretty(Stream, nullptr, PrintingPolicy());
}
return FieldName + " {" + Stream.str() + "};";
}
void diagnoseAndSuggest(const FieldDecl *Field, const FixItHint &Hint) {
if (getLangOpts().CPlusPlus) {
diag(Field->getBeginLoc(), "замените инициализацию копированием на инициализацию списком")
<< Hint;
}
}
};
// Регистрация проверки
static RegisterCheck<CopyInitToListInitCheck>
X("copy-init-to-list-init", "Преобразует инициализацию копированием в инициализацию списком.");
2. Важные моменты
- Мы используем
hasInClassInitializer()
иisEqualTo(ICIS_CopyInit)
для поиска полей, у которых есть инициализатор копированием. - Мы захватываем инициализатор и извлекаем аргументы конструктора, а затем создаем новый
InitListExpr
с полученными аргументами. - Метод
printNewListInit
формирует строку для нового инициализатора списка и используетFixItHint::CreateReplacement
для создания замены. - Наконец, мы выдаем предупреждение и предлагаем исправление.
Заключение
Таким образом, мы создаем clang-tidy проверку, которая находит и преобразует поля, инициализируемые копированием, в инициализацию списком. Это решение использует возможности AST, чтобы избежать неаккуратных строковых манипуляций и гарантировать, что генерация кода аккуратная и отражает намерения программиста.