Add clang-tidy integration and fix all warnings (#659)

* Add clang-tidy job

* Install ninja

* Remove duplicate path

* Remove compiler specification??

* Remove linebreak

* Readd compiler specification

* Remove check specification

* Install clang-tidy

* Enable default checks

* Add .clang-tidy

* Start fixing tidy

* Require tidy >=14

* Bump Ubuntu version

* Bump clang-tidy invocation to 14

* Next set of fixes

* Fix set :D

* maybe this does the trick

* ??

* ????

* Surely this is it

* Close to just giving up

* Mark operator* with noexcept

* NOLINT

* Bump clang-tidy version

* Revert "Bump clang-tidy version"

This reverts commit 3173050e17359eacd62899f284128ec2e0b4f5db.

* More NOLINT test

* Does this do the trick?

* Else suppression

* More fixes!

* Specify error

* Root cause

* Readd at one location

* Cleanup and more fixes

* Huh!

* More eradication

* fuchsia

* More fuchsia

* More

* definitions-in-headers

* definitions-in-headers 2

* Does this do the trick?

* Sussy lambda cast

* Mem

* Try default move const

* Improve declaration

* A lot more fixes!

* -

* +

* *

* Getting somewhere

* Woo

* More

* Fix process

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Wrap up and add all CI

* Accomodate weird DLL bug?

* Update main.yml

* Test reintroduce error

* Finishing touches

* Final finishing touches
diff --git a/examples/all_features/CMakeLists.txt b/examples/all_features/CMakeLists.txt
index 5b752e0..ebc75c2 100644
--- a/examples/all_features/CMakeLists.txt
+++ b/examples/all_features/CMakeLists.txt
@@ -23,7 +23,7 @@
 set(files_all
     ${files_with_output}
     concurrency.cpp
-    ../../scripts/coverage_maxout.cpp
+    coverage_maxout.cpp
     namespace1.cpp
     namespace2.cpp
     namespace3.cpp
diff --git a/examples/all_features/assert_returns_disabled.cpp b/examples/all_features/assert_returns_disabled.cpp
index 8dd5997..da5a701 100644
--- a/examples/all_features/assert_returns_disabled.cpp
+++ b/examples/all_features/assert_returns_disabled.cpp
@@ -1,17 +1,18 @@
 #define DOCTEST_CONFIG_ASSERTS_RETURN_VALUES
 #include <doctest/doctest.h>
 
-#include <cstdio>
-
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+#include <iostream>
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
 
 #ifndef TEST_FLIP
 #define TEST_FLIP 0
 #endif
 
-#define TEST_FAIL() printf("FAILED ON: %d (%s)\n", \
-     __LINE__, TEST_FLIP ? "EVALUATED" : "DISABLED")
+#define TEST_FAIL() std::cout << "FAILED ON: " << __LINE__ \
+    << "(" << (TEST_FLIP ? "EVALUATED" : "DISABLED") << ")" << std::endl
 
-static int test_disabled_var_ = [] {
+static int test_disabled_var_ = [] { // NOLINT
     // none may return true
     if (TEST_FLIP ^ CHECK(0 == 0)) { TEST_FAIL(); }
     if (TEST_FLIP ^ CHECK_FALSE(0 != 0)) { TEST_FAIL(); }
diff --git a/examples/all_features/assertion_macros.cpp b/examples/all_features/assertion_macros.cpp
index 959a9c3..193a2e9 100644
--- a/examples/all_features/assertion_macros.cpp
+++ b/examples/all_features/assertion_macros.cpp
@@ -205,7 +205,8 @@
     someAssertsInFunction();
 }
 
-inline DOCTEST_NOINLINE void comp(int a, int b) {
+// TODO: Remove NOLINT (if (false && (__VA_ARGS__));)?
+inline DOCTEST_NOINLINE void comp(int a, int b) { // NOLINT(misc-unused-parameters)
     if (CHECK(a == b)) { MESSAGE(":D"); }
     if (CHECK_FALSE(a != b)) { MESSAGE(":D"); }
     if (CHECK_EQ(a, b)) { MESSAGE(":D"); }
diff --git a/examples/all_features/coverage_maxout.cpp b/examples/all_features/coverage_maxout.cpp
new file mode 100644
index 0000000..34489da
--- /dev/null
+++ b/examples/all_features/coverage_maxout.cpp
@@ -0,0 +1,127 @@
+#include <doctest/doctest.h>
+
+#include "header.h"
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+#include <ostream>
+#include <sstream>
+#include <stdexcept>
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+// =================================================================================================
+// !!! THESE ARE NOT PROPER EXAMPLES OF LIBRARY USAGE !!! THESE ARE MEANT FOR CODE COVERAGE ONLY !!!
+// =================================================================================================
+
+TEST_CASE("exercising tricky code paths of doctest") {
+    using namespace doctest;
+
+    // trigger code path for comparing the file in "operator<" of SubcaseSignature
+    CHECK(SubcaseSignature{"", "a.cpp", 0} < SubcaseSignature{"", "b.cpp", 0});
+    // same for String
+    CHECK(String("a.cpp") < String("b.cpp"));
+
+    // trigger code path for string with nullptr
+    String       str;
+    const String const_str("omgomgomg");
+    str = const_str.c_str();
+    CHECK(const_str[0] == 'o');
+    CHECK(str.capacity() == 24);
+    CHECK(str.size() == const_str.size());
+    CHECK_MESSAGE(str.compare(const_str, true) != 0, "should fail");
+    CHECK_MESSAGE(str.compare("omgomgomg", false) != 0, "should fail");
+
+    String heap_str("012345678901234567890123456789");
+    CHECK(heap_str.capacity() == heap_str.size() + 1); // on heap with maxed capacity
+    heap_str += "0123456789";
+    CHECK(heap_str.capacity() > heap_str.size() + 1);
+    heap_str += "0123456789"; // triggers path in +=
+    CHECK(heap_str[heap_str.size() - 1] == '9');
+    heap_str = "";
+
+    CHECK(String("abc") == "abc");
+    CHECK(String("abc") > "aaa");
+    CHECK(String("abc") >= "aaa");
+    CHECK(String("abc") < "bbb");
+    CHECK(String("abc") <= "bbb");
+    CHECK(String("abc")[0] == 'a');
+
+    // toString
+    str += toString("aaa")                            //
+           + toString(nullptr)                        //
+           + toString(true)                           //
+           + toString(0u)                             //
+           + toString('c')                            //
+           + toString(static_cast<signed char>('c'))  //
+           + toString(static_cast<unsigned char>(1))  //
+           + toString(static_cast<short>(1))          //
+           + toString(1L)                             //
+           + toString(1UL)                            //
+           + toString(static_cast<unsigned short>(1)) //
+           + toString(1LL)                            //
+           + toString(1ULL);
+
+    std::ostringstream oss;
+
+    // trigger code path for String to ostream through operator<<
+    oss << str;
+    // trigger code path for assert string of a non-existent assert type
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+    try {
+        assertString(static_cast<assertType::Enum>(3));
+    } catch (const std::logic_error&) { }
+#endif
+    str += oss.str().c_str();
+    str += failureString(assertType::is_normal);
+    CHECK(str == "omgomgomgaaanullptrtrue099991111111"
+                 "omgomgomgaaanullptrtrue099991111111");
+    // trigger code path for rawMemoryToString
+    bool   isThereAnything = str.size() > 0u;
+    String unknown         = toString(skip()); // trigger code path for "{?}"
+    str                    = unknown;          // trigger code path for deleting memory in operator=
+    CHECK_FALSE_MESSAGE(isThereAnything, "should fail");
+
+    Approx a(5);
+    a.scale(4);
+    Approx b = a(7);
+
+    CHECK(b == 7);
+    CHECK(b != 6);
+    CHECK(b > 6);
+    CHECK(b < 8);
+    CHECK(b >= 7);
+    CHECK(b <= 7);
+
+    CHECK(5 == a);
+    CHECK(6 != a);
+    CHECK(6 > a);
+    CHECK(4 < a);
+    CHECK(5 >= a);
+    CHECK(5 <= a);
+
+    // trigger another single line of code... lol
+    // NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast)
+    auto oldVal = const_cast<ContextOptions*>(getContextOptions())->no_path_in_filenames;
+    const_cast<ContextOptions*>(getContextOptions())->no_path_in_filenames = false;
+    CHECK(String(skipPathFromFilename("")) == "");
+    const_cast<ContextOptions*>(getContextOptions())->no_path_in_filenames = oldVal;
+    // NOLINTEND(cppcoreguidelines-pro-type-const-cast)
+
+    // a hack to trigger a bug in doctest: currently a 0 cannot be successfully parsed for an int option!
+    Context().setOption("last", 0);
+}
+
+TEST_SUITE("will be overridden by a decorator" * doctest::test_suite("exception related")) {
+    TEST_CASE("will end from a std::string exception") {
+        throw_if(true, std::string("std::string!"));
+    }
+
+    TEST_CASE("will end from a const char* exception") { throw_if(true, "const char*!"); }
+
+    TEST_CASE("will end from an unknown exception") {
+        throw_if(true, doctest::String("unknown :("));
+    }
+}
+
+#endif // DOCTEST_CONFIG_DISABLE
diff --git a/examples/all_features/decomposition.cpp b/examples/all_features/decomposition.cpp
index d610cc7..c48f8a4 100644
--- a/examples/all_features/decomposition.cpp
+++ b/examples/all_features/decomposition.cpp
@@ -7,6 +7,8 @@
         MoveOnly(const MoveOnly&) = delete;
         MoveOnly& operator=(MoveOnly&&) = default;
         MoveOnly& operator=(const MoveOnly&) = default;
+        ~MoveOnly() = default;
+        // NOLINTNEXTLINE(readability-make-member-function-const)
         operator bool() { // NOT const!
             return i == 42;
         }
@@ -16,7 +18,7 @@
 };
 
 static MoveOnly genType(bool b) {
-    return MoveOnly(b ? 42 : 0);
+    return { b ? 42 : 0 };
 }
 
 TEST_CASE("Move Only Type") {
diff --git a/examples/all_features/enums.cpp b/examples/all_features/enums.cpp
index e58c3a9..d92127d 100644
--- a/examples/all_features/enums.cpp
+++ b/examples/all_features/enums.cpp
@@ -67,7 +67,7 @@
 template<class E, class T = typename std::underlying_type<E>::type>
 T printable(E val)
 {
-    return T(val);
+    return static_cast<T>(val);
 }
 
 }
diff --git a/examples/all_features/header.h b/examples/all_features/header.h
index 6a46aec..5c5fed4 100644
--- a/examples/all_features/header.h
+++ b/examples/all_features/header.h
@@ -7,7 +7,7 @@
 int throw_if(bool in, const T& ex) {
     if(in)
 #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
-        throw ex;
+        throw ex; // NOLINT
 #else  // DOCTEST_CONFIG_NO_EXCEPTIONS
         ((void)ex);
 #endif // DOCTEST_CONFIG_NO_EXCEPTIONS
@@ -47,6 +47,7 @@
 DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
 #endif // gcc 5
 
+// NOLINTBEGIN
 struct SomeFixture
 {
     int data;
@@ -59,6 +60,7 @@
         // teardown here
     }
 };
+// NOLINTEND
 
 TEST_CASE_FIXTURE(SomeFixture, "fixtured test") {
     data /= 2;
diff --git a/examples/all_features/logging.cpp b/examples/all_features/logging.cpp
index ddc95d3..5182b16 100644
--- a/examples/all_features/logging.cpp
+++ b/examples/all_features/logging.cpp
@@ -51,9 +51,11 @@
     throw_if(true, 0);
 }
 
+// TODO: Also remove
+// NOLINTNEXTLINE(misc-unused-parameters)
 static void thirdPartyAssert(bool result, bool is_fatal, const char* file, int line) {
-    if(result == false) {
-        if(is_fatal)
+    if(!result) {
+        if(is_fatal) // NOLINT(bugprone-branch-clone)
             ADD_FAIL_AT(file, line, "MY_ASSERT_FATAL(" << result << ")");
         else
             ADD_FAIL_CHECK_AT(file, line, "MY_ASSERT(" << result << ")");
diff --git a/examples/all_features/main.cpp b/examples/all_features/main.cpp
index 76086c7..e51542b 100644
--- a/examples/all_features/main.cpp
+++ b/examples/all_features/main.cpp
@@ -46,6 +46,6 @@
 }
 
 int program() {
-    printf("Program code.\n");
+    std::cout << "Program code." << std::endl;
     return EXIT_SUCCESS;
 }
diff --git a/examples/all_features/stringification.cpp b/examples/all_features/stringification.cpp
index 57e654a..a9518a3 100644
--- a/examples/all_features/stringification.cpp
+++ b/examples/all_features/stringification.cpp
@@ -4,7 +4,7 @@
 namespace std {
     template <typename> struct char_traits;
     template <typename, typename> class basic_ostream;
-    typedef basic_ostream<char, char_traits<char>> ostream;
+    typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using)
     template<class TRAITS>
     basic_ostream<char, TRAITS>& operator<<(basic_ostream<char, TRAITS>&, const char*);
 }
@@ -44,16 +44,16 @@
 #endif
 
 TEST_CASE("no headers") {
-    char chs[] = { '1', 'a', 's' };
+    char chs[] = { '1', 'a', 's' }; // NOLINT(*-avoid-c-arrays)
     MESSAGE(chs); CHECK(chs == nullptr);
     MESSAGE("1as"); CHECK("1as" == nullptr);
 
-    int ints[] = { 0, 1, 1, 2, 3, 5, 8, 13 };
+    int ints[] = { 0, 1, 1, 2, 3, 5, 8, 13 }; // NOLINT(*-avoid-c-arrays)
     MESSAGE(ints); CHECK(ints == nullptr);
-    MESSAGE(MOVE(ints));
+    MESSAGE(MOVE(ints)); // NOLINT(*-move-const-arg)
 
-    char* cptr = reinterpret_cast<char*>(ints + 4);
-    const char* ccptr = const_cast<const char*>(cptr);
+    char* cptr = reinterpret_cast<char*>(ints + 4); // NOLINT
+    const char* ccptr = cptr;
     void* vptr = reinterpret_cast<void*>(cptr);
     CHECK(doctest::toString(cptr) == doctest::toString(ccptr));
     CHECK(doctest::toString(ccptr) == doctest::toString(vptr));
@@ -80,7 +80,7 @@
 DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation diagnostics
 
 // the standard forbids writing in the std namespace but it works on all compilers
-namespace std
+namespace std // NOLINT(cert-dcl58-cpp)
 {
 template <typename T>
 ostream& operator<<(ostream& stream, const vector<T>& in) {
@@ -104,6 +104,7 @@
         std::ostringstream oss;
 
         oss << "[";
+        // NOLINTNEXTLINE(*-use-auto)
         for (typename std::list<T>::const_iterator it = in.begin(); it != in.end();) {
             oss << *it;
             if (++it != in.end()) { oss << ", "; }
diff --git a/examples/all_features/subcases.cpp b/examples/all_features/subcases.cpp
index 3fad25f..558baa0 100644
--- a/examples/all_features/subcases.cpp
+++ b/examples/all_features/subcases.cpp
@@ -135,7 +135,7 @@
     }
 }
 
-static void checks(int data)
+static void checks(int data) // NOLINT(misc-unused-parameters)
 {
     DOCTEST_SUBCASE("check data 1") { REQUIRE(data % 2 == 0); }
     DOCTEST_SUBCASE("check data 2") { REQUIRE(data % 4 == 0); }
diff --git a/examples/all_features/templated_test_cases.cpp b/examples/all_features/templated_test_cases.cpp
index 7c45f13..dafb32e 100644
--- a/examples/all_features/templated_test_cases.cpp
+++ b/examples/all_features/templated_test_cases.cpp
@@ -45,8 +45,8 @@
 template <typename first, typename second>
 struct TypePair
 {
-    typedef first  A;
-    typedef second B;
+    using A = first;
+    using B = second;
 };
 
 TYPE_TO_STRING_AS("Custom name test", TypePair<int, char>);
@@ -54,8 +54,8 @@
 TYPE_TO_STRING(TypePair<bool, int>);
 
 TEST_CASE_TEMPLATE("multiple types", T, TypePair<int, char>, TypePair<char, int>, TypePair<bool, int>) {
-    typedef typename T::A T1;
-    typedef typename T::B T2;
+    using T1 = typename T::A;
+    using T2 = typename T::B;
     T1 t1 = T1();
     T2 t2 = T2();
     // use T1 and T2 types
@@ -64,12 +64,12 @@
 }
 
 // currently the string result will be "int_pair" instead of "TypePair<int, int>" because of the way the type stringification works
-typedef TypePair<int, int> int_pair;
+using int_pair = TypePair<int, int>;
 TYPE_TO_STRING(int_pair);
 
 TEST_CASE_TEMPLATE("bad stringification of type pair", T, int_pair) {
-    typedef typename T::A T1;
-    typedef typename T::B T2;
+    using T1 = typename T::A;
+    using T2 = typename T::B;
     T1 t1 = T1();
     T2 t2 = T2();
     // use T1 and T2 types