1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "proper_version_checks.hpp" |
10 | |
11 | #include <clang/Lex/Lexer.h> |
12 | #include <clang/Lex/PPCallbacks.h> |
13 | #include <clang/Lex/Preprocessor.h> |
14 | |
15 | namespace libcpp { |
16 | namespace { |
17 | class proper_version_checks_callbacks : public clang::PPCallbacks { |
18 | public: |
19 | proper_version_checks_callbacks(clang::Preprocessor& preprocessor, clang::tidy::ClangTidyCheck& check) |
20 | : preprocessor_(preprocessor), check_(check) {} |
21 | |
22 | void If(clang::SourceLocation location, clang::SourceRange condition_range, ConditionValueKind) override { |
23 | check_condition(location, condition_range); |
24 | } |
25 | |
26 | void Elif(clang::SourceLocation location, |
27 | clang::SourceRange condition_range, |
28 | ConditionValueKind, |
29 | clang::SourceLocation if_location) override { |
30 | check_condition(location, condition_range); |
31 | } |
32 | |
33 | private: |
34 | void check_condition(clang::SourceLocation location, clang::SourceRange condition_range) { |
35 | std::string_view condition = clang::Lexer::getSourceText( |
36 | Range: clang::CharSourceRange::getTokenRange(R: condition_range), |
37 | SM: preprocessor_.getSourceManager(), |
38 | LangOpts: preprocessor_.getLangOpts()); |
39 | if (preprocessor_.getSourceManager().isInMainFile(Loc: location)) |
40 | return; |
41 | |
42 | if (condition.starts_with("_LIBCPP_STD_VER" ) && condition.find(">" ) != std::string_view::npos && |
43 | condition.find(">=" ) == std::string_view::npos) |
44 | check_.diag(location, "_LIBCPP_STD_VER >= version should be used instead of _LIBCPP_STD_VER > prev_version" ); |
45 | |
46 | else if (condition.starts_with("__cplusplus" )) |
47 | check_.diag(location, "Use _LIBCPP_STD_VER instead of __cplusplus to constrain based on the C++ version" ); |
48 | |
49 | else if (condition == "_LIBCPP_STD_VER >= 11" ) |
50 | check_.diag(location, "_LIBCPP_STD_VER >= 11 is always true. Did you mean '#ifndef _LIBCPP_CXX03_LANG'?" ); |
51 | |
52 | else if (condition.starts_with("_LIBCPP_STD_VER >= " ) && |
53 | std::ranges::none_of(std::array{"14" , "17" , "20" , "23" , "26" }, [&](auto val) { |
54 | return condition.find(val) != std::string_view::npos; |
55 | })) |
56 | check_.diag(location, "Not a valid value for _LIBCPP_STD_VER. Use 14, 17, 20, 23, or 26" ); |
57 | } |
58 | |
59 | clang::Preprocessor& preprocessor_; |
60 | clang::tidy::ClangTidyCheck& check_; |
61 | }; |
62 | } // namespace |
63 | |
64 | proper_version_checks::proper_version_checks(llvm::StringRef name, clang::tidy::ClangTidyContext* context) |
65 | : clang::tidy::ClangTidyCheck(name, context) {} |
66 | |
67 | void proper_version_checks::registerPPCallbacks( |
68 | const clang::SourceManager& source_manager, |
69 | clang::Preprocessor* preprocessor, |
70 | clang::Preprocessor* module_expander) { |
71 | preprocessor->addPPCallbacks(std::make_unique<proper_version_checks_callbacks>(args&: *preprocessor, args&: *this)); |
72 | } |
73 | |
74 | } // namespace libcpp |
75 | |