//===--- 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/CommentDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclTemplate.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SmallString.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 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 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(Arg) .Case("[in]", ParamCommandPassDirection::In) .Case("[out]", ParamCommandPassDirection::Out) .Cases("[in,out]", "[out,in]", ParamCommandPassDirection::InOut) .Default(static_cast(-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(-1)) { // Try again with whitespace removed. llvm::erase_if(ArgLower, clang::isWhitespace); Direction = getParamPassDirection(ArgLower); SourceRange ArgRange(ArgLocBegin, ArgLocEnd); if (Direction != static_cast(-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 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 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 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 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 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::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 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() || D->hasAttr() || D->hasAttr()) 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(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 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 ParamVarDocs; ArrayRef 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(*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 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(ThisDeclInfo->CurrentDecl); } bool Sema::isFunctionOrMethodVariadic() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->IsVariadic; } bool Sema::isObjCMethodDecl() { return isFunctionDecl() && ThisDeclInfo->CurrentDecl && isa(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(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(ThisDeclInfo->CurrentDecl)) return RD->isUnion(); return false; } static bool isClassOrStructDeclImpl(const Decl *D) { if (auto *record = dyn_cast_or_null(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(ThisDeclInfo->CurrentDecl)) { auto UnderlyingType = ThisTypedefDecl->getUnderlyingType(); if (auto ThisElaboratedType = dyn_cast(UnderlyingType)) { auto DesugaredType = ThisElaboratedType->desugar(); if (auto *DesugaredTypePtr = DesugaredType.getTypePtrOrNull()) { if (auto *ThisRecordType = dyn_cast(DesugaredTypePtr)) { return isClassOrStructDeclImpl(ThisRecordType->getAsRecordDecl()); } } } } return false; } bool Sema::isClassTemplateDecl() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->CurrentDecl && (isa(ThisDeclInfo->CurrentDecl)); } bool Sema::isFunctionTemplateDecl() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->CurrentDecl && (isa(ThisDeclInfo->CurrentDecl)); } bool Sema::isObjCInterfaceDecl() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->CurrentDecl && isa(ThisDeclInfo->CurrentDecl); } bool Sema::isObjCProtocolDecl() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->CurrentDecl && isa(ThisDeclInfo->CurrentDecl); } ArrayRef Sema::getParamVars() { if (!ThisDeclInfo->IsFilled) inspectThisDecl(); return ThisDeclInfo->ParamVars; } void Sema::inspectThisDecl() { ThisDeclInfo->fill(); } unsigned Sema::resolveParmVarReference(StringRef Name, ArrayRef 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 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 *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(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 *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(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(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