//===--- 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(*FullSourceLoc(Loc, SM).getCharacterData())) 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 = LastTokenLoc; // 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(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(*FullSourceLoc(Loc, SM).getCharacterData())) Loc = Loc.getLocWithOffset(1); if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) { // 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(forRangeStmt().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("for")) { checkStmt(Result, S->getBody(), S->getRParenLoc()); } else if (auto S = Result.Nodes.getNodeAs("for-range")) { checkStmt(Result, S->getBody(), S->getRParenLoc()); } else if (auto S = Result.Nodes.getNodeAs("do")) { checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); } else if (auto S = Result.Nodes.getNodeAs("while")) { SourceLocation StartLoc = findRParenLoc(S, SM, Context); if (StartLoc.isInvalid()) return; checkStmt(Result, S->getBody(), StartLoc); } else if (auto S = Result.Nodes.getNodeAs("if")) { SourceLocation StartLoc = findRParenLoc(S, SM, Context); if (StartLoc.isInvalid()) return; checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); const Stmt *Else = S->getElse(); if (Else && !isa(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 SourceLocation BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, const SourceManager &SM, const ASTContext *Context) { // Skip macros if (S->getLocStart().isMacroID()) return SourceLocation(); static const char *const ErrorMessage = "cannot find location of closing parenthesis ')'"; SourceLocation CondEndLoc = S->getCond()->getLocEnd(); if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) CondEndLoc = CondVar->getLocEnd(); assert(CondEndLoc.isValid()); SourceLocation PastCondEndLoc = Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts()); if (PastCondEndLoc.isInvalid()) { diag(CondEndLoc, ErrorMessage); return SourceLocation(); } SourceLocation RParenLoc = forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context); if (RParenLoc.isInvalid()) { diag(PastCondEndLoc, ErrorMessage); return SourceLocation(); } tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context); if (TokKind != tok::r_paren) { diag(RParenLoc, ErrorMessage); return SourceLocation(); } return RParenLoc; } void 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(S)) { // Already inside braces. return; } // Skip macros. if (S->getLocStart().isMacroID()) return; const SourceManager &SM = *Result.SourceManager; const ASTContext *Context = Result.Context; // InitialLoc points at the last token before opening brace to be inserted. assert(InitialLoc.isValid()); 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 { EndLoc = findEndLocation(S->getLocEnd(), 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) { unsigned StartLine = SM.getSpellingLineNumber(StartLoc); unsigned EndLine = SM.getSpellingLineNumber(EndLoc); if (EndLine - StartLine < ShortStatementLines) return; } auto Diag = diag(StartLoc, "statement should be inside braces"); Diag << FixItHint::CreateInsertion(StartLoc, " {") << FixItHint::CreateInsertion(EndLoc, ClosingInsertion); } } // namespace readability } // namespace tidy } // namespace clang