//===--- ClangTidyCheck.h - clang-tidy --------------------------*- 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H #include "ClangTidyDiagnosticConsumer.h" #include "ClangTidyOptions.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/Diagnostic.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/Error.h" #include #include #include namespace clang { class CompilerInstance; class SourceManager; namespace tidy { /// This class should be specialized by any enum type that needs to be converted /// to and from an \ref llvm::StringRef. template struct OptionEnumMapping { // Specializations of this struct must implement this function. static ArrayRef> getEnumMapping() = delete; }; template class OptionError : public llvm::ErrorInfo { std::error_code convertToErrorCode() const override { return llvm::inconvertibleErrorCode(); } public: void log(raw_ostream &OS) const override { OS << this->message(); } }; class MissingOptionError : public OptionError { public: explicit MissingOptionError(std::string OptionName) : OptionName(OptionName) {} std::string message() const override; static char ID; private: const std::string OptionName; }; class UnparseableEnumOptionError : public OptionError { public: explicit UnparseableEnumOptionError(std::string LookupName, std::string LookupValue) : LookupName(LookupName), LookupValue(LookupValue) {} explicit UnparseableEnumOptionError(std::string LookupName, std::string LookupValue, std::string SuggestedValue) : LookupName(LookupName), LookupValue(LookupValue), SuggestedValue(SuggestedValue) {} std::string message() const override; static char ID; private: const std::string LookupName; const std::string LookupValue; const llvm::Optional SuggestedValue; }; class UnparseableIntegerOptionError : public OptionError { public: explicit UnparseableIntegerOptionError(std::string LookupName, std::string LookupValue, bool IsBoolean = false) : LookupName(LookupName), LookupValue(LookupValue), IsBoolean(IsBoolean) { } std::string message() const override; static char ID; private: const std::string LookupName; const std::string LookupValue; const bool IsBoolean; }; /// Base class for all clang-tidy checks. /// /// To implement a ``ClangTidyCheck``, write a subclass and override some of the /// base class's methods. E.g. to implement a check that validates namespace /// declarations, override ``registerMatchers``: /// /// ~~~{.cpp} /// void registerMatchers(ast_matchers::MatchFinder *Finder) override { /// Finder->addMatcher(namespaceDecl().bind("namespace"), this); /// } /// ~~~ /// /// and then override ``check(const MatchResult &Result)`` to do the actual /// check for each match. /// /// A new ``ClangTidyCheck`` instance is created per translation unit. /// /// FIXME: Figure out whether carrying information from one TU to another is /// useful/necessary. class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { public: /// Initializes the check with \p CheckName and \p Context. /// /// Derived classes must implement the constructor with this signature or /// delegate it. If a check needs to read options, it can do this in the /// constructor using the Options.get() methods below. ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context); /// Override this to disable registering matchers and PP callbacks if an /// invalid language version is being used. /// /// For example if a check is examining overloaded functions then this should /// be overridden to return false when the CPlusPlus flag is not set in /// \p LangOpts. virtual bool isLanguageVersionSupported(const LangOptions &LangOpts) const { return true; } /// Override this to register ``PPCallbacks`` in the preprocessor. /// /// This should be used for clang-tidy checks that analyze preprocessor- /// dependent properties, e.g. include directives and macro definitions. /// /// This will only be executed if the function isLanguageVersionSupported /// returns true. /// /// There are two Preprocessors to choose from that differ in how they handle /// modular #includes: /// - PP is the real Preprocessor. It doesn't walk into modular #includes and /// thus doesn't generate PPCallbacks for their contents. /// - ModuleExpanderPP preprocesses the whole translation unit in the /// non-modular mode, which allows it to generate PPCallbacks not only for /// the main file and textual headers, but also for all transitively /// included modular headers when the analysis runs with modules enabled. /// When modules are not enabled ModuleExpanderPP just points to the real /// preprocessor. virtual void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {} /// Override this to register AST matchers with \p Finder. /// /// This should be used by clang-tidy checks that analyze code properties that /// dependent on AST knowledge. /// /// You can register as many matchers as necessary with \p Finder. Usually, /// "this" will be used as callback, but you can also specify other callback /// classes. Thereby, different matchers can trigger different callbacks. /// /// This will only be executed if the function isLanguageVersionSupported /// returns true. /// /// If you need to merge information between the different matchers, you can /// store these as members of the derived class. However, note that all /// matches occur in the order of the AST traversal. virtual void registerMatchers(ast_matchers::MatchFinder *Finder) {} /// ``ClangTidyChecks`` that register ASTMatchers should do the actual /// work in here. virtual void check(const ast_matchers::MatchFinder::MatchResult &Result) {} /// Add a diagnostic with the check's name. DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level = DiagnosticIDs::Warning); /// Add a diagnostic with the check's name. DiagnosticBuilder diag(StringRef Description, DiagnosticIDs::Level Level = DiagnosticIDs::Warning); /// Adds a diagnostic to report errors in the check's configuration. DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level = DiagnosticIDs::Warning); /// Should store all options supported by this check with their /// current values or default values for options that haven't been overridden. /// /// The check should use ``Options.store()`` to store each option it supports /// whether it has the default value or it has been overridden. virtual void storeOptions(ClangTidyOptions::OptionMap &Options) {} /// Provides access to the ``ClangTidyCheck`` options via check-local /// names. /// /// Methods of this class prepend ``CheckName + "."`` to translate check-local /// option names to global option names. class OptionsView { public: /// Initializes the instance using \p CheckName + "." as a prefix. OptionsView(StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions, ClangTidyContext *Context); /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// a ``MissingOptionError``. llvm::Expected get(StringRef LocalName) const; /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// \p Default. std::string get(StringRef LocalName, StringRef Default) const { if (llvm::Expected Val = get(LocalName)) return *Val; else llvm::consumeError(Val.takeError()); return Default.str(); } /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns a ``MissingOptionError``. llvm::Expected getLocalOrGlobal(StringRef LocalName) const; /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns \p Default. std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const { if (llvm::Expected Val = getLocalOrGlobal(LocalName)) return *Val; else llvm::consumeError(Val.takeError()); return Default.str(); } /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// a ``MissingOptionError``. If the corresponding key can't be parsed as /// a ``T``, return an ``UnparseableIntegerOptionError``. template std::enable_if_t::value, llvm::Expected> get(StringRef LocalName) const { if (llvm::Expected Value = get(LocalName)) { T Result{}; if (!StringRef(*Value).getAsInteger(10, Result)) return Result; return llvm::make_error( (NamePrefix + LocalName).str(), *Value); } else return std::move(Value.takeError()); } /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present or it can't be /// parsed as a ``T``, returns \p Default. template std::enable_if_t::value, T> get(StringRef LocalName, T Default) const { if (llvm::Expected ValueOr = get(LocalName)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return Default; } /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns a ``MissingOptionError``. If the corresponding /// key can't be parsed as a ``T``, return an /// ``UnparseableIntegerOptionError``. template std::enable_if_t::value, llvm::Expected> getLocalOrGlobal(StringRef LocalName) const { llvm::Expected ValueOr = get(LocalName); bool IsGlobal = false; if (!ValueOr) { IsGlobal = true; llvm::consumeError(ValueOr.takeError()); ValueOr = getLocalOrGlobal(LocalName); if (!ValueOr) return std::move(ValueOr.takeError()); } T Result{}; if (!StringRef(*ValueOr).getAsInteger(10, Result)) return Result; return llvm::make_error( (IsGlobal ? LocalName.str() : (NamePrefix + LocalName).str()), *ValueOr); } /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either or it can't be parsed as a ``T``, returns \p Default. template std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default) const { if (llvm::Expected ValueOr = getLocalOrGlobal(LocalName)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return Default; } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns a /// ``MissingOptionError``. If the key can't be parsed as a ``T`` returns a /// ``UnparseableEnumOptionError``. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value, llvm::Expected> get(StringRef LocalName, bool IgnoreCase = false) const { if (llvm::Expected ValueOr = getEnumInt(LocalName, typeEraseMapping(), false, IgnoreCase)) return static_cast(*ValueOr); else return std::move(ValueOr.takeError()); } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present or it can't be /// parsed as a ``T``, returns \p Default. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value, T> get(StringRef LocalName, T Default, bool IgnoreCase = false) const { if (auto ValueOr = get(LocalName, IgnoreCase)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return Default; } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns a ``MissingOptionError``. If the key can't be /// parsed as a ``T`` returns a ``UnparseableEnumOptionError``. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value, llvm::Expected> getLocalOrGlobal(StringRef LocalName, bool IgnoreCase = false) const { if (llvm::Expected ValueOr = getEnumInt(LocalName, typeEraseMapping(), true, IgnoreCase)) return static_cast(*ValueOr); else return std::move(ValueOr.takeError()); } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either or it can't be parsed as a ``T``, returns \p Default. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default, bool IgnoreCase = false) const { if (auto ValueOr = getLocalOrGlobal(LocalName, IgnoreCase)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return Default; } /// Returns the value for the option \p LocalName represented as a ``T``. /// If the option is missing returns None, if the option can't be parsed /// as a ``T``, log that to stderr and return None. template llvm::Optional getOptional(StringRef LocalName) const { if (auto ValueOr = get(LocalName)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return llvm::None; } /// Returns the value for the local or global option \p LocalName /// represented as a ``T``. /// If the option is missing returns None, if the /// option can't be parsed as a ``T``, log that to stderr and return None. template llvm::Optional getOptionalLocalOrGlobal(StringRef LocalName) const { if (auto ValueOr = getLocalOrGlobal(LocalName)) return *ValueOr; else reportOptionParsingError(ValueOr.takeError()); return llvm::None; } /// Stores an option with the check-local name \p LocalName with /// string value \p Value to \p Options. void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const; /// Stores an option with the check-local name \p LocalName with /// integer value \p Value to \p Options. template std::enable_if_t::value> store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, T Value) const { storeInt(Options, LocalName, Value); } /// Stores an option with the check-local name \p LocalName as the string /// representation of the Enum \p Value to \p Options. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value> store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, T Value) const { ArrayRef> Mapping = OptionEnumMapping::getEnumMapping(); auto Iter = llvm::find_if( Mapping, [&](const std::pair &NameAndEnum) { return NameAndEnum.first == Value; }); assert(Iter != Mapping.end() && "Unknown Case Value"); store(Options, LocalName, Iter->second); } private: using NameAndValue = std::pair; llvm::Expected getEnumInt(StringRef LocalName, ArrayRef Mapping, bool CheckGlobal, bool IgnoreCase) const; template std::enable_if_t::value, std::vector> typeEraseMapping() const { ArrayRef> Mapping = OptionEnumMapping::getEnumMapping(); std::vector Result; Result.reserve(Mapping.size()); for (auto &MappedItem : Mapping) { Result.emplace_back(static_cast(MappedItem.first), MappedItem.second); } return Result; } void storeInt(ClangTidyOptions::OptionMap &Options, StringRef LocalName, int64_t Value) const; /// Emits a diagnostic if \p Err is not a MissingOptionError. void reportOptionParsingError(llvm::Error &&Err) const; std::string NamePrefix; const ClangTidyOptions::OptionMap &CheckOptions; ClangTidyContext *Context; }; private: void run(const ast_matchers::MatchFinder::MatchResult &Result) override; StringRef getID() const override { return CheckName; } std::string CheckName; ClangTidyContext *Context; protected: OptionsView Options; /// Returns the main file name of the current translation unit. StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } /// Returns the language options from the context. const LangOptions &getLangOpts() const { return Context->getLangOpts(); } }; /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// a ``MissingOptionError``. If the corresponding key can't be parsed as /// a bool, return an ``UnparseableIntegerOptionError``. template <> llvm::Expected ClangTidyCheck::OptionsView::get(StringRef LocalName) const; /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present or it can't be /// parsed as a bool, returns \p Default. template <> bool ClangTidyCheck::OptionsView::get(StringRef LocalName, bool Default) const; /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns a ``MissingOptionError``. If the corresponding /// key can't be parsed as a bool, return an /// ``UnparseableIntegerOptionError``. template <> llvm::Expected ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const; /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either or it can't be parsed as a bool, returns \p Default. template <> bool ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, bool Default) const; /// Stores an option with the check-local name \p LocalName with /// bool value \p Value to \p Options. template <> void ClangTidyCheck::OptionsView::store( ClangTidyOptions::OptionMap &Options, StringRef LocalName, bool Value) const; /// Returns the value for the option \p LocalName. /// If the option is missing returns None. template <> Optional ClangTidyCheck::OptionsView::getOptional( StringRef LocalName) const; /// Returns the value for the local or global option \p LocalName. /// If the option is missing returns None. template <> Optional ClangTidyCheck::OptionsView::getOptionalLocalOrGlobal( StringRef LocalName) const; } // namespace tidy } // namespace clang #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYCHECK_H