blob: ae13fa285ad3f513c8d646fa1ff5694104b0caf0 [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();
292 else
293 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);
302 bool FoundOpen = false;
303 for (SmallVectorImpl<HTMLOpenTagComment *>::const_reverse_iterator
304 I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
305 I != E; ++I) {
306 if ((*I)->getTagName() == TagName) {
307 FoundOpen = true;
308 break;
309 }
310 }
311 if (!FoundOpen) {
312 Diag(HCT->getLocation(), diag::warn_doc_html_close_unbalanced)
313 << HCT->getSourceRange();
314 return HCT;
315 }
316
317 while (!HTMLOpenTags.empty()) {
318 const HTMLOpenTagComment *HOT = HTMLOpenTags.back();
319 HTMLOpenTags.pop_back();
320 StringRef LastNotClosedTagName = HOT->getTagName();
321 if (LastNotClosedTagName == TagName)
322 break;
323
324 if (!HTMLOpenTagNeedsClosing(LastNotClosedTagName))
325 continue;
326
327 bool OpenLineInvalid;
328 const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
329 HOT->getLocation(),
330 &OpenLineInvalid);
331 bool CloseLineInvalid;
332 const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
333 HCT->getLocation(),
334 &CloseLineInvalid);
335
336 if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine)
337 Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
338 << HOT->getTagName() << HCT->getTagName()
339 << HOT->getSourceRange() << HCT->getSourceRange();
340 else {
341 Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
342 << HOT->getTagName() << HCT->getTagName()
343 << HOT->getSourceRange();
344 Diag(HCT->getLocation(), diag::note_doc_html_closing_tag)
345 << HCT->getSourceRange();
346 }
347 }
348
349 return HCT;
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000350}
351
352FullComment *Sema::actOnFullComment(
353 ArrayRef<BlockContentComment *> Blocks) {
354 return new (Allocator) FullComment(Blocks);
355}
356
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000357void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) {
358 ParagraphComment *Paragraph = Command->getParagraph();
359 if (Paragraph->isWhitespace()) {
360 SourceLocation DiagLoc;
361 if (Command->getArgCount() > 0)
362 DiagLoc = Command->getArgRange(Command->getArgCount() - 1).getEnd();
363 if (!DiagLoc.isValid())
364 DiagLoc = Command->getCommandNameRange().getEnd();
365 Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
366 << Command->getCommandName()
367 << Command->getSourceRange();
368 }
369}
370
371unsigned Sema::resolveParmVarReference(StringRef Name,
372 const ParmVarDecl * const *ParamVars,
373 unsigned NumParams) {
374 for (unsigned i = 0; i != NumParams; ++i) {
375 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
376 if (II && II->getName() == Name)
377 return i;
378 }
379 return ParamCommandComment::InvalidParamIndex;
380}
381
382unsigned Sema::correctTypoInParmVarReference(
383 StringRef Typo,
384 const ParmVarDecl * const *ParamVars,
385 unsigned NumParams) {
386 const unsigned MaxEditDistance = (Typo.size() + 2) / 3;
Richard Smith18b7f952012-07-11 22:33:59 +0000387 unsigned BestPVDIndex = 0;
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000388 unsigned BestEditDistance = MaxEditDistance + 1;
389 for (unsigned i = 0; i != NumParams; ++i) {
390 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
391 if (II) {
392 StringRef Name = II->getName();
NAKAMURA Takumidc5796c2012-07-12 00:45:08 +0000393 unsigned MinPossibleEditDistance =
394 abs((int)Name.size() - (int)Typo.size());
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000395 if (MinPossibleEditDistance > 0 &&
396 Typo.size() / MinPossibleEditDistance < 3)
397 continue;
398
399 unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
400 if (EditDistance < BestEditDistance) {
401 BestEditDistance = EditDistance;
402 BestPVDIndex = i;
403 }
404 }
405 }
406
407 if (BestEditDistance <= MaxEditDistance)
408 return BestPVDIndex;
409 else
410 return ParamCommandComment::InvalidParamIndex;;
411}
412
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000413// TODO: tablegen
414bool Sema::isBlockCommand(StringRef Name) {
415 return llvm::StringSwitch<bool>(Name)
416 .Case("brief", true)
417 .Case("result", true)
418 .Case("return", true)
419 .Case("returns", true)
420 .Case("author", true)
421 .Case("authors", true)
422 .Case("pre", true)
423 .Case("post", true)
424 .Default(false) || isParamCommand(Name);
425}
426
427bool Sema::isParamCommand(StringRef Name) {
428 return llvm::StringSwitch<bool>(Name)
429 .Case("param", true)
430 .Case("arg", true)
431 .Default(false);
432}
433
434unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
435 return llvm::StringSwitch<unsigned>(Name)
436 .Case("brief", 0)
437 .Case("pre", 0)
438 .Case("post", 0)
439 .Case("author", 0)
440 .Case("authors", 0)
441 .Default(0);
442}
443
444bool Sema::isInlineCommand(StringRef Name) {
445 return llvm::StringSwitch<bool>(Name)
446 .Case("c", true)
447 .Case("em", true)
448 .Default(false);
449}
450
451bool Sema::HTMLOpenTagNeedsClosing(StringRef Name) {
452 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenkoa5ef44f2012-07-11 21:38:39 +0000453 .Case("br", false)
454 .Case("hr", false)
455 .Case("li", false)
Dmitri Gribenko8d3ba232012-07-06 00:28:32 +0000456 .Default(true);
457}
458
459} // end namespace comments
460} // end namespace clang
461