Index: clang-tidy/ClangTidy.h =================================================================== --- clang-tidy/ClangTidy.h +++ clang-tidy/ClangTidy.h @@ -210,12 +210,10 @@ /// /// \param Profile if provided, it enables check profile collection in /// MatchFinder, and will contain the result of the profile. -ClangTidyStats -runClangTidy(std::unique_ptr OptionsProvider, +void +runClangTidy(clang::tidy::ClangTidyContext& Context, const tooling::CompilationDatabase &Compilations, - ArrayRef InputFiles, - std::vector *Errors, - ProfileData *Profile = nullptr); + ArrayRef InputFiles); // FIXME: This interface will need to be significantly extended to be useful. // FIXME: Implement confidence levels for displaying/fixing errors. Index: clang-tidy/ClangTidy.cpp =================================================================== --- clang-tidy/ClangTidy.cpp +++ clang-tidy/ClangTidy.cpp @@ -107,6 +107,10 @@ DiagPrinter->BeginSourceFile(LangOpts); } + SourceManager& getSourceManager() { + return SourceMgr; + } + void reportDiagnostic(const ClangTidyError &Error) { const ClangTidyMessage &Message = Error.Message; SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); @@ -124,7 +128,10 @@ auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) << Message.Message << Name; for (const tooling::Replacement &Fix : Error.Fix) { - SourceLocation FixLoc = getLocation(Fix.getFilePath(), Fix.getOffset()); + SmallString<128> FixAbsoluteFilePath = Fix.getFilePath(); + Files.makeAbsolutePath(FixAbsoluteFilePath); + SourceLocation FixLoc = + getLocation(FixAbsoluteFilePath, Fix.getOffset()); SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Fix.getLength()); Diag << FixItHint::CreateReplacement(SourceRange(FixLoc, FixEndLoc), Fix.getReplacementText()); @@ -232,6 +239,16 @@ Context.setCurrentFile(File); Context.setASTContext(&Compiler.getASTContext()); + auto WorkingDir = Compiler.getSourceManager() + .getFileManager() + .getVirtualFileSystem() + ->getCurrentWorkingDirectory(); + if (WorkingDir) { + if (Context.getBuildDirectories().empty() || + Context.getBuildDirectories().back() != WorkingDir.get()) + Context.getBuildDirectories().push_back(WorkingDir.get()); + } + std::vector> Checks; CheckFactories->createChecks(&Context, Checks); @@ -389,13 +406,10 @@ return Factory.getCheckOptions(); } -ClangTidyStats -runClangTidy(std::unique_ptr OptionsProvider, - const tooling::CompilationDatabase &Compilations, - ArrayRef InputFiles, - std::vector *Errors, ProfileData *Profile) { +void runClangTidy(clang::tidy::ClangTidyContext &Context, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles) { ClangTool Tool(Compilations, InputFiles); - clang::tidy::ClangTidyContext Context(std::move(OptionsProvider)); ArgumentsAdjuster PerFileExtraArgumentsInserter = [&Context]( const CommandLineArguments &Args, StringRef Filename) { ClangTidyOptions Opts = Context.getOptionsForFile(Filename); @@ -409,8 +423,6 @@ return AdjustedArgs; }; Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); - if (Profile) - Context.setCheckProfileData(Profile); ClangTidyDiagnosticConsumer DiagConsumer(Context); @@ -439,15 +451,29 @@ ActionFactory Factory(Context); Tool.run(&Factory); - *Errors = Context.getErrors(); - return Context.getStats(); } void handleErrors(const std::vector &Errors, bool Fix, unsigned &WarningsAsErrorsCount) { ErrorReporter Reporter(Fix); - for (const ClangTidyError &Error : Errors) + vfs::FileSystem &FileSystem = + *Reporter.getSourceManager().getFileManager().getVirtualFileSystem(); + auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory(); + if (!InitialWorkingDir) + llvm::report_fatal_error("Cannot get current working path."); + + for (const ClangTidyError &Error : Errors) { + if (!Error.BuildDirectory.empty()) { + // By default, the working directory of file system is the current + // clang-tidy running directory. + // + // Change the directory to the one used during the analysis. + FileSystem.setCurrentWorkingDirectory(Error.BuildDirectory); + } Reporter.reportDiagnostic(Error); + // Return to the initial directory to correctly resolve next Error. + FileSystem.setCurrentWorkingDirectory(InitialWorkingDir.get()); + } Reporter.Finish(); WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount(); } Index: clang-tidy/ClangTidyDiagnosticConsumer.h =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.h +++ clang-tidy/ClangTidyDiagnosticConsumer.h @@ -57,13 +57,24 @@ Error = DiagnosticsEngine::Error }; - ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError); + ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError, + StringRef BuildDirectory); std::string CheckName; ClangTidyMessage Message; tooling::Replacements Fix; SmallVector Notes; + // A build directory of the diagnostic source file. The string is owned by + // ClangTidyContext. + // + // It's an absolute path which is `directory` field of the source file in + // compilation database. If users don't specify the compilation database + // directory, it is the current directory where clang-tidy runs. + // + // Note: In unittest, it is empty. + StringRef BuildDirectory; + Level DiagLevel; bool IsWarningAsError; }; @@ -198,6 +209,9 @@ void setCheckProfileData(ProfileData *Profile); ProfileData *getCheckProfileData() const { return Profile; } + /// \brief Returns all build directories. + std::vector &getBuildDirectories() { return BuildDirectories; } + private: // Calls setDiagnosticsEngine() and storeError(). friend class ClangTidyDiagnosticConsumer; @@ -222,6 +236,8 @@ ClangTidyStats Stats; + std::vector BuildDirectories; + llvm::DenseMap CheckNamesByDiagnosticID; ProfileData *Profile; Index: clang-tidy/ClangTidyDiagnosticConsumer.cpp =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -116,8 +116,9 @@ ClangTidyError::ClangTidyError(StringRef CheckName, ClangTidyError::Level DiagLevel, - bool IsWarningAsError) - : CheckName(CheckName), DiagLevel(DiagLevel), + bool IsWarningAsError, + StringRef BuildDirectory) + : CheckName(CheckName), BuildDirectory(BuildDirectory), DiagLevel(DiagLevel), IsWarningAsError(IsWarningAsError) {} // Returns true if GlobList starts with the negative indicator ('-'), removes it @@ -335,7 +336,14 @@ bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && Context.getWarningAsErrorFilter().contains(CheckName); - Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError)); + if (Context.getBuildDirectories().empty()) { + // In unittest, the BuildDirectories are empty. + Errors.push_back( + ClangTidyError(CheckName, Level, IsWarningAsError, StringRef())); + } else { + Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError, + Context.getBuildDirectories().back())); + } } // FIXME: Provide correct LangOptions for each file. Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -342,11 +342,13 @@ ProfileData Profile; - std::vector Errors; - ClangTidyStats Stats = - runClangTidy(std::move(OptionsProvider), OptionsParser.getCompilations(), - PathList, &Errors, - EnableCheckProfile ? &Profile : nullptr); + clang::tidy::ClangTidyContext Context(std::move(OptionsProvider)); + /// If Profile provided, it enables check profile collection in + /// MatchFinder, and will contain the result of the profile. + if (EnableCheckProfile) + Context.setCheckProfileData(&Profile); + runClangTidy(Context, OptionsParser.getCompilations(), PathList); + const std::vector &Errors = Context.getErrors(); bool FoundErrors = std::find_if(Errors.begin(), Errors.end(), [](const ClangTidyError &E) { return E.DiagLevel == ClangTidyError::Error; @@ -369,7 +371,7 @@ exportReplacements(Errors, OS); } - printStats(Stats); + printStats(Context.getStats()); if (DisableFixes) llvm::errs() << "Found compiler errors, but -fix-errors was not specified.\n" Index: test/clang-tidy/Inputs/compilation-database/header.h =================================================================== --- /dev/null +++ test/clang-tidy/Inputs/compilation-database/header.h @@ -0,0 +1,4 @@ +#define NULL 0 + +int *p = NULL; +// CHECK-FIX: int *p = nullptr; Index: test/clang-tidy/Inputs/compilation-database/template.json =================================================================== --- /dev/null +++ test/clang-tidy/Inputs/compilation-database/template.json @@ -0,0 +1,32 @@ +[ +{ + "directory": "test_dir/a", + "command": "clang++ -o test.o test_dir/a/a.cpp", + "file": "test_dir/a/a.cpp" +}, +{ + "directory": "test_dir/a", + "command": "clang++ -o test.o test_dir/a/b.cpp", + "file": "test_dir/a/b.cpp" +}, +{ + "directory": "test_dir/", + "command": "clang++ -o test.o test_dir/b/b.cpp", + "file": "test_dir/b/b.cpp" +}, +{ + "directory": "test_dir/b", + "command": "clang++ -o test.o ../b/c.cpp", + "file": "test_dir/b/c.cpp" +}, +{ + "directory": "test_dir/b", + "command": "clang++ -I../include -o test.o ../b/d.cpp", + "file": "test_dir/b/d.cpp" +}, +{ + "directory": "test_dir/", + "command": "clang++ -o test.o test_dir/b/not-exist.cpp", + "file": "test_dir/b/not-exist.cpp" +} +] Index: test/clang-tidy/clang-tidy-run-with-database.cpp =================================================================== --- /dev/null +++ test/clang-tidy/clang-tidy-run-with-database.cpp @@ -0,0 +1,29 @@ +// REQUIRES: shell +// RUN: mkdir -p %T/compilation-database-test +// RUN: mkdir -p %T/compilation-database-test/include +// RUN: mkdir -p %T/compilation-database-test/a +// RUN: mkdir -p %T/compilation-database-test/b +// RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp +// RUN: sed 's|pointer|AA|g' %t.cpp > %T/compilation-database-test/a/a.cpp +// RUN: sed 's|pointer|AA|g' %s > %T/compilation-database-test/a/a.cpp.msg +// RUN: sed 's|pointer|AB|g' %t.cpp > %T/compilation-database-test/a/b.cpp +// RUN: sed 's|pointer|AB|g' %s > %T/compilation-database-test/a/b.cpp.msg +// RUN: sed 's|pointer|BB|g' %t.cpp > %T/compilation-database-test/b/b.cpp +// RUN: sed 's|pointer|BB|g' %s > %T/compilation-database-test/b/b.cpp.msg +// RUN: sed 's|pointer|BC|g' %t.cpp > %T/compilation-database-test/b/c.cpp +// RUN: sed 's|pointer|BC|g' %s > %T/compilation-database-test/b/c.cpp.msg +// RUN: grep -Ev "// *[A-Z-]+:" %S/Inputs/compilation-database/header.h > %T/compilation-database-test/include/header.h +// RUN: echo '#include "header.h"' > %T/compilation-database-test/b/d.cpp +// RUN: sed 's|test_dir|%T/compilation-database-test|g' %S/Inputs/compilation-database/template.json > %T/compile_commands.json +// RUN: clang-tidy --checks=-*,modernize-use-nullptr -p %T %T/compilation-database-test/b/not-exist -header-filter=.* +// RUN: clang-tidy --checks=-*,modernize-use-nullptr -p %T %T/compilation-database-test/a/a.cpp %T/compilation-database-test/a/b.cpp %T/compilation-database-test/b/b.cpp %T/compilation-database-test/b/c.cpp %T/compilation-database-test/b/d.cpp -header-filter=.* -fix +// RUN: FileCheck -input-file=%T/compilation-database-test/a/a.cpp %T/compilation-database-test/a/a.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/a/b.cpp %T/compilation-database-test/a/b.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/b/b.cpp %T/compilation-database-test/b/b.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/b/c.cpp %T/compilation-database-test/b/c.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/include/header.h %S/Inputs/compilation-database/header.h -check-prefix=CHECK-FIX +#define NULL 0 + +int *pointer = NULL; +// CHECK-FIX: int *pointer = nullptr; +// CHECK: warning: use nullptr [modernize-use-nullpt]