blob: a73a7ba09c4f2c6fd6a8c494162cc9785133f364 [file] [log] [blame]
// 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