| //===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "BracesAroundStatementsCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Lex/Lexer.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace readability { |
| namespace { |
| |
| tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, |
| const ASTContext *Context) { |
| Token Tok; |
| SourceLocation Beginning = |
| Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts()); |
| const bool Invalid = |
| Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts()); |
| assert(!Invalid && "Expected a valid token."); |
| |
| if (Invalid) |
| return tok::NUM_TOKENS; |
| |
| return Tok.getKind(); |
| } |
| |
| SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, |
| const SourceManager &SM, |
| const ASTContext *Context) { |
| assert(Loc.isValid()); |
| for (;;) { |
| while (isWhitespace(*SM.getCharacterData(Loc))) |
| Loc = Loc.getLocWithOffset(1); |
| |
| tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); |
| if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment) |
| return Loc; |
| |
| // Fast-forward current token. |
| Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); |
| } |
| } |
| |
| SourceLocation findEndLocation(SourceLocation LastTokenLoc, |
| const SourceManager &SM, |
| const ASTContext *Context) { |
| SourceLocation Loc = |
| Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts()); |
| // Loc points to the beginning of the last (non-comment non-ws) token |
| // before end or ';'. |
| assert(Loc.isValid()); |
| bool SkipEndWhitespaceAndComments = true; |
| tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); |
| if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi || |
| TokKind == tok::r_brace) { |
| // If we are at ";" or "}", we found the last token. We could use as well |
| // `if (isa<NullStmt>(S))`, but it wouldn't work for nested statements. |
| SkipEndWhitespaceAndComments = false; |
| } |
| |
| Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); |
| // Loc points past the last token before end or after ';'. |
| if (SkipEndWhitespaceAndComments) { |
| Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context); |
| tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); |
| if (TokKind == tok::semi) |
| Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); |
| } |
| |
| for (;;) { |
| assert(Loc.isValid()); |
| while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) { |
| Loc = Loc.getLocWithOffset(1); |
| } |
| |
| if (isVerticalWhitespace(*SM.getCharacterData(Loc))) { |
| // EOL, insert brace before. |
| break; |
| } |
| tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); |
| if (TokKind != tok::comment) { |
| // Non-comment token, insert brace before. |
| break; |
| } |
| |
| SourceLocation TokEndLoc = |
| Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); |
| SourceRange TokRange(Loc, TokEndLoc); |
| StringRef Comment = Lexer::getSourceText( |
| CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts()); |
| if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) { |
| // Multi-line block comment, insert brace before. |
| break; |
| } |
| // else: Trailing comment, insert brace after the newline. |
| |
| // Fast-forward current token. |
| Loc = TokEndLoc; |
| } |
| return Loc; |
| } |
| |
| } // namespace |
| |
| BracesAroundStatementsCheck::BracesAroundStatementsCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| // Always add braces by default. |
| ShortStatementLines(Options.get("ShortStatementLines", 0U)) {} |
| |
| void BracesAroundStatementsCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "ShortStatementLines", ShortStatementLines); |
| } |
| |
| void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) { |
| Finder->addMatcher(ifStmt().bind("if"), this); |
| Finder->addMatcher(whileStmt().bind("while"), this); |
| Finder->addMatcher(doStmt().bind("do"), this); |
| Finder->addMatcher(forStmt().bind("for"), this); |
| Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this); |
| } |
| |
| void BracesAroundStatementsCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const SourceManager &SM = *Result.SourceManager; |
| const ASTContext *Context = Result.Context; |
| |
| // Get location of closing parenthesis or 'do' to insert opening brace. |
| if (auto S = Result.Nodes.getNodeAs<ForStmt>("for")) { |
| checkStmt(Result, S->getBody(), S->getRParenLoc()); |
| } else if (auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) { |
| checkStmt(Result, S->getBody(), S->getRParenLoc()); |
| } else if (auto S = Result.Nodes.getNodeAs<DoStmt>("do")) { |
| checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); |
| } else if (auto S = Result.Nodes.getNodeAs<WhileStmt>("while")) { |
| SourceLocation StartLoc = findRParenLoc(S, SM, Context); |
| if (StartLoc.isInvalid()) |
| return; |
| checkStmt(Result, S->getBody(), StartLoc); |
| } else if (auto S = Result.Nodes.getNodeAs<IfStmt>("if")) { |
| SourceLocation StartLoc = findRParenLoc(S, SM, Context); |
| if (StartLoc.isInvalid()) |
| return; |
| if (ForceBracesStmts.erase(S)) |
| ForceBracesStmts.insert(S->getThen()); |
| bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); |
| const Stmt *Else = S->getElse(); |
| if (Else && BracedIf) |
| ForceBracesStmts.insert(Else); |
| if (Else && !isa<IfStmt>(Else)) { |
| // Omit 'else if' statements here, they will be handled directly. |
| checkStmt(Result, Else, S->getElseLoc()); |
| } |
| } else { |
| llvm_unreachable("Invalid match"); |
| } |
| } |
| |
| /// Find location of right parenthesis closing condition. |
| template <typename IfOrWhileStmt> |
| SourceLocation |
| BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, |
| const SourceManager &SM, |
| const ASTContext *Context) { |
| // Skip macros. |
| if (S->getLocStart().isMacroID()) |
| return SourceLocation(); |
| |
| SourceLocation CondEndLoc = S->getCond()->getLocEnd(); |
| if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) |
| CondEndLoc = CondVar->getLocEnd(); |
| |
| if (!CondEndLoc.isValid()) { |
| return SourceLocation(); |
| } |
| |
| SourceLocation PastCondEndLoc = |
| Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts()); |
| if (PastCondEndLoc.isInvalid()) |
| return SourceLocation(); |
| SourceLocation RParenLoc = |
| forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context); |
| if (RParenLoc.isInvalid()) |
| return SourceLocation(); |
| tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context); |
| if (TokKind != tok::r_paren) |
| return SourceLocation(); |
| return RParenLoc; |
| } |
| |
| /// Determine if the statement needs braces around it, and add them if it does. |
| /// Returns true if braces where added. |
| bool BracesAroundStatementsCheck::checkStmt( |
| const MatchFinder::MatchResult &Result, const Stmt *S, |
| SourceLocation InitialLoc, SourceLocation EndLocHint) { |
| // 1) If there's a corresponding "else" or "while", the check inserts "} " |
| // right before that token. |
| // 2) If there's a multi-line block comment starting on the same line after |
| // the location we're inserting the closing brace at, or there's a non-comment |
| // token, the check inserts "\n}" right before that token. |
| // 3) Otherwise the check finds the end of line (possibly after some block or |
| // line comments) and inserts "\n}" right before that EOL. |
| if (!S || isa<CompoundStmt>(S)) { |
| // Already inside braces. |
| return false; |
| } |
| |
| if (!InitialLoc.isValid()) |
| return false; |
| const SourceManager &SM = *Result.SourceManager; |
| const ASTContext *Context = Result.Context; |
| |
| // Treat macros. |
| CharSourceRange FileRange = Lexer::makeFileCharRange( |
| CharSourceRange::getTokenRange(S->getSourceRange()), SM, |
| Context->getLangOpts()); |
| if (FileRange.isInvalid()) |
| return false; |
| |
| // Convert InitialLoc to file location, if it's on the same macro expansion |
| // level as the start of the statement. We also need file locations for |
| // Lexer::getLocForEndOfToken working properly. |
| InitialLoc = Lexer::makeFileCharRange( |
| CharSourceRange::getCharRange(InitialLoc, S->getLocStart()), |
| SM, Context->getLangOpts()) |
| .getBegin(); |
| if (InitialLoc.isInvalid()) |
| return false; |
| SourceLocation StartLoc = |
| Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts()); |
| |
| // StartLoc points at the location of the opening brace to be inserted. |
| SourceLocation EndLoc; |
| std::string ClosingInsertion; |
| if (EndLocHint.isValid()) { |
| EndLoc = EndLocHint; |
| ClosingInsertion = "} "; |
| } else { |
| const auto FREnd = FileRange.getEnd().getLocWithOffset(-1); |
| EndLoc = findEndLocation(FREnd, SM, Context); |
| ClosingInsertion = "\n}"; |
| } |
| |
| assert(StartLoc.isValid()); |
| assert(EndLoc.isValid()); |
| // Don't require braces for statements spanning less than certain number of |
| // lines. |
| if (ShortStatementLines && !ForceBracesStmts.erase(S)) { |
| unsigned StartLine = SM.getSpellingLineNumber(StartLoc); |
| unsigned EndLine = SM.getSpellingLineNumber(EndLoc); |
| if (EndLine - StartLine < ShortStatementLines) |
| return false; |
| } |
| |
| auto Diag = diag(StartLoc, "statement should be inside braces"); |
| Diag << FixItHint::CreateInsertion(StartLoc, " {") |
| << FixItHint::CreateInsertion(EndLoc, ClosingInsertion); |
| return true; |
| } |
| |
| void BracesAroundStatementsCheck::onEndOfTranslationUnit() { |
| ForceBracesStmts.clear(); |
| } |
| |
| } // namespace readability |
| } // namespace tidy |
| } // namespace clang |