xref: /llvm-project/libcxx/test/tools/clang_tidy_checks/uglify_attributes.cpp (revision c8eff9560fc1d2462a60bccb560a9ef87a4ba5bb)
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 "clang-tidy/ClangTidyCheck.h"
10 #include "clang-tidy/ClangTidyModuleRegistry.h"
11 
12 #include "uglify_attributes.hpp"
13 
14 #include <algorithm>
15 #include <string_view>
16 
17 namespace {
18 bool isUgly(std::string_view str) {
19   if (str.size() < 2)
20     return false;
21   if (str[0] == '_' && str[1] >= 'A' && str[1] <= 'Z')
22     return true;
23   return str.find("__") != std::string_view::npos;
24 }
25 
26 AST_MATCHER(clang::Attr, isPretty) {
27   if (Node.isKeywordAttribute())
28     return false;
29   if (Node.isCXX11Attribute() && !Node.hasScope()) // TODO: reject standard attributes that are version extensions
30     return false;
31   if (Node.hasScope())
32     if (!isUgly(Node.getScopeName()->getName()))
33       return true;
34 
35   if (Node.getAttrName())
36     return !isUgly(Node.getAttrName()->getName());
37 
38   return false;
39 }
40 
41 std::optional<std::string> getUglyfiedCXX11Attr(const clang::Attr& attr) {
42   // Don't try to fix attributes with `using` in them.
43   if (std::ranges::search(std::string_view(attr.getSpelling()), std::string_view("::")).empty())
44     return std::nullopt;
45 
46   std::string attr_string;
47   if (attr.isClangScope())
48     attr_string += "_Clang::";
49   else if (attr.isGNUScope())
50     attr_string += "__gnu__::";
51 
52   if (!attr.getAttrName()->getName().starts_with("__")) {
53     attr_string += "__";
54     attr_string += attr.getAttrName()->getName();
55     attr_string += "__";
56   } else {
57     attr_string += attr.getAttrName()->getName();
58   }
59   return std::move(attr_string);
60 }
61 
62 std::optional<std::string> getUglyfiedGNUAttr(const clang::Attr& attr) {
63   return "__" + attr.getAttrName()->getName().str() + "__";
64 }
65 
66 std::optional<std::string> getUglified(const clang::Attr& attr) {
67   if (attr.isCXX11Attribute()) {
68     return getUglyfiedCXX11Attr(attr);
69   } else if (attr.isGNUAttribute()) {
70     return getUglyfiedGNUAttr(attr);
71   }
72 
73   return std::nullopt;
74 }
75 } // namespace
76 
77 namespace libcpp {
78 uglify_attributes::uglify_attributes(llvm::StringRef name, clang::tidy::ClangTidyContext* context)
79     : clang::tidy::ClangTidyCheck(name, context) {}
80 
81 void uglify_attributes::registerMatchers(clang::ast_matchers::MatchFinder* finder) {
82   using namespace clang::ast_matchers;
83   finder->addMatcher(attr(isPretty()).bind("normal_attribute"), this);
84 }
85 
86 void uglify_attributes::check(const clang::ast_matchers::MatchFinder::MatchResult& result) {
87   if (const auto* call = result.Nodes.getNodeAs<clang::Attr>("normal_attribute"); call != nullptr) {
88     auto diagnostic = diag(call->getLoc(), "Non-standard attributes should use the _Ugly spelling");
89     auto uglified   = getUglified(*call);
90     if (uglified.has_value()) {
91       diagnostic << clang::FixItHint::CreateReplacement(call->getRange(), *uglified);
92     }
93   }
94 }
95 } // namespace libcpp
96