|  | //===--- CommentSema.cpp - Doxygen comment semantic analysis --------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/AST/CommentSema.h" | 
|  | #include "clang/AST/Attr.h" | 
|  | #include "clang/AST/CommentCommandTraits.h" | 
|  | #include "clang/AST/Decl.h" | 
|  | #include "clang/AST/DeclTemplate.h" | 
|  | #include "clang/Basic/DiagnosticComment.h" | 
|  | #include "clang/Basic/LLVM.h" | 
|  | #include "clang/Basic/SourceManager.h" | 
|  | #include "clang/Lex/Preprocessor.h" | 
|  | #include "llvm/ADT/StringSwitch.h" | 
|  |  | 
|  | namespace clang { | 
|  | namespace comments { | 
|  |  | 
|  | namespace { | 
|  | #include "clang/AST/CommentHTMLTagsProperties.inc" | 
|  | } // end anonymous namespace | 
|  |  | 
|  | Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr, | 
|  | DiagnosticsEngine &Diags, CommandTraits &Traits, | 
|  | const Preprocessor *PP) : | 
|  | Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), Traits(Traits), | 
|  | PP(PP), ThisDeclInfo(nullptr), BriefCommand(nullptr), | 
|  | HeaderfileCommand(nullptr) { | 
|  | } | 
|  |  | 
|  | void Sema::setDecl(const Decl *D) { | 
|  | if (!D) | 
|  | return; | 
|  |  | 
|  | ThisDeclInfo = new (Allocator) DeclInfo; | 
|  | ThisDeclInfo->CommentDecl = D; | 
|  | ThisDeclInfo->IsFilled = false; | 
|  | } | 
|  |  | 
|  | ParagraphComment *Sema::actOnParagraphComment( | 
|  | ArrayRef<InlineContentComment *> Content) { | 
|  | return new (Allocator) ParagraphComment(Content); | 
|  | } | 
|  |  | 
|  | BlockCommandComment *Sema::actOnBlockCommandStart( | 
|  | SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | unsigned CommandID, | 
|  | CommandMarkerKind CommandMarker) { | 
|  | BlockCommandComment *BC = new (Allocator) BlockCommandComment(LocBegin, LocEnd, | 
|  | CommandID, | 
|  | CommandMarker); | 
|  | checkContainerDecl(BC); | 
|  | return BC; | 
|  | } | 
|  |  | 
|  | void Sema::actOnBlockCommandArgs(BlockCommandComment *Command, | 
|  | ArrayRef<BlockCommandComment::Argument> Args) { | 
|  | Command->setArgs(Args); | 
|  | } | 
|  |  | 
|  | void Sema::actOnBlockCommandFinish(BlockCommandComment *Command, | 
|  | ParagraphComment *Paragraph) { | 
|  | Command->setParagraph(Paragraph); | 
|  | checkBlockCommandEmptyParagraph(Command); | 
|  | checkBlockCommandDuplicate(Command); | 
|  | if (ThisDeclInfo) { | 
|  | // These checks only make sense if the comment is attached to a | 
|  | // declaration. | 
|  | checkReturnsCommand(Command); | 
|  | checkDeprecatedCommand(Command); | 
|  | } | 
|  | } | 
|  |  | 
|  | ParamCommandComment *Sema::actOnParamCommandStart( | 
|  | SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | unsigned CommandID, | 
|  | CommandMarkerKind CommandMarker) { | 
|  | ParamCommandComment *Command = | 
|  | new (Allocator) ParamCommandComment(LocBegin, LocEnd, CommandID, | 
|  | CommandMarker); | 
|  |  | 
|  | if (!involvesFunctionType()) | 
|  | Diag(Command->getLocation(), | 
|  | diag::warn_doc_param_not_attached_to_a_function_decl) | 
|  | << CommandMarker | 
|  | << Command->getCommandNameRange(Traits); | 
|  |  | 
|  | return Command; | 
|  | } | 
|  |  | 
|  | void Sema::checkFunctionDeclVerbatimLine(const BlockCommandComment *Comment) { | 
|  | const CommandInfo *Info = Traits.getCommandInfo(Comment->getCommandID()); | 
|  | if (!Info->IsFunctionDeclarationCommand) | 
|  | return; | 
|  |  | 
|  | unsigned DiagSelect; | 
|  | switch (Comment->getCommandID()) { | 
|  | case CommandTraits::KCI_function: | 
|  | DiagSelect = (!isAnyFunctionDecl() && !isFunctionTemplateDecl())? 1 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_functiongroup: | 
|  | DiagSelect = (!isAnyFunctionDecl() && !isFunctionTemplateDecl())? 2 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_method: | 
|  | DiagSelect = !isObjCMethodDecl() ? 3 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_methodgroup: | 
|  | DiagSelect = !isObjCMethodDecl() ? 4 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_callback: | 
|  | DiagSelect = !isFunctionPointerVarDecl() ? 5 : 0; | 
|  | break; | 
|  | default: | 
|  | DiagSelect = 0; | 
|  | break; | 
|  | } | 
|  | if (DiagSelect) | 
|  | Diag(Comment->getLocation(), diag::warn_doc_function_method_decl_mismatch) | 
|  | << Comment->getCommandMarker() | 
|  | << (DiagSelect-1) << (DiagSelect-1) | 
|  | << Comment->getSourceRange(); | 
|  | } | 
|  |  | 
|  | void Sema::checkContainerDeclVerbatimLine(const BlockCommandComment *Comment) { | 
|  | const CommandInfo *Info = Traits.getCommandInfo(Comment->getCommandID()); | 
|  | if (!Info->IsRecordLikeDeclarationCommand) | 
|  | return; | 
|  | unsigned DiagSelect; | 
|  | switch (Comment->getCommandID()) { | 
|  | case CommandTraits::KCI_class: | 
|  | DiagSelect = | 
|  | (!isClassOrStructOrTagTypedefDecl() && !isClassTemplateDecl()) ? 1 | 
|  | : 0; | 
|  | // Allow @class command on @interface declarations. | 
|  | // FIXME. Currently, \class and @class are indistinguishable. So, | 
|  | // \class is also allowed on an @interface declaration | 
|  | if (DiagSelect && Comment->getCommandMarker() && isObjCInterfaceDecl()) | 
|  | DiagSelect = 0; | 
|  | break; | 
|  | case CommandTraits::KCI_interface: | 
|  | DiagSelect = !isObjCInterfaceDecl() ? 2 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_protocol: | 
|  | DiagSelect = !isObjCProtocolDecl() ? 3 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_struct: | 
|  | DiagSelect = !isClassOrStructOrTagTypedefDecl() ? 4 : 0; | 
|  | break; | 
|  | case CommandTraits::KCI_union: | 
|  | DiagSelect = !isUnionDecl() ? 5 : 0; | 
|  | break; | 
|  | default: | 
|  | DiagSelect = 0; | 
|  | break; | 
|  | } | 
|  | if (DiagSelect) | 
|  | Diag(Comment->getLocation(), diag::warn_doc_api_container_decl_mismatch) | 
|  | << Comment->getCommandMarker() | 
|  | << (DiagSelect-1) << (DiagSelect-1) | 
|  | << Comment->getSourceRange(); | 
|  | } | 
|  |  | 
|  | void Sema::checkContainerDecl(const BlockCommandComment *Comment) { | 
|  | const CommandInfo *Info = Traits.getCommandInfo(Comment->getCommandID()); | 
|  | if (!Info->IsRecordLikeDetailCommand || isRecordLikeDecl()) | 
|  | return; | 
|  | unsigned DiagSelect; | 
|  | switch (Comment->getCommandID()) { | 
|  | case CommandTraits::KCI_classdesign: | 
|  | DiagSelect = 1; | 
|  | break; | 
|  | case CommandTraits::KCI_coclass: | 
|  | DiagSelect = 2; | 
|  | break; | 
|  | case CommandTraits::KCI_dependency: | 
|  | DiagSelect = 3; | 
|  | break; | 
|  | case CommandTraits::KCI_helper: | 
|  | DiagSelect = 4; | 
|  | break; | 
|  | case CommandTraits::KCI_helperclass: | 
|  | DiagSelect = 5; | 
|  | break; | 
|  | case CommandTraits::KCI_helps: | 
|  | DiagSelect = 6; | 
|  | break; | 
|  | case CommandTraits::KCI_instancesize: | 
|  | DiagSelect = 7; | 
|  | break; | 
|  | case CommandTraits::KCI_ownership: | 
|  | DiagSelect = 8; | 
|  | break; | 
|  | case CommandTraits::KCI_performance: | 
|  | DiagSelect = 9; | 
|  | break; | 
|  | case CommandTraits::KCI_security: | 
|  | DiagSelect = 10; | 
|  | break; | 
|  | case CommandTraits::KCI_superclass: | 
|  | DiagSelect = 11; | 
|  | break; | 
|  | default: | 
|  | DiagSelect = 0; | 
|  | break; | 
|  | } | 
|  | if (DiagSelect) | 
|  | Diag(Comment->getLocation(), diag::warn_doc_container_decl_mismatch) | 
|  | << Comment->getCommandMarker() | 
|  | << (DiagSelect-1) | 
|  | << Comment->getSourceRange(); | 
|  | } | 
|  |  | 
|  | /// Turn a string into the corresponding PassDirection or -1 if it's not | 
|  | /// valid. | 
|  | static ParamCommandPassDirection getParamPassDirection(StringRef Arg) { | 
|  | return llvm::StringSwitch<ParamCommandPassDirection>(Arg) | 
|  | .Case("[in]", ParamCommandPassDirection::In) | 
|  | .Case("[out]", ParamCommandPassDirection::Out) | 
|  | .Cases("[in,out]", "[out,in]", ParamCommandPassDirection::InOut) | 
|  | .Default(static_cast<ParamCommandPassDirection>(-1)); | 
|  | } | 
|  |  | 
|  | void Sema::actOnParamCommandDirectionArg(ParamCommandComment *Command, | 
|  | SourceLocation ArgLocBegin, | 
|  | SourceLocation ArgLocEnd, | 
|  | StringRef Arg) { | 
|  | std::string ArgLower = Arg.lower(); | 
|  | ParamCommandPassDirection Direction = getParamPassDirection(ArgLower); | 
|  |  | 
|  | if (Direction == static_cast<ParamCommandPassDirection>(-1)) { | 
|  | // Try again with whitespace removed. | 
|  | llvm::erase_if(ArgLower, clang::isWhitespace); | 
|  | Direction = getParamPassDirection(ArgLower); | 
|  |  | 
|  | SourceRange ArgRange(ArgLocBegin, ArgLocEnd); | 
|  | if (Direction != static_cast<ParamCommandPassDirection>(-1)) { | 
|  | const char *FixedName = | 
|  | ParamCommandComment::getDirectionAsString(Direction); | 
|  | Diag(ArgLocBegin, diag::warn_doc_param_spaces_in_direction) | 
|  | << ArgRange << FixItHint::CreateReplacement(ArgRange, FixedName); | 
|  | } else { | 
|  | Diag(ArgLocBegin, diag::warn_doc_param_invalid_direction) << ArgRange; | 
|  | Direction = ParamCommandPassDirection::In; // Sane fall back. | 
|  | } | 
|  | } | 
|  | Command->setDirection(Direction, | 
|  | /*Explicit=*/true); | 
|  | } | 
|  |  | 
|  | void Sema::actOnParamCommandParamNameArg(ParamCommandComment *Command, | 
|  | SourceLocation ArgLocBegin, | 
|  | SourceLocation ArgLocEnd, | 
|  | StringRef Arg) { | 
|  | // Parser will not feed us more arguments than needed. | 
|  | assert(Command->getNumArgs() == 0); | 
|  |  | 
|  | if (!Command->isDirectionExplicit()) { | 
|  | // User didn't provide a direction argument. | 
|  | Command->setDirection(ParamCommandPassDirection::In, | 
|  | /* Explicit = */ false); | 
|  | } | 
|  | auto *A = new (Allocator) | 
|  | Comment::Argument{SourceRange(ArgLocBegin, ArgLocEnd), Arg}; | 
|  | Command->setArgs(llvm::ArrayRef(A, 1)); | 
|  | } | 
|  |  | 
|  | void Sema::actOnParamCommandFinish(ParamCommandComment *Command, | 
|  | ParagraphComment *Paragraph) { | 
|  | Command->setParagraph(Paragraph); | 
|  | checkBlockCommandEmptyParagraph(Command); | 
|  | } | 
|  |  | 
|  | TParamCommandComment *Sema::actOnTParamCommandStart( | 
|  | SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | unsigned CommandID, | 
|  | CommandMarkerKind CommandMarker) { | 
|  | TParamCommandComment *Command = | 
|  | new (Allocator) TParamCommandComment(LocBegin, LocEnd, CommandID, | 
|  | CommandMarker); | 
|  |  | 
|  | if (!isTemplateOrSpecialization()) | 
|  | Diag(Command->getLocation(), | 
|  | diag::warn_doc_tparam_not_attached_to_a_template_decl) | 
|  | << CommandMarker | 
|  | << Command->getCommandNameRange(Traits); | 
|  |  | 
|  | return Command; | 
|  | } | 
|  |  | 
|  | void Sema::actOnTParamCommandParamNameArg(TParamCommandComment *Command, | 
|  | SourceLocation ArgLocBegin, | 
|  | SourceLocation ArgLocEnd, | 
|  | StringRef Arg) { | 
|  | // Parser will not feed us more arguments than needed. | 
|  | assert(Command->getNumArgs() == 0); | 
|  |  | 
|  | auto *A = new (Allocator) | 
|  | Comment::Argument{SourceRange(ArgLocBegin, ArgLocEnd), Arg}; | 
|  | Command->setArgs(llvm::ArrayRef(A, 1)); | 
|  |  | 
|  | if (!isTemplateOrSpecialization()) { | 
|  | // We already warned that this \\tparam is not attached to a template decl. | 
|  | return; | 
|  | } | 
|  |  | 
|  | const TemplateParameterList *TemplateParameters = | 
|  | ThisDeclInfo->TemplateParameters; | 
|  | SmallVector<unsigned, 2> Position; | 
|  | if (resolveTParamReference(Arg, TemplateParameters, &Position)) { | 
|  | Command->setPosition(copyArray(llvm::ArrayRef(Position))); | 
|  | TParamCommandComment *&PrevCommand = TemplateParameterDocs[Arg]; | 
|  | if (PrevCommand) { | 
|  | SourceRange ArgRange(ArgLocBegin, ArgLocEnd); | 
|  | Diag(ArgLocBegin, diag::warn_doc_tparam_duplicate) | 
|  | << Arg << ArgRange; | 
|  | Diag(PrevCommand->getLocation(), diag::note_doc_tparam_previous) | 
|  | << PrevCommand->getParamNameRange(); | 
|  | } | 
|  | PrevCommand = Command; | 
|  | return; | 
|  | } | 
|  |  | 
|  | SourceRange ArgRange(ArgLocBegin, ArgLocEnd); | 
|  | Diag(ArgLocBegin, diag::warn_doc_tparam_not_found) | 
|  | << Arg << ArgRange; | 
|  |  | 
|  | if (!TemplateParameters || TemplateParameters->size() == 0) | 
|  | return; | 
|  |  | 
|  | StringRef CorrectedName; | 
|  | if (TemplateParameters->size() == 1) { | 
|  | const NamedDecl *Param = TemplateParameters->getParam(0); | 
|  | const IdentifierInfo *II = Param->getIdentifier(); | 
|  | if (II) | 
|  | CorrectedName = II->getName(); | 
|  | } else { | 
|  | CorrectedName = correctTypoInTParamReference(Arg, TemplateParameters); | 
|  | } | 
|  |  | 
|  | if (!CorrectedName.empty()) { | 
|  | Diag(ArgLocBegin, diag::note_doc_tparam_name_suggestion) | 
|  | << CorrectedName | 
|  | << FixItHint::CreateReplacement(ArgRange, CorrectedName); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Sema::actOnTParamCommandFinish(TParamCommandComment *Command, | 
|  | ParagraphComment *Paragraph) { | 
|  | Command->setParagraph(Paragraph); | 
|  | checkBlockCommandEmptyParagraph(Command); | 
|  | } | 
|  |  | 
|  | InlineCommandComment * | 
|  | Sema::actOnInlineCommand(SourceLocation CommandLocBegin, | 
|  | SourceLocation CommandLocEnd, unsigned CommandID, | 
|  | ArrayRef<Comment::Argument> Args) { | 
|  | StringRef CommandName = Traits.getCommandInfo(CommandID)->Name; | 
|  |  | 
|  | return new (Allocator) | 
|  | InlineCommandComment(CommandLocBegin, CommandLocEnd, CommandID, | 
|  | getInlineCommandRenderKind(CommandName), Args); | 
|  | } | 
|  |  | 
|  | InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | StringRef CommandName) { | 
|  | unsigned CommandID = Traits.registerUnknownCommand(CommandName)->getID(); | 
|  | return actOnUnknownCommand(LocBegin, LocEnd, CommandID); | 
|  | } | 
|  |  | 
|  | InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | unsigned CommandID) { | 
|  | ArrayRef<InlineCommandComment::Argument> Args; | 
|  | return new (Allocator) InlineCommandComment( | 
|  | LocBegin, LocEnd, CommandID, InlineCommandRenderKind::Normal, Args); | 
|  | } | 
|  |  | 
|  | TextComment *Sema::actOnText(SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | StringRef Text) { | 
|  | return new (Allocator) TextComment(LocBegin, LocEnd, Text); | 
|  | } | 
|  |  | 
|  | VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc, | 
|  | unsigned CommandID) { | 
|  | StringRef CommandName = Traits.getCommandInfo(CommandID)->Name; | 
|  | return new (Allocator) VerbatimBlockComment( | 
|  | Loc, | 
|  | Loc.getLocWithOffset(1 + CommandName.size()), | 
|  | CommandID); | 
|  | } | 
|  |  | 
|  | VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc, | 
|  | StringRef Text) { | 
|  | return new (Allocator) VerbatimBlockLineComment(Loc, Text); | 
|  | } | 
|  |  | 
|  | void Sema::actOnVerbatimBlockFinish( | 
|  | VerbatimBlockComment *Block, | 
|  | SourceLocation CloseNameLocBegin, | 
|  | StringRef CloseName, | 
|  | ArrayRef<VerbatimBlockLineComment *> Lines) { | 
|  | Block->setCloseName(CloseName, CloseNameLocBegin); | 
|  | Block->setLines(Lines); | 
|  | } | 
|  |  | 
|  | VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin, | 
|  | unsigned CommandID, | 
|  | SourceLocation TextBegin, | 
|  | StringRef Text) { | 
|  | VerbatimLineComment *VL = new (Allocator) VerbatimLineComment( | 
|  | LocBegin, | 
|  | TextBegin.getLocWithOffset(Text.size()), | 
|  | CommandID, | 
|  | TextBegin, | 
|  | Text); | 
|  | checkFunctionDeclVerbatimLine(VL); | 
|  | checkContainerDeclVerbatimLine(VL); | 
|  | return VL; | 
|  | } | 
|  |  | 
|  | HTMLStartTagComment *Sema::actOnHTMLStartTagStart(SourceLocation LocBegin, | 
|  | StringRef TagName) { | 
|  | return new (Allocator) HTMLStartTagComment(LocBegin, TagName); | 
|  | } | 
|  |  | 
|  | void Sema::actOnHTMLStartTagFinish( | 
|  | HTMLStartTagComment *Tag, | 
|  | ArrayRef<HTMLStartTagComment::Attribute> Attrs, | 
|  | SourceLocation GreaterLoc, | 
|  | bool IsSelfClosing) { | 
|  | Tag->setAttrs(Attrs); | 
|  | Tag->setGreaterLoc(GreaterLoc); | 
|  | if (IsSelfClosing) | 
|  | Tag->setSelfClosing(); | 
|  | else if (!isHTMLEndTagForbidden(Tag->getTagName())) | 
|  | HTMLOpenTags.push_back(Tag); | 
|  | } | 
|  |  | 
|  | HTMLEndTagComment *Sema::actOnHTMLEndTag(SourceLocation LocBegin, | 
|  | SourceLocation LocEnd, | 
|  | StringRef TagName) { | 
|  | HTMLEndTagComment *HET = | 
|  | new (Allocator) HTMLEndTagComment(LocBegin, LocEnd, TagName); | 
|  | if (isHTMLEndTagForbidden(TagName)) { | 
|  | Diag(HET->getLocation(), diag::warn_doc_html_end_forbidden) | 
|  | << TagName << HET->getSourceRange(); | 
|  | HET->setIsMalformed(); | 
|  | return HET; | 
|  | } | 
|  |  | 
|  | bool FoundOpen = false; | 
|  | for (SmallVectorImpl<HTMLStartTagComment *>::const_reverse_iterator | 
|  | I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend(); | 
|  | I != E; ++I) { | 
|  | if ((*I)->getTagName() == TagName) { | 
|  | FoundOpen = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!FoundOpen) { | 
|  | Diag(HET->getLocation(), diag::warn_doc_html_end_unbalanced) | 
|  | << HET->getSourceRange(); | 
|  | HET->setIsMalformed(); | 
|  | return HET; | 
|  | } | 
|  |  | 
|  | while (!HTMLOpenTags.empty()) { | 
|  | HTMLStartTagComment *HST = HTMLOpenTags.pop_back_val(); | 
|  | StringRef LastNotClosedTagName = HST->getTagName(); | 
|  | if (LastNotClosedTagName == TagName) { | 
|  | // If the start tag is malformed, end tag is malformed as well. | 
|  | if (HST->isMalformed()) | 
|  | HET->setIsMalformed(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (isHTMLEndTagOptional(LastNotClosedTagName)) | 
|  | continue; | 
|  |  | 
|  | bool OpenLineInvalid; | 
|  | const unsigned OpenLine = SourceMgr.getPresumedLineNumber( | 
|  | HST->getLocation(), | 
|  | &OpenLineInvalid); | 
|  | bool CloseLineInvalid; | 
|  | const unsigned CloseLine = SourceMgr.getPresumedLineNumber( | 
|  | HET->getLocation(), | 
|  | &CloseLineInvalid); | 
|  |  | 
|  | if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine) { | 
|  | Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch) | 
|  | << HST->getTagName() << HET->getTagName() | 
|  | << HST->getSourceRange() << HET->getSourceRange(); | 
|  | HST->setIsMalformed(); | 
|  | } else { | 
|  | Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch) | 
|  | << HST->getTagName() << HET->getTagName() | 
|  | << HST->getSourceRange(); | 
|  | Diag(HET->getLocation(), diag::note_doc_html_end_tag) | 
|  | << HET->getSourceRange(); | 
|  | HST->setIsMalformed(); | 
|  | } | 
|  | } | 
|  |  | 
|  | return HET; | 
|  | } | 
|  |  | 
|  | FullComment *Sema::actOnFullComment( | 
|  | ArrayRef<BlockContentComment *> Blocks) { | 
|  | FullComment *FC = new (Allocator) FullComment(Blocks, ThisDeclInfo); | 
|  | resolveParamCommandIndexes(FC); | 
|  |  | 
|  | // Complain about HTML tags that are not closed. | 
|  | while (!HTMLOpenTags.empty()) { | 
|  | HTMLStartTagComment *HST = HTMLOpenTags.pop_back_val(); | 
|  | if (isHTMLEndTagOptional(HST->getTagName())) | 
|  | continue; | 
|  |  | 
|  | Diag(HST->getLocation(), diag::warn_doc_html_missing_end_tag) | 
|  | << HST->getTagName() << HST->getSourceRange(); | 
|  | HST->setIsMalformed(); | 
|  | } | 
|  |  | 
|  | return FC; | 
|  | } | 
|  |  | 
|  | void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) { | 
|  | if (Traits.getCommandInfo(Command->getCommandID())->IsEmptyParagraphAllowed) | 
|  | return; | 
|  |  | 
|  | ParagraphComment *Paragraph = Command->getParagraph(); | 
|  | if (Paragraph->isWhitespace()) { | 
|  | SourceLocation DiagLoc; | 
|  | if (Command->getNumArgs() > 0) | 
|  | DiagLoc = Command->getArgRange(Command->getNumArgs() - 1).getEnd(); | 
|  | if (!DiagLoc.isValid()) | 
|  | DiagLoc = Command->getCommandNameRange(Traits).getEnd(); | 
|  | Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph) | 
|  | << Command->getCommandMarker() | 
|  | << Command->getCommandName(Traits) | 
|  | << Command->getSourceRange(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Sema::checkReturnsCommand(const BlockCommandComment *Command) { | 
|  | if (!Traits.getCommandInfo(Command->getCommandID())->IsReturnsCommand) | 
|  | return; | 
|  |  | 
|  | assert(ThisDeclInfo && "should not call this check on a bare comment"); | 
|  |  | 
|  | // We allow the return command for all @properties because it can be used | 
|  | // to document the value that the property getter returns. | 
|  | if (isObjCPropertyDecl()) | 
|  | return; | 
|  | if (involvesFunctionType()) { | 
|  | assert(!ThisDeclInfo->ReturnType.isNull() && | 
|  | "should have a valid return type"); | 
|  | if (ThisDeclInfo->ReturnType->isVoidType()) { | 
|  | unsigned DiagKind; | 
|  | switch (ThisDeclInfo->CommentDecl->getKind()) { | 
|  | default: | 
|  | if (ThisDeclInfo->IsObjCMethod) | 
|  | DiagKind = 3; | 
|  | else | 
|  | DiagKind = 0; | 
|  | break; | 
|  | case Decl::CXXConstructor: | 
|  | DiagKind = 1; | 
|  | break; | 
|  | case Decl::CXXDestructor: | 
|  | DiagKind = 2; | 
|  | break; | 
|  | } | 
|  | Diag(Command->getLocation(), | 
|  | diag::warn_doc_returns_attached_to_a_void_function) | 
|  | << Command->getCommandMarker() | 
|  | << Command->getCommandName(Traits) | 
|  | << DiagKind | 
|  | << Command->getSourceRange(); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | Diag(Command->getLocation(), | 
|  | diag::warn_doc_returns_not_attached_to_a_function_decl) | 
|  | << Command->getCommandMarker() | 
|  | << Command->getCommandName(Traits) | 
|  | << Command->getSourceRange(); | 
|  | } | 
|  |  | 
|  | void Sema::checkBlockCommandDuplicate(const BlockCommandComment *Command) { | 
|  | const CommandInfo *Info = Traits.getCommandInfo(Command->getCommandID()); | 
|  | const BlockCommandComment *PrevCommand = nullptr; | 
|  | if (Info->IsBriefCommand) { | 
|  | if (!BriefCommand) { | 
|  | BriefCommand = Command; | 
|  | return; | 
|  | } | 
|  | PrevCommand = BriefCommand; | 
|  | } else if (Info->IsHeaderfileCommand) { | 
|  | if (!HeaderfileCommand) { | 
|  | HeaderfileCommand = Command; | 
|  | return; | 
|  | } | 
|  | PrevCommand = HeaderfileCommand; | 
|  | } else { | 
|  | // We don't want to check this command for duplicates. | 
|  | return; | 
|  | } | 
|  | StringRef CommandName = Command->getCommandName(Traits); | 
|  | StringRef PrevCommandName = PrevCommand->getCommandName(Traits); | 
|  | Diag(Command->getLocation(), diag::warn_doc_block_command_duplicate) | 
|  | << Command->getCommandMarker() | 
|  | << CommandName | 
|  | << Command->getSourceRange(); | 
|  | if (CommandName == PrevCommandName) | 
|  | Diag(PrevCommand->getLocation(), diag::note_doc_block_command_previous) | 
|  | << PrevCommand->getCommandMarker() | 
|  | << PrevCommandName | 
|  | << PrevCommand->getSourceRange(); | 
|  | else | 
|  | Diag(PrevCommand->getLocation(), | 
|  | diag::note_doc_block_command_previous_alias) | 
|  | << PrevCommand->getCommandMarker() | 
|  | << PrevCommandName | 
|  | << CommandName; | 
|  | } | 
|  |  | 
|  | void Sema::checkDeprecatedCommand(const BlockCommandComment *Command) { | 
|  | if (!Traits.getCommandInfo(Command->getCommandID())->IsDeprecatedCommand) | 
|  | return; | 
|  |  | 
|  | assert(ThisDeclInfo && "should not call this check on a bare comment"); | 
|  |  | 
|  | const Decl *D = ThisDeclInfo->CommentDecl; | 
|  | if (!D) | 
|  | return; | 
|  |  | 
|  | if (D->hasAttr<DeprecatedAttr>() || | 
|  | D->hasAttr<AvailabilityAttr>() || | 
|  | D->hasAttr<UnavailableAttr>()) | 
|  | return; | 
|  |  | 
|  | Diag(Command->getLocation(), diag::warn_doc_deprecated_not_sync) | 
|  | << Command->getSourceRange() << Command->getCommandMarker(); | 
|  |  | 
|  | // Try to emit a fixit with a deprecation attribute. | 
|  | if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { | 
|  | // Don't emit a Fix-It for non-member function definitions.  GCC does not | 
|  | // accept attributes on them. | 
|  | const DeclContext *Ctx = FD->getDeclContext(); | 
|  | if ((!Ctx || !Ctx->isRecord()) && | 
|  | FD->doesThisDeclarationHaveABody()) | 
|  | return; | 
|  |  | 
|  | const LangOptions &LO = FD->getLangOpts(); | 
|  | const bool DoubleSquareBracket = LO.CPlusPlus14 || LO.C23; | 
|  | StringRef AttributeSpelling = | 
|  | DoubleSquareBracket ? "[[deprecated]]" : "__attribute__((deprecated))"; | 
|  | if (PP) { | 
|  | // Try to find a replacement macro: | 
|  | // - In C23/C++14 we prefer [[deprecated]]. | 
|  | // - If not found or an older C/C++ look for __attribute__((deprecated)). | 
|  | StringRef MacroName; | 
|  | if (DoubleSquareBracket) { | 
|  | TokenValue Tokens[] = {tok::l_square, tok::l_square, | 
|  | PP->getIdentifierInfo("deprecated"), | 
|  | tok::r_square, tok::r_square}; | 
|  | MacroName = PP->getLastMacroWithSpelling(FD->getLocation(), Tokens); | 
|  | if (!MacroName.empty()) | 
|  | AttributeSpelling = MacroName; | 
|  | } | 
|  |  | 
|  | if (MacroName.empty()) { | 
|  | TokenValue Tokens[] = { | 
|  | tok::kw___attribute, tok::l_paren, | 
|  | tok::l_paren,        PP->getIdentifierInfo("deprecated"), | 
|  | tok::r_paren,        tok::r_paren}; | 
|  | StringRef MacroName = | 
|  | PP->getLastMacroWithSpelling(FD->getLocation(), Tokens); | 
|  | if (!MacroName.empty()) | 
|  | AttributeSpelling = MacroName; | 
|  | } | 
|  | } | 
|  |  | 
|  | SmallString<64> TextToInsert = AttributeSpelling; | 
|  | TextToInsert += " "; | 
|  | SourceLocation Loc = FD->getSourceRange().getBegin(); | 
|  | Diag(Loc, diag::note_add_deprecation_attr) | 
|  | << FixItHint::CreateInsertion(Loc, TextToInsert); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Sema::resolveParamCommandIndexes(const FullComment *FC) { | 
|  | if (!involvesFunctionType()) { | 
|  | // We already warned that \\param commands are not attached to a function | 
|  | // decl. | 
|  | return; | 
|  | } | 
|  |  | 
|  | SmallVector<ParamCommandComment *, 8> UnresolvedParamCommands; | 
|  |  | 
|  | // Comment AST nodes that correspond to \c ParamVars for which we have | 
|  | // found a \\param command or NULL if no documentation was found so far. | 
|  | SmallVector<ParamCommandComment *, 8> ParamVarDocs; | 
|  |  | 
|  | ArrayRef<const ParmVarDecl *> ParamVars = getParamVars(); | 
|  | ParamVarDocs.resize(ParamVars.size(), nullptr); | 
|  |  | 
|  | // First pass over all \\param commands: resolve all parameter names. | 
|  | for (Comment::child_iterator I = FC->child_begin(), E = FC->child_end(); | 
|  | I != E; ++I) { | 
|  | ParamCommandComment *PCC = dyn_cast<ParamCommandComment>(*I); | 
|  | if (!PCC || !PCC->hasParamName()) | 
|  | continue; | 
|  | StringRef ParamName = PCC->getParamNameAsWritten(); | 
|  |  | 
|  | // Check that referenced parameter name is in the function decl. | 
|  | const unsigned ResolvedParamIndex = resolveParmVarReference(ParamName, | 
|  | ParamVars); | 
|  | if (ResolvedParamIndex == ParamCommandComment::VarArgParamIndex) { | 
|  | PCC->setIsVarArgParam(); | 
|  | continue; | 
|  | } | 
|  | if (ResolvedParamIndex == ParamCommandComment::InvalidParamIndex) { | 
|  | UnresolvedParamCommands.push_back(PCC); | 
|  | continue; | 
|  | } | 
|  | PCC->setParamIndex(ResolvedParamIndex); | 
|  | if (ParamVarDocs[ResolvedParamIndex]) { | 
|  | SourceRange ArgRange = PCC->getParamNameRange(); | 
|  | Diag(ArgRange.getBegin(), diag::warn_doc_param_duplicate) | 
|  | << ParamName << ArgRange; | 
|  | ParamCommandComment *PrevCommand = ParamVarDocs[ResolvedParamIndex]; | 
|  | Diag(PrevCommand->getLocation(), diag::note_doc_param_previous) | 
|  | << PrevCommand->getParamNameRange(); | 
|  | } | 
|  | ParamVarDocs[ResolvedParamIndex] = PCC; | 
|  | } | 
|  |  | 
|  | // Find parameter declarations that have no corresponding \\param. | 
|  | SmallVector<const ParmVarDecl *, 8> OrphanedParamDecls; | 
|  | for (unsigned i = 0, e = ParamVarDocs.size(); i != e; ++i) { | 
|  | if (!ParamVarDocs[i]) | 
|  | OrphanedParamDecls.push_back(ParamVars[i]); | 
|  | } | 
|  |  | 
|  | // Second pass over unresolved \\param commands: do typo correction. | 
|  | // Suggest corrections from a set of parameter declarations that have no | 
|  | // corresponding \\param. | 
|  | for (unsigned i = 0, e = UnresolvedParamCommands.size(); i != e; ++i) { | 
|  | const ParamCommandComment *PCC = UnresolvedParamCommands[i]; | 
|  |  | 
|  | SourceRange ArgRange = PCC->getParamNameRange(); | 
|  | StringRef ParamName = PCC->getParamNameAsWritten(); | 
|  | Diag(ArgRange.getBegin(), diag::warn_doc_param_not_found) | 
|  | << ParamName << ArgRange; | 
|  |  | 
|  | // All parameters documented -- can't suggest a correction. | 
|  | if (OrphanedParamDecls.size() == 0) | 
|  | continue; | 
|  |  | 
|  | unsigned CorrectedParamIndex = ParamCommandComment::InvalidParamIndex; | 
|  | if (OrphanedParamDecls.size() == 1) { | 
|  | // If one parameter is not documented then that parameter is the only | 
|  | // possible suggestion. | 
|  | CorrectedParamIndex = 0; | 
|  | } else { | 
|  | // Do typo correction. | 
|  | CorrectedParamIndex = correctTypoInParmVarReference(ParamName, | 
|  | OrphanedParamDecls); | 
|  | } | 
|  | if (CorrectedParamIndex != ParamCommandComment::InvalidParamIndex) { | 
|  | const ParmVarDecl *CorrectedPVD = OrphanedParamDecls[CorrectedParamIndex]; | 
|  | if (const IdentifierInfo *CorrectedII = CorrectedPVD->getIdentifier()) | 
|  | Diag(ArgRange.getBegin(), diag::note_doc_param_name_suggestion) | 
|  | << CorrectedII->getName() | 
|  | << FixItHint::CreateReplacement(ArgRange, CorrectedII->getName()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Sema::involvesFunctionType() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->involvesFunctionType(); | 
|  | } | 
|  |  | 
|  | bool Sema::isFunctionDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->getKind() == DeclInfo::FunctionKind; | 
|  | } | 
|  |  | 
|  | bool Sema::isAnyFunctionDecl() { | 
|  | return isFunctionDecl() && ThisDeclInfo->CurrentDecl && | 
|  | isa<FunctionDecl>(ThisDeclInfo->CurrentDecl); | 
|  | } | 
|  |  | 
|  | bool Sema::isFunctionOrMethodVariadic() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->IsVariadic; | 
|  | } | 
|  |  | 
|  | bool Sema::isObjCMethodDecl() { | 
|  | return isFunctionDecl() && ThisDeclInfo->CurrentDecl && | 
|  | isa<ObjCMethodDecl>(ThisDeclInfo->CurrentDecl); | 
|  | } | 
|  |  | 
|  | bool Sema::isFunctionPointerVarDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | if (ThisDeclInfo->getKind() == DeclInfo::VariableKind) { | 
|  | if (const VarDecl *VD = dyn_cast_or_null<VarDecl>(ThisDeclInfo->CurrentDecl)) { | 
|  | QualType QT = VD->getType(); | 
|  | return QT->isFunctionPointerType(); | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Sema::isObjCPropertyDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->CurrentDecl->getKind() == Decl::ObjCProperty; | 
|  | } | 
|  |  | 
|  | bool Sema::isTemplateOrSpecialization() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->getTemplateKind() != DeclInfo::NotTemplate; | 
|  | } | 
|  |  | 
|  | bool Sema::isRecordLikeDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return isUnionDecl() || isClassOrStructDecl() || isObjCInterfaceDecl() || | 
|  | isObjCProtocolDecl(); | 
|  | } | 
|  |  | 
|  | bool Sema::isUnionDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | if (const RecordDecl *RD = | 
|  | dyn_cast_or_null<RecordDecl>(ThisDeclInfo->CurrentDecl)) | 
|  | return RD->isUnion(); | 
|  | return false; | 
|  | } | 
|  | static bool isClassOrStructDeclImpl(const Decl *D) { | 
|  | if (auto *record = dyn_cast_or_null<RecordDecl>(D)) | 
|  | return !record->isUnion(); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Sema::isClassOrStructDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  |  | 
|  | if (!ThisDeclInfo->CurrentDecl) | 
|  | return false; | 
|  |  | 
|  | return isClassOrStructDeclImpl(ThisDeclInfo->CurrentDecl); | 
|  | } | 
|  |  | 
|  | bool Sema::isClassOrStructOrTagTypedefDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  |  | 
|  | if (!ThisDeclInfo->CurrentDecl) | 
|  | return false; | 
|  |  | 
|  | if (isClassOrStructDeclImpl(ThisDeclInfo->CurrentDecl)) | 
|  | return true; | 
|  |  | 
|  | if (auto *ThisTypedefDecl = dyn_cast<TypedefDecl>(ThisDeclInfo->CurrentDecl)) { | 
|  | auto UnderlyingType = ThisTypedefDecl->getUnderlyingType(); | 
|  | if (auto ThisElaboratedType = dyn_cast<ElaboratedType>(UnderlyingType)) { | 
|  | auto DesugaredType = ThisElaboratedType->desugar(); | 
|  | if (auto *DesugaredTypePtr = DesugaredType.getTypePtrOrNull()) { | 
|  | if (auto *ThisRecordType = dyn_cast<RecordType>(DesugaredTypePtr)) { | 
|  | return isClassOrStructDeclImpl(ThisRecordType->getAsRecordDecl()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Sema::isClassTemplateDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->CurrentDecl && | 
|  | (isa<ClassTemplateDecl>(ThisDeclInfo->CurrentDecl)); | 
|  | } | 
|  |  | 
|  | bool Sema::isFunctionTemplateDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->CurrentDecl && | 
|  | (isa<FunctionTemplateDecl>(ThisDeclInfo->CurrentDecl)); | 
|  | } | 
|  |  | 
|  | bool Sema::isObjCInterfaceDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->CurrentDecl && | 
|  | isa<ObjCInterfaceDecl>(ThisDeclInfo->CurrentDecl); | 
|  | } | 
|  |  | 
|  | bool Sema::isObjCProtocolDecl() { | 
|  | if (!ThisDeclInfo) | 
|  | return false; | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->CurrentDecl && | 
|  | isa<ObjCProtocolDecl>(ThisDeclInfo->CurrentDecl); | 
|  | } | 
|  |  | 
|  | ArrayRef<const ParmVarDecl *> Sema::getParamVars() { | 
|  | if (!ThisDeclInfo->IsFilled) | 
|  | inspectThisDecl(); | 
|  | return ThisDeclInfo->ParamVars; | 
|  | } | 
|  |  | 
|  | void Sema::inspectThisDecl() { | 
|  | ThisDeclInfo->fill(); | 
|  | } | 
|  |  | 
|  | unsigned Sema::resolveParmVarReference(StringRef Name, | 
|  | ArrayRef<const ParmVarDecl *> ParamVars) { | 
|  | for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) { | 
|  | const IdentifierInfo *II = ParamVars[i]->getIdentifier(); | 
|  | if (II && II->getName() == Name) | 
|  | return i; | 
|  | } | 
|  | if (Name == "..." && isFunctionOrMethodVariadic()) | 
|  | return ParamCommandComment::VarArgParamIndex; | 
|  | return ParamCommandComment::InvalidParamIndex; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | class SimpleTypoCorrector { | 
|  | const NamedDecl *BestDecl; | 
|  |  | 
|  | StringRef Typo; | 
|  | const unsigned MaxEditDistance; | 
|  |  | 
|  | unsigned BestEditDistance; | 
|  | unsigned BestIndex; | 
|  | unsigned NextIndex; | 
|  |  | 
|  | public: | 
|  | explicit SimpleTypoCorrector(StringRef Typo) | 
|  | : BestDecl(nullptr), Typo(Typo), MaxEditDistance((Typo.size() + 2) / 3), | 
|  | BestEditDistance(MaxEditDistance + 1), BestIndex(0), NextIndex(0) {} | 
|  |  | 
|  | void addDecl(const NamedDecl *ND); | 
|  |  | 
|  | const NamedDecl *getBestDecl() const { | 
|  | if (BestEditDistance > MaxEditDistance) | 
|  | return nullptr; | 
|  |  | 
|  | return BestDecl; | 
|  | } | 
|  |  | 
|  | unsigned getBestDeclIndex() const { | 
|  | assert(getBestDecl()); | 
|  | return BestIndex; | 
|  | } | 
|  | }; | 
|  |  | 
|  | void SimpleTypoCorrector::addDecl(const NamedDecl *ND) { | 
|  | unsigned CurrIndex = NextIndex++; | 
|  |  | 
|  | const IdentifierInfo *II = ND->getIdentifier(); | 
|  | if (!II) | 
|  | return; | 
|  |  | 
|  | StringRef Name = II->getName(); | 
|  | unsigned MinPossibleEditDistance = abs((int)Name.size() - (int)Typo.size()); | 
|  | if (MinPossibleEditDistance > 0 && | 
|  | Typo.size() / MinPossibleEditDistance < 3) | 
|  | return; | 
|  |  | 
|  | unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance); | 
|  | if (EditDistance < BestEditDistance) { | 
|  | BestEditDistance = EditDistance; | 
|  | BestDecl = ND; | 
|  | BestIndex = CurrIndex; | 
|  | } | 
|  | } | 
|  | } // end anonymous namespace | 
|  |  | 
|  | unsigned Sema::correctTypoInParmVarReference( | 
|  | StringRef Typo, | 
|  | ArrayRef<const ParmVarDecl *> ParamVars) { | 
|  | SimpleTypoCorrector Corrector(Typo); | 
|  | for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) | 
|  | Corrector.addDecl(ParamVars[i]); | 
|  | if (Corrector.getBestDecl()) | 
|  | return Corrector.getBestDeclIndex(); | 
|  | else | 
|  | return ParamCommandComment::InvalidParamIndex; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | bool ResolveTParamReferenceHelper( | 
|  | StringRef Name, | 
|  | const TemplateParameterList *TemplateParameters, | 
|  | SmallVectorImpl<unsigned> *Position) { | 
|  | for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) { | 
|  | const NamedDecl *Param = TemplateParameters->getParam(i); | 
|  | const IdentifierInfo *II = Param->getIdentifier(); | 
|  | if (II && II->getName() == Name) { | 
|  | Position->push_back(i); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (const TemplateTemplateParmDecl *TTP = | 
|  | dyn_cast<TemplateTemplateParmDecl>(Param)) { | 
|  | Position->push_back(i); | 
|  | if (ResolveTParamReferenceHelper(Name, TTP->getTemplateParameters(), | 
|  | Position)) | 
|  | return true; | 
|  | Position->pop_back(); | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } // end anonymous namespace | 
|  |  | 
|  | bool Sema::resolveTParamReference( | 
|  | StringRef Name, | 
|  | const TemplateParameterList *TemplateParameters, | 
|  | SmallVectorImpl<unsigned> *Position) { | 
|  | Position->clear(); | 
|  | if (!TemplateParameters) | 
|  | return false; | 
|  |  | 
|  | return ResolveTParamReferenceHelper(Name, TemplateParameters, Position); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | void CorrectTypoInTParamReferenceHelper( | 
|  | const TemplateParameterList *TemplateParameters, | 
|  | SimpleTypoCorrector &Corrector) { | 
|  | for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) { | 
|  | const NamedDecl *Param = TemplateParameters->getParam(i); | 
|  | Corrector.addDecl(Param); | 
|  |  | 
|  | if (const TemplateTemplateParmDecl *TTP = | 
|  | dyn_cast<TemplateTemplateParmDecl>(Param)) | 
|  | CorrectTypoInTParamReferenceHelper(TTP->getTemplateParameters(), | 
|  | Corrector); | 
|  | } | 
|  | } | 
|  | } // end anonymous namespace | 
|  |  | 
|  | StringRef Sema::correctTypoInTParamReference( | 
|  | StringRef Typo, | 
|  | const TemplateParameterList *TemplateParameters) { | 
|  | SimpleTypoCorrector Corrector(Typo); | 
|  | CorrectTypoInTParamReferenceHelper(TemplateParameters, Corrector); | 
|  | if (const NamedDecl *ND = Corrector.getBestDecl()) { | 
|  | const IdentifierInfo *II = ND->getIdentifier(); | 
|  | assert(II && "SimpleTypoCorrector should not return this decl"); | 
|  | return II->getName(); | 
|  | } | 
|  | return StringRef(); | 
|  | } | 
|  |  | 
|  | InlineCommandRenderKind Sema::getInlineCommandRenderKind(StringRef Name) const { | 
|  | assert(Traits.getCommandInfo(Name)->IsInlineCommand); | 
|  |  | 
|  | return llvm::StringSwitch<InlineCommandRenderKind>(Name) | 
|  | .Case("b", InlineCommandRenderKind::Bold) | 
|  | .Cases("c", "p", InlineCommandRenderKind::Monospaced) | 
|  | .Cases("a", "e", "em", InlineCommandRenderKind::Emphasized) | 
|  | .Case("anchor", InlineCommandRenderKind::Anchor) | 
|  | .Default(InlineCommandRenderKind::Normal); | 
|  | } | 
|  |  | 
|  | } // end namespace comments | 
|  | } // end namespace clang |