//===-- CollectMacrosTests.cpp ----------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#include "AST.h"
#include "Annotations.h"
#include "CollectMacros.h"
#include "SourceCode.h"
#include "TestTU.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace {

using testing::UnorderedElementsAreArray;

TEST(CollectMainFileMacros, SelectedMacros) {
  // References of the same symbol must have the ranges with the same
  // name(integer). If there are N different symbols then they must be named
  // from 1 to N. Macros for which SymbolID cannot be computed must be named
  // "Unknown".
  const char *Tests[] = {
      R"cpp(// Macros: Cursor on definition.
        #define $1[[FOO]](x,y) (x + y)
        int main() { int x = $1[[FOO]]($1[[FOO]](3, 4), $1[[FOO]](5, 6)); }
      )cpp",
      R"cpp(
        #define $1[[M]](X) X;
        #define $2[[abc]] 123
        int s = $1[[M]]($2[[abc]]);
      )cpp",
      // FIXME: Locating macro in duplicate definitions doesn't work. Enable
      // this once LocateMacro is fixed.
      // R"cpp(// Multiple definitions.
      //   #define $1[[abc]] 1
      //   int func1() { int a = $1[[abc]];}
      //   #undef $1[[abc]]

      //   #define $2[[abc]] 2
      //   int func2() { int a = $2[[abc]];}
      //   #undef $2[[abc]]
      // )cpp",
      R"cpp(
        #ifdef $Unknown[[UNDEFINED]]
        #endif
      )cpp",
      R"cpp(
        #ifndef $Unknown[[abc]]
        #define $1[[abc]]
        #ifdef $1[[abc]]
        #endif
        #endif
      )cpp",
      R"cpp(
        // Macros from token concatenations not included.
        #define $1[[CONCAT]](X) X##A()
        #define $2[[PREPEND]](X) MACRO##X()
        #define $3[[MACROA]]() 123
        int B = $1[[CONCAT]](MACRO);
        int D = $2[[PREPEND]](A);
      )cpp",
      R"cpp(
        // FIXME: Macro names in a definition are not detected.
        #define $1[[MACRO_ARGS2]](X, Y) X Y
        #define $2[[FOO]] BAR
        #define $3[[BAR]] 1
        int A = $2[[FOO]];
      )cpp"};
  for (const char *Test : Tests) {
    Annotations T(Test);
    auto AST = TestTU::withCode(T.code()).build();
    auto ActualMacroRefs = AST.getMacros();
    auto &SM = AST.getSourceManager();
    auto &PP = AST.getPreprocessor();

    // Known macros.
    for (int I = 1;; I++) {
      const auto ExpectedRefs = T.ranges(llvm::to_string(I));
      if (ExpectedRefs.empty())
        break;

      auto Loc = sourceLocationInMainFile(SM, ExpectedRefs.begin()->start);
      ASSERT_TRUE(bool(Loc));
      const auto *Id = syntax::spelledIdentifierTouching(*Loc, AST.getTokens());
      ASSERT_TRUE(Id);
      auto Macro = locateMacroAt(*Id, PP);
      assert(Macro);
      auto SID = getSymbolID(Macro->Name, Macro->Info, SM);

      std::vector<Range> Ranges;
      for (const auto &Ref : ActualMacroRefs.MacroRefs[SID])
        Ranges.push_back(Ref.Rng);
      EXPECT_THAT(ExpectedRefs, UnorderedElementsAreArray(Ranges))
          << "Annotation=" << I << ", MacroName=" << Macro->Name
          << ", Test = " << Test;
    }
    // Unknown macros.
    std::vector<Range> Ranges;
    for (const auto &Ref : AST.getMacros().UnknownMacros)
      Ranges.push_back(Ref.Rng);
    EXPECT_THAT(Ranges, UnorderedElementsAreArray(T.ranges("Unknown")))
        << "Unknown macros doesn't match in " << Test;
  }
}
} // namespace
} // namespace clangd
} // namespace clang
