10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Lex/Lexer.h" 13 #include "clang/Lex/Token.h" 15 #include "../utils/LexerUtils.h" 23 ArgumentCommentCheck::ArgumentCommentCheck(StringRef
Name,
26 StrictMode(Options.getLocalOrGlobal(
"StrictMode", 0) != 0),
27 IgnoreSingleArgument(Options.get(
"IgnoreSingleArgument", 0) != 0),
28 CommentBoolLiterals(Options.get(
"CommentBoolLiterals", 0) != 0),
29 CommentIntegerLiterals(Options.get(
"CommentIntegerLiterals", 0) != 0),
30 CommentFloatLiterals(Options.get(
"CommentFloatLiterals", 0) != 0),
31 CommentStringLiterals(Options.get(
"CommentStringLiterals", 0) != 0),
32 CommentUserDefinedLiterals(Options.get(
"CommentUserDefinedLiterals", 0) !=
34 CommentCharacterLiterals(Options.get(
"CommentCharacterLiterals", 0) != 0),
35 CommentNullPtrs(Options.get(
"CommentNullPtrs", 0) != 0),
36 IdentRE(
"^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
40 Options.
store(Opts,
"IgnoreSingleArgument", IgnoreSingleArgument);
41 Options.
store(Opts,
"CommentBoolLiterals", CommentBoolLiterals);
42 Options.
store(Opts,
"CommentIntegerLiterals", CommentIntegerLiterals);
43 Options.
store(Opts,
"CommentFloatLiterals", CommentFloatLiterals);
44 Options.
store(Opts,
"CommentStringLiterals", CommentStringLiterals);
45 Options.
store(Opts,
"CommentUserDefinedLiterals", CommentUserDefinedLiterals);
46 Options.
store(Opts,
"CommentCharacterLiterals", CommentCharacterLiterals);
52 callExpr(unless(cxxOperatorCallExpr()),
56 unless(hasDeclaration(functionDecl(
57 hasAnyName(
"NewCallback",
"NewPermanentCallback")))))
60 Finder->addMatcher(cxxConstructExpr().bind(
"expr"),
this);
63 static std::vector<std::pair<SourceLocation, StringRef>>
65 std::vector<std::pair<SourceLocation, StringRef>> Comments;
66 auto &SM = Ctx->getSourceManager();
67 std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
68 EndLoc = SM.getDecomposedLoc(Range.getEnd());
70 if (BeginLoc.first != EndLoc.first)
74 StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
78 const char *StrData = Buffer.data() + BeginLoc.second;
80 Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
81 Buffer.begin(), StrData, Buffer.end());
82 TheLexer.SetCommentRetentionState(
true);
86 if (TheLexer.LexFromRawLexer(Tok))
88 if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof))
91 if (Tok.is(tok::comment)) {
92 std::pair<FileID, unsigned> CommentLoc =
93 SM.getDecomposedLoc(Tok.getLocation());
94 assert(CommentLoc.first == BeginLoc.first);
95 Comments.emplace_back(
97 StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
107 static std::vector<std::pair<SourceLocation, StringRef>>
109 std::vector<std::pair<SourceLocation, StringRef>> Comments;
110 while (Loc.isValid()) {
112 Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
114 if (Tok.isNot(tok::comment))
116 Loc = Tok.getLocation();
117 Comments.emplace_back(
119 Lexer::getSourceText(CharSourceRange::getCharRange(
120 Loc, Loc.getLocWithOffset(Tok.getLength())),
121 Ctx->getSourceManager(), Ctx->getLangOpts()));
127 StringRef ArgName,
unsigned ArgIndex) {
128 std::string ArgNameLowerStr = ArgName.lower();
129 StringRef ArgNameLower = ArgNameLowerStr;
131 unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
132 unsigned ThisED = ArgNameLower.edit_distance(
133 Params[ArgIndex]->getIdentifier()->getName().
lower(),
135 if (ThisED >= UpperBound)
138 for (
unsigned I = 0,
E = Params.size(); I !=
E; ++I) {
141 IdentifierInfo *II = Params[I]->getIdentifier();
145 const unsigned Threshold = 2;
149 unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
152 if (OtherED < ThisED + Threshold)
159 static bool sameName(StringRef InComment, StringRef InDecl,
bool StrictMode) {
161 return InComment == InDecl;
162 InComment = InComment.trim(
'_');
163 InDecl = InDecl.trim(
'_');
165 return InComment.compare_lower(InDecl) == 0;
169 return Expect !=
nullptr && Expect->getLocation().isMacroID() &&
170 Expect->getNameInfo().getName().isIdentifier() &&
171 Expect->getName().startswith(
"gmock_");
174 const CXXMethodDecl *Expect) {
176 return Mock !=
nullptr && Mock->getNextDeclInContext() == Expect &&
177 Mock->getNumParams() == Expect->getNumParams() &&
178 Mock->getLocation().isMacroID() &&
179 Mock->getNameInfo().getName().isIdentifier() &&
180 Mock->getName() == Expect->getName().substr(strlen(
"gmock_"));
190 const DeclContext *
Ctx = Method->getDeclContext();
191 if (Ctx ==
nullptr || !Ctx->isRecord())
193 for (
const auto *D : Ctx->decls()) {
194 if (D->getNextDeclInContext() == Method) {
195 const auto *Previous = dyn_cast<CXXMethodDecl>(D);
201 if (
const auto *Next =
202 dyn_cast_or_null<CXXMethodDecl>(Method->getNextDeclInContext())) {
214 if (
const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
218 if (MockedMethod->size_overridden_methods() > 0) {
219 return *MockedMethod->begin_overridden_methods();
229 bool ArgumentCommentCheck::shouldAddComment(
const Expr *Arg)
const {
230 Arg = Arg->IgnoreImpCasts();
231 if (isa<UnaryOperator>(Arg))
232 Arg = cast<UnaryOperator>(Arg)->getSubExpr();
233 if (Arg->getExprLoc().isMacroID())
235 return (CommentBoolLiterals && isa<CXXBoolLiteralExpr>(Arg)) ||
236 (CommentIntegerLiterals && isa<IntegerLiteral>(Arg)) ||
237 (CommentFloatLiterals && isa<FloatingLiteral>(Arg)) ||
238 (CommentUserDefinedLiterals && isa<UserDefinedLiteral>(Arg)) ||
239 (CommentCharacterLiterals && isa<CharacterLiteral>(Arg)) ||
240 (CommentStringLiterals && isa<StringLiteral>(Arg)) ||
241 (CommentNullPtrs && isa<CXXNullPtrLiteralExpr>(Arg));
244 void ArgumentCommentCheck::checkCallArgs(ASTContext *
Ctx,
245 const FunctionDecl *OriginalCallee,
246 SourceLocation ArgBeginLoc,
247 llvm::ArrayRef<const Expr *> Args) {
248 const FunctionDecl *Callee =
resolveMocks(OriginalCallee);
252 Callee = Callee->getFirstDecl();
253 unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
254 if ((NumArgs == 0) || (IgnoreSingleArgument && NumArgs == 1))
257 auto MakeFileCharRange = [
Ctx](SourceLocation Begin, SourceLocation End) {
258 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
259 Ctx->getSourceManager(),
263 for (
unsigned I = 0; I < NumArgs; ++I) {
264 const ParmVarDecl *PVD = Callee->getParamDecl(I);
265 IdentifierInfo *II = PVD->getIdentifier();
268 if (
auto Template = Callee->getTemplateInstantiationPattern()) {
273 if (Template->getNumParams() <= I ||
274 Template->getParamDecl(I)->isParameterPack()) {
279 CharSourceRange BeforeArgument =
280 MakeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
281 ArgBeginLoc = Args[I]->getEndLoc();
283 std::vector<std::pair<SourceLocation, StringRef>> Comments;
284 if (BeforeArgument.isValid()) {
288 CharSourceRange ArgsRange =
289 MakeFileCharRange(Args[I]->getBeginLoc(), Args[I]->getEndLoc());
293 for (
auto Comment : Comments) {
294 llvm::SmallVector<StringRef, 2> Matches;
295 if (IdentRE.match(Comment.second, &Matches) &&
296 !
sameName(Matches[2], II->getName(), StrictMode)) {
298 DiagnosticBuilder Diag =
299 diag(Comment.first,
"argument name '%0' in comment does not " 300 "match parameter name %1")
302 if (
isLikelyTypo(Callee->parameters(), Matches[2], I)) {
303 Diag << FixItHint::CreateReplacement(
304 Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
307 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note) << II;
308 if (OriginalCallee != Callee) {
309 diag(OriginalCallee->getLocation(),
310 "actual callee (%0) is declared here", DiagnosticIDs::Note)
317 if (Comments.empty() && shouldAddComment(Args[I])) {
318 std::string ArgComment =
319 (llvm::Twine(
"/*") + II->getName() +
"=*/").str();
320 DiagnosticBuilder Diag =
321 diag(Args[I]->getBeginLoc(),
322 "argument comment missing for literal argument %0")
324 << FixItHint::CreateInsertion(Args[I]->getBeginLoc(), ArgComment);
330 const auto *
E = Result.Nodes.getNodeAs<Expr>(
"expr");
331 if (
const auto *Call = dyn_cast<CallExpr>(
E)) {
332 const FunctionDecl *Callee = Call->getDirectCallee();
336 checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
337 llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
339 const auto *Construct = cast<CXXConstructExpr>(
E);
340 if (Construct->getNumArgs() > 0 &&
341 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
346 Result.Context, Construct->getConstructor(),
347 Construct->getParenOrBraceRange().getBegin(),
348 llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
SourceLocation Loc
'#' location in the include directive
static bool looksLikeExpectMethod(const CXXMethodDecl *Expect)
static const FunctionDecl * resolveMocks(const FunctionDecl *Func)
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc)
Base class for all clang-tidy checks.
static bool areMockAndExpectMethods(const CXXMethodDecl *Mock, const CXXMethodDecl *Expect)
static bool isLikelyTypo(llvm::ArrayRef< ParmVarDecl *> Params, StringRef ArgName, unsigned ArgIndex)
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsInRange(ASTContext *Ctx, CharSourceRange Range)
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
static char lower(char C)
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const CXXMethodDecl * findMockedMethod(const CXXMethodDecl *Method)
CharSourceRange Range
SourceRange for the file name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.