//===--- AvoidUnconditionalPreprocessorIfCheck.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 "AvoidUnconditionalPreprocessorIfCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

namespace {
struct AvoidUnconditionalPreprocessorIfPPCallbacks : public PPCallbacks {

  explicit AvoidUnconditionalPreprocessorIfPPCallbacks(ClangTidyCheck &Check,
                                                       Preprocessor &PP)
      : Check(Check), PP(PP) {}

  void If(SourceLocation Loc, SourceRange ConditionRange,
          ConditionValueKind ConditionValue) override {
    if (ConditionValue == CVK_NotEvaluated)
      return;
    SourceManager &SM = PP.getSourceManager();
    if (!isImmutable(SM, PP.getLangOpts(), ConditionRange))
      return;

    if (ConditionValue == CVK_True)
      Check.diag(Loc, "preprocessor condition is always 'true', consider "
                      "removing condition but leaving its contents");
    else
      Check.diag(Loc, "preprocessor condition is always 'false', consider "
                      "removing both the condition and its contents");
  }

  bool isImmutable(SourceManager &SM, const LangOptions &LangOpts,
                   SourceRange ConditionRange) {
    SourceLocation Loc = ConditionRange.getBegin();
    if (Loc.isMacroID())
      return false;

    Token Tok;
    if (Lexer::getRawToken(Loc, Tok, SM, LangOpts, true)) {
      std::optional<Token> TokOpt = Lexer::findNextToken(Loc, SM, LangOpts);
      if (!TokOpt || TokOpt->getLocation().isMacroID())
        return false;
      Tok = *TokOpt;
    }

    while (Tok.getLocation() <= ConditionRange.getEnd()) {
      if (!isImmutableToken(Tok))
        return false;

      std::optional<Token> TokOpt =
          Lexer::findNextToken(Tok.getLocation(), SM, LangOpts);
      if (!TokOpt || TokOpt->getLocation().isMacroID())
        return false;
      Tok = *TokOpt;
    }

    return true;
  }

  bool isImmutableToken(const Token &Tok) {
    switch (Tok.getKind()) {
    case tok::eod:
    case tok::eof:
    case tok::numeric_constant:
    case tok::char_constant:
    case tok::wide_char_constant:
    case tok::utf8_char_constant:
    case tok::utf16_char_constant:
    case tok::utf32_char_constant:
    case tok::string_literal:
    case tok::wide_string_literal:
    case tok::comment:
      return true;
    case tok::raw_identifier:
      return (Tok.getRawIdentifier() == "true" ||
              Tok.getRawIdentifier() == "false");
    default:
      return Tok.getKind() >= tok::l_square && Tok.getKind() <= tok::caretcaret;
    }
  }

  ClangTidyCheck &Check;
  Preprocessor &PP;
};

} // namespace

void AvoidUnconditionalPreprocessorIfCheck::registerPPCallbacks(
    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
  PP->addPPCallbacks(
      std::make_unique<AvoidUnconditionalPreprocessorIfPPCallbacks>(*this,
                                                                    *PP));
}

} // namespace clang::tidy::readability
