blob: 4cf2b2acc3461b29725c2fcf9dcc8c75f4bd31b3 [file] [log] [blame] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "DuplicateIncludeCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Regex.h"
#include <memory>
namespace clang::tidy::readability {
static SourceLocation advanceBeyondCurrentLine(const SourceManager &SM,
SourceLocation Start,
int Offset) {
const FileID Id = SM.getFileID(Start);
const unsigned LineNumber = SM.getSpellingLineNumber(Start);
while (SM.getFileID(Start) == Id &&
SM.getSpellingLineNumber(Start.getLocWithOffset(Offset)) == LineNumber)
Start = Start.getLocWithOffset(Offset);
return Start;
}
namespace {
using FileList = SmallVector<StringRef>;
class DuplicateIncludeCallbacks : public PPCallbacks {
public:
DuplicateIncludeCallbacks(DuplicateIncludeCheck &Check,
const SourceManager &SM,
llvm::ArrayRef<StringRef> IgnoredList);
void FileChanged(SourceLocation Loc, FileChangeReason Reason,
SrcMgr::CharacteristicKind FileType,
FileID PrevFID) override;
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange,
OptionalFileEntryRef File, StringRef SearchPath,
StringRef RelativePath, const Module *SuggestedModule,
bool ModuleImported,
SrcMgr::CharacteristicKind FileType) override;
void MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) override;
void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
const MacroDirective *Undef) override;
private:
// A list of included files is kept for each file we enter.
SmallVector<FileList> Files;
DuplicateIncludeCheck &Check;
const SourceManager &SM;
SmallVector<llvm::Regex> AllowedRegexes;
};
} // namespace
DuplicateIncludeCheck::DuplicateIncludeCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IgnoredFilesList(utils::options::parseStringList(
Options.get("IgnoredFilesList", ""))) {}
DuplicateIncludeCallbacks::DuplicateIncludeCallbacks(
DuplicateIncludeCheck &Check, const SourceManager &SM,
llvm::ArrayRef<StringRef> IgnoredList)
: Check(Check), SM(SM) {
// The main file doesn't participate in the FileChanged notification.
Files.emplace_back();
AllowedRegexes.reserve(IgnoredList.size());
for (const StringRef &It : IgnoredList)
if (!It.empty())
AllowedRegexes.emplace_back(It);
}
void DuplicateIncludeCallbacks::FileChanged(SourceLocation Loc,
FileChangeReason Reason,
SrcMgr::CharacteristicKind FileType,
FileID PrevFID) {
if (Reason == EnterFile)
Files.emplace_back();
else if (Reason == ExitFile)
Files.pop_back();
}
void DuplicateIncludeCallbacks::InclusionDirective(
SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef /*File*/,
StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,
bool ModuleImported, SrcMgr::CharacteristicKind FileType) {
// Skip includes behind macros
if (FilenameRange.getBegin().isMacroID() ||
FilenameRange.getEnd().isMacroID())
return;
if (llvm::is_contained(Files.back(), FileName)) {
if (llvm::any_of(AllowedRegexes, [&FileName](const llvm::Regex &R) {
return R.match(FileName);
}))
return;
// We want to delete the entire line, so make sure that [Start,End] covers
// everything.
const SourceLocation Start =
advanceBeyondCurrentLine(SM, HashLoc, -1).getLocWithOffset(-1);
const SourceLocation End =
advanceBeyondCurrentLine(SM, FilenameRange.getEnd(), 1);
Check.diag(HashLoc, "duplicate include")
<< FixItHint::CreateRemoval(SourceRange{Start, End});
} else
Files.back().push_back(FileName);
}
void DuplicateIncludeCallbacks::MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) {
Files.back().clear();
}
void DuplicateIncludeCallbacks::MacroUndefined(const Token &MacroNameTok,
const MacroDefinition &MD,
const MacroDirective *Undef) {
Files.back().clear();
}
void DuplicateIncludeCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
PP->addPPCallbacks(
std::make_unique<DuplicateIncludeCallbacks>(*this, SM, IgnoredFilesList));
}
void DuplicateIncludeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoredFilesList",
utils::options::serializeStringList(IgnoredFilesList));
}
} // namespace clang::tidy::readability