| // Part of the Crubit project, under the Apache License v2.0 with LLVM | |
| // Exceptions. See /LICENSE for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| #include "nullability/loc_filter.h" | |
| #include <memory> | |
| #include <string> | |
| #include "absl/log/check.h" | |
| #include "clang/Basic/FileEntry.h" | |
| #include "clang/Basic/SourceLocation.h" | |
| #include "clang/Basic/SourceManager.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Support/Regex.h" | |
| namespace clang::tidy::nullability { | |
| namespace { | |
| // A filter for SourceLocations that restricts to those in the main file or its | |
| // associated header. | |
| class InMainFileOrHeader : public LocFilter { | |
| private: | |
| OptionalFileEntryRef MainFile; | |
| std::string MainFileStem; | |
| std::string MainFileStemWithoutTestSuffix; | |
| const SourceManager &SM; | |
| llvm::DenseMap<FileID, bool> IsMainFileOrHeaderCache; | |
| bool AllowMainFile; | |
| static const llvm::Regex TestSuffixRegex; | |
| public: | |
| InMainFileOrHeader(const SourceManager &SM, bool AllowMainFile) | |
| : SM(SM), AllowMainFile(AllowMainFile) { | |
| FileID MainFileID = SM.getMainFileID(); | |
| MainFile = SM.getFileEntryRefForID(MainFileID); | |
| CHECK(MainFile) << "Unable to compute main file for filtering."; | |
| llvm::StringRef MainFileName = MainFile->getName(); | |
| CHECK(!MainFileName.empty()); | |
| MainFileStem = llvm::sys::path::stem(MainFileName); | |
| IsMainFileOrHeaderCache.insert({MainFileID, true}); | |
| llvm::SmallVector<llvm::StringRef, 2> TestFileSuffixMatches; | |
| if (TestSuffixRegex.match(MainFileStem, &TestFileSuffixMatches)) { | |
| MainFileStemWithoutTestSuffix = MainFileStem.substr( | |
| 0, MainFileStem.size() - TestFileSuffixMatches[1].size()); | |
| } | |
| } | |
| bool isMainFileOrHeader(FileID FileID) { | |
| OptionalFileEntryRef FileEntry = SM.getFileEntryRefForID(FileID); | |
| if (!FileEntry) return false; | |
| if (FileEntry->getDir() != MainFile->getDir()) return false; | |
| // Compare the directory and the stem, but not the file extension, to allow | |
| // matches for the main implementation file and the associated header. | |
| // Also, allow "foo_test.cc" to be associated with "foo.h" | |
| llvm::StringRef FileStem = llvm::sys::path::stem(FileEntry->getName()); | |
| return FileStem == MainFileStem || | |
| (!MainFileStemWithoutTestSuffix.empty() && | |
| FileStem == MainFileStemWithoutTestSuffix); | |
| } | |
| // Returns whether `loc` is in the main file or its associated header (i.e. | |
| // a header that has the same file path except for the extension). | |
| bool check(SourceLocation Loc) override { | |
| if (Loc.isInvalid()) return false; | |
| if (SM.isInMainFile(Loc)) { | |
| return AllowMainFile; | |
| } | |
| auto FileID = SM.getFileID(Loc); | |
| if (FileID.isInvalid()) return false; | |
| // Insert true (arbitrarily chosen over false) to avoid pre-computing the | |
| // result for cache hits. This does not overwrite the existing value for | |
| // cache hits. | |
| auto [It, Inserted] = IsMainFileOrHeaderCache.try_emplace(FileID, true); | |
| if (Inserted) { | |
| It->second = isMainFileOrHeader(FileID); | |
| } | |
| return It->second; | |
| } | |
| }; | |
| const llvm::Regex InMainFileOrHeader::TestSuffixRegex( | |
| "^.*(_test|-test|_unittest|-unittest)$"); | |
| // A filter that allows all locations. | |
| class NoOpFilter : public LocFilter { | |
| bool check(SourceLocation) override { return true; } | |
| }; | |
| // A filter that allows all locations, except the main file. | |
| class AllButMainFileFilter : public LocFilter { | |
| const SourceManager &SM; | |
| public: | |
| explicit AllButMainFileFilter(const SourceManager &SM) : SM(SM) {} | |
| bool check(SourceLocation Loc) override { | |
| if (Loc.isInvalid()) return true; | |
| if (SM.isInMainFile(Loc)) return false; | |
| return true; | |
| } | |
| }; | |
| } // namespace | |
| std::unique_ptr<LocFilter> getLocFilter(const SourceManager &SM, | |
| LocFilterKind FilterKind) { | |
| switch (FilterKind) { | |
| case LocFilterKind::kAllowAll: | |
| return std::make_unique<NoOpFilter>(); | |
| case LocFilterKind::kMainFileOrHeader: | |
| return std::make_unique<InMainFileOrHeader>(SM, /*AllowMainFile=*/true); | |
| case LocFilterKind::kAllowAllButNotMainFile: | |
| return std::make_unique<AllButMainFileFilter>(SM); | |
| case LocFilterKind::kMainHeaderButNotMainFile: | |
| return std::make_unique<InMainFileOrHeader>(SM, /*AllowMainFile=*/false); | |
| } | |
| llvm_unreachable("Unknown LocFilterKind"); | |
| return std::make_unique<NoOpFilter>(); | |
| } | |
| } // namespace clang::tidy::nullability |