aboutsummaryrefslogtreecommitdiff
path: root/clang-tidy/readability
diff options
context:
space:
mode:
authorJonas Toth <jonas.toth@gmail.com>2018-10-31 16:50:44 +0000
committerJonas Toth <jonas.toth@gmail.com>2018-10-31 16:50:44 +0000
commit5e3a6032b25a92c2da1ca96f09aeaf9ce88d890b (patch)
tree5e7dc412865f737810033e3f7c1939d2553e8b19 /clang-tidy/readability
parent7319efb2073862a1bb46a3325106e28cfd48d0b3 (diff)
[clang-tidy] new check 'readability-isolate-declaration'
Summary: This patch introduces a new clang-tidy check that matches on all `declStmt` that declare more then one variable and transform them into one statement per declaration if possible. It currently only focusses on variable declarations but should be extended to cover more kinds of declarations in the future. It is related to https://reviews.llvm.org/D27621 and does use it's extensive test-suite. Thank you to firolino for his work! Reviewers: rsmith, aaron.ballman, alexfh, hokein, kbobyrev Reviewed By: aaron.ballman Subscribers: ZaMaZaN4iK, mgehre, nemanjai, kbarton, lebedev.ri, Eugene.Zelenko, mgorny, xazax.hun, cfe-commits Tags: #clang-tools-extra Differential Revision: https://reviews.llvm.org/D51949 git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@345735 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'clang-tidy/readability')
-rw-r--r--clang-tidy/readability/CMakeLists.txt1
-rw-r--r--clang-tidy/readability/IsolateDeclarationCheck.cpp279
-rw-r--r--clang-tidy/readability/IsolateDeclarationCheck.h36
-rw-r--r--clang-tidy/readability/ReadabilityTidyModule.cpp3
4 files changed, 319 insertions, 0 deletions
diff --git a/clang-tidy/readability/CMakeLists.txt b/clang-tidy/readability/CMakeLists.txt
index bd010af9..dace226a 100644
--- a/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tidy/readability/CMakeLists.txt
@@ -11,6 +11,7 @@ add_clang_library(clangTidyReadabilityModule
IdentifierNamingCheck.cpp
ImplicitBoolConversionCheck.cpp
InconsistentDeclarationParameterNameCheck.cpp
+ IsolateDeclarationCheck.cpp
MagicNumbersCheck.cpp
MisleadingIndentationCheck.cpp
MisplacedArrayIndexCheck.cpp
diff --git a/clang-tidy/readability/IsolateDeclarationCheck.cpp b/clang-tidy/readability/IsolateDeclarationCheck.cpp
new file mode 100644
index 00000000..3155be39
--- /dev/null
+++ b/clang-tidy/readability/IsolateDeclarationCheck.cpp
@@ -0,0 +1,279 @@
+//===--- IsolateDeclarationCheck.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 "IsolateDeclarationCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+using namespace clang::tidy::utils::lexer;
+
+namespace clang {
+namespace tidy {
+namespace readability {
+
+namespace {
+AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); }
+AST_MATCHER(DeclStmt, onlyDeclaresVariables) {
+ return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); });
+}
+} // namespace
+
+void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ declStmt(allOf(onlyDeclaresVariables(), unless(isSingleDecl()),
+ hasParent(compoundStmt())))
+ .bind("decl_stmt"),
+ this);
+}
+
+static SourceLocation findStartOfIndirection(SourceLocation Start,
+ int Indirections,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ assert(Indirections >= 0 && "Indirections must be non-negative");
+ if (Indirections == 0)
+ return Start;
+
+ // Note that the post-fix decrement is necessary to perform the correct
+ // number of transformations.
+ while (Indirections-- != 0) {
+ Start = findPreviousAnyTokenKind(Start, SM, LangOpts, tok::star, tok::amp);
+ if (Start.isInvalid() || Start.isMacroID())
+ return SourceLocation();
+ }
+ return Start;
+}
+
+static bool isMacroID(SourceRange R) {
+ return R.getBegin().isMacroID() || R.getEnd().isMacroID();
+}
+
+/// This function counts the number of written indirections for the given
+/// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing
+/// the source code.
+/// \see declRanges
+static int countIndirections(const Type *T, int Indirections = 0) {
+ if (T->isFunctionPointerType()) {
+ const auto *Pointee = T->getPointeeType()->castAs<FunctionType>();
+ return countIndirections(
+ Pointee->getReturnType().IgnoreParens().getTypePtr(), ++Indirections);
+ }
+
+ // Note: Do not increment the 'Indirections' because it is not yet clear
+ // if there is an indirection added in the source code of the array
+ // declaration.
+ if (const auto *AT = dyn_cast<ArrayType>(T))
+ return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(),
+ Indirections);
+
+ if (isa<PointerType>(T) || isa<ReferenceType>(T))
+ return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(),
+ ++Indirections);
+
+ return Indirections;
+}
+
+static bool typeIsMemberPointer(const Type *T) {
+ if (isa<ArrayType>(T))
+ return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual());
+
+ if ((isa<PointerType>(T) || isa<ReferenceType>(T)) &&
+ isa<PointerType>(T->getPointeeType()))
+ return typeIsMemberPointer(T->getPointeeType().getTypePtr());
+
+ return isa<MemberPointerType>(T);
+}
+
+/// This function tries to extract the SourceRanges that make up all
+/// declarations in this \c DeclStmt.
+///
+/// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}.
+/// Each \c SourceRange is of the form [Begin, End).
+/// If any of the create ranges is invalid or in a macro the result will be
+/// \c None.
+/// If the \c DeclStmt contains only one declaration, the result is \c None.
+/// If the \c DeclStmt contains declarations other than \c VarDecl the result
+/// is \c None.
+///
+/// \code
+/// int * ptr1 = nullptr, value = 42;
+/// // [ ][ ] [ ] - The ranges here are inclusive
+/// \endcode
+/// \todo Generalize this function to take other declarations than \c VarDecl.
+static Optional<std::vector<SourceRange>>
+declRanges(const DeclStmt *DS, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ std::size_t DeclCount = std::distance(DS->decl_begin(), DS->decl_end());
+ if (DeclCount < 2)
+ return None;
+
+ if (rangeContainsExpansionsOrDirectives(DS->getSourceRange(), SM, LangOpts))
+ return None;
+
+ // The initial type of the declaration and each declaration has it's own
+ // slice. This is necessary, because pointers and references bind only
+ // to the local variable and not to all variables in the declaration.
+ // Example: 'int *pointer, value = 42;'
+ std::vector<SourceRange> Slices;
+ Slices.reserve(DeclCount + 1);
+
+ // Calculate the first slice, for now only variables are handled but in the
+ // future this should be relaxed and support various kinds of declarations.
+ const auto *FirstDecl = dyn_cast<VarDecl>(*DS->decl_begin());
+
+ if (FirstDecl == nullptr)
+ return None;
+
+ // FIXME: Member pointers are not transformed correctly right now, that's
+ // why they are treated as problematic here.
+ if (typeIsMemberPointer(FirstDecl->getType().IgnoreParens().getTypePtr()))
+ return None;
+
+ // Consider the following case: 'int * pointer, value = 42;'
+ // Created slices (inclusive) [ ][ ] [ ]
+ // Because 'getBeginLoc' points to the start of the variable *name*, the
+ // location of the pointer must be determined separatly.
+ SourceLocation Start = findStartOfIndirection(
+ FirstDecl->getLocation(),
+ countIndirections(FirstDecl->getType().IgnoreParens().getTypePtr()), SM,
+ LangOpts);
+
+ // Fix function-pointer declarations that have a '(' in front of the
+ // pointer.
+ // Example: 'void (*f2)(int), (*g2)(int, float) = gg;'
+ // Slices: [ ][ ] [ ]
+ if (FirstDecl->getType()->isFunctionPointerType())
+ Start = findPreviousTokenKind(Start, SM, LangOpts, tok::l_paren);
+
+ // It is popssible that a declarator is wrapped with parens.
+ // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;'
+ // The slice for the type-part must not contain these parens. Consequently
+ // 'Start' is moved to the most left paren if there are parens.
+ while (true) {
+ if (Start.isInvalid() || Start.isMacroID())
+ break;
+
+ Token T = getPreviousToken(Start, SM, LangOpts);
+ if (T.is(tok::l_paren)) {
+ Start = findPreviousTokenStart(Start, SM, LangOpts);
+ continue;
+ }
+ break;
+ }
+
+ SourceRange DeclRange(DS->getBeginLoc(), Start);
+ if (DeclRange.isInvalid() || isMacroID(DeclRange))
+ return None;
+
+ // The first slice, that is prepended to every isolated declaration, is
+ // created.
+ Slices.emplace_back(DeclRange);
+
+ // Create all following slices that each declare a variable.
+ SourceLocation DeclBegin = Start;
+ for (const auto &Decl : DS->decls()) {
+ const auto *CurrentDecl = cast<VarDecl>(Decl);
+
+ // FIXME: Member pointers are not transformed correctly right now, that's
+ // why they are treated as problematic here.
+ if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr()))
+ return None;
+
+ SourceLocation DeclEnd =
+ CurrentDecl->hasInit()
+ ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM,
+ LangOpts)
+ : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts);
+
+ SourceRange VarNameRange(DeclBegin, DeclEnd);
+ if (VarNameRange.isInvalid() || isMacroID(VarNameRange))
+ return None;
+
+ Slices.emplace_back(VarNameRange);
+ DeclBegin = DeclEnd.getLocWithOffset(1);
+ }
+ return Slices;
+}
+
+static Optional<std::vector<StringRef>>
+collectSourceRanges(llvm::ArrayRef<SourceRange> Ranges, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ std::vector<StringRef> Snippets;
+ Snippets.reserve(Ranges.size());
+
+ for (const auto &Range : Ranges) {
+ CharSourceRange CharRange = Lexer::getAsCharRange(
+ CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), SM,
+ LangOpts);
+
+ if (CharRange.isInvalid())
+ return None;
+
+ bool InvalidText = false;
+ StringRef Snippet =
+ Lexer::getSourceText(CharRange, SM, LangOpts, &InvalidText);
+
+ if (InvalidText)
+ return None;
+
+ Snippets.emplace_back(Snippet);
+ }
+
+ return Snippets;
+}
+
+/// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
+static std::vector<std::string>
+createIsolatedDecls(llvm::ArrayRef<StringRef> Snippets) {
+ // The first section is the type snippet, which does not make a decl itself.
+ assert(Snippets.size() > 2 && "Not enough snippets to create isolated decls");
+ std::vector<std::string> Decls(Snippets.size() - 1);
+
+ for (std::size_t I = 1; I < Snippets.size(); ++I)
+ Decls[I - 1] = Twine(Snippets[0])
+ .concat(Snippets[0].endswith(" ") ? "" : " ")
+ .concat(Snippets[I].ltrim())
+ .concat(";")
+ .str();
+
+ return Decls;
+}
+
+void IsolateDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *WholeDecl = Result.Nodes.getNodeAs<DeclStmt>("decl_stmt");
+
+ auto Diag =
+ diag(WholeDecl->getBeginLoc(),
+ "multiple declarations in a single statement reduces readability");
+
+ Optional<std::vector<SourceRange>> PotentialRanges =
+ declRanges(WholeDecl, *Result.SourceManager, getLangOpts());
+ if (!PotentialRanges)
+ return;
+
+ Optional<std::vector<StringRef>> PotentialSnippets = collectSourceRanges(
+ *PotentialRanges, *Result.SourceManager, getLangOpts());
+
+ if (!PotentialSnippets)
+ return;
+
+ std::vector<std::string> NewDecls = createIsolatedDecls(*PotentialSnippets);
+ std::string Replacement = llvm::join(
+ NewDecls,
+ (Twine("\n") + Lexer::getIndentationForLine(WholeDecl->getBeginLoc(),
+ *Result.SourceManager))
+ .str());
+
+ Diag << FixItHint::CreateReplacement(WholeDecl->getSourceRange(),
+ Replacement);
+}
+} // namespace readability
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/readability/IsolateDeclarationCheck.h b/clang-tidy/readability/IsolateDeclarationCheck.h
new file mode 100644
index 00000000..b7f4793d
--- /dev/null
+++ b/clang-tidy/readability/IsolateDeclarationCheck.h
@@ -0,0 +1,36 @@
+//===--- IsolateDeclarationCheck.h - clang-tidy -----------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+namespace readability {
+
+/// This check diagnoses all DeclStmt's declaring more than one variable and
+/// tries to refactor the code to one statement per declaration.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/readability-isolate-declaration.html
+class IsolateDeclarationCheck : public ClangTidyCheck {
+public:
+ IsolateDeclarationCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace readability
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H
diff --git a/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tidy/readability/ReadabilityTidyModule.cpp
index ca49f6f1..5855bb07 100644
--- a/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -20,6 +20,7 @@
#include "IdentifierNamingCheck.h"
#include "ImplicitBoolConversionCheck.h"
#include "InconsistentDeclarationParameterNameCheck.h"
+#include "IsolateDeclarationCheck.h"
#include "MagicNumbersCheck.h"
#include "MisleadingIndentationCheck.h"
#include "MisplacedArrayIndexCheck.h"
@@ -67,6 +68,8 @@ public:
"readability-implicit-bool-conversion");
CheckFactories.registerCheck<InconsistentDeclarationParameterNameCheck>(
"readability-inconsistent-declaration-parameter-name");
+ CheckFactories.registerCheck<IsolateDeclarationCheck>(
+ "readability-isolate-declaration");
CheckFactories.registerCheck<MagicNumbersCheck>(
"readability-magic-numbers");
CheckFactories.registerCheck<MisleadingIndentationCheck>(