1 | //===--- AvoidEndlCheck.cpp - clang-tidy ----------------------------------===// |
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 "AvoidEndlCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/DeclCXX.h" |
12 | #include "clang/AST/Expr.h" |
13 | #include "clang/AST/ExprCXX.h" |
14 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
15 | #include "clang/ASTMatchers/ASTMatchers.h" |
16 | #include "clang/Lex/Lexer.h" |
17 | |
18 | using namespace clang::ast_matchers; |
19 | |
20 | namespace clang::tidy::performance { |
21 | |
22 | void AvoidEndlCheck::registerMatchers(MatchFinder *Finder) { |
23 | Finder->addMatcher( |
24 | NodeMatch: callExpr( |
25 | unless(isExpansionInSystemHeader()), |
26 | anyOf(cxxOperatorCallExpr( |
27 | hasOverloadedOperatorName(Name: "<<" ), |
28 | hasRHS(InnerMatcher: declRefExpr(to(InnerMatcher: namedDecl(hasName(Name: "::std::endl" )))) |
29 | .bind(ID: "expr" ))), |
30 | callExpr(argumentCountIs(N: 1), |
31 | callee(InnerMatcher: functionDecl(hasName(Name: "::std::endl" )))) |
32 | .bind(ID: "expr" ))), |
33 | Action: this); |
34 | } |
35 | |
36 | void AvoidEndlCheck::check(const MatchFinder::MatchResult &Result) { |
37 | const auto *Expression = Result.Nodes.getNodeAs<Expr>(ID: "expr" ); |
38 | assert(Expression); |
39 | assert(isa<DeclRefExpr>(Expression) || isa<CallExpr>(Expression)); |
40 | |
41 | // FIXME: It would be great if we could transform |
42 | // 'std::cout << "Hi" << std::endl;' into |
43 | // 'std::cout << "Hi\n"'; |
44 | |
45 | if (llvm::isa<DeclRefExpr>(Val: Expression)) { |
46 | // Handle the more common streaming '... << std::endl' case |
47 | const CharSourceRange TokenRange = |
48 | CharSourceRange::getTokenRange(Expression->getSourceRange()); |
49 | const StringRef SourceText = Lexer::getSourceText( |
50 | Range: TokenRange, SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts()); |
51 | |
52 | auto Diag = diag(Expression->getBeginLoc(), |
53 | "do not use '%0' with streams; use '\\n' instead" ) |
54 | << SourceText; |
55 | |
56 | Diag << FixItHint::CreateReplacement(RemoveRange: TokenRange, Code: "'\\n'" ); |
57 | } else { |
58 | // Handle the less common function call 'std::endl(...)' case |
59 | const auto *CallExpression = llvm::cast<CallExpr>(Val: Expression); |
60 | assert(CallExpression->getNumArgs() == 1); |
61 | |
62 | const StringRef SourceText = Lexer::getSourceText( |
63 | Range: CharSourceRange::getTokenRange( |
64 | CallExpression->getCallee()->getSourceRange()), |
65 | SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts()); |
66 | |
67 | const CharSourceRange ArgTokenRange = CharSourceRange::getTokenRange( |
68 | CallExpression->getArg(Arg: 0)->getSourceRange()); |
69 | const StringRef ArgSourceText = Lexer::getSourceText( |
70 | Range: ArgTokenRange, SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts()); |
71 | |
72 | const std::string ReplacementString = |
73 | std::string(ArgSourceText) + " << '\\n'" ; |
74 | |
75 | diag(Loc: CallExpression->getBeginLoc(), |
76 | Description: "do not use '%0' with streams; use '\\n' instead" ) |
77 | << SourceText |
78 | << FixItHint::CreateReplacement( |
79 | CharSourceRange::getTokenRange(CallExpression->getSourceRange()), |
80 | ReplacementString); |
81 | } |
82 | } |
83 | |
84 | } // namespace clang::tidy::performance |
85 | |