blob: 0903372f8f9a3d5cd44612bb9ca291f0e5a334a9 [file] [log] [blame]
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +00001//===--- CommentSema.cpp - Doxygen comment semantic analysis --------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "clang/AST/CommentSema.h"
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000011#include "clang/AST/CommentDiagnostic.h"
12#include "clang/AST/Decl.h"
13#include "clang/AST/DeclObjC.h"
14#include "clang/Basic/SourceManager.h"
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000015#include "llvm/ADT/StringSwitch.h"
16
17namespace clang {
18namespace comments {
19
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000020Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
21 DiagnosticsEngine &Diags) :
22 Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), ThisDecl(NULL) {
23}
24
25void Sema::setDecl(const Decl *D) {
26 ThisDecl = D;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000027}
28
29ParagraphComment *Sema::actOnParagraphComment(
30 ArrayRef<InlineContentComment *> Content) {
31 return new (Allocator) ParagraphComment(Content);
32}
33
34BlockCommandComment *Sema::actOnBlockCommandStart(SourceLocation LocBegin,
35 SourceLocation LocEnd,
36 StringRef Name) {
37 return new (Allocator) BlockCommandComment(LocBegin, LocEnd, Name);
38}
39
40BlockCommandComment *Sema::actOnBlockCommandArgs(
41 BlockCommandComment *Command,
42 ArrayRef<BlockCommandComment::Argument> Args) {
43 Command->setArgs(Args);
44 return Command;
45}
46
47BlockCommandComment *Sema::actOnBlockCommandFinish(
48 BlockCommandComment *Command,
49 ParagraphComment *Paragraph) {
50 Command->setParagraph(Paragraph);
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000051 checkBlockCommandEmptyParagraph(Command);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000052 return Command;
53}
54
55ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin,
56 SourceLocation LocEnd,
57 StringRef Name) {
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000058 ParamCommandComment *Command =
59 new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name);
60
61 if (!ThisDecl ||
62 !(isa<FunctionDecl>(ThisDecl) || isa<ObjCMethodDecl>(ThisDecl)))
63 Diag(Command->getLocation(),
64 diag::warn_doc_param_not_attached_to_a_function_decl)
65 << Command->getCommandNameRange();
66
67 return Command;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000068}
69
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000070ParamCommandComment *Sema::actOnParamCommandDirectionArg(
71 ParamCommandComment *Command,
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000072 SourceLocation ArgLocBegin,
73 SourceLocation ArgLocEnd,
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000074 StringRef Arg) {
75 ParamCommandComment::PassDirection Direction;
76 std::string ArgLower = Arg.lower();
77 // TODO: optimize: lower Name first (need an API in SmallString for that),
78 // after that StringSwitch.
79 if (ArgLower == "[in]")
80 Direction = ParamCommandComment::In;
81 else if (ArgLower == "[out]")
82 Direction = ParamCommandComment::Out;
83 else if (ArgLower == "[in,out]" || ArgLower == "[out,in]")
84 Direction = ParamCommandComment::InOut;
85 else {
86 // Remove spaces.
87 std::string::iterator O = ArgLower.begin();
88 for (std::string::iterator I = ArgLower.begin(), E = ArgLower.end();
89 I != E; ++I) {
90 const char C = *I;
91 if (C != ' ' && C != '\n' && C != '\r' &&
92 C != '\t' && C != '\v' && C != '\f')
93 *O++ = C;
94 }
95 ArgLower.resize(O - ArgLower.begin());
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +000096
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +000097 bool RemovingWhitespaceHelped = false;
98 if (ArgLower == "[in]") {
99 Direction = ParamCommandComment::In;
100 RemovingWhitespaceHelped = true;
101 } else if (ArgLower == "[out]") {
102 Direction = ParamCommandComment::Out;
103 RemovingWhitespaceHelped = true;
104 } else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") {
105 Direction = ParamCommandComment::InOut;
106 RemovingWhitespaceHelped = true;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000107 } else {
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000108 Direction = ParamCommandComment::In;
109 RemovingWhitespaceHelped = false;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000110 }
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000111
112 SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
113 if (RemovingWhitespaceHelped)
114 Diag(ArgLocBegin, diag::warn_doc_param_spaces_in_direction)
115 << ArgRange
116 << FixItHint::CreateReplacement(
117 ArgRange,
118 ParamCommandComment::getDirectionAsString(Direction));
119 else
120 Diag(ArgLocBegin, diag::warn_doc_param_invalid_direction)
121 << ArgRange;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000122 }
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000123 Command->setDirection(Direction, /* Explicit = */ true);
124 return Command;
125}
126
127ParamCommandComment *Sema::actOnParamCommandParamNameArg(
128 ParamCommandComment *Command,
129 SourceLocation ArgLocBegin,
130 SourceLocation ArgLocEnd,
131 StringRef Arg) {
132 // Parser will not feed us more arguments than needed.
Dmitri Gribenko0eaf69d2012-07-13 19:02:42 +0000133 assert(Command->getNumArgs() == 0);
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000134
135 if (!Command->isDirectionExplicit()) {
136 // User didn't provide a direction argument.
137 Command->setDirection(ParamCommandComment::In, /* Explicit = */ false);
138 }
139 typedef BlockCommandComment::Argument Argument;
140 Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin,
141 ArgLocEnd),
142 Arg);
143 Command->setArgs(llvm::makeArrayRef(A, 1));
144
145 if (!ThisDecl)
146 return Command;
147
148 const ParmVarDecl * const *ParamVars;
149 unsigned NumParams;
150 if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(ThisDecl)) {
151 ParamVars = FD->param_begin();
152 NumParams = FD->getNumParams();
153 } else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ThisDecl)) {
154 ParamVars = MD->param_begin();
155 NumParams = MD->param_size();
156 } else {
157 // We already warned that this \\param is not attached to a function decl.
158 return Command;
159 }
160
161 // Check that referenced parameter name is in the function decl.
162 const unsigned ResolvedParamIndex = resolveParmVarReference(Arg, ParamVars,
163 NumParams);
164 if (ResolvedParamIndex != ParamCommandComment::InvalidParamIndex) {
165 Command->setParamIndex(ResolvedParamIndex);
166 return Command;
167 }
168
169 SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
170 Diag(ArgLocBegin, diag::warn_doc_param_not_found)
171 << Arg << ArgRange;
172
173 unsigned CorrectedParamIndex = ParamCommandComment::InvalidParamIndex;
174 if (NumParams == 1) {
175 // If function has only one parameter then only that parameter
176 // can be documented.
177 CorrectedParamIndex = 0;
178 } else {
179 // Do typo correction.
180 CorrectedParamIndex = correctTypoInParmVarReference(Arg, ParamVars,
181 NumParams);
182 }
183 if (CorrectedParamIndex != ParamCommandComment::InvalidParamIndex) {
184 const ParmVarDecl *CorrectedPVD = ParamVars[CorrectedParamIndex];
185 if (const IdentifierInfo *CorrectedII = CorrectedPVD->getIdentifier())
186 Diag(ArgLocBegin, diag::note_doc_param_name_suggestion)
187 << CorrectedII->getName()
188 << FixItHint::CreateReplacement(ArgRange, CorrectedII->getName());
189 }
190
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000191 return Command;
192}
193
194ParamCommandComment *Sema::actOnParamCommandFinish(ParamCommandComment *Command,
195 ParagraphComment *Paragraph) {
196 Command->setParagraph(Paragraph);
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000197 checkBlockCommandEmptyParagraph(Command);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000198 return Command;
199}
200
201InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
202 SourceLocation CommandLocEnd,
203 StringRef CommandName) {
204 ArrayRef<InlineCommandComment::Argument> Args;
205 return new (Allocator) InlineCommandComment(CommandLocBegin,
206 CommandLocEnd,
207 CommandName,
208 Args);
209}
210
211InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
212 SourceLocation CommandLocEnd,
213 StringRef CommandName,
214 SourceLocation ArgLocBegin,
215 SourceLocation ArgLocEnd,
216 StringRef Arg) {
217 typedef InlineCommandComment::Argument Argument;
218 Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin,
219 ArgLocEnd),
220 Arg);
221
222 return new (Allocator) InlineCommandComment(CommandLocBegin,
223 CommandLocEnd,
224 CommandName,
225 llvm::makeArrayRef(A, 1));
226}
227
228InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
229 SourceLocation LocEnd,
230 StringRef Name) {
231 ArrayRef<InlineCommandComment::Argument> Args;
232 return new (Allocator) InlineCommandComment(LocBegin, LocEnd, Name, Args);
233}
234
235TextComment *Sema::actOnText(SourceLocation LocBegin,
236 SourceLocation LocEnd,
237 StringRef Text) {
238 return new (Allocator) TextComment(LocBegin, LocEnd, Text);
239}
240
241VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc,
242 StringRef Name) {
243 return new (Allocator) VerbatimBlockComment(
244 Loc,
245 Loc.getLocWithOffset(1 + Name.size()),
246 Name);
247}
248
249VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc,
250 StringRef Text) {
251 return new (Allocator) VerbatimBlockLineComment(Loc, Text);
252}
253
254VerbatimBlockComment *Sema::actOnVerbatimBlockFinish(
255 VerbatimBlockComment *Block,
256 SourceLocation CloseNameLocBegin,
257 StringRef CloseName,
258 ArrayRef<VerbatimBlockLineComment *> Lines) {
259 Block->setCloseName(CloseName, CloseNameLocBegin);
260 Block->setLines(Lines);
261 return Block;
262}
263
264VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin,
265 StringRef Name,
266 SourceLocation TextBegin,
267 StringRef Text) {
268 return new (Allocator) VerbatimLineComment(
269 LocBegin,
270 TextBegin.getLocWithOffset(Text.size()),
271 Name,
272 TextBegin,
273 Text);
274}
275
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000276HTMLStartTagComment *Sema::actOnHTMLStartTagStart(SourceLocation LocBegin,
277 StringRef TagName) {
278 return new (Allocator) HTMLStartTagComment(LocBegin, TagName);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000279}
280
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000281HTMLStartTagComment *Sema::actOnHTMLStartTagFinish(
282 HTMLStartTagComment *Tag,
283 ArrayRef<HTMLStartTagComment::Attribute> Attrs,
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000284 SourceLocation GreaterLoc,
285 bool IsSelfClosing) {
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000286 Tag->setAttrs(Attrs);
287 Tag->setGreaterLoc(GreaterLoc);
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000288 if (IsSelfClosing)
289 Tag->setSelfClosing();
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000290 else if (!isHTMLEndTagForbidden(Tag->getTagName()))
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000291 HTMLOpenTags.push_back(Tag);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000292 return Tag;
293}
294
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000295HTMLEndTagComment *Sema::actOnHTMLEndTag(SourceLocation LocBegin,
296 SourceLocation LocEnd,
297 StringRef TagName) {
298 HTMLEndTagComment *HET =
299 new (Allocator) HTMLEndTagComment(LocBegin, LocEnd, TagName);
300 if (isHTMLEndTagForbidden(TagName)) {
301 Diag(HET->getLocation(), diag::warn_doc_html_end_forbidden)
302 << TagName << HET->getSourceRange();
303 return HET;
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000304 }
305
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000306 bool FoundOpen = false;
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000307 for (SmallVectorImpl<HTMLStartTagComment *>::const_reverse_iterator
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000308 I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
309 I != E; ++I) {
310 if ((*I)->getTagName() == TagName) {
311 FoundOpen = true;
312 break;
313 }
314 }
315 if (!FoundOpen) {
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000316 Diag(HET->getLocation(), diag::warn_doc_html_end_unbalanced)
317 << HET->getSourceRange();
318 return HET;
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000319 }
320
321 while (!HTMLOpenTags.empty()) {
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000322 const HTMLStartTagComment *HST = HTMLOpenTags.back();
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000323 HTMLOpenTags.pop_back();
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000324 StringRef LastNotClosedTagName = HST->getTagName();
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000325 if (LastNotClosedTagName == TagName)
326 break;
327
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000328 if (isHTMLEndTagOptional(LastNotClosedTagName))
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000329 continue;
330
331 bool OpenLineInvalid;
332 const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000333 HST->getLocation(),
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000334 &OpenLineInvalid);
335 bool CloseLineInvalid;
336 const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000337 HET->getLocation(),
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000338 &CloseLineInvalid);
339
340 if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine)
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000341 Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch)
342 << HST->getTagName() << HET->getTagName()
343 << HST->getSourceRange() << HET->getSourceRange();
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000344 else {
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000345 Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch)
346 << HST->getTagName() << HET->getTagName()
347 << HST->getSourceRange();
348 Diag(HET->getLocation(), diag::note_doc_html_end_tag)
349 << HET->getSourceRange();
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000350 }
351 }
352
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000353 return HET;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000354}
355
356FullComment *Sema::actOnFullComment(
357 ArrayRef<BlockContentComment *> Blocks) {
358 return new (Allocator) FullComment(Blocks);
359}
360
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000361void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) {
362 ParagraphComment *Paragraph = Command->getParagraph();
363 if (Paragraph->isWhitespace()) {
364 SourceLocation DiagLoc;
Dmitri Gribenko0eaf69d2012-07-13 19:02:42 +0000365 if (Command->getNumArgs() > 0)
366 DiagLoc = Command->getArgRange(Command->getNumArgs() - 1).getEnd();
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000367 if (!DiagLoc.isValid())
368 DiagLoc = Command->getCommandNameRange().getEnd();
369 Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
370 << Command->getCommandName()
371 << Command->getSourceRange();
372 }
373}
374
375unsigned Sema::resolveParmVarReference(StringRef Name,
376 const ParmVarDecl * const *ParamVars,
377 unsigned NumParams) {
378 for (unsigned i = 0; i != NumParams; ++i) {
379 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
380 if (II && II->getName() == Name)
381 return i;
382 }
383 return ParamCommandComment::InvalidParamIndex;
384}
385
386unsigned Sema::correctTypoInParmVarReference(
387 StringRef Typo,
388 const ParmVarDecl * const *ParamVars,
389 unsigned NumParams) {
390 const unsigned MaxEditDistance = (Typo.size() + 2) / 3;
Richard Smith18b7f952012-07-11 22:33:59 +0000391 unsigned BestPVDIndex = 0;
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000392 unsigned BestEditDistance = MaxEditDistance + 1;
393 for (unsigned i = 0; i != NumParams; ++i) {
394 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
395 if (II) {
396 StringRef Name = II->getName();
NAKAMURA Takumidc5796c2012-07-12 00:45:08 +0000397 unsigned MinPossibleEditDistance =
398 abs((int)Name.size() - (int)Typo.size());
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000399 if (MinPossibleEditDistance > 0 &&
400 Typo.size() / MinPossibleEditDistance < 3)
401 continue;
402
403 unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
404 if (EditDistance < BestEditDistance) {
405 BestEditDistance = EditDistance;
406 BestPVDIndex = i;
407 }
408 }
409 }
410
411 if (BestEditDistance <= MaxEditDistance)
412 return BestPVDIndex;
413 else
414 return ParamCommandComment::InvalidParamIndex;;
415}
416
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000417// TODO: tablegen
418bool Sema::isBlockCommand(StringRef Name) {
419 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenko3d3d22c2012-07-18 00:44:55 +0000420 .Cases("brief", "short", true)
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000421 .Case("result", true)
422 .Case("return", true)
423 .Case("returns", true)
424 .Case("author", true)
425 .Case("authors", true)
426 .Case("pre", true)
427 .Case("post", true)
428 .Default(false) || isParamCommand(Name);
429}
430
431bool Sema::isParamCommand(StringRef Name) {
432 return llvm::StringSwitch<bool>(Name)
433 .Case("param", true)
434 .Case("arg", true)
435 .Default(false);
436}
437
438unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
439 return llvm::StringSwitch<unsigned>(Name)
Dmitri Gribenko3d3d22c2012-07-18 00:44:55 +0000440 .Cases("brief", "short", 0)
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000441 .Case("pre", 0)
442 .Case("post", 0)
443 .Case("author", 0)
444 .Case("authors", 0)
445 .Default(0);
446}
447
448bool Sema::isInlineCommand(StringRef Name) {
449 return llvm::StringSwitch<bool>(Name)
450 .Case("c", true)
451 .Case("em", true)
452 .Default(false);
453}
454
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000455bool Sema::isHTMLEndTagOptional(StringRef Name) {
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000456 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000457 .Case("p", true)
458 .Case("li", true)
459 .Case("dt", true)
460 .Case("dd", true)
461 .Case("tr", true)
462 .Case("th", true)
463 .Case("td", true)
464 .Case("thead", true)
465 .Case("tfoot", true)
466 .Case("tbody", true)
467 .Case("colgroup", true)
468 .Default(false);
469}
470
Dmitri Gribenko3f38bf22012-07-13 00:44:24 +0000471bool Sema::isHTMLEndTagForbidden(StringRef Name) {
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000472 return llvm::StringSwitch<bool>(Name)
473 .Case("br", true)
474 .Case("hr", true)
475 .Case("img", true)
476 .Case("col", true)
477 .Default(false);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000478}
479
480} // end namespace comments
481} // end namespace clang
482