brintos

brintos / llvm-project-archived public Read only

0
0
Text · 13.6 KiB · 9c4241b Raw
365 lines · cpp
1//===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "CodeCompletionStrings.h"10#include "Config.h"11#include "SymbolDocumentation.h"12#include "clang-c/Index.h"13#include "clang/AST/ASTContext.h"14#include "clang/AST/Comment.h"15#include "clang/AST/Decl.h"16#include "clang/AST/RawCommentList.h"17#include "clang/Basic/SourceManager.h"18#include "clang/Sema/CodeCompleteConsumer.h"19#include "llvm/Support/Compiler.h"20#include "llvm/Support/JSON.h"21#include "llvm/Support/raw_ostream.h"22#include <limits>23#include <utility>24 25namespace clang {26namespace clangd {27namespace {28 29bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {30  return Chunk.Kind == CodeCompletionString::CK_Informative &&31         llvm::StringRef(Chunk.Text).ends_with("::");32}33 34void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {35  for (const auto Character : Text) {36    if (Character == '$' || Character == '}' || Character == '\\')37      Out->push_back('\\');38    Out->push_back(Character);39  }40}41 42void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) {43  for (const CodeCompletionString::Chunk &C : CCS) {44    switch (C.Kind) {45    case CodeCompletionString::CK_Optional:46      assert(C.Optional &&47             "Expected the optional code completion string to be non-null.");48      appendOptionalChunk(*C.Optional, Out);49      break;50    default:51      *Out += C.Text;52      break;53    }54  }55}56 57bool looksLikeDocComment(llvm::StringRef CommentText) {58  // We don't report comments that only contain "special" chars.59  // This avoids reporting various delimiters, like:60  //   =================61  //   -----------------62  //   *****************63  return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos;64}65 66// Determine whether the completion string should be patched67// to replace the last placeholder with $0.68bool shouldPatchPlaceholder0(CodeCompletionResult::ResultKind ResultKind,69                             CXCursorKind CursorKind) {70  bool CompletingPattern = ResultKind == CodeCompletionResult::RK_Pattern;71 72  if (!CompletingPattern)73    return false;74 75  // If the result kind of CodeCompletionResult(CCR) is `RK_Pattern`, it doesn't76  // always mean we're completing a chunk of statements.  Constructors defined77  // in base class, for example, are considered as a type of pattern, with the78  // cursor type set to CXCursor_Constructor.79  if (CursorKind == CXCursorKind::CXCursor_Constructor ||80      CursorKind == CXCursorKind::CXCursor_Destructor)81    return false;82 83  return true;84}85 86} // namespace87 88std::string getDocComment(const ASTContext &Ctx,89                          const CodeCompletionResult &Result,90                          bool CommentsFromHeaders) {91  // FIXME: clang's completion also returns documentation for RK_Pattern if they92  // contain a pattern for ObjC properties. Unfortunately, there is no API to93  // get this declaration, so we don't show documentation in that case.94  if (Result.Kind != CodeCompletionResult::RK_Declaration)95    return "";96  return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration())97                                 : "";98}99 100std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {101  if (isa<NamespaceDecl>(Decl)) {102    // Namespaces often have too many redecls for any particular redecl comment103    // to be useful. Moreover, we often confuse file headers or generated104    // comments with namespace comments. Therefore we choose to just ignore105    // the comments for namespaces.106    return "";107  }108 109  const RawComment *RC = nullptr;110  const Config &Cfg = Config::current();111 112  std::string Doc;113 114  if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen &&115      isa<ParmVarDecl, TemplateTypeParmDecl>(Decl)) {116    // Parameters are documented in their declaration context (function or117    // template function).118    const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext());119    if (!ND)120      return "";121 122    RC = getCompletionComment(Ctx, ND);123    if (!RC)124      return "";125 126    // Sanity check that the comment does not come from the PCH. We choose to127    // not write them into PCH, because they are racy and slow to load.128    assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));129 130    comments::FullComment *FC = RC->parse(Ctx, /*PP=*/nullptr, ND);131    if (!FC)132      return "";133 134    SymbolDocCommentVisitor V(FC, Ctx.getLangOpts().CommentOpts);135    std::string RawDoc;136    llvm::raw_string_ostream OS(RawDoc);137 138    if (auto *PVD = dyn_cast<ParmVarDecl>(&Decl))139      V.parameterDocToString(PVD->getName(), OS);140    else141      V.templateTypeParmDocToString(142          cast<TemplateTypeParmDecl>(&Decl)->getName(), OS);143 144    Doc = StringRef(RawDoc).trim().str();145  } else {146    RC = getCompletionComment(Ctx, &Decl);147    if (!RC)148      return "";149    // Sanity check that the comment does not come from the PCH. We choose to150    // not write them into PCH, because they are racy and slow to load.151    assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));152    Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());153    if (!looksLikeDocComment(Doc))154      return "";155  }156 157  // Clang requires source to be UTF-8, but doesn't enforce this in comments.158  if (!llvm::json::isUTF8(Doc))159    Doc = llvm::json::fixUTF8(Doc);160  return Doc;161}162 163void getSignature(const CodeCompletionString &CCS, std::string *Signature,164                  std::string *Snippet,165                  CodeCompletionResult::ResultKind ResultKind,166                  CXCursorKind CursorKind, bool IncludeFunctionArguments,167                  std::string *RequiredQualifiers) {168  // Placeholder with this index will be $0 to mark final cursor position.169  // Usually we do not add $0, so the cursor is placed at end of completed text.170  unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max();171 172  // If the snippet contains a group of statements, we replace the173  // last placeholder with $0 to leave the cursor there, e.g.174  //    namespace ${1:name} {175  //      ${0:decls}176  //    }177  // We try to identify such cases using the ResultKind and CursorKind.178  if (shouldPatchPlaceholder0(ResultKind, CursorKind)) {179    CursorSnippetArg =180        llvm::count_if(CCS, [](const CodeCompletionString::Chunk &C) {181          return C.Kind == CodeCompletionString::CK_Placeholder;182        });183  }184  unsigned SnippetArg = 0;185  bool HadObjCArguments = false;186  bool HadInformativeChunks = false;187 188  std::optional<unsigned> TruncateSnippetAt;189  for (const auto &Chunk : CCS) {190    // Informative qualifier chunks only clutter completion results, skip191    // them.192    if (isInformativeQualifierChunk(Chunk))193      continue;194 195    switch (Chunk.Kind) {196    case CodeCompletionString::CK_TypedText:197      // The typed-text chunk is the actual name. We don't record this chunk.198      // C++:199      //   In general our string looks like <qualifiers><name><signature>.200      //   So once we see the name, any text we recorded so far should be201      //   reclassified as qualifiers.202      //203      // Objective-C:204      //   Objective-C methods expressions may have multiple typed-text chunks,205      //   so we must treat them carefully. For Objective-C methods, all206      //   typed-text and informative chunks will end in ':' (unless there are207      //   no arguments, in which case we can safely treat them as C++).208      //209      //   Completing a method declaration itself (not a method expression) is210      //   similar except that we use the `RequiredQualifiers` to store the211      //   text before the selector, e.g. `- (void)`.212      if (!llvm::StringRef(Chunk.Text).ends_with(":")) { // Treat as C++.213        if (RequiredQualifiers)214          *RequiredQualifiers = std::move(*Signature);215        Signature->clear();216        Snippet->clear();217      } else { // Objective-C method with args.218        // If this is the first TypedText to the Objective-C method, discard any219        // text that we've previously seen (such as previous parameter selector,220        // which will be marked as Informative text).221        //222        // TODO: Make previous parameters part of the signature for Objective-C223        // methods.224        if (!HadObjCArguments) {225          HadObjCArguments = true;226          // If we have no previous informative chunks (informative selector227          // fragments in practice), we treat any previous chunks as228          // `RequiredQualifiers` so they will be added as a prefix during the229          // completion.230          //231          // e.g. to complete `- (void)doSomething:(id)argument`:232          // - Completion name: `doSomething:`233          // - RequiredQualifiers: `- (void)`234          // - Snippet/Signature suffix: `(id)argument`235          //236          // This differs from the case when we're completing a method237          // expression with a previous informative selector fragment.238          //239          // e.g. to complete `[self doSomething:nil ^somethingElse:(id)]`:240          // - Previous Informative Chunk: `doSomething:`241          // - Completion name: `somethingElse:`242          // - Snippet/Signature suffix: `(id)`243          if (!HadInformativeChunks) {244            if (RequiredQualifiers)245              *RequiredQualifiers = std::move(*Signature);246            Snippet->clear();247          }248          Signature->clear();249        } else { // Subsequent argument, considered part of snippet/signature.250          *Signature += Chunk.Text;251          *Snippet += Chunk.Text;252        }253      }254      break;255    case CodeCompletionString::CK_Text:256      *Signature += Chunk.Text;257      *Snippet += Chunk.Text;258      break;259    case CodeCompletionString::CK_Optional:260      assert(Chunk.Optional);261      // No need to create placeholders for default arguments in Snippet.262      appendOptionalChunk(*Chunk.Optional, Signature);263      break;264    case CodeCompletionString::CK_Placeholder:265      *Signature += Chunk.Text;266      ++SnippetArg;267      if (SnippetArg == CursorSnippetArg) {268        // We'd like to make $0 a placeholder too, but vscode does not support269        // this (https://github.com/microsoft/vscode/issues/152837).270        *Snippet += "$0";271      } else {272        *Snippet += "${" + std::to_string(SnippetArg) + ':';273        appendEscapeSnippet(Chunk.Text, Snippet);274        *Snippet += '}';275      }276      break;277    case CodeCompletionString::CK_Informative:278      HadInformativeChunks = true;279      // For example, the word "const" for a const method, or the name of280      // the base class for methods that are part of the base class.281      *Signature += Chunk.Text;282      // Don't put the informative chunks in the snippet.283      break;284    case CodeCompletionString::CK_ResultType:285      // This is not part of the signature.286      break;287    case CodeCompletionString::CK_CurrentParameter:288      // This should never be present while collecting completion items,289      // only while collecting overload candidates.290      llvm_unreachable("Unexpected CK_CurrentParameter while collecting "291                       "CompletionItems");292      break;293    case CodeCompletionString::CK_LeftParen:294      // We're assuming that a LeftParen in a declaration starts a function295      // call, and arguments following the parenthesis could be discarded if296      // IncludeFunctionArguments is false.297      if (!IncludeFunctionArguments &&298          ResultKind == CodeCompletionResult::RK_Declaration)299        TruncateSnippetAt.emplace(Snippet->size());300      [[fallthrough]];301    case CodeCompletionString::CK_RightParen:302    case CodeCompletionString::CK_LeftBracket:303    case CodeCompletionString::CK_RightBracket:304    case CodeCompletionString::CK_LeftBrace:305    case CodeCompletionString::CK_RightBrace:306    case CodeCompletionString::CK_LeftAngle:307    case CodeCompletionString::CK_RightAngle:308    case CodeCompletionString::CK_Comma:309    case CodeCompletionString::CK_Colon:310    case CodeCompletionString::CK_SemiColon:311    case CodeCompletionString::CK_Equal:312    case CodeCompletionString::CK_HorizontalSpace:313      *Signature += Chunk.Text;314      *Snippet += Chunk.Text;315      break;316    case CodeCompletionString::CK_VerticalSpace:317      *Snippet += Chunk.Text;318      // Don't even add a space to the signature.319      break;320    }321  }322  if (TruncateSnippetAt)323    *Snippet = Snippet->substr(0, *TruncateSnippetAt);324}325 326std::string formatDocumentation(const CodeCompletionString &CCS,327                                llvm::StringRef DocComment) {328  // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this329  // information in the documentation field.330  std::string Result;331  const unsigned AnnotationCount = CCS.getAnnotationCount();332  if (AnnotationCount > 0) {333    Result += "Annotation";334    if (AnnotationCount == 1) {335      Result += ": ";336    } else /* AnnotationCount > 1 */ {337      Result += "s: ";338    }339    for (unsigned I = 0; I < AnnotationCount; ++I) {340      Result += CCS.getAnnotation(I);341      Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');342    }343  }344  // Add brief documentation (if there is any).345  if (!DocComment.empty()) {346    if (!Result.empty()) {347      // This means we previously added annotations. Add an extra newline348      // character to make the annotations stand out.349      Result.push_back('\n');350    }351    Result += DocComment;352  }353  return Result;354}355 356std::string getReturnType(const CodeCompletionString &CCS) {357  for (const auto &Chunk : CCS)358    if (Chunk.Kind == CodeCompletionString::CK_ResultType)359      return Chunk.Text;360  return "";361}362 363} // namespace clangd364} // namespace clang365