blob: 69cc01683c4969b055afc5331a3dbb9f5a6563ef [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.
133 assert(Command->getArgCount() == 0);
134
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
276HTMLOpenTagComment *Sema::actOnHTMLOpenTagStart(SourceLocation LocBegin,
277 StringRef TagName) {
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000278 HTMLOpenTagComment *HOT =
279 new (Allocator) HTMLOpenTagComment(LocBegin, TagName);
280 return HOT;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000281}
282
283HTMLOpenTagComment *Sema::actOnHTMLOpenTagFinish(
284 HTMLOpenTagComment *Tag,
285 ArrayRef<HTMLOpenTagComment::Attribute> Attrs,
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000286 SourceLocation GreaterLoc,
287 bool IsSelfClosing) {
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000288 Tag->setAttrs(Attrs);
289 Tag->setGreaterLoc(GreaterLoc);
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000290 if (IsSelfClosing)
291 Tag->setSelfClosing();
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000292 else if (!isHTMLCloseTagForbidden(Tag->getTagName()))
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000293 HTMLOpenTags.push_back(Tag);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000294 return Tag;
295}
296
297HTMLCloseTagComment *Sema::actOnHTMLCloseTag(SourceLocation LocBegin,
298 SourceLocation LocEnd,
299 StringRef TagName) {
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000300 HTMLCloseTagComment *HCT =
301 new (Allocator) HTMLCloseTagComment(LocBegin, LocEnd, TagName);
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000302 if (isHTMLCloseTagForbidden(TagName)) {
303 Diag(HCT->getLocation(), diag::warn_doc_html_close_forbidden)
304 << TagName << HCT->getSourceRange();
305 return HCT;
306 }
307
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000308 bool FoundOpen = false;
309 for (SmallVectorImpl<HTMLOpenTagComment *>::const_reverse_iterator
310 I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
311 I != E; ++I) {
312 if ((*I)->getTagName() == TagName) {
313 FoundOpen = true;
314 break;
315 }
316 }
317 if (!FoundOpen) {
318 Diag(HCT->getLocation(), diag::warn_doc_html_close_unbalanced)
319 << HCT->getSourceRange();
320 return HCT;
321 }
322
323 while (!HTMLOpenTags.empty()) {
324 const HTMLOpenTagComment *HOT = HTMLOpenTags.back();
325 HTMLOpenTags.pop_back();
326 StringRef LastNotClosedTagName = HOT->getTagName();
327 if (LastNotClosedTagName == TagName)
328 break;
329
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000330 if (isHTMLCloseTagOptional(LastNotClosedTagName))
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000331 continue;
332
333 bool OpenLineInvalid;
334 const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
335 HOT->getLocation(),
336 &OpenLineInvalid);
337 bool CloseLineInvalid;
338 const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
339 HCT->getLocation(),
340 &CloseLineInvalid);
341
342 if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine)
343 Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
344 << HOT->getTagName() << HCT->getTagName()
345 << HOT->getSourceRange() << HCT->getSourceRange();
346 else {
347 Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
348 << HOT->getTagName() << HCT->getTagName()
349 << HOT->getSourceRange();
350 Diag(HCT->getLocation(), diag::note_doc_html_closing_tag)
351 << HCT->getSourceRange();
352 }
353 }
354
355 return HCT;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000356}
357
358FullComment *Sema::actOnFullComment(
359 ArrayRef<BlockContentComment *> Blocks) {
360 return new (Allocator) FullComment(Blocks);
361}
362
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000363void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) {
364 ParagraphComment *Paragraph = Command->getParagraph();
365 if (Paragraph->isWhitespace()) {
366 SourceLocation DiagLoc;
367 if (Command->getArgCount() > 0)
368 DiagLoc = Command->getArgRange(Command->getArgCount() - 1).getEnd();
369 if (!DiagLoc.isValid())
370 DiagLoc = Command->getCommandNameRange().getEnd();
371 Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
372 << Command->getCommandName()
373 << Command->getSourceRange();
374 }
375}
376
377unsigned Sema::resolveParmVarReference(StringRef Name,
378 const ParmVarDecl * const *ParamVars,
379 unsigned NumParams) {
380 for (unsigned i = 0; i != NumParams; ++i) {
381 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
382 if (II && II->getName() == Name)
383 return i;
384 }
385 return ParamCommandComment::InvalidParamIndex;
386}
387
388unsigned Sema::correctTypoInParmVarReference(
389 StringRef Typo,
390 const ParmVarDecl * const *ParamVars,
391 unsigned NumParams) {
392 const unsigned MaxEditDistance = (Typo.size() + 2) / 3;
Richard Smith18b7f952012-07-11 22:33:59 +0000393 unsigned BestPVDIndex = 0;
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000394 unsigned BestEditDistance = MaxEditDistance + 1;
395 for (unsigned i = 0; i != NumParams; ++i) {
396 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
397 if (II) {
398 StringRef Name = II->getName();
NAKAMURA Takumidc5796c2012-07-12 00:45:08 +0000399 unsigned MinPossibleEditDistance =
400 abs((int)Name.size() - (int)Typo.size());
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000401 if (MinPossibleEditDistance > 0 &&
402 Typo.size() / MinPossibleEditDistance < 3)
403 continue;
404
405 unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
406 if (EditDistance < BestEditDistance) {
407 BestEditDistance = EditDistance;
408 BestPVDIndex = i;
409 }
410 }
411 }
412
413 if (BestEditDistance <= MaxEditDistance)
414 return BestPVDIndex;
415 else
416 return ParamCommandComment::InvalidParamIndex;;
417}
418
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000419// TODO: tablegen
420bool Sema::isBlockCommand(StringRef Name) {
421 return llvm::StringSwitch<bool>(Name)
422 .Case("brief", true)
423 .Case("result", true)
424 .Case("return", true)
425 .Case("returns", true)
426 .Case("author", true)
427 .Case("authors", true)
428 .Case("pre", true)
429 .Case("post", true)
430 .Default(false) || isParamCommand(Name);
431}
432
433bool Sema::isParamCommand(StringRef Name) {
434 return llvm::StringSwitch<bool>(Name)
435 .Case("param", true)
436 .Case("arg", true)
437 .Default(false);
438}
439
440unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
441 return llvm::StringSwitch<unsigned>(Name)
442 .Case("brief", 0)
443 .Case("pre", 0)
444 .Case("post", 0)
445 .Case("author", 0)
446 .Case("authors", 0)
447 .Default(0);
448}
449
450bool Sema::isInlineCommand(StringRef Name) {
451 return llvm::StringSwitch<bool>(Name)
452 .Case("c", true)
453 .Case("em", true)
454 .Default(false);
455}
456
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000457bool Sema::isHTMLCloseTagOptional(StringRef Name) {
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000458 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenko3d986982012-07-12 23:37:09 +0000459 .Case("p", true)
460 .Case("li", true)
461 .Case("dt", true)
462 .Case("dd", true)
463 .Case("tr", true)
464 .Case("th", true)
465 .Case("td", true)
466 .Case("thead", true)
467 .Case("tfoot", true)
468 .Case("tbody", true)
469 .Case("colgroup", true)
470 .Default(false);
471}
472
473bool Sema::isHTMLCloseTagForbidden(StringRef Name) {
474 return llvm::StringSwitch<bool>(Name)
475 .Case("br", true)
476 .Case("hr", true)
477 .Case("img", true)
478 .Case("col", true)
479 .Default(false);
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000480}
481
482} // end namespace comments
483} // end namespace clang
484