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,13 @@ Context.setCurrentFile(File); Context.setASTContext(&Compiler.getASTContext()); + auto WorkingDir = Compiler.getSourceManager() + .getFileManager() + .getVirtualFileSystem() + ->getCurrentWorkingDirectory(); + if (WorkingDir) + Context.setCurrentBuildDirectory(WorkingDir.get()); + std::vector> Checks; CheckFactories->createChecks(&Context, Checks); @@ -446,8 +460,24 @@ 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,23 @@ 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. + // + // 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: it is empty in unittest. + std::string BuildDirectory; + Level DiagLevel; bool IsWarningAsError; }; @@ -198,6 +208,16 @@ void setCheckProfileData(ProfileData *Profile); ProfileData *getCheckProfileData() const { return Profile; } + /// \brief Should be called when starting to process new translation unit. + void setCurrentBuildDirectory(StringRef BuildDirectory) { + CurrentBuildDirectory = BuildDirectory; + } + + /// \brief Returns all build directories. + const std::string &getCurrentBuildDirectory() { + return CurrentBuildDirectory; + } + private: // Calls setDiagnosticsEngine() and storeError(). friend class ClangTidyDiagnosticConsumer; @@ -222,6 +242,8 @@ ClangTidyStats Stats; + std::string CurrentBuildDirectory; + 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,8 @@ bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && Context.getWarningAsErrorFilter().contains(CheckName); - Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError)); + Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError, + Context.getCurrentBuildDirectory())); } // FIXME: Provide correct LangOptions for each file. 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,26 @@ +// 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: echo 'int *AA = 0;' > %T/compilation-database-test/a/a.cpp +// RUN: echo 'int *AB = 0;' > %T/compilation-database-test/a/b.cpp +// RUN: echo 'int *BB = 0;' > %T/compilation-database-test/b/b.cpp +// RUN: echo 'int *BC = 0;' > %T/compilation-database-test/b/c.cpp +// RUN: echo 'int *HP = 0;' > %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: grep -Ev "// *[A-Z-]+:" %s > %t.cpp +// RUN: FileCheck -input-file=%T/compilation-database-test/a/a.cpp %t.cpp -check-prefix=CHECK-FIX1 +// RUN: FileCheck -input-file=%T/compilation-database-test/a/b.cpp %t.cpp -check-prefix=CHECK-FIX2 +// RUN: FileCheck -input-file=%T/compilation-database-test/b/b.cpp %t.cpp -check-prefix=CHECK-FIX3 +// RUN: FileCheck -input-file=%T/compilation-database-test/b/c.cpp %t.cpp -check-prefix=CHECK-FIX4 +// RUN: FileCheck -input-file=%T/compilation-database-test/include/header.h %t.cpp -check-prefix=CHECK-FIX5 + +// CHECK-FIX1: int *AA = nullptr; +// CHECK-FIX2: int *AB = nullptr; +// CHECK-FIX3: int *BB = nullptr; +// CHECK-FIX4: int *BC = nullptr; +// CHECK-FIX5: int *HP = nullptr;