11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/Lex/Lexer.h" 14 #include "clang/Lex/Token.h" 15 #include "../utils/LexerUtils.h" 23 ArgumentCommentCheck::ArgumentCommentCheck(StringRef
Name,
26 StrictMode(Options.getLocalOrGlobal(
"StrictMode", 0) != 0),
27 IdentRE(
"^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
35 callExpr(unless(cxxOperatorCallExpr()),
39 unless(hasDeclaration(functionDecl(
40 hasAnyName(
"NewCallback",
"NewPermanentCallback")))))
43 Finder->addMatcher(cxxConstructExpr().bind(
"expr"),
this);
46 static std::vector<std::pair<SourceLocation, StringRef>>
48 std::vector<std::pair<SourceLocation, StringRef>> Comments;
49 auto &SM = Ctx->getSourceManager();
50 std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
51 EndLoc = SM.getDecomposedLoc(Range.getEnd());
53 if (BeginLoc.first != EndLoc.first)
57 StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
61 const char *StrData = Buffer.data() + BeginLoc.second;
63 Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
64 Buffer.begin(), StrData, Buffer.end());
65 TheLexer.SetCommentRetentionState(
true);
69 if (TheLexer.LexFromRawLexer(Tok))
71 if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof))
74 if (Tok.is(tok::comment)) {
75 std::pair<FileID, unsigned> CommentLoc =
76 SM.getDecomposedLoc(Tok.getLocation());
77 assert(CommentLoc.first == BeginLoc.first);
78 Comments.emplace_back(
80 StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
90 static std::vector<std::pair<SourceLocation, StringRef>>
92 std::vector<std::pair<SourceLocation, StringRef>> Comments;
93 while (Loc.isValid()) {
95 Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
97 if (Tok.isNot(tok::comment))
99 Loc = Tok.getLocation();
100 Comments.emplace_back(
102 Lexer::getSourceText(CharSourceRange::getCharRange(
103 Loc, Loc.getLocWithOffset(Tok.getLength())),
104 Ctx->getSourceManager(), Ctx->getLangOpts()));
110 StringRef ArgName,
unsigned ArgIndex) {
111 std::string ArgNameLowerStr = ArgName.lower();
112 StringRef ArgNameLower = ArgNameLowerStr;
114 unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
115 unsigned ThisED = ArgNameLower.edit_distance(
116 Params[ArgIndex]->getIdentifier()->getName().
lower(),
118 if (ThisED >= UpperBound)
121 for (
unsigned I = 0, E = Params.size(); I != E; ++I) {
124 IdentifierInfo *II = Params[I]->getIdentifier();
128 const unsigned Threshold = 2;
132 unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
135 if (OtherED < ThisED + Threshold)
142 static bool sameName(StringRef InComment, StringRef InDecl,
bool StrictMode) {
144 return InComment == InDecl;
145 InComment = InComment.trim(
'_');
146 InDecl = InDecl.trim(
'_');
148 return InComment.compare_lower(InDecl) == 0;
152 return Expect !=
nullptr && Expect->getLocation().isMacroID() &&
153 Expect->getNameInfo().getName().isIdentifier() &&
154 Expect->getName().startswith(
"gmock_");
157 const CXXMethodDecl *Expect) {
159 return Mock !=
nullptr && Mock->getNextDeclInContext() == Expect &&
160 Mock->getNumParams() == Expect->getNumParams() &&
161 Mock->getLocation().isMacroID() &&
162 Mock->getNameInfo().getName().isIdentifier() &&
163 Mock->getName() == Expect->getName().substr(strlen(
"gmock_"));
173 const DeclContext *
Ctx = Method->getDeclContext();
174 if (Ctx ==
nullptr || !Ctx->isRecord())
176 for (
const auto *
D : Ctx->decls()) {
177 if (
D->getNextDeclInContext() == Method) {
178 const auto *Previous = dyn_cast<CXXMethodDecl>(
D);
184 if (
const auto *Next = dyn_cast_or_null<CXXMethodDecl>(
185 Method->getNextDeclInContext())) {
197 if (
const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
201 if (MockedMethod->size_overridden_methods() > 0) {
202 return *MockedMethod->begin_overridden_methods();
210 void ArgumentCommentCheck::checkCallArgs(ASTContext *
Ctx,
211 const FunctionDecl *OriginalCallee,
212 SourceLocation ArgBeginLoc,
213 llvm::ArrayRef<const Expr *> Args) {
214 const FunctionDecl *Callee =
resolveMocks(OriginalCallee);
218 Callee = Callee->getFirstDecl();
219 unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
223 auto makeFileCharRange = [
Ctx](SourceLocation Begin, SourceLocation End) {
224 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
225 Ctx->getSourceManager(),
229 for (
unsigned I = 0; I < NumArgs; ++I) {
230 const ParmVarDecl *PVD = Callee->getParamDecl(I);
231 IdentifierInfo *II = PVD->getIdentifier();
234 if (
auto Template = Callee->getTemplateInstantiationPattern()) {
239 if (Template->getNumParams() <= I ||
240 Template->getParamDecl(I)->isParameterPack()) {
245 CharSourceRange BeforeArgument =
246 makeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
247 ArgBeginLoc = Args[I]->getEndLoc();
249 std::vector<std::pair<SourceLocation, StringRef>> Comments;
250 if (BeforeArgument.isValid()) {
254 CharSourceRange ArgsRange = makeFileCharRange(
255 Args[I]->getBeginLoc(), Args[NumArgs - 1]->getEndLoc());
259 for (
auto Comment : Comments) {
260 llvm::SmallVector<StringRef, 2> Matches;
261 if (IdentRE.match(Comment.second, &Matches) &&
262 !
sameName(Matches[2], II->getName(), StrictMode)) {
264 DiagnosticBuilder Diag =
265 diag(Comment.first,
"argument name '%0' in comment does not " 266 "match parameter name %1")
268 if (
isLikelyTypo(Callee->parameters(), Matches[2], I)) {
269 Diag << FixItHint::CreateReplacement(
270 Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
273 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note) << II;
274 if (OriginalCallee != Callee) {
275 diag(OriginalCallee->getLocation(),
276 "actual callee (%0) is declared here", DiagnosticIDs::Note)
285 const auto *E = Result.Nodes.getNodeAs<Expr>(
"expr");
286 if (
const auto *Call = dyn_cast<CallExpr>(E)) {
287 const FunctionDecl *Callee = Call->getDirectCallee();
291 checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
292 llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
294 const auto *Construct = cast<CXXConstructExpr>(E);
295 if (Construct->getNumArgs() == 1 &&
296 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
301 Result.Context, Construct->getConstructor(),
302 Construct->getParenOrBraceRange().getBegin(),
303 llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
SourceLocation Loc
'#' location in the include directive
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 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)
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
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.