//===--- StringviewNullptrCheck.cpp - clang-tidy --------------------------===//
//
// 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 "StringviewNullptrCheck.h"
#include "../utils/TransformerClangTidyCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/OperationKinds.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Transformer/RangeSelector.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/Stencil.h"
#include "llvm/ADT/StringRef.h"

namespace clang::tidy::bugprone {

using namespace ::clang::ast_matchers;
using namespace ::clang::transformer;

namespace {

AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) {
  return Node.getNumInits() == N;
}

AST_MATCHER(clang::VarDecl, isDirectInitialization) {
  return Node.getInitStyle() != clang::VarDecl::InitializationStyle::CInit;
}

} // namespace

RewriteRuleWith<std::string> StringviewNullptrCheckImpl() {
  auto construction_warning =
      cat("constructing basic_string_view from null is undefined; replace with "
          "the default constructor");
  auto static_cast_warning =
      cat("casting to basic_string_view from null is undefined; replace with "
          "the empty string");
  auto argument_construction_warning =
      cat("passing null as basic_string_view is undefined; replace with the "
          "empty string");
  auto assignment_warning =
      cat("assignment to basic_string_view from null is undefined; replace "
          "with the default constructor");
  auto relative_comparison_warning =
      cat("comparing basic_string_view to null is undefined; replace with the "
          "empty string");
  auto equality_comparison_warning =
      cat("comparing basic_string_view to null is undefined; replace with the "
          "emptiness query");

  // Matches declarations and expressions of type `basic_string_view`
  auto HasBasicStringViewType = hasType(hasUnqualifiedDesugaredType(recordType(
      hasDeclaration(cxxRecordDecl(hasName("::std::basic_string_view"))))));

  // Matches `nullptr` and `(nullptr)` binding to a pointer
  auto NullLiteral = implicitCastExpr(
      hasCastKind(clang::CK_NullToPointer),
      hasSourceExpression(ignoringParens(cxxNullPtrLiteralExpr())));

  // Matches `{nullptr}` and `{(nullptr)}` binding to a pointer
  auto NullInitList = initListExpr(initCountIs(1), hasInit(0, NullLiteral));

  // Matches `{}`
  auto EmptyInitList = initListExpr(initCountIs(0));

  // Matches null construction without `basic_string_view` type spelling
  auto BasicStringViewConstructingFromNullExpr =
      cxxConstructExpr(
          HasBasicStringViewType, argumentCountIs(1),
          hasAnyArgument(/* `hasArgument` would skip over parens */ anyOf(
              NullLiteral, NullInitList, EmptyInitList)),
          unless(cxxTemporaryObjectExpr(/* filters out type spellings */)),
          has(expr().bind("null_arg_expr")))
          .bind("construct_expr");

  // `std::string_view(null_arg_expr)`
  auto HandleTemporaryCXXFunctionalCastExpr =
      makeRule(cxxFunctionalCastExpr(hasSourceExpression(
                   BasicStringViewConstructingFromNullExpr)),
               remove(node("null_arg_expr")), construction_warning);

  // `std::string_view{null_arg_expr}` and `(std::string_view){null_arg_expr}`
  auto HandleTemporaryCXXTemporaryObjectExprAndCompoundLiteralExpr = makeRule(
      cxxTemporaryObjectExpr(cxxConstructExpr(
          HasBasicStringViewType, argumentCountIs(1),
          hasAnyArgument(/* `hasArgument` would skip over parens */ anyOf(
              NullLiteral, NullInitList, EmptyInitList)),
          has(expr().bind("null_arg_expr")))),
      remove(node("null_arg_expr")), construction_warning);

  // `(std::string_view) null_arg_expr`
  auto HandleTemporaryCStyleCastExpr = makeRule(
      cStyleCastExpr(
          hasSourceExpression(BasicStringViewConstructingFromNullExpr)),
      changeTo(node("null_arg_expr"), cat("{}")), construction_warning);

  // `static_cast<std::string_view>(null_arg_expr)`
  auto HandleTemporaryCXXStaticCastExpr = makeRule(
      cxxStaticCastExpr(
          hasSourceExpression(BasicStringViewConstructingFromNullExpr)),
      changeTo(node("null_arg_expr"), cat("\"\"")), static_cast_warning);

  // `std::string_view sv = null_arg_expr;`
  auto HandleStackCopyInitialization = makeRule(
      varDecl(HasBasicStringViewType,
              hasInitializer(ignoringImpCasts(
                  cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                   unless(isListInitialization())))),
              unless(isDirectInitialization())),
      changeTo(node("null_arg_expr"), cat("{}")), construction_warning);

  // `std::string_view sv = {null_arg_expr};`
  auto HandleStackCopyListInitialization =
      makeRule(varDecl(HasBasicStringViewType,
                       hasInitializer(cxxConstructExpr(
                           BasicStringViewConstructingFromNullExpr,
                           isListInitialization())),
                       unless(isDirectInitialization())),
               remove(node("null_arg_expr")), construction_warning);

  // `std::string_view sv(null_arg_expr);`
  auto HandleStackDirectInitialization =
      makeRule(varDecl(HasBasicStringViewType,
                       hasInitializer(cxxConstructExpr(
                           BasicStringViewConstructingFromNullExpr,
                           unless(isListInitialization()))),
                       isDirectInitialization())
                   .bind("var_decl"),
               changeTo(node("construct_expr"), cat(name("var_decl"))),
               construction_warning);

  // `std::string_view sv{null_arg_expr};`
  auto HandleStackDirectListInitialization =
      makeRule(varDecl(HasBasicStringViewType,
                       hasInitializer(cxxConstructExpr(
                           BasicStringViewConstructingFromNullExpr,
                           isListInitialization())),
                       isDirectInitialization()),
               remove(node("null_arg_expr")), construction_warning);

  // `struct S { std::string_view sv = null_arg_expr; };`
  auto HandleFieldInClassCopyInitialization = makeRule(
      fieldDecl(HasBasicStringViewType,
                hasInClassInitializer(ignoringImpCasts(
                    cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                     unless(isListInitialization()))))),
      changeTo(node("null_arg_expr"), cat("{}")), construction_warning);

  // `struct S { std::string_view sv = {null_arg_expr}; };` and
  // `struct S { std::string_view sv{null_arg_expr}; };`
  auto HandleFieldInClassCopyListAndDirectListInitialization = makeRule(
      fieldDecl(HasBasicStringViewType,
                hasInClassInitializer(ignoringImpCasts(
                    cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                     isListInitialization())))),
      remove(node("null_arg_expr")), construction_warning);

  // `class C { std::string_view sv; C() : sv(null_arg_expr) {} };`
  auto HandleConstructorDirectInitialization =
      makeRule(cxxCtorInitializer(forField(fieldDecl(HasBasicStringViewType)),
                                  withInitializer(cxxConstructExpr(
                                      BasicStringViewConstructingFromNullExpr,
                                      unless(isListInitialization())))),
               remove(node("null_arg_expr")), construction_warning);

  // `class C { std::string_view sv; C() : sv{null_arg_expr} {} };`
  auto HandleConstructorDirectListInitialization =
      makeRule(cxxCtorInitializer(forField(fieldDecl(HasBasicStringViewType)),
                                  withInitializer(cxxConstructExpr(
                                      BasicStringViewConstructingFromNullExpr,
                                      isListInitialization()))),
               remove(node("null_arg_expr")), construction_warning);

  // `void f(std::string_view sv = null_arg_expr);`
  auto HandleDefaultArgumentCopyInitialization = makeRule(
      parmVarDecl(HasBasicStringViewType,
                  hasInitializer(ignoringImpCasts(
                      cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                       unless(isListInitialization()))))),
      changeTo(node("null_arg_expr"), cat("{}")), construction_warning);

  // `void f(std::string_view sv = {null_arg_expr});`
  auto HandleDefaultArgumentCopyListInitialization =
      makeRule(parmVarDecl(HasBasicStringViewType,
                           hasInitializer(cxxConstructExpr(
                               BasicStringViewConstructingFromNullExpr,
                               isListInitialization()))),
               remove(node("null_arg_expr")), construction_warning);

  // `new std::string_view(null_arg_expr)`
  auto HandleHeapDirectInitialization = makeRule(
      cxxNewExpr(has(cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                      unless(isListInitialization()))),
                 unless(isArray()), unless(hasAnyPlacementArg(anything()))),
      remove(node("null_arg_expr")), construction_warning);

  // `new std::string_view{null_arg_expr}`
  auto HandleHeapDirectListInitialization = makeRule(
      cxxNewExpr(has(cxxConstructExpr(BasicStringViewConstructingFromNullExpr,
                                      isListInitialization())),
                 unless(isArray()), unless(hasAnyPlacementArg(anything()))),
      remove(node("null_arg_expr")), construction_warning);

  // `function(null_arg_expr)`
  auto HandleFunctionArgumentInitialization =
      makeRule(callExpr(hasAnyArgument(ignoringImpCasts(
                            BasicStringViewConstructingFromNullExpr)),
                        unless(cxxOperatorCallExpr())),
               changeTo(node("construct_expr"), cat("\"\"")),
               argument_construction_warning);

  // `sv = null_arg_expr`
  auto HandleAssignment = makeRule(
      cxxOperatorCallExpr(hasOverloadedOperatorName("="),
                          hasRHS(materializeTemporaryExpr(
                              has(BasicStringViewConstructingFromNullExpr)))),
      changeTo(node("construct_expr"), cat("{}")), assignment_warning);

  // `sv < null_arg_expr`
  auto HandleRelativeComparison = makeRule(
      cxxOperatorCallExpr(hasAnyOverloadedOperatorName("<", "<=", ">", ">="),
                          hasEitherOperand(ignoringImpCasts(
                              BasicStringViewConstructingFromNullExpr))),
      changeTo(node("construct_expr"), cat("\"\"")),
      relative_comparison_warning);

  // `sv == null_arg_expr`
  auto HandleEmptyEqualityComparison = makeRule(
      cxxOperatorCallExpr(
          hasOverloadedOperatorName("=="),
          hasOperands(ignoringImpCasts(BasicStringViewConstructingFromNullExpr),
                      traverse(clang::TK_IgnoreUnlessSpelledInSource,
                               expr().bind("instance"))))
          .bind("root"),
      changeTo(node("root"), cat(access("instance", cat("empty")), "()")),
      equality_comparison_warning);

  // `sv != null_arg_expr`
  auto HandleNonEmptyEqualityComparison = makeRule(
      cxxOperatorCallExpr(
          hasOverloadedOperatorName("!="),
          hasOperands(ignoringImpCasts(BasicStringViewConstructingFromNullExpr),
                      traverse(clang::TK_IgnoreUnlessSpelledInSource,
                               expr().bind("instance"))))
          .bind("root"),
      changeTo(node("root"), cat("!", access("instance", cat("empty")), "()")),
      equality_comparison_warning);

  // `return null_arg_expr;`
  auto HandleReturnStatement = makeRule(
      returnStmt(hasReturnValue(
          ignoringImpCasts(BasicStringViewConstructingFromNullExpr))),
      changeTo(node("construct_expr"), cat("{}")), construction_warning);

  // `T(null_arg_expr)`
  auto HandleConstructorInvocation =
      makeRule(cxxConstructExpr(
                   hasAnyArgument(/* `hasArgument` would skip over parens */
                                  ignoringImpCasts(
                                      BasicStringViewConstructingFromNullExpr)),
                   unless(HasBasicStringViewType)),
               changeTo(node("construct_expr"), cat("\"\"")),
               argument_construction_warning);

  return applyFirst(
      {HandleTemporaryCXXFunctionalCastExpr,
       HandleTemporaryCXXTemporaryObjectExprAndCompoundLiteralExpr,
       HandleTemporaryCStyleCastExpr,
       HandleTemporaryCXXStaticCastExpr,
       HandleStackCopyInitialization,
       HandleStackCopyListInitialization,
       HandleStackDirectInitialization,
       HandleStackDirectListInitialization,
       HandleFieldInClassCopyInitialization,
       HandleFieldInClassCopyListAndDirectListInitialization,
       HandleConstructorDirectInitialization,
       HandleConstructorDirectListInitialization,
       HandleDefaultArgumentCopyInitialization,
       HandleDefaultArgumentCopyListInitialization,
       HandleHeapDirectInitialization,
       HandleHeapDirectListInitialization,
       HandleFunctionArgumentInitialization,
       HandleAssignment,
       HandleRelativeComparison,
       HandleEmptyEqualityComparison,
       HandleNonEmptyEqualityComparison,
       HandleReturnStatement,
       HandleConstructorInvocation});
}

StringviewNullptrCheck::StringviewNullptrCheck(StringRef Name,
                                               ClangTidyContext *Context)
    : utils::TransformerClangTidyCheck(StringviewNullptrCheckImpl(), Name,
                                       Context) {}

} // namespace clang::tidy::bugprone
