//===-- InlineFunctionDeclCheck.cpp ---------------------------------------===//
//
// 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 "InlineFunctionDeclCheck.h"
#include "../utils/FileExtensionsUtils.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::llvm_libc {

namespace {

const TemplateParameterList *
getLastTemplateParameterList(const FunctionDecl *FuncDecl) {
  const TemplateParameterList *ReturnList =
      FuncDecl->getDescribedTemplateParams();

  if (!ReturnList) {
    const unsigned NumberOfTemplateParameterLists =
        FuncDecl->getNumTemplateParameterLists();

    if (NumberOfTemplateParameterLists > 0)
      ReturnList = FuncDecl->getTemplateParameterList(
          NumberOfTemplateParameterLists - 1);
  }

  return ReturnList;
}

} // namespace

InlineFunctionDeclCheck::InlineFunctionDeclCheck(StringRef Name,
                                                 ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      HeaderFileExtensions(Context->getHeaderFileExtensions()) {}

void InlineFunctionDeclCheck::registerMatchers(MatchFinder *Finder) {
  // Ignore functions that have been deleted.
  Finder->addMatcher(decl(functionDecl(unless(isDeleted()))).bind("func_decl"),
                     this);
}

void InlineFunctionDeclCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");

  // Consider only explicitly or implicitly inline functions.
  if (FuncDecl == nullptr || !FuncDecl->isInlined())
    return;

  SourceLocation SrcBegin = FuncDecl->getBeginLoc();

  // If we have a template parameter list, we need to skip that because the
  // LIBC_INLINE macro must be placed after that.
  if (const TemplateParameterList *TemplateParams =
          getLastTemplateParameterList(FuncDecl)) {
    SrcBegin = TemplateParams->getRAngleLoc();
    std::optional<Token> NextToken =
        utils::lexer::findNextTokenSkippingComments(
            SrcBegin, *Result.SourceManager, Result.Context->getLangOpts());
    if (NextToken)
      SrcBegin = NextToken->getLocation();
  }

  // Consider functions only in header files.
  if (!utils::isSpellingLocInHeaderFile(SrcBegin, *Result.SourceManager,
                                        HeaderFileExtensions))
    return;

  // Ignore lambda functions as they are internal and implicit.
  if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))
    if (MethodDecl->getParent()->isLambda())
      return;

  // Check if decl starts with LIBC_INLINE
  auto Loc = FullSourceLoc(Result.SourceManager->getFileLoc(SrcBegin),
                           *Result.SourceManager);
  llvm::StringRef SrcText = Loc.getBufferData().drop_front(Loc.getFileOffset());
  if (SrcText.starts_with("LIBC_INLINE"))
    return;

  diag(SrcBegin, "%0 must be tagged with the LIBC_INLINE macro; the macro "
                 "should be placed at the beginning of the declaration")
      << FuncDecl << FixItHint::CreateInsertion(Loc, "LIBC_INLINE ");
}

} // namespace clang::tidy::llvm_libc
