|
| 1 | +# Clang-Tidy |
| 2 | + |
| 3 | +The library comes with a [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) plugin. |
| 4 | +It is disabled by default but can be enabled by enabling the `JSON_ClangTidyPlugin` [CMake option](cmake.md#json_clangtidyplugin). |
| 5 | +Clang-tidy [Plugins](https://clang.llvm.org/extra/clang-tidy/ExternalClang-TidyExamples.html) are only supported by Clang 16 and later. |
| 6 | + |
| 7 | +## Building the plugin |
| 8 | + |
| 9 | +You will need to have the development files matching your version of clang-tidy installed to build the plugin. |
| 10 | +For example, if you are running on a Debian-derived Linux distribution: |
| 11 | + |
| 12 | +```sh |
| 13 | +apt install clang-tidy libclang-dev |
| 14 | +``` |
| 15 | +but if this installs a version that is older than Clang 16 then you might be able to specify a newer version. For example: |
| 16 | +```sh |
| 17 | +apt install clang-tidy-19 libclang-19-dev |
| 18 | +``` |
| 19 | + |
| 20 | +```sh |
| 21 | +mkdir build |
| 22 | +cd build |
| 23 | +cmake -DJSON_ClangTidyPlugin=ON .. |
| 24 | +cmake -build . |
| 25 | +``` |
| 26 | + |
| 27 | +# Running the plugin |
| 28 | +To tell clang-tidy to use the plugin you must pass a path to it as an argument to the `-load` option. |
| 29 | +For example, you can run clang-tidy with only the _modernize-nlohmann-json-explicit-conversion_ check using the plugin on a single file with: |
| 30 | +```sh |
| 31 | +clang-tidy -load .../path/to/build/clang_tidy_plugin/libNlohmannJsonClangTidyPlugin.so -checks='-*,modernize-nlohmann-json-explicit-conversions` -fix source.cpp |
| 32 | +clang-tidy |
| 33 | +``` |
| 34 | +or you can create a `.clang-tidy` file to enable the checks you require. |
| 35 | +
|
| 36 | +# Checks |
| 37 | +
|
| 38 | +At the moment the plugin contains only a single check. |
| 39 | +
|
| 40 | +## modernize-nlohmann-json-explicit-conversions |
| 41 | +
|
| 42 | +This check converts code that takes advantage of [implicit conversions](../api/macros/json_use_implicit_conversions.md) to use explicit `get()` calls using the correct templated type. |
| 43 | +For example, it turns: |
| 44 | +```c++ |
| 45 | +void f(const nlohmann::json &j1, const nlohmann::json &j2) |
| 46 | +{ |
| 47 | + int i = j1; |
| 48 | + double d = j2.at("value"); |
| 49 | + bool b = *j2.find("valid"); |
| 50 | + std::cout << i << " " << d << " " << b << "\n"; |
| 51 | +} |
| 52 | +``` |
| 53 | +into |
| 54 | +```c++ |
| 55 | +void f(const nlohmann::json &j1, const nlohmann::json &j2) |
| 56 | +{ |
| 57 | + int i = j1.get<int>(); |
| 58 | + double d = j2.at("value").get<double>(); |
| 59 | + bool b = j2.find("valid")->get<bool>(); |
| 60 | + std::cout << i << " " << d << " " << b << "\n"; |
| 61 | +} |
| 62 | +``` |
| 63 | +by knowing what the target type is for the implicit conversion and turning |
| 64 | +that into an explicit call to the `get()` method with that type as the |
| 65 | +template parameter. |
| 66 | +
|
| 67 | +Unfortunately the check does not work very well if the implicit conversion |
| 68 | +occurs in templated code or in a system header. For example, the following |
| 69 | +won't be fixed because the implicit conversion will happen inside |
| 70 | +`std::optional`'s constructor: |
| 71 | +```c++ |
| 72 | +void f(const nlohmann::json &j) |
| 73 | +{ |
| 74 | + std::optional<int> oi; |
| 75 | + const auto &it = j.find("value"); |
| 76 | + if (it != j.end()) |
| 77 | + oi = *it; |
| 78 | + // ... |
| 79 | +} |
| 80 | +``` |
| 81 | +After you have run this check you can set [JSON_USE_IMPLICIT_CONVERSIONS=0](../api/macros/json_use_implicit_conversions.md) to find any occurrences that the check have not been fixed automatically. |
| 82 | +
|
| 83 | +### Limitations |
| 84 | +
|
| 85 | +#### Typedefs may leak platform specifics |
| 86 | +
|
| 87 | +The check sees through `typedef` which can cause types that ought to vary by platform to become fixed by the check. This is particularly a problem when using the fixed-sized types from `<cstdint>` or other platform-specific types like `size_t` and `off_t`. For example, converting: |
| 88 | +```c++ |
| 89 | +uint64_t u64 = j.at("value"); |
| 90 | +``` |
| 91 | +in a 64-bit Linux environment will result in: |
| 92 | +```c++ |
| 93 | +uint64_t u64 = j.at("value").get<unsigned long>(); |
| 94 | +``` |
| 95 | +but in a 32-bit Linux environment will result in: |
| 96 | +```c++ |
| 97 | +uint64_t u64 = j.at("value").get<unsigned long long>(); |
| 98 | +``` |
| 99 | +
|
| 100 | +This could cause serious bugs. If you use such types in code you apply the check to it is recommended that you run on all your target platforms and compare the results. |
| 101 | +
|
| 102 | +Other more-benign uses of `typedef` types may also cause the resultant code to look uglier. |
| 103 | +
|
| 104 | +#### Redundant casts |
| 105 | +
|
| 106 | +Code using the library may use casts to force the correct type. Such code will be fixed but the unnecessary cast will remain, which could cause confusion and be brittle when the code is changed. For example, converting: |
| 107 | +```c++ |
| 108 | +const auto value = static_cast<int>(j["value"]) |
| 109 | +``` |
| 110 | +will yield: |
| 111 | +``` |
| 112 | +const auto value = static_cast<int>(j["value"].get<int>()) |
| 113 | +``` |
0 commit comments