//===- ExtractAPI/DeclarationFragments.h ------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file defines the Declaration Fragments related classes.
///
/// Declaration Fragments represent parts of a symbol declaration tagged with
/// syntactic/semantic information.
/// See https://github.com/apple/swift-docc-symbolkit
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_EXTRACTAPI_DECLARATION_FRAGMENTS_H
#define LLVM_CLANG_EXTRACTAPI_DECLARATION_FRAGMENTS_H

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Lex/MacroInfo.h"
#include <iterator>
#include <utility>
#include <vector>

namespace clang {
namespace extractapi {

/// DeclarationFragments is a vector of tagged important parts of a symbol's
/// declaration.
///
/// The fragments sequence can be joined to form spans of declaration text, with
/// attached information useful for purposes like syntax-highlighting etc.
/// For example:
/// \code
///   const -> keyword    "const"
///   int   -> type       "int"
///   pi;   -> identifier "pi"
/// \endcode
class DeclarationFragments {
public:
  DeclarationFragments() = default;

  /// The kind of a fragment.
  enum class FragmentKind {
    /// Unknown fragment kind.
    None,

    Keyword,
    Attribute,
    NumberLiteral,
    StringLiteral,
    Identifier,

    /// Identifier that refers to a type in the context.
    TypeIdentifier,

    /// Parameter that's used as generics in the context. For example template
    /// parameters.
    GenericParameter,

    /// External parameters in Objective-C methods.
    /// For example, \c forKey in
    /// \code{.m}
    ///   - (void) setValue:(Value)value forKey(Key)key
    /// \endcode
    ExternalParam,

    /// Internal/local parameters in Objective-C methods.
    /// For example, \c key in
    /// \code{.m}
    ///   - (void) setValue:(Value)value forKey(Key)key
    /// \endcode
    InternalParam,

    Text,
  };

  /// Fragment holds information of a single fragment.
  struct Fragment {
    std::string Spelling;
    FragmentKind Kind;

    /// The USR of the fragment symbol, if applicable.
    std::string PreciseIdentifier;

    /// The associated declaration, if applicable. This is not intended to be
    /// used outside of libclang.
    const Decl *Declaration;

    Fragment(StringRef Spelling, FragmentKind Kind, StringRef PreciseIdentifier,
             const Decl *Declaration)
        : Spelling(Spelling), Kind(Kind), PreciseIdentifier(PreciseIdentifier),
          Declaration(Declaration) {}
  };

  using FragmentIterator = std::vector<Fragment>::iterator;
  using ConstFragmentIterator = std::vector<Fragment>::const_iterator;

  const std::vector<Fragment> &getFragments() const { return Fragments; }

  FragmentIterator begin() { return Fragments.begin(); }

  FragmentIterator end() { return Fragments.end(); }

  ConstFragmentIterator cbegin() const { return Fragments.cbegin(); }

  ConstFragmentIterator cend() const { return Fragments.cend(); }

  /// Prepend another DeclarationFragments to the beginning.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &prepend(DeclarationFragments Other) {
    return insert(begin(), std::move(Other));
  }

  /// Append another DeclarationFragments to the end.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &append(DeclarationFragments Other) {
    return insert(end(), std::move(Other));
  }

  /// Append a new Fragment to the end of the Fragments.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &append(StringRef Spelling, FragmentKind Kind,
                               StringRef PreciseIdentifier = "",
                               const Decl *Declaration = nullptr) {
    if (Kind == FragmentKind::Text && !Fragments.empty() &&
        Fragments.back().Kind == FragmentKind::Text) {
      // If appending a text fragment, and the last fragment is also text,
      // merge into the last fragment.
      Fragments.back().Spelling.append(Spelling.data(), Spelling.size());
    } else {
      Fragments.emplace_back(Spelling, Kind, PreciseIdentifier, Declaration);
    }
    return *this;
  }

  /// Inserts another DeclarationFragments at \p It.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &insert(FragmentIterator It,
                               DeclarationFragments Other) {
    if (Other.Fragments.empty())
      return *this;

    if (Fragments.empty()) {
      Fragments = std::move(Other.Fragments);
      return *this;
    }

    const auto &OtherFrags = Other.Fragments;
    auto ToInsertBegin = std::make_move_iterator(Other.begin());
    auto ToInsertEnd = std::make_move_iterator(Other.end());

    // If we aren't inserting at the end let's make sure that we merge their
    // last fragment with It if both are text fragments.
    if (It != end() && It->Kind == FragmentKind::Text &&
        OtherFrags.back().Kind == FragmentKind::Text) {
      auto &TheirBackSpelling = OtherFrags.back().Spelling;
      It->Spelling.reserve(It->Spelling.size() + TheirBackSpelling.size());
      It->Spelling.insert(It->Spelling.begin(), TheirBackSpelling.begin(),
                          TheirBackSpelling.end());
      --ToInsertEnd;
    }

    // If we aren't inserting at the beginning we want to merge their first
    // fragment with the fragment before It if both are text fragments.
    if (It != begin() && std::prev(It)->Kind == FragmentKind::Text &&
        OtherFrags.front().Kind == FragmentKind::Text) {
      auto PrevIt = std::prev(It);
      auto &TheirFrontSpelling = OtherFrags.front().Spelling;
      PrevIt->Spelling.reserve(PrevIt->Spelling.size() +
                               TheirFrontSpelling.size());
      PrevIt->Spelling.append(TheirFrontSpelling);
      ++ToInsertBegin;
    }

    Fragments.insert(It, ToInsertBegin, ToInsertEnd);
    return *this;
  }

  DeclarationFragments &pop_back() {
    Fragments.pop_back();
    return *this;
  }

  DeclarationFragments &replace(std::string NewSpelling, unsigned Position) {
    Fragments.at(Position).Spelling = NewSpelling;
    return *this;
  }

  /// Append a text Fragment of a space character.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &appendSpace();

  /// Append a text Fragment of a semicolon character.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// appending to chain up consecutive operations.
  DeclarationFragments &appendSemicolon();

  /// Removes a trailing semicolon character if present.
  ///
  /// \returns a reference to the DeclarationFragments object itself after
  /// removing to chain up consecutive operations.
  DeclarationFragments &removeTrailingSemicolon();

  /// Get the string description of a FragmentKind \p Kind.
  static StringRef getFragmentKindString(FragmentKind Kind);

  /// Get the corresponding FragmentKind from string \p S.
  static FragmentKind parseFragmentKindFromString(StringRef S);

  static DeclarationFragments
  getExceptionSpecificationString(ExceptionSpecificationType ExceptionSpec);

  static DeclarationFragments getStructureTypeFragment(const RecordDecl *Decl);

private:
  DeclarationFragments &appendUnduplicatedTextCharacter(char Character);
  std::vector<Fragment> Fragments;
};

class AccessControl {
public:
  AccessControl(std::string Access) : Access(Access) {}
  AccessControl() : Access("public") {}

  const std::string &getAccess() const { return Access; }

  bool empty() const { return Access.empty(); }

private:
  std::string Access;
};

/// Store function signature information with DeclarationFragments of the
/// return type and parameters.
class FunctionSignature {
public:
  FunctionSignature() = default;

  /// Parameter holds the name and DeclarationFragments of a single parameter.
  struct Parameter {
    std::string Name;
    DeclarationFragments Fragments;

    Parameter(StringRef Name, DeclarationFragments Fragments)
        : Name(Name), Fragments(Fragments) {}
  };

  const std::vector<Parameter> &getParameters() const { return Parameters; }
  const DeclarationFragments &getReturnType() const { return ReturnType; }

  FunctionSignature &addParameter(StringRef Name,
                                  DeclarationFragments Fragments) {
    Parameters.emplace_back(Name, Fragments);
    return *this;
  }

  void setReturnType(DeclarationFragments RT) { ReturnType = RT; }

  /// Determine if the FunctionSignature is empty.
  ///
  /// \returns true if the return type DeclarationFragments is empty and there
  /// is no parameter, otherwise false.
  bool empty() const {
    return Parameters.empty() && ReturnType.getFragments().empty();
  }

private:
  std::vector<Parameter> Parameters;
  DeclarationFragments ReturnType;
};

/// A factory class to build DeclarationFragments for different kinds of Decl.
class DeclarationFragmentsBuilder {
public:
  /// Build FunctionSignature for a function-like declaration \c FunctionT like
  /// FunctionDecl, ObjCMethodDecl, or CXXMethodDecl.
  ///
  /// The logic and implementation of building a signature for a FunctionDecl,
  /// CXXMethodDecl, and ObjCMethodDecl are exactly the same, but they do not
  /// share a common base. This template helps reuse the code.
  template <typename FunctionT>
  static FunctionSignature getFunctionSignature(const FunctionT *Function);

  static AccessControl getAccessControl(const Decl *Decl) {
    switch (Decl->getAccess()) {
    case AS_public:
    case AS_none:
      return AccessControl("public");
    case AS_private:
      return AccessControl("private");
    case AS_protected:
      return AccessControl("protected");
    }
    llvm_unreachable("Unhandled access control");
  }

  static DeclarationFragments
  getFragmentsForNamespace(const NamespaceDecl *Decl);

  /// Build DeclarationFragments for a variable declaration VarDecl.
  static DeclarationFragments getFragmentsForVar(const VarDecl *);

  static DeclarationFragments getFragmentsForVarTemplate(const VarDecl *);

  /// Build DeclarationFragments for a function declaration FunctionDecl.
  static DeclarationFragments getFragmentsForFunction(const FunctionDecl *);

  /// Build DeclarationFragments for an enum constant declaration
  /// EnumConstantDecl.
  static DeclarationFragments
  getFragmentsForEnumConstant(const EnumConstantDecl *);

  /// Build DeclarationFragments for an enum declaration EnumDecl.
  static DeclarationFragments getFragmentsForEnum(const EnumDecl *);

  /// Build DeclarationFragments for a field declaration FieldDecl.
  static DeclarationFragments getFragmentsForField(const FieldDecl *);

  /// Build DeclarationFragments for a struct/union record declaration
  /// RecordDecl.
  static DeclarationFragments getFragmentsForRecordDecl(const RecordDecl *);

  static DeclarationFragments getFragmentsForCXXClass(const CXXRecordDecl *);

  static DeclarationFragments
  getFragmentsForSpecialCXXMethod(const CXXMethodDecl *);

  static DeclarationFragments getFragmentsForCXXMethod(const CXXMethodDecl *);

  static DeclarationFragments
  getFragmentsForConversionFunction(const CXXConversionDecl *);

  static DeclarationFragments
  getFragmentsForOverloadedOperator(const CXXMethodDecl *);

  static DeclarationFragments
      getFragmentsForTemplateParameters(ArrayRef<NamedDecl *>);

  static DeclarationFragments getFragmentsForTemplateArguments(
      const ArrayRef<TemplateArgument>, ASTContext &,
      const std::optional<ArrayRef<TemplateArgumentLoc>>);

  static DeclarationFragments getFragmentsForConcept(const ConceptDecl *);

  static DeclarationFragments
  getFragmentsForRedeclarableTemplate(const RedeclarableTemplateDecl *);

  static DeclarationFragments getFragmentsForClassTemplateSpecialization(
      const ClassTemplateSpecializationDecl *);

  static DeclarationFragments getFragmentsForClassTemplatePartialSpecialization(
      const ClassTemplatePartialSpecializationDecl *);

  static DeclarationFragments getFragmentsForVarTemplateSpecialization(
      const VarTemplateSpecializationDecl *);

  static DeclarationFragments getFragmentsForVarTemplatePartialSpecialization(
      const VarTemplatePartialSpecializationDecl *);

  static DeclarationFragments
  getFragmentsForFunctionTemplate(const FunctionTemplateDecl *Decl);

  static DeclarationFragments
  getFragmentsForFunctionTemplateSpecialization(const FunctionDecl *Decl);

  /// Build DeclarationFragments for an Objective-C category declaration
  /// ObjCCategoryDecl.
  static DeclarationFragments
  getFragmentsForObjCCategory(const ObjCCategoryDecl *);

  /// Build DeclarationFragments for an Objective-C interface declaration
  /// ObjCInterfaceDecl.
  static DeclarationFragments
  getFragmentsForObjCInterface(const ObjCInterfaceDecl *);

  /// Build DeclarationFragments for an Objective-C method declaration
  /// ObjCMethodDecl.
  static DeclarationFragments getFragmentsForObjCMethod(const ObjCMethodDecl *);

  /// Build DeclarationFragments for an Objective-C property declaration
  /// ObjCPropertyDecl.
  static DeclarationFragments
  getFragmentsForObjCProperty(const ObjCPropertyDecl *);

  /// Build DeclarationFragments for an Objective-C protocol declaration
  /// ObjCProtocolDecl.
  static DeclarationFragments
  getFragmentsForObjCProtocol(const ObjCProtocolDecl *);

  /// Build DeclarationFragments for a macro.
  ///
  /// \param Name name of the macro.
  /// \param MI the associated MacroInfo.
  static DeclarationFragments getFragmentsForMacro(StringRef Name,
                                                   const MacroInfo *MI);

  /// Build DeclarationFragments for a typedef \p TypedefNameDecl.
  static DeclarationFragments
  getFragmentsForTypedef(const TypedefNameDecl *Decl);

  /// Build sub-heading fragments for a NamedDecl.
  static DeclarationFragments getSubHeading(const NamedDecl *);

  /// Build sub-heading fragments for an Objective-C method.
  static DeclarationFragments getSubHeading(const ObjCMethodDecl *);

  /// Build a sub-heading for macro \p Name.
  static DeclarationFragments getSubHeadingForMacro(StringRef Name);

private:
  DeclarationFragmentsBuilder() = delete;

  /// Build DeclarationFragments for a QualType.
  static DeclarationFragments getFragmentsForType(const QualType, ASTContext &,
                                                  DeclarationFragments &);

  /// Build DeclarationFragments for a Type.
  static DeclarationFragments getFragmentsForType(const Type *, ASTContext &,
                                                  DeclarationFragments &);

  /// Build DeclarationFragments for a NestedNameSpecifier.
  static DeclarationFragments
  getFragmentsForNNS(NestedNameSpecifier, ASTContext &, DeclarationFragments &);

  /// Build DeclarationFragments for Qualifiers.
  static DeclarationFragments getFragmentsForQualifiers(const Qualifiers quals);

  /// Build DeclarationFragments for a parameter variable declaration
  /// ParmVarDecl.
  static DeclarationFragments getFragmentsForParam(const ParmVarDecl *);

  static DeclarationFragments
  getFragmentsForBlock(const NamedDecl *BlockDecl, FunctionTypeLoc &Block,
                       FunctionProtoTypeLoc &BlockProto,
                       DeclarationFragments &After);
};

template <typename FunctionT>
FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionT *Function) {
  FunctionSignature Signature;

  DeclarationFragments ReturnType, After;
  ReturnType = getFragmentsForType(Function->getReturnType(),
                                   Function->getASTContext(), After);
  if (isa<FunctionDecl>(Function) &&
      dyn_cast<FunctionDecl>(Function)->getDescribedFunctionTemplate() &&
      StringRef(ReturnType.begin()->Spelling).starts_with("type-parameter")) {
    std::string ProperArgName = Function->getReturnType().getAsString();
    ReturnType.begin()->Spelling.swap(ProperArgName);
  }
  ReturnType.append(std::move(After));
  Signature.setReturnType(ReturnType);

  for (const auto *Param : Function->parameters())
    Signature.addParameter(Param->getName(), getFragmentsForParam(Param));

  return Signature;
}

} // namespace extractapi
} // namespace clang

#endif // LLVM_CLANG_EXTRACTAPI_DECLARATION_FRAGMENTS_H
