1 //===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "RedundantStringInitCheck.h"
10 #include "../utils/Matchers.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
14 using namespace clang::ast_matchers
;
15 using namespace clang::tidy::matchers
;
19 namespace readability
{
21 const char DefaultStringNames
[] =
22 "::std::basic_string_view;::std::basic_string";
24 static std::vector
<StringRef
> removeNamespaces(ArrayRef
<StringRef
> Names
) {
25 std::vector
<StringRef
> Result
;
26 Result
.reserve(Names
.size());
27 for (StringRef Name
: Names
) {
28 StringRef::size_type ColonPos
= Name
.rfind(':');
30 Name
.drop_front(ColonPos
== StringRef::npos
? 0 : ColonPos
+ 1));
35 static const CXXConstructExpr
*
36 getConstructExpr(const CXXCtorInitializer
&CtorInit
) {
37 const Expr
*InitExpr
= CtorInit
.getInit();
38 if (const auto *CleanUpExpr
= dyn_cast
<ExprWithCleanups
>(InitExpr
))
39 InitExpr
= CleanUpExpr
->getSubExpr();
40 return dyn_cast
<CXXConstructExpr
>(InitExpr
);
43 static llvm::Optional
<SourceRange
>
44 getConstructExprArgRange(const CXXConstructExpr
&Construct
) {
46 for (const Expr
*Arg
: Construct
.arguments()) {
48 B
= Arg
->getBeginLoc();
49 if (Arg
->getEndLoc().isValid())
52 if (B
.isInvalid() || E
.isInvalid())
54 return SourceRange(B
, E
);
57 RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name
,
58 ClangTidyContext
*Context
)
59 : ClangTidyCheck(Name
, Context
),
60 StringNames(utils::options::parseStringList(
61 Options
.get("StringNames", DefaultStringNames
))) {}
63 void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
64 Options
.store(Opts
, "StringNames", DefaultStringNames
);
67 void RedundantStringInitCheck::registerMatchers(MatchFinder
*Finder
) {
68 const auto HasStringTypeName
= hasAnyName(StringNames
);
69 const auto HasStringCtorName
= hasAnyName(removeNamespaces(StringNames
));
71 // Match string constructor.
72 const auto StringConstructorExpr
= expr(
73 anyOf(cxxConstructExpr(argumentCountIs(1),
74 hasDeclaration(cxxMethodDecl(HasStringCtorName
))),
75 // If present, the second argument is the alloc object which must
76 // not be present explicitly.
77 cxxConstructExpr(argumentCountIs(2),
78 hasDeclaration(cxxMethodDecl(HasStringCtorName
)),
79 hasArgument(1, cxxDefaultArgExpr()))));
81 // Match a string constructor expression with an empty string literal.
82 const auto EmptyStringCtorExpr
= cxxConstructExpr(
83 StringConstructorExpr
,
84 hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0)))));
86 const auto EmptyStringCtorExprWithTemporaries
=
87 cxxConstructExpr(StringConstructorExpr
,
88 hasArgument(0, ignoringImplicit(EmptyStringCtorExpr
)));
90 const auto StringType
= hasType(hasUnqualifiedDesugaredType(
91 recordType(hasDeclaration(cxxRecordDecl(HasStringTypeName
)))));
92 const auto EmptyStringInit
= traverse(
93 TK_AsIs
, expr(ignoringImplicit(anyOf(
94 EmptyStringCtorExpr
, EmptyStringCtorExprWithTemporaries
))));
96 // Match a variable declaration with an empty string literal as initializer.
102 namedDecl(varDecl(StringType
, hasInitializer(EmptyStringInit
))
104 unless(parmVarDecl()))),
106 // Match a field declaration with an empty string literal as initializer.
108 namedDecl(fieldDecl(StringType
, hasInClassInitializer(EmptyStringInit
))
111 // Matches Constructor Initializers with an empty string literal as
114 // Foo() : SomeString("") {}
118 forField(allOf(StringType
, optionally(hasInClassInitializer(
119 EmptyStringInit
.bind("empty_init"))))),
120 withInitializer(EmptyStringInit
))
125 void RedundantStringInitCheck::check(const MatchFinder::MatchResult
&Result
) {
126 if (const auto *VDecl
= Result
.Nodes
.getNodeAs
<VarDecl
>("vardecl")) {
127 // VarDecl's getSourceRange() spans 'string foo = ""' or 'string bar("")'.
128 // So start at getLocation() to span just 'foo = ""' or 'bar("")'.
129 SourceRange
ReplaceRange(VDecl
->getLocation(), VDecl
->getEndLoc());
130 diag(VDecl
->getLocation(), "redundant string initialization")
131 << FixItHint::CreateReplacement(ReplaceRange
, VDecl
->getName());
133 if (const auto *FDecl
= Result
.Nodes
.getNodeAs
<FieldDecl
>("fieldDecl")) {
134 // FieldDecl's getSourceRange() spans 'string foo = ""'.
135 // So start at getLocation() to span just 'foo = ""'.
136 SourceRange
ReplaceRange(FDecl
->getLocation(), FDecl
->getEndLoc());
137 diag(FDecl
->getLocation(), "redundant string initialization")
138 << FixItHint::CreateReplacement(ReplaceRange
, FDecl
->getName());
140 if (const auto *CtorInit
=
141 Result
.Nodes
.getNodeAs
<CXXCtorInitializer
>("ctorInit")) {
142 if (const FieldDecl
*Member
= CtorInit
->getMember()) {
143 if (!Member
->hasInClassInitializer() ||
144 Result
.Nodes
.getNodeAs
<Expr
>("empty_init")) {
145 // The String isn't declared in the class with an initializer or its
146 // declared with a redundant initializer, which will be removed. Either
147 // way the string will be default initialized, therefore we can remove
148 // the constructor initializer entirely.
149 diag(CtorInit
->getMemberLocation(), "redundant string initialization")
150 << FixItHint::CreateRemoval(CtorInit
->getSourceRange());
154 const CXXConstructExpr
*Construct
= getConstructExpr(*CtorInit
);
157 if (llvm::Optional
<SourceRange
> RemovalRange
=
158 getConstructExprArgRange(*Construct
))
159 diag(CtorInit
->getMemberLocation(), "redundant string initialization")
160 << FixItHint::CreateRemoval(*RemovalRange
);
164 } // namespace readability