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()) {
96 if (Tok.isNot(tok::comment))
98 Loc = Tok.getLocation();
99 Comments.emplace_back(
101 Lexer::getSourceText(CharSourceRange::getCharRange(
102 Loc, Loc.getLocWithOffset(Tok.getLength())),
103 Ctx->getSourceManager(), Ctx->getLangOpts()));
109 StringRef ArgName,
unsigned ArgIndex) {
110 std::string ArgNameLowerStr = ArgName.lower();
111 StringRef ArgNameLower = ArgNameLowerStr;
113 unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
114 unsigned ThisED = ArgNameLower.edit_distance(
115 Params[ArgIndex]->getIdentifier()->getName().
lower(),
117 if (ThisED >= UpperBound)
120 for (
unsigned I = 0, E = Params.size(); I != E; ++I) {
123 IdentifierInfo *II = Params[I]->getIdentifier();
127 const unsigned Threshold = 2;
131 unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
134 if (OtherED < ThisED + Threshold)
141 static bool sameName(StringRef InComment, StringRef InDecl,
bool StrictMode) {
143 return InComment == InDecl;
144 InComment = InComment.trim(
'_');
145 InDecl = InDecl.trim(
'_');
147 return InComment.compare_lower(InDecl) == 0;
151 return Expect !=
nullptr && Expect->getLocation().isMacroID() &&
152 Expect->getNameInfo().getName().isIdentifier() &&
153 Expect->getName().startswith(
"gmock_");
156 const CXXMethodDecl *Expect) {
158 return Mock !=
nullptr && Mock->getNextDeclInContext() == Expect &&
159 Mock->getNumParams() == Expect->getNumParams() &&
160 Mock->getLocation().isMacroID() &&
161 Mock->getNameInfo().getName().isIdentifier() &&
162 Mock->getName() == Expect->getName().substr(strlen(
"gmock_"));
172 const DeclContext *
Ctx = Method->getDeclContext();
173 if (Ctx ==
nullptr || !Ctx->isRecord())
175 for (
const auto *D : Ctx->decls()) {
176 if (D->getNextDeclInContext() == Method) {
177 const auto *Previous = dyn_cast<CXXMethodDecl>(D);
183 if (
const auto *Next = dyn_cast_or_null<CXXMethodDecl>(
184 Method->getNextDeclInContext())) {
196 if (
const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
200 if (MockedMethod->size_overridden_methods() > 0) {
201 return *MockedMethod->begin_overridden_methods();
209 void ArgumentCommentCheck::checkCallArgs(ASTContext *
Ctx,
210 const FunctionDecl *OriginalCallee,
211 SourceLocation ArgBeginLoc,
212 llvm::ArrayRef<const Expr *> Args) {
213 const FunctionDecl *Callee =
resolveMocks(OriginalCallee);
217 Callee = Callee->getFirstDecl();
218 unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
222 auto makeFileCharRange = [
Ctx](SourceLocation Begin, SourceLocation End) {
223 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
224 Ctx->getSourceManager(),
228 for (
unsigned I = 0; I < NumArgs; ++I) {
229 const ParmVarDecl *PVD = Callee->getParamDecl(I);
230 IdentifierInfo *II = PVD->getIdentifier();
233 if (
auto Template = Callee->getTemplateInstantiationPattern()) {
238 if (Template->getNumParams() <= I ||
239 Template->getParamDecl(I)->isParameterPack()) {
244 CharSourceRange BeforeArgument =
245 makeFileCharRange(ArgBeginLoc, Args[I]->getLocStart());
246 ArgBeginLoc = Args[I]->getLocEnd();
248 std::vector<std::pair<SourceLocation, StringRef>> Comments;
249 if (BeforeArgument.isValid()) {
253 CharSourceRange ArgsRange = makeFileCharRange(
254 Args[I]->getLocStart(), Args[NumArgs - 1]->getLocEnd());
258 for (
auto Comment : Comments) {
259 llvm::SmallVector<StringRef, 2> Matches;
260 if (IdentRE.match(Comment.second, &Matches) &&
261 !
sameName(Matches[2], II->getName(), StrictMode)) {
263 DiagnosticBuilder Diag =
264 diag(Comment.first,
"argument name '%0' in comment does not " 265 "match parameter name %1")
267 if (
isLikelyTypo(Callee->parameters(), Matches[2], I)) {
268 Diag << FixItHint::CreateReplacement(
269 Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
272 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note) << II;
273 if (OriginalCallee != Callee) {
274 diag(OriginalCallee->getLocation(),
275 "actual callee (%0) is declared here", DiagnosticIDs::Note)
284 const auto *E = Result.Nodes.getNodeAs<Expr>(
"expr");
285 if (
const auto *Call = dyn_cast<CallExpr>(E)) {
286 const FunctionDecl *Callee = Call->getDirectCallee();
290 checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
291 llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
293 const auto *Construct = cast<CXXConstructExpr>(E);
294 if (Construct->getNumArgs() == 1 &&
295 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
300 Result.Context, Construct->getConstructor(),
301 Construct->getParenOrBraceRange().getBegin(),
302 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(const ASTContext &Context, SourceLocation Location, 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)
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.
tooling::ExecutionContext * Ctx
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.