Add a clang-tidy plugin containing one check to replace implicit conversions (as enabled by default with JSON_USE_IMPLICIT_CONVERSIONS) with explicit ones. This will make it easier for library users to switch away from using implicit conversions which should make it possible for the library to start disallowing them sooner. Being able to test the plugin in a similar way to how checks are tested within clang-tidy itself requires copying the check_clang_tidy.py script from the LLVM code itself. The check itself is virtually identical to the one proposed for inclusion in clang-tidy itself at https://github.com/llvm/llvm-project/pull/126425 . Unfortunately it is necessary to add "C" to the languages for the project in CMakeLists.txt for find_package to work for LLVM. Signed-off-by: Mike Crowe <mac@mcrowe.com>
68 lines
2.2 KiB
C++
68 lines
2.2 KiB
C++
// SPDX-FileCopyrightText: 2025 Mike Crowe
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "ModernizeNlohmannJsonExplicitConversionsCheck.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::modernize {
|
|
|
|
void NlohmannJsonExplicitConversionsCheck::registerMatchers(
|
|
MatchFinder *Finder) {
|
|
auto Matcher =
|
|
cxxMemberCallExpr(
|
|
on(expr().bind("arg")),
|
|
callee(cxxConversionDecl(ofClass(hasName("nlohmann::basic_json")))
|
|
.bind("conversionDecl")))
|
|
.bind("conversionCall");
|
|
Finder->addMatcher(Matcher, this);
|
|
}
|
|
|
|
void NlohmannJsonExplicitConversionsCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *Decl =
|
|
Result.Nodes.getNodeAs<CXXConversionDecl>("conversionDecl");
|
|
const auto *Call =
|
|
Result.Nodes.getNodeAs<CXXMemberCallExpr>("conversionCall");
|
|
const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
|
|
|
|
const QualType DestinationType = Decl->getConversionType();
|
|
std::string DestinationTypeStr =
|
|
DestinationType.getAsString(Result.Context->getPrintingPolicy());
|
|
if (DestinationTypeStr == "std::basic_string<char>")
|
|
DestinationTypeStr = "std::string";
|
|
|
|
const SourceRange ExprRange = Call->getSourceRange();
|
|
if (!ExprRange.isValid())
|
|
return;
|
|
|
|
bool Deref = false;
|
|
if (const auto *Op = llvm::dyn_cast<UnaryOperator>(Arg);
|
|
Op && Op->getOpcode() == UO_Deref)
|
|
Deref = true;
|
|
else if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(Arg);
|
|
Op && Op->getOperator() == OO_Star)
|
|
Deref = true;
|
|
|
|
llvm::StringRef SourceText = clang::Lexer::getSourceText(
|
|
clang::CharSourceRange::getTokenRange(ExprRange), *Result.SourceManager,
|
|
Result.Context->getLangOpts());
|
|
|
|
if (Deref)
|
|
SourceText.consume_front("*");
|
|
|
|
const std::string ReplacementText =
|
|
(llvm::Twine(SourceText) + (Deref ? "->" : ".") + "get<" +
|
|
DestinationTypeStr + ">()")
|
|
.str();
|
|
diag(Call->getExprLoc(),
|
|
"implicit nlohmann::json conversion to %0 should be explicit")
|
|
<< DestinationTypeStr
|
|
<< FixItHint::CreateReplacement(CharSourceRange::getTokenRange(ExprRange),
|
|
ReplacementText);
|
|
}
|
|
|
|
} // namespace clang::tidy::modernize
|