Index: src/Clazy.h =================================================================== --- src/Clazy.h +++ src/Clazy.h @@ -86,13 +86,15 @@ explicit ClazyStandaloneASTAction(const std::string &checkList, const std::string &headerFilter, const std::string &ignoreDirs, + const std::string &exportFixes, ClazyContext::ClazyOptions = ClazyContext::ClazyOption_None); protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &ci, llvm::StringRef) override; private: const std::string m_checkList; const std::string m_headerFilter; const std::string m_ignoreDirs; + const std::string m_exportFixes; const ClazyContext::ClazyOptions m_options; }; Index: src/Clazy.cpp =================================================================== --- src/Clazy.cpp +++ src/Clazy.cpp @@ -169,7 +169,8 @@ void ClazyASTConsumer::HandleTranslationUnit(ASTContext &ctx) { // FIXME: EndSourceFile() is called automatically, but not BeginsSourceFile() - m_context->exporter->BeginSourceFile(clang::LangOptions()); + if (m_context->exporter) + m_context->exporter->BeginSourceFile(clang::LangOptions()); if ((m_context->options & ClazyContext::ClazyOption_OnlyQt) && !m_context->isQt()) return; @@ -233,9 +234,10 @@ const string headerFilter = getEnvVariable("CLAZY_HEADER_FILTER"); const string ignoreDirs = getEnvVariable("CLAZY_IGNORE_DIRS"); + std::string exportFixes; if (parseArgument("help", args)) { - m_context = new ClazyContext(ci, headerFilter, ignoreDirs, ClazyContext::ClazyOption_None); + m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixes, ClazyContext::ClazyOption_None); PrintHelp(llvm::errs()); return true; } @@ -268,7 +270,10 @@ if (parseArgument("ignore-included-files", args)) m_options |= ClazyContext::ClazyOption_IgnoreIncludedFiles; - m_context = new ClazyContext(ci, headerFilter, ignoreDirs, m_options); + if (parseArgument("export-fixes", args)) + exportFixes = args.at(0); + + m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixes, m_options); // This argument is for debugging purposes const bool dbgPrintRequestedChecks = parseArgument("print-requested-checks", args); @@ -373,19 +378,20 @@ ros << "FixIts are experimental and rewrite your code therefore only one FixIt is allowed per build.\nSpecifying a list of different FixIts is not supported.\nBackup your code before running them.\n"; } -ClazyStandaloneASTAction::ClazyStandaloneASTAction(const string &checkList, const string &headerFilter, const string &ignoreDirs, +ClazyStandaloneASTAction::ClazyStandaloneASTAction(const string &checkList, const string &headerFilter, const string &ignoreDirs, const string &exportFixes, ClazyContext::ClazyOptions options) : clang::ASTFrontendAction() , m_checkList(checkList.empty() ? "level1" : checkList) , m_headerFilter(headerFilter.empty() ? getEnvVariable("CLAZY_HEADER_FILTER") : headerFilter) , m_ignoreDirs(ignoreDirs.empty() ? getEnvVariable("CLAZY_IGNORE_DIRS") : ignoreDirs) + , m_exportFixes(exportFixes) , m_options(options) { } unique_ptr ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { - auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_options); + auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_exportFixes, m_options); auto astConsumer = new ClazyASTConsumer(context); auto cm = CheckManager::instance(); Index: src/ClazyContext.h =================================================================== --- src/ClazyContext.h +++ src/ClazyContext.h @@ -74,6 +74,7 @@ explicit ClazyContext(const clang::CompilerInstance &ci, const std::string &headerFilter, const std::string &ignoreDirs, + const std::string &exportFixes, ClazyOptions = ClazyOption_None); ~ClazyContext(); @@ -97,6 +98,11 @@ return allFixitsEnabled || !requestedFixitName.empty(); } + bool exportFixesEnabled() const + { + return !exportFixes.empty(); + } + bool isQtDeveloper() const { return options & ClazyOption_QtDeveloper; @@ -185,6 +191,7 @@ FixItExporter *exporter = nullptr; bool allFixitsEnabled = false; std::string requestedFixitName; + std::string exportFixes; clang::CXXMethodDecl *lastMethodDecl = nullptr; clang::FunctionDecl *lastFunctionDecl = nullptr; clang::Decl *lastDecl = nullptr; Index: src/ClazyContext.cpp =================================================================== --- src/ClazyContext.cpp +++ src/ClazyContext.cpp @@ -62,13 +62,14 @@ }; ClazyContext::ClazyContext(const clang::CompilerInstance &compiler, - const string &headerFilter, const string &ignoreDirs, ClazyOptions opts) + const string &headerFilter, const string &ignoreDirs, const string &exportFixes, ClazyOptions opts) : ci(compiler) , astContext(ci.getASTContext()) , sm(ci.getSourceManager()) , m_noWerror(getenv("CLAZY_NO_WERROR") != nullptr) // Allows user to make clazy ignore -Werror , options(opts) , extraOptions(clazy::splitString(getenv("CLAZY_EXTRA_OPTIONS"), ',')) + , exportFixes(exportFixes) { if (!headerFilter.empty()) headerFilterRegex = std::unique_ptr(new llvm::Regex(headerFilter)); @@ -87,12 +88,14 @@ } } - if (fixitsEnabled() && !(options & ClazyOption_NoFixitsAutoWrite)) - rewriter = new FixItRewriter(ci.getDiagnostics(), sm, - ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); - - exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), - new ClazyFixItOptions(fixitsAreInplace())); + if (fixitsEnabled()) { + if (exportFixesEnabled()) + exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), + exportFixes); + else if (!(options & ClazyOption_NoFixitsAutoWrite)) + rewriter = new FixItRewriter(ci.getDiagnostics(), sm, + ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); + } } ClazyContext::~ClazyContext() Index: src/ClazyStandaloneMain.cpp =================================================================== --- src/ClazyStandaloneMain.cpp +++ src/ClazyStandaloneMain.cpp @@ -50,6 +50,9 @@ static cl::opt s_enableAllFixits("enable-all-fixits", cl::desc("Enables all fixits"), cl::init(false), cl::cat(s_clazyCategory)); +static cl::opt s_exportFixes("export-fixes", cl::desc("YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements."), + cl::init(""), cl::cat(s_clazyCategory)); + static cl::opt s_qt4Compat("qt4-compat", cl::desc("Turns off checks not compatible with Qt 4"), cl::init(false), cl::cat(s_clazyCategory)); @@ -108,7 +111,8 @@ if (s_ignoreIncludedFiles.getValue()) options |= ClazyContext::ClazyOption_IgnoreIncludedFiles; - return new ClazyStandaloneASTAction(s_checks.getValue(), s_headerFilter.getValue(), s_ignoreDirs.getValue(), options); + // TODO: We need to agregate the fixes with previous run + return new ClazyStandaloneASTAction(s_checks.getValue(), s_headerFilter.getValue(), s_ignoreDirs.getValue(), s_exportFixes.getValue(), options); } }; Index: src/FixItExporter.h =================================================================== --- src/FixItExporter.h +++ src/FixItExporter.h @@ -33,7 +33,7 @@ class FixItExporter : public clang::DiagnosticConsumer { public: FixItExporter(clang::DiagnosticsEngine &DiagEngine, clang::SourceManager &SourceMgr, - const clang::LangOptions &LangOpts, clang::FixItOptions *FixItOpts); + const clang::LangOptions &LangOpts, const std::string &exportFixes); ~FixItExporter() override; @@ -56,7 +56,7 @@ clang::DiagnosticsEngine &DiagEngine; clang::SourceManager &SourceMgr; const clang::LangOptions &LangOpts; - clang::FixItOptions *FixItOpts; + const std::string exportFixes; DiagnosticConsumer *Client; std::unique_ptr Owner; clang::tooling::TranslationUnitDiagnostics TUDiag; Index: src/FixItExporter.cpp =================================================================== --- src/FixItExporter.cpp +++ src/FixItExporter.cpp @@ -35,9 +35,9 @@ using namespace clang; -FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, const LangOptions &LangOpts, FixItOptions *FixItOpts) +FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, const LangOptions &LangOpts, const std::string &exportFixes) : DiagEngine(DiagEngine), SourceMgr(SourceMgr), LangOpts(LangOpts), - FixItOpts(FixItOpts) { + exportFixes(exportFixes) { Owner = DiagEngine.takeClient(); Client = DiagEngine.getClient(); DiagEngine.setClient(this, false); @@ -67,7 +67,7 @@ tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info) { - SmallString<100> TmpMessageText; + SmallString<256> TmpMessageText; Info.FormatDiagnostic(TmpMessageText); const auto MessageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str(); const auto CheckName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, @@ -83,6 +83,8 @@ tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint) { + // TODO: Proper handling of macros + // https://stackoverflow.com/questions/24062989/clang-fails-replacing-a-statement-if-it-contains-a-macro tooling::Replacement Replacement; if (Hint.CodeToInsert.empty()) { if (Hint.InsertFromRange.isValid()) { @@ -93,7 +95,7 @@ _e = SourceMgr.getSpellingLoc(_e); clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); StringRef Text(SourceMgr.getCharacterData(b), - SourceMgr.getCharacterData(e)-SourceMgr.getCharacterData(b)); + SourceMgr.getCharacterData(e) - SourceMgr.getCharacterData(b)); return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); @@ -105,44 +107,43 @@ // Default implementation (Warnings/errors count). DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); - // Let origianl client do it's handling - if (Client) - Client->HandleDiagnostic(DiagLevel, Info); - - // We only deal with warnings - if (DiagLevel != DiagnosticsEngine::Warning) - return; - - // Convert and record this diagnostic - auto ToolingDiag = ConvertDiagnostic(Info); - for (unsigned Idx = 0, Last = Info.getNumFixItHints(); - Idx < Last; ++Idx) { - const FixItHint &Hint = Info.getFixItHint(Idx); - const auto replacement = ConvertFixIt(Hint); + // Convert and record warning diagnostics + if (DiagLevel == DiagnosticsEngine::Warning) { + auto ToolingDiag = ConvertDiagnostic(Info); + for (unsigned Idx = 0, Last = Info.getNumFixItHints(); + Idx < Last; ++Idx) { + const FixItHint &Hint = Info.getFixItHint(Idx); + const auto replacement = ConvertFixIt(Hint); #ifdef DEBUG_FIX_IT_EXPORTER - const auto FileName = SourceMgr.getFilename(Info.getLocation()); - std::cerr << "Handling Fixit #" << Idx << " for " << FileName.str() << std::endl; - std::cerr << "F: " - << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" - << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " - << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" - << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " - << Hint.BeforePreviousInsertions << " " - << Hint.CodeToInsert << std::endl; - std::cerr << "R: " << replacement.toString() << std::endl; + const auto FileName = SourceMgr.getFilename(Info.getLocation()); + std::cerr << "Handling Fixit #" << Idx << " for " << FileName.str() << std::endl; + std::cerr << "F: " + << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" + << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " + << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" + << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " + << Hint.BeforePreviousInsertions << " " + << Hint.CodeToInsert << std::endl; + std::cerr << "R: " << replacement.toString() << std::endl; #endif - auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; - auto error = Replacements.add(ConvertFixIt(Hint)); - if (error) { - Diag(Info.getLocation(), diag::note_fixit_failed); + auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; + auto error = Replacements.add(ConvertFixIt(Hint)); + if (error) { + Diag(Info.getLocation(), diag::note_fixit_failed); + } } + TUDiag.Diagnostics.push_back(ToolingDiag); } - TUDiag.Diagnostics.push_back(ToolingDiag); + + // Let original client do it's handling + if (Client) + Client->HandleDiagnostic(DiagLevel, Info); } void FixItExporter::Export() { std::error_code EC; - llvm::raw_fd_ostream OS(TUDiag.MainSourceFile + ".yaml", EC, llvm::sys::fs::F_None); + // FIXME: aggregation + llvm::raw_fd_ostream OS(exportFixes, EC, llvm::sys::fs::F_None); llvm::yaml::Output YAML(OS); YAML << TUDiag; }