blob: 6c37452e070bb9fa450e725b57350bdd93b176c3 [file] [log] [blame]
Dmitri Gribenkoec925312012-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 Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +000015#include "llvm/ADT/StringSwitch.h"
16
17namespace clang {
18namespace comments {
19
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-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 Gribenkof26054f2012-07-11 21:38:39 +000051 checkBlockCommandEmptyParagraph(Command);
Dmitri Gribenkoec925312012-07-06 00:28:32 +000052 return Command;
53}
54
55ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin,
56 SourceLocation LocEnd,
57 StringRef Name) {
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +000068}
69
Dmitri Gribenkof26054f2012-07-11 21:38:39 +000070ParamCommandComment *Sema::actOnParamCommandDirectionArg(
71 ParamCommandComment *Command,
Dmitri Gribenkoec925312012-07-06 00:28:32 +000072 SourceLocation ArgLocBegin,
73 SourceLocation ArgLocEnd,
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +000096
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +0000107 } else {
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000108 Direction = ParamCommandComment::In;
109 RemovingWhitespaceHelped = false;
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000110 }
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +0000122 }
Dmitri Gribenkof26054f2012-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 Gribenko619e75e2012-07-13 19:02:42 +0000133 assert(Command->getNumArgs() == 0);
Dmitri Gribenkof26054f2012-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 Gribenkoec925312012-07-06 00:28:32 +0000191 return Command;
192}
193
194ParamCommandComment *Sema::actOnParamCommandFinish(ParamCommandComment *Command,
195 ParagraphComment *Paragraph) {
196 Command->setParagraph(Paragraph);
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000197 checkBlockCommandEmptyParagraph(Command);
Dmitri Gribenkoec925312012-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;
Dmitri Gribenkod73e4ce2012-07-23 16:43:01 +0000205 return new (Allocator) InlineCommandComment(
206 CommandLocBegin,
207 CommandLocEnd,
208 CommandName,
209 getInlineCommandRenderKind(CommandName),
210 Args);
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000211}
212
213InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
214 SourceLocation CommandLocEnd,
215 StringRef CommandName,
216 SourceLocation ArgLocBegin,
217 SourceLocation ArgLocEnd,
218 StringRef Arg) {
219 typedef InlineCommandComment::Argument Argument;
220 Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin,
221 ArgLocEnd),
222 Arg);
223
Dmitri Gribenkod73e4ce2012-07-23 16:43:01 +0000224 return new (Allocator) InlineCommandComment(
225 CommandLocBegin,
226 CommandLocEnd,
227 CommandName,
228 getInlineCommandRenderKind(CommandName),
229 llvm::makeArrayRef(A, 1));
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000230}
231
232InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
233 SourceLocation LocEnd,
234 StringRef Name) {
235 ArrayRef<InlineCommandComment::Argument> Args;
Dmitri Gribenkod73e4ce2012-07-23 16:43:01 +0000236 return new (Allocator) InlineCommandComment(
237 LocBegin, LocEnd, Name,
238 InlineCommandComment::RenderNormal,
239 Args);
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000240}
241
242TextComment *Sema::actOnText(SourceLocation LocBegin,
243 SourceLocation LocEnd,
244 StringRef Text) {
245 return new (Allocator) TextComment(LocBegin, LocEnd, Text);
246}
247
248VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc,
249 StringRef Name) {
250 return new (Allocator) VerbatimBlockComment(
251 Loc,
252 Loc.getLocWithOffset(1 + Name.size()),
253 Name);
254}
255
256VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc,
257 StringRef Text) {
258 return new (Allocator) VerbatimBlockLineComment(Loc, Text);
259}
260
261VerbatimBlockComment *Sema::actOnVerbatimBlockFinish(
262 VerbatimBlockComment *Block,
263 SourceLocation CloseNameLocBegin,
264 StringRef CloseName,
265 ArrayRef<VerbatimBlockLineComment *> Lines) {
266 Block->setCloseName(CloseName, CloseNameLocBegin);
267 Block->setLines(Lines);
268 return Block;
269}
270
271VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin,
272 StringRef Name,
273 SourceLocation TextBegin,
274 StringRef Text) {
275 return new (Allocator) VerbatimLineComment(
276 LocBegin,
277 TextBegin.getLocWithOffset(Text.size()),
278 Name,
279 TextBegin,
280 Text);
281}
282
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000283HTMLStartTagComment *Sema::actOnHTMLStartTagStart(SourceLocation LocBegin,
284 StringRef TagName) {
285 return new (Allocator) HTMLStartTagComment(LocBegin, TagName);
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000286}
287
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000288HTMLStartTagComment *Sema::actOnHTMLStartTagFinish(
289 HTMLStartTagComment *Tag,
290 ArrayRef<HTMLStartTagComment::Attribute> Attrs,
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000291 SourceLocation GreaterLoc,
292 bool IsSelfClosing) {
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000293 Tag->setAttrs(Attrs);
294 Tag->setGreaterLoc(GreaterLoc);
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000295 if (IsSelfClosing)
296 Tag->setSelfClosing();
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000297 else if (!isHTMLEndTagForbidden(Tag->getTagName()))
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000298 HTMLOpenTags.push_back(Tag);
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000299 return Tag;
300}
301
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000302HTMLEndTagComment *Sema::actOnHTMLEndTag(SourceLocation LocBegin,
303 SourceLocation LocEnd,
304 StringRef TagName) {
305 HTMLEndTagComment *HET =
306 new (Allocator) HTMLEndTagComment(LocBegin, LocEnd, TagName);
307 if (isHTMLEndTagForbidden(TagName)) {
308 Diag(HET->getLocation(), diag::warn_doc_html_end_forbidden)
309 << TagName << HET->getSourceRange();
310 return HET;
Dmitri Gribenko9460fbf2012-07-12 23:37:09 +0000311 }
312
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000313 bool FoundOpen = false;
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000314 for (SmallVectorImpl<HTMLStartTagComment *>::const_reverse_iterator
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000315 I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
316 I != E; ++I) {
317 if ((*I)->getTagName() == TagName) {
318 FoundOpen = true;
319 break;
320 }
321 }
322 if (!FoundOpen) {
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000323 Diag(HET->getLocation(), diag::warn_doc_html_end_unbalanced)
324 << HET->getSourceRange();
325 return HET;
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000326 }
327
328 while (!HTMLOpenTags.empty()) {
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000329 const HTMLStartTagComment *HST = HTMLOpenTags.back();
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000330 HTMLOpenTags.pop_back();
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000331 StringRef LastNotClosedTagName = HST->getTagName();
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000332 if (LastNotClosedTagName == TagName)
333 break;
334
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000335 if (isHTMLEndTagOptional(LastNotClosedTagName))
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000336 continue;
337
338 bool OpenLineInvalid;
339 const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000340 HST->getLocation(),
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000341 &OpenLineInvalid);
342 bool CloseLineInvalid;
343 const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000344 HET->getLocation(),
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000345 &CloseLineInvalid);
346
347 if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine)
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000348 Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch)
349 << HST->getTagName() << HET->getTagName()
350 << HST->getSourceRange() << HET->getSourceRange();
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000351 else {
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000352 Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch)
353 << HST->getTagName() << HET->getTagName()
354 << HST->getSourceRange();
355 Diag(HET->getLocation(), diag::note_doc_html_end_tag)
356 << HET->getSourceRange();
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000357 }
358 }
359
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000360 return HET;
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000361}
362
363FullComment *Sema::actOnFullComment(
364 ArrayRef<BlockContentComment *> Blocks) {
365 return new (Allocator) FullComment(Blocks);
366}
367
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000368void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) {
369 ParagraphComment *Paragraph = Command->getParagraph();
370 if (Paragraph->isWhitespace()) {
371 SourceLocation DiagLoc;
Dmitri Gribenko619e75e2012-07-13 19:02:42 +0000372 if (Command->getNumArgs() > 0)
373 DiagLoc = Command->getArgRange(Command->getNumArgs() - 1).getEnd();
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000374 if (!DiagLoc.isValid())
375 DiagLoc = Command->getCommandNameRange().getEnd();
376 Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
377 << Command->getCommandName()
378 << Command->getSourceRange();
379 }
380}
381
382unsigned Sema::resolveParmVarReference(StringRef Name,
383 const ParmVarDecl * const *ParamVars,
384 unsigned NumParams) {
385 for (unsigned i = 0; i != NumParams; ++i) {
386 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
387 if (II && II->getName() == Name)
388 return i;
389 }
390 return ParamCommandComment::InvalidParamIndex;
391}
392
393unsigned Sema::correctTypoInParmVarReference(
394 StringRef Typo,
395 const ParmVarDecl * const *ParamVars,
396 unsigned NumParams) {
397 const unsigned MaxEditDistance = (Typo.size() + 2) / 3;
Richard Smith3d460522012-07-11 22:33:59 +0000398 unsigned BestPVDIndex = 0;
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000399 unsigned BestEditDistance = MaxEditDistance + 1;
400 for (unsigned i = 0; i != NumParams; ++i) {
401 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
402 if (II) {
403 StringRef Name = II->getName();
NAKAMURA Takumi0e449c42012-07-12 00:45:08 +0000404 unsigned MinPossibleEditDistance =
405 abs((int)Name.size() - (int)Typo.size());
Dmitri Gribenkof26054f2012-07-11 21:38:39 +0000406 if (MinPossibleEditDistance > 0 &&
407 Typo.size() / MinPossibleEditDistance < 3)
408 continue;
409
410 unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
411 if (EditDistance < BestEditDistance) {
412 BestEditDistance = EditDistance;
413 BestPVDIndex = i;
414 }
415 }
416 }
417
418 if (BestEditDistance <= MaxEditDistance)
419 return BestPVDIndex;
420 else
421 return ParamCommandComment::InvalidParamIndex;;
422}
423
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000424// TODO: tablegen
425bool Sema::isBlockCommand(StringRef Name) {
426 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenko31005192012-07-18 00:44:55 +0000427 .Cases("brief", "short", true)
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000428 .Case("result", true)
429 .Case("return", true)
430 .Case("returns", true)
431 .Case("author", true)
432 .Case("authors", true)
433 .Case("pre", true)
434 .Case("post", true)
435 .Default(false) || isParamCommand(Name);
436}
437
438bool Sema::isParamCommand(StringRef Name) {
439 return llvm::StringSwitch<bool>(Name)
440 .Case("param", true)
441 .Case("arg", true)
442 .Default(false);
443}
444
445unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
446 return llvm::StringSwitch<unsigned>(Name)
Dmitri Gribenko31005192012-07-18 00:44:55 +0000447 .Cases("brief", "short", 0)
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000448 .Case("pre", 0)
449 .Case("post", 0)
450 .Case("author", 0)
451 .Case("authors", 0)
452 .Default(0);
453}
454
Dmitri Gribenkod73e4ce2012-07-23 16:43:01 +0000455bool Sema::isInlineCommand(StringRef Name) const {
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000456 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenkoe82ae812012-07-19 00:21:03 +0000457 .Case("b", true)
458 .Cases("c", "p", true)
459 .Cases("a", "e", "em", true)
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000460 .Default(false);
461}
462
Dmitri Gribenkod73e4ce2012-07-23 16:43:01 +0000463InlineCommandComment::RenderKind
464Sema::getInlineCommandRenderKind(StringRef Name) const {
465 assert(isInlineCommand(Name));
466
467 return llvm::StringSwitch<InlineCommandComment::RenderKind>(Name)
468 .Case("b", InlineCommandComment::RenderBold)
469 .Cases("c", "p", InlineCommandComment::RenderMonospaced)
470 .Cases("a", "e", "em", InlineCommandComment::RenderEmphasized)
471 .Default(InlineCommandComment::RenderNormal);
472}
473
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000474bool Sema::isHTMLEndTagOptional(StringRef Name) {
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000475 return llvm::StringSwitch<bool>(Name)
Dmitri Gribenko9460fbf2012-07-12 23:37:09 +0000476 .Case("p", true)
477 .Case("li", true)
478 .Case("dt", true)
479 .Case("dd", true)
480 .Case("tr", true)
481 .Case("th", true)
482 .Case("td", true)
483 .Case("thead", true)
484 .Case("tfoot", true)
485 .Case("tbody", true)
486 .Case("colgroup", true)
487 .Default(false);
488}
489
Dmitri Gribenkoe00ffc72012-07-13 00:44:24 +0000490bool Sema::isHTMLEndTagForbidden(StringRef Name) {
Dmitri Gribenko9460fbf2012-07-12 23:37:09 +0000491 return llvm::StringSwitch<bool>(Name)
492 .Case("br", true)
493 .Case("hr", true)
494 .Case("img", true)
495 .Case("col", true)
496 .Default(false);
Dmitri Gribenkoec925312012-07-06 00:28:32 +0000497}
498
499} // end namespace comments
500} // end namespace clang
501