162 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===---------- TransformerClangTidyCheck.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 "TransformerClangTidyCheck.h"
 | |
| #include "clang/Basic/DiagnosticIDs.h"
 | |
| #include "clang/Lex/Preprocessor.h"
 | |
| #include "llvm/ADT/STLExtras.h"
 | |
| 
 | |
| namespace clang {
 | |
| namespace tidy {
 | |
| namespace utils {
 | |
| using transformer::RewriteRuleWith;
 | |
| 
 | |
| #ifndef NDEBUG
 | |
| static bool hasGenerator(const transformer::Generator<std::string> &G) {
 | |
|   return G != nullptr;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static void verifyRule(const RewriteRuleWith<std::string> &Rule) {
 | |
|   assert(llvm::all_of(Rule.Metadata, hasGenerator) &&
 | |
|          "clang-tidy checks must have an explanation by default;"
 | |
|          " explicitly provide an empty explanation if none is desired");
 | |
| }
 | |
| 
 | |
| // If a string unintentionally containing '%' is passed as a diagnostic, Clang
 | |
| // will claim the string is ill-formed and assert-fail. This function escapes
 | |
| // such strings so they can be safely used in diagnostics.
 | |
| std::string escapeForDiagnostic(std::string ToEscape) {
 | |
|   // Optimize for the common case that the string does not contain `%` at the
 | |
|   // cost of an extra scan over the string in the slow case.
 | |
|   auto Pos = ToEscape.find('%');
 | |
|   if (Pos == ToEscape.npos)
 | |
|     return ToEscape;
 | |
| 
 | |
|   std::string Result;
 | |
|   Result.reserve(ToEscape.size());
 | |
|   // Convert position to a count.
 | |
|   ++Pos;
 | |
|   Result.append(ToEscape, 0, Pos);
 | |
|   Result += '%';
 | |
| 
 | |
|   for (auto N = ToEscape.size(); Pos < N; ++Pos) {
 | |
|     const char C = ToEscape.at(Pos);
 | |
|     Result += C;
 | |
|     if (C == '%')
 | |
|       Result += '%';
 | |
|   }
 | |
| 
 | |
|   return Result;
 | |
| }
 | |
| 
 | |
| TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name,
 | |
|                                                      ClangTidyContext *Context)
 | |
|     : ClangTidyCheck(Name, Context),
 | |
|       Inserter(Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM),
 | |
|                areDiagsSelfContained()) {}
 | |
| 
 | |
| // This constructor cannot dispatch to the simpler one (below), because, in
 | |
| // order to get meaningful results from `getLangOpts` and `Options`, we need the
 | |
| // `ClangTidyCheck()` constructor to have been called. If we were to dispatch,
 | |
| // we would be accessing `getLangOpts` and `Options` before the underlying
 | |
| // `ClangTidyCheck` instance was properly initialized.
 | |
| TransformerClangTidyCheck::TransformerClangTidyCheck(
 | |
|     std::function<Optional<RewriteRuleWith<std::string>>(const LangOptions &,
 | |
|                                                          const OptionsView &)>
 | |
|         MakeRule,
 | |
|     StringRef Name, ClangTidyContext *Context)
 | |
|     : TransformerClangTidyCheck(Name, Context) {
 | |
|   if (Optional<RewriteRuleWith<std::string>> R =
 | |
|           MakeRule(getLangOpts(), Options))
 | |
|     setRule(std::move(*R));
 | |
| }
 | |
| 
 | |
| TransformerClangTidyCheck::TransformerClangTidyCheck(
 | |
|     RewriteRuleWith<std::string> R, StringRef Name, ClangTidyContext *Context)
 | |
|     : TransformerClangTidyCheck(Name, Context) {
 | |
|   setRule(std::move(R));
 | |
| }
 | |
| 
 | |
| void TransformerClangTidyCheck::setRule(
 | |
|     transformer::RewriteRuleWith<std::string> R) {
 | |
|   verifyRule(R);
 | |
|   Rule = std::move(R);
 | |
| }
 | |
| 
 | |
| void TransformerClangTidyCheck::registerPPCallbacks(
 | |
|     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
 | |
|   Inserter.registerPreprocessor(PP);
 | |
| }
 | |
| 
 | |
| void TransformerClangTidyCheck::registerMatchers(
 | |
|     ast_matchers::MatchFinder *Finder) {
 | |
|   if (!Rule.Cases.empty())
 | |
|     for (auto &Matcher : transformer::detail::buildMatchers(Rule))
 | |
|       Finder->addDynamicMatcher(Matcher, this);
 | |
| }
 | |
| 
 | |
| void TransformerClangTidyCheck::check(
 | |
|     const ast_matchers::MatchFinder::MatchResult &Result) {
 | |
|   if (Result.Context->getDiagnostics().hasErrorOccurred())
 | |
|     return;
 | |
| 
 | |
|   size_t I = transformer::detail::findSelectedCase(Result, Rule);
 | |
|   Expected<SmallVector<transformer::Edit, 1>> Edits =
 | |
|       Rule.Cases[I].Edits(Result);
 | |
|   if (!Edits) {
 | |
|     llvm::errs() << "Rewrite failed: " << llvm::toString(Edits.takeError())
 | |
|                  << "\n";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // No rewrite applied, but no error encountered either.
 | |
|   if (Edits->empty())
 | |
|     return;
 | |
| 
 | |
|   Expected<std::string> Explanation = Rule.Metadata[I]->eval(Result);
 | |
|   if (!Explanation) {
 | |
|     llvm::errs() << "Error in explanation: "
 | |
|                  << llvm::toString(Explanation.takeError()) << "\n";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Associate the diagnostic with the location of the first change.
 | |
|   {
 | |
|     DiagnosticBuilder Diag =
 | |
|         diag((*Edits)[0].Range.getBegin(), escapeForDiagnostic(*Explanation));
 | |
|     for (const auto &T : *Edits) {
 | |
|       switch (T.Kind) {
 | |
|       case transformer::EditKind::Range:
 | |
|         Diag << FixItHint::CreateReplacement(T.Range, T.Replacement);
 | |
|         break;
 | |
|       case transformer::EditKind::AddInclude:
 | |
|         Diag << Inserter.createIncludeInsertion(
 | |
|             Result.SourceManager->getFileID(T.Range.getBegin()), T.Replacement);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   // Emit potential notes.
 | |
|   for (const auto &T : *Edits) {
 | |
|     if (!T.Note.empty()) {
 | |
|       diag(T.Range.getBegin(), escapeForDiagnostic(T.Note),
 | |
|            DiagnosticIDs::Note);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TransformerClangTidyCheck::storeOptions(
 | |
|     ClangTidyOptions::OptionMap &Opts) {
 | |
|   Options.store(Opts, "IncludeStyle", Inserter.getStyle());
 | |
| }
 | |
| 
 | |
| } // namespace utils
 | |
| } // namespace tidy
 | |
| } // namespace clang
 |