Skip to content

Commit adf306b

Browse files
committed
Add clang-tidy plugin for modernize-nlohmann-json-explicit-conversions
Add a clang-tidy plugin containing one check to replace implicit conversions with explicit ones. 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 llvm/llvm-project#126425 . Unfortunately it is necessary to add "C" to the languages for the project in CMakeLists.txt for find_package to work for LLVM.
1 parent 8215dba commit adf306b

File tree

9 files changed

+859
-1
lines changed

9 files changed

+859
-1
lines changed

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.1...3.14)
44
## PROJECT
55
## name and version
66
##
7-
project(nlohmann_json VERSION 3.11.3 LANGUAGES CXX)
7+
project(nlohmann_json VERSION 3.11.3 LANGUAGES C CXX)
88

99
##
1010
## MAIN_PROJECT CHECK
@@ -48,6 +48,7 @@ option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value compar
4848
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
4949
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
5050
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
51+
option(JSON_ClangTidyPlugin "Build clang-tidy plug-in." OFF)
5152

5253
if (JSON_CI)
5354
include(ci)
@@ -158,6 +159,12 @@ CONFIGURE_FILE(
158159
@ONLY
159160
)
160161

162+
if (JSON_ClangTidyPlugin)
163+
find_package(LLVM REQUIRED CONFIG)
164+
find_package(Clang REQUIRED CONFIG)
165+
add_subdirectory(clang_tidy_plugin)
166+
endif()
167+
161168
##
162169
## TESTS
163170
## create and configure the unit test target

clang_tidy_plugin/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
add_library(NlohmannJsonClangTidyPlugin MODULE "")
2+
target_compile_options(NlohmannJsonClangTidyPlugin PRIVATE -fno-rtti)
3+
target_include_directories(NlohmannJsonClangTidyPlugin
4+
PRIVATE
5+
${CLANG_INCLUDE_DIRS}
6+
${LLVM_INCLUDE_DIRS}
7+
)
8+
target_sources(NlohmannJsonClangTidyPlugin PRIVATE
9+
${CMAKE_CURRENT_LIST_DIR}/Module.cpp
10+
${CMAKE_CURRENT_LIST_DIR}/ModernizeNlohmannJsonExplicitConversionsCheck.cpp
11+
)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-FileCopyrightText: 2025 Mike Crowe
2+
// SPDX-License-Identifier: MIT
3+
4+
#include "ModernizeNlohmannJsonExplicitConversionsCheck.h"
5+
#include "clang/ASTMatchers/ASTMatchFinder.h"
6+
#include "clang/Lex/Lexer.h"
7+
8+
using namespace clang::ast_matchers;
9+
10+
namespace clang::tidy::modernize {
11+
12+
void NlohmannJsonExplicitConversionsCheck::registerMatchers(
13+
MatchFinder *Finder) {
14+
auto Matcher =
15+
cxxMemberCallExpr(
16+
on(expr().bind("arg")),
17+
callee(cxxConversionDecl(ofClass(hasName("nlohmann::basic_json")))
18+
.bind("conversionDecl")))
19+
.bind("conversionCall");
20+
Finder->addMatcher(Matcher, this);
21+
}
22+
23+
void NlohmannJsonExplicitConversionsCheck::check(
24+
const MatchFinder::MatchResult &Result) {
25+
const auto *Decl =
26+
Result.Nodes.getNodeAs<CXXConversionDecl>("conversionDecl");
27+
const auto *Call =
28+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("conversionCall");
29+
const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
30+
31+
const QualType DestinationType = Decl->getConversionType();
32+
std::string DestinationTypeStr =
33+
DestinationType.getAsString(Result.Context->getPrintingPolicy());
34+
if (DestinationTypeStr == "std::basic_string<char>")
35+
DestinationTypeStr = "std::string";
36+
37+
const SourceRange ExprRange = Call->getSourceRange();
38+
if (!ExprRange.isValid())
39+
return;
40+
41+
bool Deref = false;
42+
if (const auto *Op = llvm::dyn_cast<UnaryOperator>(Arg);
43+
Op && Op->getOpcode() == UO_Deref)
44+
Deref = true;
45+
else if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(Arg);
46+
Op && Op->getOperator() == OO_Star)
47+
Deref = true;
48+
49+
llvm::StringRef SourceText = clang::Lexer::getSourceText(
50+
clang::CharSourceRange::getTokenRange(ExprRange), *Result.SourceManager,
51+
Result.Context->getLangOpts());
52+
53+
if (Deref)
54+
SourceText.consume_front("*");
55+
56+
const std::string ReplacementText =
57+
(llvm::Twine(SourceText) + (Deref ? "->" : ".") + "get<" +
58+
DestinationTypeStr + ">()")
59+
.str();
60+
diag(Call->getExprLoc(),
61+
"implicit nlohmann::json conversion to %0 should be explicit")
62+
<< DestinationTypeStr
63+
<< FixItHint::CreateReplacement(CharSourceRange::getTokenRange(ExprRange),
64+
ReplacementText);
65+
}
66+
67+
} // namespace clang::tidy::modernize
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-FileCopyrightText: 2025 Mike Crowe
2+
// SPDX-License-Identifier: MIT
3+
4+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H
5+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H
6+
7+
#include "clang-tidy/ClangTidyCheck.h"
8+
9+
namespace clang::tidy::modernize {
10+
11+
/// Convert implicit conversions via operator ValueType() from nlohmann::json to
12+
/// explicit calls to the get<> method because the next major version of the
13+
/// library will remove support for implicit conversions.
14+
///
15+
/// For the user-facing documentation see:
16+
/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/nlohmann-json-explicit-conversions.html
17+
class NlohmannJsonExplicitConversionsCheck : public ClangTidyCheck {
18+
public:
19+
NlohmannJsonExplicitConversionsCheck(StringRef Name,
20+
ClangTidyContext *Context)
21+
: ClangTidyCheck(Name, Context) {}
22+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
23+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
24+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
25+
return LangOpts.CPlusPlus;
26+
}
27+
};
28+
29+
} // namespace clang::tidy::modernize
30+
31+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H

clang_tidy_plugin/Module.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-FileCopyrightText: 2025 Mike Crowe
2+
// SPDX-License-Identifier: MIT
3+
4+
#include "clang-tidy/ClangTidyModule.h"
5+
#include "clang-tidy/ClangTidyModuleRegistry.h"
6+
#include "ModernizeNlohmannJsonExplicitConversionsCheck.h"
7+
8+
namespace {
9+
10+
using namespace clang::tidy;
11+
12+
class NlohmannJsonChecksModule : public ClangTidyModule
13+
{
14+
public:
15+
void addCheckFactories(ClangTidyCheckFactories& CheckFactories) override
16+
{
17+
CheckFactories.registerCheck<clang::tidy::modernize::NlohmannJsonExplicitConversionsCheck>(
18+
"modernize-nlohmann-json-explicit-conversions");
19+
}
20+
};
21+
22+
} // namespace
23+
24+
namespace clang::tidy {
25+
26+
// Register the module using this statically initialized variable.
27+
static ClangTidyModuleRegistry::Add<::NlohmannJsonChecksModule> nlohmannJsonChecksInit(
28+
"nlohmann-json-checks-module",
29+
"Adds 'modernize-nlohmann-json-explicit-conversions' check.");
30+
31+
} // namespace clang::tidy

tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ json_test_add_test_for(src/unit-comparison.cpp
168168

169169
add_subdirectory(abi)
170170

171+
if (JSON_ClangTidyPlugin)
172+
add_subdirectory(clang_tidy_plugin)
173+
endif()
174+
171175
#############################################################################
172176
# Test the generated build configs
173177
#############################################################################
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
include(CTest)
2+
3+
if(NOT LLVM_TOOLS_BINARY_DIR)
4+
message(FATAL_ERROR "Could not determine LLVM binary directory")
5+
endif()
6+
7+
add_test(NAME modernize-nlohmann-json-explicit-conversions-check COMMAND ${CMAKE_COMMAND} -E env PATH=${LLVM_TOOLS_BINARY_DIR}:$ENV{PATH}
8+
${CMAKE_CURRENT_LIST_DIR}/check_clang_tidy.py -std=c++17
9+
-plugin=$<TARGET_FILE:NlohmannJsonClangTidyPlugin>
10+
${CMAKE_CURRENT_LIST_DIR}/modernize-nlohmann-json-explicit-conversions.cpp modernize-nlohmann-json-explicit-conversions temp)

0 commit comments

Comments
 (0)