Merge commit 96f2a40217bb
* upstream svn@328819
Test: git diff 96f2a40217bb == git diff f8988e8e88b8 5383c119dd1
Change-Id: I4c9a3b9d0814fd2d7c7797e50bf211c61738b6f7
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 760340a..c434682 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,6 +7,7 @@
endif()
add_subdirectory(change-namespace)
+add_subdirectory(clang-doc)
add_subdirectory(clang-query)
add_subdirectory(clang-move)
add_subdirectory(clangd)
diff --git a/change-namespace/ChangeNamespace.cpp b/change-namespace/ChangeNamespace.cpp
index 68adada..35c321a 100644
--- a/change-namespace/ChangeNamespace.cpp
+++ b/change-namespace/ChangeNamespace.cpp
@@ -478,13 +478,13 @@
hasAncestor(namespaceDecl(isAnonymous())),
hasAncestor(cxxRecordDecl()))),
hasParent(namespaceDecl()));
- Finder->addMatcher(decl(forEachDescendant(expr(anyOf(
- callExpr(callee(FuncMatcher)).bind("call"),
- declRefExpr(to(FuncMatcher.bind("func_decl")))
- .bind("func_ref")))),
- IsInMovedNs, unless(isImplicit()))
- .bind("dc"),
- this);
+ Finder->addMatcher(
+ expr(allOf(hasAncestor(decl().bind("dc")), IsInMovedNs,
+ unless(hasAncestor(isImplicit())),
+ anyOf(callExpr(callee(FuncMatcher)).bind("call"),
+ declRefExpr(to(FuncMatcher.bind("func_decl")))
+ .bind("func_ref")))),
+ this);
auto GlobalVarMatcher = varDecl(
hasGlobalStorage(), hasParent(namespaceDecl()),
diff --git a/clang-doc/BitcodeWriter.cpp b/clang-doc/BitcodeWriter.cpp
new file mode 100644
index 0000000..a6c9a41
--- /dev/null
+++ b/clang-doc/BitcodeWriter.cpp
@@ -0,0 +1,510 @@
+//===-- BitcodeWriter.cpp - ClangDoc Bitcode Writer ------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "BitcodeWriter.h"
+#include "llvm/ADT/IndexedMap.h"
+#include <initializer_list>
+
+namespace clang {
+namespace doc {
+
+// Since id enums are not zero-indexed, we need to transform the given id into
+// its associated index.
+struct BlockIdToIndexFunctor {
+ using argument_type = unsigned;
+ unsigned operator()(unsigned ID) const { return ID - BI_FIRST; }
+};
+
+struct RecordIdToIndexFunctor {
+ using argument_type = unsigned;
+ unsigned operator()(unsigned ID) const { return ID - RI_FIRST; }
+};
+
+using AbbrevDsc = void (*)(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev);
+
+static void AbbrevGen(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev,
+ const std::initializer_list<llvm::BitCodeAbbrevOp> Ops) {
+ for (const auto &Op : Ops)
+ Abbrev->Add(Op);
+}
+
+static void BoolAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(Abbrev,
+ {// 0. Boolean
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::BoolSize)});
+}
+
+static void IntAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(Abbrev,
+ {// 0. Fixed-size integer
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::IntSize)});
+}
+
+static void SymbolIDAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(Abbrev,
+ {// 0. Fixed-size integer (length of the sha1'd USR)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::USRLengthSize),
+ // 1. Fixed-size array of Char6 (USR)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array),
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::USRBitLengthSize)});
+}
+
+static void StringAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(Abbrev,
+ {// 0. Fixed-size integer (length of the following string)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::StringLengthSize),
+ // 1. The string blob
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)});
+}
+
+// Assumes that the file will not have more than 65535 lines.
+static void LocationAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(
+ Abbrev,
+ {// 0. Fixed-size integer (line number)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::LineNumberSize),
+ // 1. Fixed-size integer (length of the following string (filename))
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::StringLengthSize),
+ // 2. The string blob
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)});
+}
+
+static void ReferenceAbbrev(std::shared_ptr<llvm::BitCodeAbbrev> &Abbrev) {
+ AbbrevGen(Abbrev,
+ {// 0. Fixed-size integer (ref type)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::ReferenceTypeSize),
+ // 1. Fixed-size integer (length of the USR or UnresolvedName)
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed,
+ BitCodeConstants::StringLengthSize),
+ // 2. The string blob
+ llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)});
+}
+
+struct RecordIdDsc {
+ llvm::StringRef Name;
+ AbbrevDsc Abbrev = nullptr;
+
+ RecordIdDsc() = default;
+ RecordIdDsc(llvm::StringRef Name, AbbrevDsc Abbrev)
+ : Name(Name), Abbrev(Abbrev) {}
+
+ // Is this 'description' valid?
+ operator bool() const {
+ return Abbrev != nullptr && Name.data() != nullptr && !Name.empty();
+ }
+};
+
+static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
+ BlockIdNameMap = []() {
+ llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor> BlockIdNameMap;
+ BlockIdNameMap.resize(BlockIdCount);
+
+ // There is no init-list constructor for the IndexedMap, so have to
+ // improvise
+ static const std::vector<std::pair<BlockId, const char *const>> Inits = {
+ {BI_VERSION_BLOCK_ID, "VersionBlock"},
+ {BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"},
+ {BI_ENUM_BLOCK_ID, "EnumBlock"},
+ {BI_TYPE_BLOCK_ID, "TypeBlock"},
+ {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"},
+ {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"},
+ {BI_RECORD_BLOCK_ID, "RecordBlock"},
+ {BI_FUNCTION_BLOCK_ID, "FunctionBlock"},
+ {BI_COMMENT_BLOCK_ID, "CommentBlock"}};
+ assert(Inits.size() == BlockIdCount);
+ for (const auto &Init : Inits)
+ BlockIdNameMap[Init.first] = Init.second;
+ assert(BlockIdNameMap.size() == BlockIdCount);
+ return BlockIdNameMap;
+ }();
+
+static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
+ RecordIdNameMap = []() {
+ llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor> RecordIdNameMap;
+ RecordIdNameMap.resize(RecordIdCount);
+
+ // There is no init-list constructor for the IndexedMap, so have to
+ // improvise
+ static const std::vector<std::pair<RecordId, RecordIdDsc>> Inits = {
+ {VERSION, {"Version", &IntAbbrev}},
+ {COMMENT_KIND, {"Kind", &StringAbbrev}},
+ {COMMENT_TEXT, {"Text", &StringAbbrev}},
+ {COMMENT_NAME, {"Name", &StringAbbrev}},
+ {COMMENT_DIRECTION, {"Direction", &StringAbbrev}},
+ {COMMENT_PARAMNAME, {"ParamName", &StringAbbrev}},
+ {COMMENT_CLOSENAME, {"CloseName", &StringAbbrev}},
+ {COMMENT_SELFCLOSING, {"SelfClosing", &BoolAbbrev}},
+ {COMMENT_EXPLICIT, {"Explicit", &BoolAbbrev}},
+ {COMMENT_ATTRKEY, {"AttrKey", &StringAbbrev}},
+ {COMMENT_ATTRVAL, {"AttrVal", &StringAbbrev}},
+ {COMMENT_ARG, {"Arg", &StringAbbrev}},
+ {TYPE_REF, {"Type", &ReferenceAbbrev}},
+ {FIELD_TYPE_REF, {"Type", &ReferenceAbbrev}},
+ {FIELD_TYPE_NAME, {"Name", &StringAbbrev}},
+ {MEMBER_TYPE_REF, {"Type", &ReferenceAbbrev}},
+ {MEMBER_TYPE_NAME, {"Name", &StringAbbrev}},
+ {MEMBER_TYPE_ACCESS, {"Access", &IntAbbrev}},
+ {NAMESPACE_USR, {"USR", &SymbolIDAbbrev}},
+ {NAMESPACE_NAME, {"Name", &StringAbbrev}},
+ {NAMESPACE_NAMESPACE, {"Namespace", &ReferenceAbbrev}},
+ {ENUM_USR, {"USR", &SymbolIDAbbrev}},
+ {ENUM_NAME, {"Name", &StringAbbrev}},
+ {ENUM_NAMESPACE, {"Namespace", &ReferenceAbbrev}},
+ {ENUM_DEFLOCATION, {"DefLocation", &LocationAbbrev}},
+ {ENUM_LOCATION, {"Location", &LocationAbbrev}},
+ {ENUM_MEMBER, {"Member", &StringAbbrev}},
+ {ENUM_SCOPED, {"Scoped", &BoolAbbrev}},
+ {RECORD_USR, {"USR", &SymbolIDAbbrev}},
+ {RECORD_NAME, {"Name", &StringAbbrev}},
+ {RECORD_NAMESPACE, {"Namespace", &ReferenceAbbrev}},
+ {RECORD_DEFLOCATION, {"DefLocation", &LocationAbbrev}},
+ {RECORD_LOCATION, {"Location", &LocationAbbrev}},
+ {RECORD_TAG_TYPE, {"TagType", &IntAbbrev}},
+ {RECORD_PARENT, {"Parent", &ReferenceAbbrev}},
+ {RECORD_VPARENT, {"VParent", &ReferenceAbbrev}},
+ {FUNCTION_USR, {"USR", &SymbolIDAbbrev}},
+ {FUNCTION_NAME, {"Name", &StringAbbrev}},
+ {FUNCTION_NAMESPACE, {"Namespace", &ReferenceAbbrev}},
+ {FUNCTION_DEFLOCATION, {"DefLocation", &LocationAbbrev}},
+ {FUNCTION_LOCATION, {"Location", &LocationAbbrev}},
+ {FUNCTION_PARENT, {"Parent", &ReferenceAbbrev}},
+ {FUNCTION_ACCESS, {"Access", &IntAbbrev}},
+ {FUNCTION_IS_METHOD, {"IsMethod", &BoolAbbrev}}};
+ assert(Inits.size() == RecordIdCount);
+ for (const auto &Init : Inits) {
+ RecordIdNameMap[Init.first] = Init.second;
+ assert((Init.second.Name.size() + 1) <= BitCodeConstants::RecordSize);
+ }
+ assert(RecordIdNameMap.size() == RecordIdCount);
+ return RecordIdNameMap;
+ }();
+
+static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
+ RecordsByBlock{
+ // Version Block
+ {BI_VERSION_BLOCK_ID, {VERSION}},
+ // Comment Block
+ {BI_COMMENT_BLOCK_ID,
+ {COMMENT_KIND, COMMENT_TEXT, COMMENT_NAME, COMMENT_DIRECTION,
+ COMMENT_PARAMNAME, COMMENT_CLOSENAME, COMMENT_SELFCLOSING,
+ COMMENT_EXPLICIT, COMMENT_ATTRKEY, COMMENT_ATTRVAL, COMMENT_ARG}},
+ // Type Block
+ {BI_TYPE_BLOCK_ID, {TYPE_REF}},
+ // FieldType Block
+ {BI_FIELD_TYPE_BLOCK_ID, {FIELD_TYPE_REF, FIELD_TYPE_NAME}},
+ // MemberType Block
+ {BI_MEMBER_TYPE_BLOCK_ID,
+ {MEMBER_TYPE_REF, MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS}},
+ // Enum Block
+ {BI_ENUM_BLOCK_ID,
+ {ENUM_USR, ENUM_NAME, ENUM_NAMESPACE, ENUM_DEFLOCATION, ENUM_LOCATION,
+ ENUM_MEMBER, ENUM_SCOPED}},
+ // Namespace Block
+ {BI_NAMESPACE_BLOCK_ID,
+ {NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_NAMESPACE}},
+ // Record Block
+ {BI_RECORD_BLOCK_ID,
+ {RECORD_USR, RECORD_NAME, RECORD_NAMESPACE, RECORD_DEFLOCATION,
+ RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_PARENT, RECORD_VPARENT}},
+ // Function Block
+ {BI_FUNCTION_BLOCK_ID,
+ {FUNCTION_USR, FUNCTION_NAME, FUNCTION_NAMESPACE, FUNCTION_DEFLOCATION,
+ FUNCTION_LOCATION, FUNCTION_PARENT, FUNCTION_ACCESS,
+ FUNCTION_IS_METHOD}}};
+
+// AbbreviationMap
+
+void ClangDocBitcodeWriter::AbbreviationMap::add(RecordId RID,
+ unsigned AbbrevID) {
+ assert(RecordIdNameMap[RID] && "Unknown RecordId.");
+ assert(Abbrevs.find(RID) == Abbrevs.end() && "Abbreviation already added.");
+ Abbrevs[RID] = AbbrevID;
+}
+
+unsigned ClangDocBitcodeWriter::AbbreviationMap::get(RecordId RID) const {
+ assert(RecordIdNameMap[RID] && "Unknown RecordId.");
+ assert(Abbrevs.find(RID) != Abbrevs.end() && "Unknown abbreviation.");
+ return Abbrevs.lookup(RID);
+}
+
+// Validation and Overview Blocks
+
+/// \brief Emits the magic number header to check that its the right format,
+/// in this case, 'DOCS'.
+void ClangDocBitcodeWriter::emitHeader() {
+ for (char C : llvm::StringRef("DOCS"))
+ Stream.Emit((unsigned)C, BitCodeConstants::SignatureBitSize);
+}
+
+void ClangDocBitcodeWriter::emitVersionBlock() {
+ StreamSubBlockGuard Block(Stream, BI_VERSION_BLOCK_ID);
+ emitRecord(VersionNumber, VERSION);
+}
+
+/// \brief Emits a block ID and the block name to the BLOCKINFO block.
+void ClangDocBitcodeWriter::emitBlockID(BlockId BID) {
+ const auto &BlockIdName = BlockIdNameMap[BID];
+ assert(BlockIdName.data() && BlockIdName.size() && "Unknown BlockId.");
+
+ Record.clear();
+ Record.push_back(BID);
+ Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Record);
+ Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME,
+ ArrayRef<unsigned char>(BlockIdName.bytes_begin(),
+ BlockIdName.bytes_end()));
+}
+
+/// \brief Emits a record name to the BLOCKINFO block.
+void ClangDocBitcodeWriter::emitRecordID(RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ prepRecordData(ID);
+ Record.append(RecordIdNameMap[ID].Name.begin(),
+ RecordIdNameMap[ID].Name.end());
+ Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Record);
+}
+
+// Abbreviations
+
+void ClangDocBitcodeWriter::emitAbbrev(RecordId ID, BlockId Block) {
+ assert(RecordIdNameMap[ID] && "Unknown abbreviation.");
+ auto Abbrev = std::make_shared<llvm::BitCodeAbbrev>();
+ Abbrev->Add(llvm::BitCodeAbbrevOp(ID));
+ RecordIdNameMap[ID].Abbrev(Abbrev);
+ Abbrevs.add(ID, Stream.EmitBlockInfoAbbrev(Block, std::move(Abbrev)));
+}
+
+// Records
+
+void ClangDocBitcodeWriter::emitRecord(const SymbolID &Sym, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &SymbolIDAbbrev &&
+ "Abbrev type mismatch.");
+ if (!prepRecordData(ID, !Sym.empty()))
+ return;
+ assert(Sym.size() == 20);
+ Record.push_back(Sym.size());
+ Record.append(Sym.begin(), Sym.end());
+ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record);
+}
+
+void ClangDocBitcodeWriter::emitRecord(llvm::StringRef Str, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &StringAbbrev &&
+ "Abbrev type mismatch.");
+ if (!prepRecordData(ID, !Str.empty()))
+ return;
+ assert(Str.size() < (1U << BitCodeConstants::StringLengthSize));
+ Record.push_back(Str.size());
+ Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Str);
+}
+
+void ClangDocBitcodeWriter::emitRecord(const Location &Loc, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &LocationAbbrev &&
+ "Abbrev type mismatch.");
+ if (!prepRecordData(ID, true))
+ return;
+ // FIXME: Assert that the line number is of the appropriate size.
+ Record.push_back(Loc.LineNumber);
+ assert(Loc.Filename.size() < (1U << BitCodeConstants::StringLengthSize));
+ // Record.push_back(Loc.Filename.size());
+ // Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Loc.Filename);
+ Record.push_back(4);
+ Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, "test");
+}
+
+void ClangDocBitcodeWriter::emitRecord(const Reference &Ref, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &ReferenceAbbrev &&
+ "Abbrev type mismatch.");
+ SmallString<40> StringUSR;
+ StringRef OutString;
+ if (Ref.RefType == InfoType::IT_default)
+ OutString = Ref.UnresolvedName;
+ else {
+ StringUSR = llvm::toHex(llvm::toStringRef(Ref.USR));
+ OutString = StringUSR;
+ }
+ if (!prepRecordData(ID, !OutString.empty()))
+ return;
+ assert(OutString.size() < (1U << BitCodeConstants::StringLengthSize));
+ Record.push_back((int)Ref.RefType);
+ Record.push_back(OutString.size());
+ Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, OutString);
+}
+
+void ClangDocBitcodeWriter::emitRecord(bool Val, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &BoolAbbrev && "Abbrev type mismatch.");
+ if (!prepRecordData(ID, Val))
+ return;
+ Record.push_back(Val);
+ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record);
+}
+
+void ClangDocBitcodeWriter::emitRecord(int Val, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &IntAbbrev && "Abbrev type mismatch.");
+ if (!prepRecordData(ID, Val))
+ return;
+ // FIXME: Assert that the integer is of the appropriate size.
+ Record.push_back(Val);
+ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record);
+}
+
+void ClangDocBitcodeWriter::emitRecord(unsigned Val, RecordId ID) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ assert(RecordIdNameMap[ID].Abbrev == &IntAbbrev && "Abbrev type mismatch.");
+ if (!prepRecordData(ID, Val))
+ return;
+ assert(Val < (1U << BitCodeConstants::IntSize));
+ Record.push_back(Val);
+ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record);
+}
+
+bool ClangDocBitcodeWriter::prepRecordData(RecordId ID, bool ShouldEmit) {
+ assert(RecordIdNameMap[ID] && "Unknown RecordId.");
+ if (!ShouldEmit)
+ return false;
+ Record.clear();
+ Record.push_back(ID);
+ return true;
+}
+
+// BlockInfo Block
+
+void ClangDocBitcodeWriter::emitBlockInfoBlock() {
+ Stream.EnterBlockInfoBlock();
+ for (const auto &Block : RecordsByBlock) {
+ assert(Block.second.size() < (1U << BitCodeConstants::SubblockIDSize));
+ emitBlockInfo(Block.first, Block.second);
+ }
+ Stream.ExitBlock();
+}
+
+void ClangDocBitcodeWriter::emitBlockInfo(BlockId BID,
+ const std::vector<RecordId> &RIDs) {
+ assert(RIDs.size() < (1U << BitCodeConstants::SubblockIDSize));
+ emitBlockID(BID);
+ for (RecordId RID : RIDs) {
+ emitRecordID(RID);
+ emitAbbrev(RID, BID);
+ }
+}
+
+// Block emission
+
+void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) {
+ StreamSubBlockGuard Block(Stream, BI_TYPE_BLOCK_ID);
+ emitRecord(T.Type, TYPE_REF);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const FieldTypeInfo &T) {
+ StreamSubBlockGuard Block(Stream, BI_FIELD_TYPE_BLOCK_ID);
+ emitRecord(T.Type, FIELD_TYPE_REF);
+ emitRecord(T.Name, FIELD_TYPE_NAME);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) {
+ StreamSubBlockGuard Block(Stream, BI_MEMBER_TYPE_BLOCK_ID);
+ emitRecord(T.Type, MEMBER_TYPE_REF);
+ emitRecord(T.Name, MEMBER_TYPE_NAME);
+ emitRecord(T.Access, MEMBER_TYPE_ACCESS);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const CommentInfo &I) {
+ StreamSubBlockGuard Block(Stream, BI_COMMENT_BLOCK_ID);
+ for (const auto &L :
+ std::vector<std::pair<llvm::StringRef, RecordId>>{
+ {I.Kind, COMMENT_KIND},
+ {I.Text, COMMENT_TEXT},
+ {I.Name, COMMENT_NAME},
+ {I.Direction, COMMENT_DIRECTION},
+ {I.ParamName, COMMENT_PARAMNAME},
+ {I.CloseName, COMMENT_CLOSENAME}})
+ emitRecord(L.first, L.second);
+ emitRecord(I.SelfClosing, COMMENT_SELFCLOSING);
+ emitRecord(I.Explicit, COMMENT_EXPLICIT);
+ for (const auto &A : I.AttrKeys)
+ emitRecord(A, COMMENT_ATTRKEY);
+ for (const auto &A : I.AttrValues)
+ emitRecord(A, COMMENT_ATTRVAL);
+ for (const auto &A : I.Args)
+ emitRecord(A, COMMENT_ARG);
+ for (const auto &C : I.Children)
+ emitBlock(*C);
+}
+
+#define EMITINFO(X) \
+ emitRecord(I.USR, X##_USR); \
+ emitRecord(I.Name, X##_NAME); \
+ for (const auto &N : I.Namespace) \
+ emitRecord(N, X##_NAMESPACE); \
+ for (const auto &CI : I.Description) \
+ emitBlock(CI);
+
+void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
+ StreamSubBlockGuard Block(Stream, BI_NAMESPACE_BLOCK_ID);
+ EMITINFO(NAMESPACE)
+}
+
+void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
+ StreamSubBlockGuard Block(Stream, BI_ENUM_BLOCK_ID);
+ EMITINFO(ENUM)
+ if (I.DefLoc)
+ emitRecord(I.DefLoc.getValue(), ENUM_DEFLOCATION);
+ for (const auto &L : I.Loc)
+ emitRecord(L, ENUM_LOCATION);
+ emitRecord(I.Scoped, ENUM_SCOPED);
+ for (const auto &N : I.Members)
+ emitRecord(N, ENUM_MEMBER);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
+ StreamSubBlockGuard Block(Stream, BI_RECORD_BLOCK_ID);
+ EMITINFO(RECORD)
+ if (I.DefLoc)
+ emitRecord(I.DefLoc.getValue(), RECORD_DEFLOCATION);
+ for (const auto &L : I.Loc)
+ emitRecord(L, RECORD_LOCATION);
+ emitRecord(I.TagType, RECORD_TAG_TYPE);
+ for (const auto &N : I.Members)
+ emitBlock(N);
+ for (const auto &P : I.Parents)
+ emitRecord(P, RECORD_PARENT);
+ for (const auto &P : I.VirtualParents)
+ emitRecord(P, RECORD_VPARENT);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
+ StreamSubBlockGuard Block(Stream, BI_FUNCTION_BLOCK_ID);
+ EMITINFO(FUNCTION)
+ emitRecord(I.IsMethod, FUNCTION_IS_METHOD);
+ if (I.DefLoc)
+ emitRecord(I.DefLoc.getValue(), FUNCTION_DEFLOCATION);
+ for (const auto &L : I.Loc)
+ emitRecord(L, FUNCTION_LOCATION);
+ emitRecord(I.Parent, FUNCTION_PARENT);
+ emitBlock(I.ReturnType);
+ for (const auto &N : I.Params)
+ emitBlock(N);
+}
+
+#undef EMITINFO
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/BitcodeWriter.h b/clang-doc/BitcodeWriter.h
new file mode 100644
index 0000000..d0e0689
--- /dev/null
+++ b/clang-doc/BitcodeWriter.h
@@ -0,0 +1,201 @@
+//===-- BitcodeWriter.h - ClangDoc Bitcode Writer --------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements a writer for serializing the clang-doc internal
+// representation to LLVM bitcode. The writer takes in a stream and emits the
+// generated bitcode to that stream.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H
+
+#include "Representation.h"
+#include "clang/AST/AST.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Bitcode/BitstreamWriter.h"
+#include <initializer_list>
+#include <vector>
+
+namespace clang {
+namespace doc {
+
+// Current version number of clang-doc bitcode.
+// Should be bumped when removing or changing BlockIds, RecordIds, or
+// BitCodeConstants, though they can be added without breaking it.
+static const unsigned VersionNumber = 1;
+
+struct BitCodeConstants {
+ static constexpr unsigned RecordSize = 16U;
+ static constexpr unsigned SignatureBitSize = 8U;
+ static constexpr unsigned SubblockIDSize = 4U;
+ static constexpr unsigned BoolSize = 1U;
+ static constexpr unsigned IntSize = 16U;
+ static constexpr unsigned StringLengthSize = 16U;
+ static constexpr unsigned FilenameLengthSize = 16U;
+ static constexpr unsigned LineNumberSize = 16U;
+ static constexpr unsigned ReferenceTypeSize = 8U;
+ static constexpr unsigned USRLengthSize = 6U;
+ static constexpr unsigned USRBitLengthSize = 8U;
+};
+
+// New Ids need to be added to both the enum here and the relevant IdNameMap in
+// the implementation file.
+enum BlockId {
+ BI_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID,
+ BI_NAMESPACE_BLOCK_ID,
+ BI_ENUM_BLOCK_ID,
+ BI_TYPE_BLOCK_ID,
+ BI_FIELD_TYPE_BLOCK_ID,
+ BI_MEMBER_TYPE_BLOCK_ID,
+ BI_RECORD_BLOCK_ID,
+ BI_FUNCTION_BLOCK_ID,
+ BI_COMMENT_BLOCK_ID,
+ BI_FIRST = BI_VERSION_BLOCK_ID,
+ BI_LAST = BI_COMMENT_BLOCK_ID
+};
+
+// New Ids need to be added to the enum here, and to the relevant IdNameMap and
+// initialization list in the implementation file.
+#define INFORECORDS(X) X##_USR, X##_NAME, X##_NAMESPACE
+
+enum RecordId {
+ VERSION = 1,
+ INFORECORDS(FUNCTION),
+ FUNCTION_DEFLOCATION,
+ FUNCTION_LOCATION,
+ FUNCTION_PARENT,
+ FUNCTION_ACCESS,
+ FUNCTION_IS_METHOD,
+ COMMENT_KIND,
+ COMMENT_TEXT,
+ COMMENT_NAME,
+ COMMENT_DIRECTION,
+ COMMENT_PARAMNAME,
+ COMMENT_CLOSENAME,
+ COMMENT_SELFCLOSING,
+ COMMENT_EXPLICIT,
+ COMMENT_ATTRKEY,
+ COMMENT_ATTRVAL,
+ COMMENT_ARG,
+ TYPE_REF,
+ FIELD_TYPE_REF,
+ FIELD_TYPE_NAME,
+ MEMBER_TYPE_REF,
+ MEMBER_TYPE_NAME,
+ MEMBER_TYPE_ACCESS,
+ INFORECORDS(NAMESPACE),
+ INFORECORDS(ENUM),
+ ENUM_DEFLOCATION,
+ ENUM_LOCATION,
+ ENUM_MEMBER,
+ ENUM_SCOPED,
+ INFORECORDS(RECORD),
+ RECORD_DEFLOCATION,
+ RECORD_LOCATION,
+ RECORD_TAG_TYPE,
+ RECORD_PARENT,
+ RECORD_VPARENT,
+ RI_FIRST = VERSION,
+ RI_LAST = RECORD_VPARENT
+};
+
+static constexpr unsigned BlockIdCount = BI_LAST - BI_FIRST + 1;
+static constexpr unsigned RecordIdCount = RI_LAST - RI_FIRST + 1;
+
+#undef INFORECORDS
+
+class ClangDocBitcodeWriter {
+public:
+ ClangDocBitcodeWriter(llvm::BitstreamWriter &Stream) : Stream(Stream) {
+ emitHeader();
+ emitBlockInfoBlock();
+ emitVersionBlock();
+ }
+
+#ifndef NDEBUG // Don't want explicit dtor unless needed.
+ ~ClangDocBitcodeWriter() {
+ // Check that the static size is large-enough.
+ assert(Record.capacity() > BitCodeConstants::RecordSize);
+ }
+#endif
+
+ // Block emission of different info types.
+ void emitBlock(const NamespaceInfo &I);
+ void emitBlock(const RecordInfo &I);
+ void emitBlock(const FunctionInfo &I);
+ void emitBlock(const EnumInfo &I);
+ void emitBlock(const TypeInfo &B);
+ void emitBlock(const FieldTypeInfo &B);
+ void emitBlock(const MemberTypeInfo &B);
+ void emitBlock(const CommentInfo &B);
+
+private:
+ class AbbreviationMap {
+ llvm::DenseMap<unsigned, unsigned> Abbrevs;
+
+ public:
+ AbbreviationMap() : Abbrevs(RecordIdCount) {}
+
+ void add(RecordId RID, unsigned AbbrevID);
+ unsigned get(RecordId RID) const;
+ };
+
+ class StreamSubBlockGuard {
+ llvm::BitstreamWriter &Stream;
+
+ public:
+ StreamSubBlockGuard(llvm::BitstreamWriter &Stream_, BlockId ID)
+ : Stream(Stream_) {
+ // NOTE: SubBlockIDSize could theoretically be calculated on the fly,
+ // based on the initialization list of records in each block.
+ Stream.EnterSubblock(ID, BitCodeConstants::SubblockIDSize);
+ }
+
+ StreamSubBlockGuard() = default;
+ StreamSubBlockGuard(const StreamSubBlockGuard &) = delete;
+ StreamSubBlockGuard &operator=(const StreamSubBlockGuard &) = delete;
+
+ ~StreamSubBlockGuard() { Stream.ExitBlock(); }
+ };
+
+ // Emission of validation and overview blocks.
+ void emitHeader();
+ void emitVersionBlock();
+ void emitRecordID(RecordId ID);
+ void emitBlockID(BlockId ID);
+ void emitBlockInfoBlock();
+ void emitBlockInfo(BlockId BID, const std::vector<RecordId> &RIDs);
+
+ // Emission of individual record types.
+ void emitRecord(StringRef Str, RecordId ID);
+ void emitRecord(const SymbolID &Str, RecordId ID);
+ void emitRecord(const Location &Loc, RecordId ID);
+ void emitRecord(const Reference &Ref, RecordId ID);
+ void emitRecord(bool Value, RecordId ID);
+ void emitRecord(int Value, RecordId ID);
+ void emitRecord(unsigned Value, RecordId ID);
+ bool prepRecordData(RecordId ID, bool ShouldEmit = true);
+
+ // Emission of appropriate abbreviation type.
+ void emitAbbrev(RecordId ID, BlockId Block);
+
+ // Static size is the maximum length of the block/record names we're pushing
+ // to this + 1. Longest is currently `MemberTypeBlock` at 15 chars.
+ SmallVector<uint32_t, BitCodeConstants::RecordSize> Record;
+ llvm::BitstreamWriter &Stream;
+ AbbreviationMap Abbrevs;
+};
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H
diff --git a/clang-doc/CMakeLists.txt b/clang-doc/CMakeLists.txt
new file mode 100644
index 0000000..1852baa
--- /dev/null
+++ b/clang-doc/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(LLVM_LINK_COMPONENTS
+ support
+ )
+
+add_clang_library(clangDoc
+ BitcodeWriter.cpp
+ ClangDoc.cpp
+ Mapper.cpp
+ Serialize.cpp
+
+ LINK_LIBS
+ clangAnalysis
+ clangAST
+ clangASTMatchers
+ clangBasic
+ clangFrontend
+ clangIndex
+ clangLex
+ clangTooling
+ clangToolingCore
+ )
+
+add_subdirectory(tool)
diff --git a/clang-doc/ClangDoc.cpp b/clang-doc/ClangDoc.cpp
new file mode 100644
index 0000000..cd73723
--- /dev/null
+++ b/clang-doc/ClangDoc.cpp
@@ -0,0 +1,61 @@
+//===-- ClangDoc.cpp - ClangDoc ---------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the main entry point for the clang-doc tool. It runs
+// the clang-doc mapper on a given set of source code files using a
+// FrontendActionFactory.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangDoc.h"
+#include "Mapper.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Frontend/ASTConsumers.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+
+namespace clang {
+namespace doc {
+
+class MapperActionFactory : public tooling::FrontendActionFactory {
+public:
+ MapperActionFactory(tooling::ExecutionContext *ECtx) : ECtx(ECtx) {}
+ clang::FrontendAction *create() override;
+
+private:
+ tooling::ExecutionContext *ECtx;
+};
+
+clang::FrontendAction *MapperActionFactory::create() {
+ class ClangDocAction : public clang::ASTFrontendAction {
+ public:
+ ClangDocAction(ExecutionContext *ECtx) : ECtx(ECtx) {}
+
+ std::unique_ptr<clang::ASTConsumer>
+ CreateASTConsumer(clang::CompilerInstance &Compiler,
+ llvm::StringRef InFile) override {
+ return llvm::make_unique<MapASTVisitor>(&Compiler.getASTContext(), ECtx);
+ }
+
+ private:
+ ExecutionContext *ECtx;
+ };
+ return new ClangDocAction(ECtx);
+}
+
+std::unique_ptr<tooling::FrontendActionFactory>
+newMapperActionFactory(tooling::ExecutionContext *ECtx) {
+ return llvm::make_unique<MapperActionFactory>(ECtx);
+}
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/ClangDoc.h b/clang-doc/ClangDoc.h
new file mode 100644
index 0000000..9a9817c
--- /dev/null
+++ b/clang-doc/ClangDoc.h
@@ -0,0 +1,33 @@
+//===-- ClangDoc.h - ClangDoc -----------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file exposes a method to craete the FrontendActionFactory for the
+// clang-doc tool. The factory runs the clang-doc mapper on a given set of
+// source code files, storing the results key-value pairs in its
+// ExecutionContext.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H
+
+#include "clang/Tooling/Execution.h"
+#include "clang/Tooling/StandaloneExecution.h"
+#include "clang/Tooling/Tooling.h"
+
+namespace clang {
+namespace doc {
+
+std::unique_ptr<tooling::FrontendActionFactory>
+newMapperActionFactory(tooling::ExecutionContext *ECtx);
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H
diff --git a/clang-doc/Mapper.cpp b/clang-doc/Mapper.cpp
new file mode 100644
index 0000000..f3ef99e
--- /dev/null
+++ b/clang-doc/Mapper.cpp
@@ -0,0 +1,86 @@
+//===-- Mapper.cpp - ClangDoc Mapper ----------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Mapper.h"
+#include "BitcodeWriter.h"
+#include "Serialize.h"
+#include "clang/AST/Comment.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/ADT/StringExtras.h"
+
+using clang::comments::FullComment;
+
+namespace clang {
+namespace doc {
+
+void MapASTVisitor::HandleTranslationUnit(ASTContext &Context) {
+ TraverseDecl(Context.getTranslationUnitDecl());
+}
+
+template <typename T> bool MapASTVisitor::mapDecl(const T *D) {
+ // If we're looking a decl not in user files, skip this decl.
+ if (D->getASTContext().getSourceManager().isInSystemHeader(D->getLocation()))
+ return true;
+
+ llvm::SmallString<128> USR;
+ // If there is an error generating a USR for the decl, skip this decl.
+ if (index::generateUSRForDecl(D, USR))
+ return true;
+
+ ECtx->reportResult(llvm::toHex(llvm::toStringRef(serialize::hashUSR(USR))),
+ serialize::emitInfo(D, getComment(D, D->getASTContext()),
+ getLine(D, D->getASTContext()),
+ getFile(D, D->getASTContext())));
+ return true;
+}
+
+bool MapASTVisitor::VisitNamespaceDecl(const NamespaceDecl *D) {
+ return mapDecl(D);
+}
+
+bool MapASTVisitor::VisitRecordDecl(const RecordDecl *D) { return mapDecl(D); }
+
+bool MapASTVisitor::VisitEnumDecl(const EnumDecl *D) { return mapDecl(D); }
+
+bool MapASTVisitor::VisitCXXMethodDecl(const CXXMethodDecl *D) {
+ return mapDecl(D);
+}
+
+bool MapASTVisitor::VisitFunctionDecl(const FunctionDecl *D) {
+ // Don't visit CXXMethodDecls twice
+ if (dyn_cast<CXXMethodDecl>(D))
+ return true;
+ return mapDecl(D);
+}
+
+comments::FullComment *
+MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
+ RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
+ // FIXME: Move setAttached to the initial comment parsing.
+ if (Comment) {
+ Comment->setAttached();
+ return Comment->parse(Context, nullptr, D);
+ }
+ return nullptr;
+}
+
+int MapASTVisitor::getLine(const NamedDecl *D,
+ const ASTContext &Context) const {
+ return Context.getSourceManager().getPresumedLoc(D->getLocStart()).getLine();
+}
+
+llvm::StringRef MapASTVisitor::getFile(const NamedDecl *D,
+ const ASTContext &Context) const {
+ return Context.getSourceManager()
+ .getPresumedLoc(D->getLocStart())
+ .getFilename();
+}
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/Mapper.h b/clang-doc/Mapper.h
new file mode 100644
index 0000000..1aa3f46
--- /dev/null
+++ b/clang-doc/Mapper.h
@@ -0,0 +1,57 @@
+//===-- Mapper.h - ClangDoc Mapper ------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the Mapper piece of the clang-doc tool. It implements
+// a RecursiveASTVisitor to look at each declaration and populate the info
+// into the internal representation. Each seen declaration is serialized to
+// to bitcode and written out to the ExecutionContext as a KV pair where the
+// key is the declaration's USR and the value is the serialized bitcode.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H
+
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Tooling/Execution.h"
+
+using namespace clang::comments;
+using namespace clang::tooling;
+
+namespace clang {
+namespace doc {
+
+class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
+ public ASTConsumer {
+public:
+ explicit MapASTVisitor(ASTContext *Ctx, ExecutionContext *ECtx)
+ : ECtx(ECtx) {}
+
+ void HandleTranslationUnit(ASTContext &Context) override;
+ bool VisitNamespaceDecl(const NamespaceDecl *D);
+ bool VisitRecordDecl(const RecordDecl *D);
+ bool VisitEnumDecl(const EnumDecl *D);
+ bool VisitCXXMethodDecl(const CXXMethodDecl *D);
+ bool VisitFunctionDecl(const FunctionDecl *D);
+
+private:
+ template <typename T> bool mapDecl(const T *D);
+
+ int getLine(const NamedDecl *D, const ASTContext &Context) const;
+ StringRef getFile(const NamedDecl *D, const ASTContext &Context) const;
+ comments::FullComment *getComment(const NamedDecl *D,
+ const ASTContext &Context) const;
+
+ ExecutionContext *ECtx;
+};
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H
diff --git a/clang-doc/Representation.h b/clang-doc/Representation.h
new file mode 100644
index 0000000..8b772a3
--- /dev/null
+++ b/clang-doc/Representation.h
@@ -0,0 +1,184 @@
+///===-- Representation.h - ClangDoc Represenation --------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the internal representations of different declaration
+// types for the clang-doc tool.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H
+
+#include "clang/AST/Type.h"
+#include "clang/Basic/Specifiers.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
+#include <array>
+#include <string>
+
+namespace clang {
+namespace doc {
+
+using SymbolID = std::array<uint8_t, 20>;
+
+struct Info;
+enum class InfoType {
+ IT_namespace,
+ IT_record,
+ IT_function,
+ IT_enum,
+ IT_default
+};
+
+// A representation of a parsed comment.
+struct CommentInfo {
+ CommentInfo() = default;
+ CommentInfo(CommentInfo &&Other) : Children(std::move(Other.Children)) {}
+
+ SmallString<16>
+ Kind; // Kind of comment (TextComment, InlineCommandComment,
+ // HTMLStartTagComment, HTMLEndTagComment, BlockCommandComment,
+ // ParamCommandComment, TParamCommandComment, VerbatimBlockComment,
+ // VerbatimBlockLineComment, VerbatimLineComment).
+ SmallString<64> Text; // Text of the comment.
+ SmallString<16> Name; // Name of the comment (for Verbatim and HTML).
+ SmallString<8> Direction; // Parameter direction (for (T)ParamCommand).
+ SmallString<16> ParamName; // Parameter name (for (T)ParamCommand).
+ SmallString<16> CloseName; // Closing tag name (for VerbatimBlock).
+ bool SelfClosing = false; // Indicates if tag is self-closing (for HTML).
+ bool Explicit = false; // Indicates if the direction of a param is explicit
+ // (for (T)ParamCommand).
+ llvm::SmallVector<SmallString<16>, 4>
+ AttrKeys; // List of attribute keys (for HTML).
+ llvm::SmallVector<SmallString<16>, 4>
+ AttrValues; // List of attribute values for each key (for HTML).
+ llvm::SmallVector<SmallString<16>, 4>
+ Args; // List of arguments to commands (for InlineCommand).
+ std::vector<std::unique_ptr<CommentInfo>>
+ Children; // List of child comments for this CommentInfo.
+};
+
+struct Reference {
+ Reference() = default;
+ Reference(llvm::StringRef Name) : UnresolvedName(Name) {}
+ Reference(SymbolID USR, InfoType IT) : USR(USR), RefType(IT) {}
+
+ SymbolID USR; // Unique identifer for referenced decl
+ SmallString<16> UnresolvedName; // Name of unresolved type.
+ InfoType RefType =
+ InfoType::IT_default; // Indicates the type of this Reference (namespace,
+ // record, function, enum, default).
+};
+
+// A base struct for TypeInfos
+struct TypeInfo {
+ TypeInfo() = default;
+ TypeInfo(SymbolID &Type, InfoType IT) : Type(Type, IT) {}
+ TypeInfo(llvm::StringRef RefName) : Type(RefName) {}
+
+ Reference Type; // Referenced type in this info.
+};
+
+// Info for field types.
+struct FieldTypeInfo : public TypeInfo {
+ FieldTypeInfo() = default;
+ FieldTypeInfo(SymbolID &Type, InfoType IT, llvm::StringRef Name)
+ : TypeInfo(Type, IT), Name(Name) {}
+ FieldTypeInfo(llvm::StringRef RefName, llvm::StringRef Name)
+ : TypeInfo(RefName), Name(Name) {}
+
+ SmallString<16> Name; // Name associated with this info.
+};
+
+// Info for member types.
+struct MemberTypeInfo : public FieldTypeInfo {
+ MemberTypeInfo() = default;
+ MemberTypeInfo(SymbolID &Type, InfoType IT, llvm::StringRef Name)
+ : FieldTypeInfo(Type, IT, Name) {}
+ MemberTypeInfo(llvm::StringRef RefName, llvm::StringRef Name)
+ : FieldTypeInfo(RefName, Name) {}
+
+ AccessSpecifier Access =
+ clang::AccessSpecifier::AS_none; // Access level associated with this
+ // info (public, protected, private,
+ // none).
+};
+
+struct Location {
+ Location() = default;
+ Location(int LineNumber, SmallString<16> Filename)
+ : LineNumber(LineNumber), Filename(std::move(Filename)) {}
+
+ int LineNumber; // Line number of this Location.
+ SmallString<32> Filename; // File for this Location.
+};
+
+/// A base struct for Infos.
+struct Info {
+ Info() = default;
+ Info(Info &&Other) : Description(std::move(Other.Description)) {}
+ virtual ~Info() = default;
+
+ SymbolID USR; // Unique identifier for the decl described by this Info.
+ SmallString<16> Name; // Unqualified name of the decl.
+ llvm::SmallVector<Reference, 4>
+ Namespace; // List of parent namespaces for this decl.
+ std::vector<CommentInfo> Description; // Comment description of this decl.
+};
+
+// Info for namespaces.
+struct NamespaceInfo : public Info {};
+
+// Info for symbols.
+struct SymbolInfo : public Info {
+ llvm::Optional<Location> DefLoc; // Location where this decl is defined.
+ llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
+};
+
+// TODO: Expand to allow for documenting templating and default args.
+// Info for functions.
+struct FunctionInfo : public SymbolInfo {
+ bool IsMethod = false; // Indicates whether this function is a class method.
+ Reference Parent; // Reference to the parent class decl for this method.
+ TypeInfo ReturnType; // Info about the return type of this function.
+ llvm::SmallVector<FieldTypeInfo, 4> Params; // List of parameters.
+ AccessSpecifier Access =
+ AccessSpecifier::AS_none; // Access level for this method (public,
+ // private, protected, none).
+};
+
+// TODO: Expand to allow for documenting templating, inheritance access,
+// friend classes
+// Info for types.
+struct RecordInfo : public SymbolInfo {
+ TagTypeKind TagType = TagTypeKind::TTK_Struct; // Type of this record (struct,
+ // class, union, interface).
+ llvm::SmallVector<MemberTypeInfo, 4>
+ Members; // List of info about record members.
+ llvm::SmallVector<Reference, 4> Parents; // List of base/parent records (does
+ // not include virtual parents).
+ llvm::SmallVector<Reference, 4>
+ VirtualParents; // List of virtual base/parent records.
+};
+
+// TODO: Expand to allow for documenting templating.
+// Info for types.
+struct EnumInfo : public SymbolInfo {
+ bool Scoped =
+ false; // Indicates whether this enum is scoped (e.g. enum class).
+ llvm::SmallVector<SmallString<16>, 4> Members; // List of enum members.
+};
+
+// TODO: Add functionality to include separate markdown pages.
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H
diff --git a/clang-doc/Serialize.cpp b/clang-doc/Serialize.cpp
new file mode 100644
index 0000000..ccde579
--- /dev/null
+++ b/clang-doc/Serialize.cpp
@@ -0,0 +1,336 @@
+//===-- Serializer.cpp - ClangDoc Serializer --------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Serialize.h"
+#include "BitcodeWriter.h"
+#include "clang/AST/Comment.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/SHA1.h"
+
+using clang::comments::FullComment;
+
+namespace clang {
+namespace doc {
+namespace serialize {
+
+SymbolID hashUSR(llvm::StringRef USR) {
+ return llvm::SHA1::hash(arrayRefFromStringRef(USR));
+}
+
+class ClangDocCommentVisitor
+ : public ConstCommentVisitor<ClangDocCommentVisitor> {
+public:
+ ClangDocCommentVisitor(CommentInfo &CI) : CurrentCI(CI) {}
+
+ void parseComment(const comments::Comment *C);
+
+ void visitTextComment(const TextComment *C);
+ void visitInlineCommandComment(const InlineCommandComment *C);
+ void visitHTMLStartTagComment(const HTMLStartTagComment *C);
+ void visitHTMLEndTagComment(const HTMLEndTagComment *C);
+ void visitBlockCommandComment(const BlockCommandComment *C);
+ void visitParamCommandComment(const ParamCommandComment *C);
+ void visitTParamCommandComment(const TParamCommandComment *C);
+ void visitVerbatimBlockComment(const VerbatimBlockComment *C);
+ void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
+ void visitVerbatimLineComment(const VerbatimLineComment *C);
+
+private:
+ std::string getCommandName(unsigned CommandID) const;
+ bool isWhitespaceOnly(StringRef S) const;
+
+ CommentInfo &CurrentCI;
+};
+
+void ClangDocCommentVisitor::parseComment(const comments::Comment *C) {
+ CurrentCI.Kind = C->getCommentKindName();
+ ConstCommentVisitor<ClangDocCommentVisitor>::visit(C);
+ for (comments::Comment *Child :
+ llvm::make_range(C->child_begin(), C->child_end())) {
+ CurrentCI.Children.emplace_back(llvm::make_unique<CommentInfo>());
+ ClangDocCommentVisitor Visitor(*CurrentCI.Children.back());
+ Visitor.parseComment(Child);
+ }
+}
+
+void ClangDocCommentVisitor::visitTextComment(const TextComment *C) {
+ if (!isWhitespaceOnly(C->getText()))
+ CurrentCI.Text = C->getText();
+}
+
+void ClangDocCommentVisitor::visitInlineCommandComment(
+ const InlineCommandComment *C) {
+ CurrentCI.Name = getCommandName(C->getCommandID());
+ for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I)
+ CurrentCI.Args.push_back(C->getArgText(I));
+}
+
+void ClangDocCommentVisitor::visitHTMLStartTagComment(
+ const HTMLStartTagComment *C) {
+ CurrentCI.Name = C->getTagName();
+ CurrentCI.SelfClosing = C->isSelfClosing();
+ for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) {
+ const HTMLStartTagComment::Attribute &Attr = C->getAttr(I);
+ CurrentCI.AttrKeys.push_back(Attr.Name);
+ CurrentCI.AttrValues.push_back(Attr.Value);
+ }
+}
+
+void ClangDocCommentVisitor::visitHTMLEndTagComment(
+ const HTMLEndTagComment *C) {
+ CurrentCI.Name = C->getTagName();
+ CurrentCI.SelfClosing = true;
+}
+
+void ClangDocCommentVisitor::visitBlockCommandComment(
+ const BlockCommandComment *C) {
+ CurrentCI.Name = getCommandName(C->getCommandID());
+ for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I)
+ CurrentCI.Args.push_back(C->getArgText(I));
+}
+
+void ClangDocCommentVisitor::visitParamCommandComment(
+ const ParamCommandComment *C) {
+ CurrentCI.Direction =
+ ParamCommandComment::getDirectionAsString(C->getDirection());
+ CurrentCI.Explicit = C->isDirectionExplicit();
+ if (C->hasParamName())
+ CurrentCI.ParamName = C->getParamNameAsWritten();
+}
+
+void ClangDocCommentVisitor::visitTParamCommandComment(
+ const TParamCommandComment *C) {
+ if (C->hasParamName())
+ CurrentCI.ParamName = C->getParamNameAsWritten();
+}
+
+void ClangDocCommentVisitor::visitVerbatimBlockComment(
+ const VerbatimBlockComment *C) {
+ CurrentCI.Name = getCommandName(C->getCommandID());
+ CurrentCI.CloseName = C->getCloseName();
+}
+
+void ClangDocCommentVisitor::visitVerbatimBlockLineComment(
+ const VerbatimBlockLineComment *C) {
+ if (!isWhitespaceOnly(C->getText()))
+ CurrentCI.Text = C->getText();
+}
+
+void ClangDocCommentVisitor::visitVerbatimLineComment(
+ const VerbatimLineComment *C) {
+ if (!isWhitespaceOnly(C->getText()))
+ CurrentCI.Text = C->getText();
+}
+
+bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const {
+ return std::all_of(S.begin(), S.end(), isspace);
+}
+
+std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const {
+ const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID);
+ if (Info)
+ return Info->Name;
+ // TODO: Add parsing for \file command.
+ return "<not a builtin command>";
+}
+
+// Serializing functions.
+
+template <typename T> static std::string serialize(T &I) {
+ SmallString<2048> Buffer;
+ llvm::BitstreamWriter Stream(Buffer);
+ ClangDocBitcodeWriter Writer(Stream);
+ Writer.emitBlock(I);
+ return Buffer.str().str();
+}
+
+static void parseFullComment(const FullComment *C, CommentInfo &CI) {
+ ClangDocCommentVisitor Visitor(CI);
+ Visitor.parseComment(C);
+}
+
+static SymbolID getUSRForDecl(const Decl *D) {
+ llvm::SmallString<128> USR;
+ if (index::generateUSRForDecl(D, USR))
+ return SymbolID();
+ return hashUSR(USR);
+}
+
+static RecordDecl *getDeclForType(const QualType &T) {
+ auto *Ty = T->getAs<RecordType>();
+ if (!Ty)
+ return nullptr;
+ return Ty->getDecl()->getDefinition();
+}
+
+static void parseFields(RecordInfo &I, const RecordDecl *D) {
+ for (const FieldDecl *F : D->fields()) {
+ // FIXME: Set Access to the appropriate value.
+ SymbolID Type;
+ std::string Name;
+ InfoType RefType;
+ if (const auto *T = getDeclForType(F->getTypeSourceInfo()->getType())) {
+ Type = getUSRForDecl(T);
+ if (dyn_cast<EnumDecl>(T))
+ RefType = InfoType::IT_enum;
+ else if (dyn_cast<RecordDecl>(T))
+ RefType = InfoType::IT_record;
+ I.Members.emplace_back(Type, RefType, F->getQualifiedNameAsString());
+ } else {
+ Name = F->getTypeSourceInfo()->getType().getAsString();
+ I.Members.emplace_back(Name, F->getQualifiedNameAsString());
+ }
+ }
+}
+
+static void parseEnumerators(EnumInfo &I, const EnumDecl *D) {
+ for (const EnumConstantDecl *E : D->enumerators())
+ I.Members.emplace_back(E->getNameAsString());
+}
+
+static void parseParameters(FunctionInfo &I, const FunctionDecl *D) {
+ for (const ParmVarDecl *P : D->parameters()) {
+ SymbolID Type;
+ std::string Name;
+ InfoType RefType;
+ if (const auto *T = getDeclForType(P->getOriginalType())) {
+ Type = getUSRForDecl(T);
+ if (dyn_cast<EnumDecl>(T))
+ RefType = InfoType::IT_enum;
+ else if (dyn_cast<RecordDecl>(T))
+ RefType = InfoType::IT_record;
+ I.Params.emplace_back(Type, RefType, P->getQualifiedNameAsString());
+ } else {
+ Name = P->getOriginalType().getAsString();
+ I.Params.emplace_back(Name, P->getQualifiedNameAsString());
+ }
+ }
+}
+
+static void parseBases(RecordInfo &I, const CXXRecordDecl *D) {
+ for (const CXXBaseSpecifier &B : D->bases()) {
+ if (B.isVirtual())
+ continue;
+ if (const auto *P = getDeclForType(B.getType()))
+ I.Parents.emplace_back(getUSRForDecl(P), InfoType::IT_record);
+ else
+ I.Parents.emplace_back(B.getType().getAsString());
+ }
+ for (const CXXBaseSpecifier &B : D->vbases()) {
+ if (const auto *P = getDeclForType(B.getType()))
+ I.VirtualParents.emplace_back(getUSRForDecl(P), InfoType::IT_record);
+ else
+ I.VirtualParents.emplace_back(B.getType().getAsString());
+ }
+}
+
+template <typename T>
+static void
+populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
+ const T *D) {
+ const auto *DC = dyn_cast<DeclContext>(D);
+ while ((DC = DC->getParent())) {
+ if (const auto *N = dyn_cast<NamespaceDecl>(DC))
+ Namespaces.emplace_back(getUSRForDecl(N), InfoType::IT_namespace);
+ else if (const auto *N = dyn_cast<RecordDecl>(DC))
+ Namespaces.emplace_back(getUSRForDecl(N), InfoType::IT_record);
+ else if (const auto *N = dyn_cast<FunctionDecl>(DC))
+ Namespaces.emplace_back(getUSRForDecl(N), InfoType::IT_function);
+ else if (const auto *N = dyn_cast<EnumDecl>(DC))
+ Namespaces.emplace_back(getUSRForDecl(N), InfoType::IT_enum);
+ }
+}
+
+template <typename T>
+static void populateInfo(Info &I, const T *D, const FullComment *C) {
+ I.USR = getUSRForDecl(D);
+ I.Name = D->getNameAsString();
+ populateParentNamespaces(I.Namespace, D);
+ if (C) {
+ I.Description.emplace_back();
+ parseFullComment(C, I.Description.back());
+ }
+}
+
+template <typename T>
+static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C,
+ int LineNumber, StringRef Filename) {
+ populateInfo(I, D, C);
+ if (D->isThisDeclarationADefinition())
+ I.DefLoc.emplace(LineNumber, Filename);
+ else
+ I.Loc.emplace_back(LineNumber, Filename);
+}
+
+static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
+ const FullComment *FC, int LineNumber,
+ StringRef Filename) {
+ populateSymbolInfo(I, D, FC, LineNumber, Filename);
+ if (const auto *T = getDeclForType(D->getReturnType())) {
+ I.ReturnType.Type.USR = getUSRForDecl(T);
+ if (dyn_cast<EnumDecl>(T))
+ I.ReturnType.Type.RefType = InfoType::IT_enum;
+ else if (dyn_cast<RecordDecl>(T))
+ I.ReturnType.Type.RefType = InfoType::IT_record;
+ } else {
+ I.ReturnType.Type.UnresolvedName = D->getReturnType().getAsString();
+ }
+ parseParameters(I, D);
+}
+
+std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File) {
+ NamespaceInfo I;
+ populateInfo(I, D, FC);
+ return serialize(I);
+}
+
+std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
+ llvm::StringRef File) {
+ RecordInfo I;
+ populateSymbolInfo(I, D, FC, LineNumber, File);
+ I.TagType = D->getTagKind();
+ parseFields(I, D);
+ if (const auto *C = dyn_cast<CXXRecordDecl>(D))
+ parseBases(I, C);
+ return serialize(I);
+}
+
+std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File) {
+ FunctionInfo I;
+ populateFunctionInfo(I, D, FC, LineNumber, File);
+ I.Access = clang::AccessSpecifier::AS_none;
+ return serialize(I);
+}
+
+std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File) {
+ FunctionInfo I;
+ populateFunctionInfo(I, D, FC, LineNumber, File);
+ I.IsMethod = true;
+ I.Parent = Reference(getUSRForDecl(D->getParent()), InfoType::IT_record);
+ I.Access = D->getAccess();
+ return serialize(I);
+}
+
+std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
+ llvm::StringRef File) {
+ EnumInfo I;
+ populateSymbolInfo(I, D, FC, LineNumber, File);
+ I.Scoped = D->isScoped();
+ parseEnumerators(I, D);
+ return serialize(I);
+}
+
+} // namespace serialize
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/Serialize.h b/clang-doc/Serialize.h
new file mode 100644
index 0000000..5f13798
--- /dev/null
+++ b/clang-doc/Serialize.h
@@ -0,0 +1,53 @@
+//===-- Serializer.h - ClangDoc Serializer ----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the serializing functions fro the clang-doc tool. Given
+// a particular declaration, it collects the appropriate information and returns
+// a serialized bitcode string for the declaration.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H
+
+#include "Representation.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/CommentVisitor.h"
+#include <string>
+#include <vector>
+
+using namespace clang::comments;
+
+namespace clang {
+namespace doc {
+namespace serialize {
+
+std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File);
+std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
+ StringRef File);
+std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
+ StringRef File);
+std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File);
+std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File);
+
+// Function to hash a given USR value for storage.
+// As USRs (Unified Symbol Resolution) could be large, especially for functions
+// with long type arguments, we use 160-bits SHA1(USR) values to
+// guarantee the uniqueness of symbols while using a relatively small amount of
+// memory (vs storing USRs directly).
+SymbolID hashUSR(llvm::StringRef USR);
+
+} // namespace serialize
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H
diff --git a/clang-doc/tool/CMakeLists.txt b/clang-doc/tool/CMakeLists.txt
new file mode 100644
index 0000000..d7f28cf
--- /dev/null
+++ b/clang-doc/tool/CMakeLists.txt
@@ -0,0 +1,17 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
+
+add_clang_executable(clang-doc
+ ClangDocMain.cpp
+ )
+
+target_link_libraries(clang-doc
+ PRIVATE
+ clangAST
+ clangASTMatchers
+ clangBasic
+ clangFrontend
+ clangDoc
+ clangTooling
+ clangToolingCore
+ )
+
\ No newline at end of file
diff --git a/clang-doc/tool/ClangDocMain.cpp b/clang-doc/tool/ClangDocMain.cpp
new file mode 100644
index 0000000..51c3aa9
--- /dev/null
+++ b/clang-doc/tool/ClangDocMain.cpp
@@ -0,0 +1,114 @@
+//===-- ClangDocMain.cpp - ClangDoc -----------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This tool for generating C and C++ documenation from source code
+// and comments. Generally, it runs a LibTooling FrontendAction on source files,
+// mapping each declaration in those files to its USR and serializing relevant
+// information into LLVM bitcode. It then runs a pass over the collected
+// declaration information, reducing by USR. There is an option to dump this
+// intermediate result to bitcode. Finally, it hands the reduced information
+// off to a generator, which does the final parsing from the intermediate
+// representation to the desired output format.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangDoc.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/Decl.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/Driver/Options.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Execution.h"
+#include "clang/Tooling/StandaloneExecution.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace clang::ast_matchers;
+using namespace clang::tooling;
+using namespace clang;
+
+static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
+static llvm::cl::OptionCategory ClangDocCategory("clang-doc options");
+
+static llvm::cl::opt<std::string>
+ OutDirectory("output",
+ llvm::cl::desc("Directory for outputting generated files."),
+ llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory));
+
+static llvm::cl::opt<bool>
+ DumpMapperResult("dump-mapper",
+ llvm::cl::desc("Dump mapper results to bitcode file."),
+ llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+
+static llvm::cl::opt<bool> DoxygenOnly(
+ "doxygen",
+ llvm::cl::desc("Use only doxygen-style comments to generate docs."),
+ llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+
+int main(int argc, const char **argv) {
+ llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
+ std::error_code OK;
+
+ auto Exec = clang::tooling::createExecutorFromCommandLineArgs(
+ argc, argv, ClangDocCategory);
+
+ if (!Exec) {
+ llvm::errs() << toString(Exec.takeError()) << "\n";
+ return 1;
+ }
+
+ ArgumentsAdjuster ArgAdjuster;
+ if (!DoxygenOnly)
+ ArgAdjuster = combineAdjusters(
+ getInsertArgumentAdjuster("-fparse-all-comments",
+ tooling::ArgumentInsertPosition::END),
+ ArgAdjuster);
+
+ // Mapping phase
+ llvm::outs() << "Mapping decls...\n";
+ auto Err = Exec->get()->execute(doc::newMapperActionFactory(
+ Exec->get()->getExecutionContext()),
+ ArgAdjuster);
+ if (Err)
+ llvm::errs() << toString(std::move(Err)) << "\n";
+
+ if (DumpMapperResult) {
+ Exec->get()->getToolResults()->forEachResult([&](StringRef Key,
+ StringRef Value) {
+ SmallString<128> IRRootPath;
+ llvm::sys::path::native(OutDirectory, IRRootPath);
+ llvm::sys::path::append(IRRootPath, "bc");
+ std::error_code DirectoryStatus =
+ llvm::sys::fs::create_directories(IRRootPath);
+ if (DirectoryStatus != OK) {
+ llvm::errs() << "Unable to create documentation directories.\n";
+ return;
+ }
+ llvm::sys::path::append(IRRootPath, Key + ".bc");
+ std::error_code OutErrorInfo;
+ llvm::raw_fd_ostream OS(IRRootPath, OutErrorInfo, llvm::sys::fs::F_None);
+ if (OutErrorInfo != OK) {
+ llvm::errs() << "Error opening documentation file.\n";
+ return;
+ }
+ OS << Value;
+ OS.close();
+ });
+ }
+
+ return 0;
+}
diff --git a/clang-tidy/CMakeLists.txt b/clang-tidy/CMakeLists.txt
index a307ec0..6738c31 100644
--- a/clang-tidy/CMakeLists.txt
+++ b/clang-tidy/CMakeLists.txt
@@ -27,6 +27,7 @@
)
add_subdirectory(android)
+add_subdirectory(abseil)
add_subdirectory(boost)
add_subdirectory(bugprone)
add_subdirectory(cert)
@@ -41,6 +42,8 @@
add_subdirectory(objc)
add_subdirectory(performance)
add_subdirectory(plugin)
+add_subdirectory(portability)
add_subdirectory(readability)
add_subdirectory(tool)
add_subdirectory(utils)
+add_subdirectory(zircon)
diff --git a/clang-tidy/abseil/AbseilTidyModule.cpp b/clang-tidy/abseil/AbseilTidyModule.cpp
new file mode 100644
index 0000000..8e427f0
--- /dev/null
+++ b/clang-tidy/abseil/AbseilTidyModule.cpp
@@ -0,0 +1,38 @@
+//===------- AbseilTidyModule.cpp - clang-tidy ----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../ClangTidy.h"
+#include "../ClangTidyModule.h"
+#include "../ClangTidyModuleRegistry.h"
+#include "StringFindStartswithCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace abseil {
+
+class AbseilModule : public ClangTidyModule {
+public:
+ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<StringFindStartswithCheck>(
+ "abseil-string-find-startswith");
+ }
+};
+
+// Register the AbseilModule using this statically initialized variable.
+static ClangTidyModuleRegistry::Add<AbseilModule> X("abseil-module",
+ "Add Abseil checks.");
+
+} // namespace abseil
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the AbseilModule.
+volatile int AbseilModuleAnchorSource = 0;
+
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/abseil/CMakeLists.txt b/clang-tidy/abseil/CMakeLists.txt
new file mode 100644
index 0000000..dd59dcf
--- /dev/null
+++ b/clang-tidy/abseil/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_library(clangTidyAbseilModule
+ AbseilTidyModule.cpp
+ StringFindStartswithCheck.cpp
+
+ LINK_LIBS
+ clangAST
+ clangASTMatchers
+ clangBasic
+ clangLex
+ clangTidy
+ clangTidyUtils
+ )
diff --git a/clang-tidy/abseil/StringFindStartswithCheck.cpp b/clang-tidy/abseil/StringFindStartswithCheck.cpp
new file mode 100644
index 0000000..44df264
--- /dev/null
+++ b/clang-tidy/abseil/StringFindStartswithCheck.cpp
@@ -0,0 +1,133 @@
+//===--- StringFindStartswithCheck.cc - clang-tidy---------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "StringFindStartswithCheck.h"
+
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+#include <cassert>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace abseil {
+
+StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ StringLikeClasses(utils::options::parseStringList(
+ Options.get("StringLikeClasses", "::std::basic_string"))),
+ IncludeStyle(utils::IncludeSorter::parseIncludeStyle(
+ Options.getLocalOrGlobal("IncludeStyle", "llvm"))),
+ AbseilStringsMatchHeader(
+ Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {}
+
+void StringFindStartswithCheck::registerMatchers(MatchFinder *Finder) {
+ auto ZeroLiteral = integerLiteral(equals(0));
+ auto StringClassMatcher = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 4>(
+ StringLikeClasses.begin(), StringLikeClasses.end())));
+
+ auto StringFind = cxxMemberCallExpr(
+ // .find()-call on a string...
+ callee(cxxMethodDecl(hasName("find"), ofClass(StringClassMatcher))),
+ // ... with some search expression ...
+ hasArgument(0, expr().bind("needle")),
+ // ... and either "0" as second argument or the default argument (also 0).
+ anyOf(hasArgument(1, ZeroLiteral), hasArgument(1, cxxDefaultArgExpr())));
+
+ Finder->addMatcher(
+ // Match [=!]= with a zero on one side and a string.find on the other.
+ binaryOperator(
+ anyOf(hasOperatorName("=="), hasOperatorName("!=")),
+ hasEitherOperand(ignoringParenImpCasts(ZeroLiteral)),
+ hasEitherOperand(ignoringParenImpCasts(StringFind.bind("findexpr"))))
+ .bind("expr"),
+ this);
+}
+
+void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
+ const ASTContext &Context = *Result.Context;
+ const SourceManager &Source = Context.getSourceManager();
+
+ // Extract matching (sub)expressions
+ const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
+ assert(ComparisonExpr != nullptr);
+ const auto *Needle = Result.Nodes.getNodeAs<Expr>("needle");
+ assert(Needle != nullptr);
+ const Expr *Haystack = Result.Nodes.getNodeAs<CXXMemberCallExpr>("findexpr")
+ ->getImplicitObjectArgument();
+ assert(Haystack != nullptr);
+
+ if (ComparisonExpr->getLocStart().isMacroID())
+ return;
+
+ // Get the source code blocks (as characters) for both the string object
+ // and the search expression
+ const StringRef NeedleExprCode = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(Needle->getSourceRange()), Source,
+ Context.getLangOpts());
+ const StringRef HaystackExprCode = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(Haystack->getSourceRange()), Source,
+ Context.getLangOpts());
+
+ // Create the StartsWith string, negating if comparison was "!=".
+ bool Neg = ComparisonExpr->getOpcodeStr() == "!=";
+ StringRef StartswithStr;
+ if (Neg) {
+ StartswithStr = "!absl::StartsWith";
+ } else {
+ StartswithStr = "absl::StartsWith";
+ }
+
+ // Create the warning message and a FixIt hint replacing the original expr.
+ auto Diagnostic =
+ diag(ComparisonExpr->getLocStart(),
+ (StringRef("use ") + StartswithStr + " instead of find() " +
+ ComparisonExpr->getOpcodeStr() + " 0")
+ .str());
+
+ Diagnostic << FixItHint::CreateReplacement(
+ ComparisonExpr->getSourceRange(),
+ (StartswithStr + "(" + HaystackExprCode + ", " + NeedleExprCode + ")")
+ .str());
+
+ // Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks
+ // whether this already exists).
+ auto IncludeHint = IncludeInserter->CreateIncludeInsertion(
+ Source.getFileID(ComparisonExpr->getLocStart()), AbseilStringsMatchHeader,
+ false);
+ if (IncludeHint) {
+ Diagnostic << *IncludeHint;
+ }
+}
+
+void StringFindStartswithCheck::registerPPCallbacks(
+ CompilerInstance &Compiler) {
+ IncludeInserter = llvm::make_unique<clang::tidy::utils::IncludeInserter>(
+ Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle);
+ Compiler.getPreprocessor().addPPCallbacks(
+ IncludeInserter->CreatePPCallbacks());
+}
+
+void StringFindStartswithCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "StringLikeClasses",
+ utils::options::serializeStringList(StringLikeClasses));
+ Options.store(Opts, "IncludeStyle",
+ utils::IncludeSorter::toString(IncludeStyle));
+ Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader);
+}
+
+} // namespace abseil
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/abseil/StringFindStartswithCheck.h b/clang-tidy/abseil/StringFindStartswithCheck.h
new file mode 100644
index 0000000..1c04e80
--- /dev/null
+++ b/clang-tidy/abseil/StringFindStartswithCheck.h
@@ -0,0 +1,48 @@
+//===--- StringFindStartswithCheck.h - clang-tidy----------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRINGFINDSTARTSWITHCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRINGFINDSTARTSWITHCHECK_H
+
+#include "../ClangTidy.h"
+#include "../utils/IncludeInserter.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace tidy {
+namespace abseil {
+
+// Find string.find(...) == 0 comparisons and suggest replacing with StartsWith.
+// FIXME(niko): Add similar check for EndsWith
+// FIXME(niko): Add equivalent modernize checks for C++20's std::starts_With
+class StringFindStartswithCheck : public ClangTidyCheck {
+public:
+ using ClangTidyCheck::ClangTidyCheck;
+ StringFindStartswithCheck(StringRef Name, ClangTidyContext *Context);
+ void registerPPCallbacks(CompilerInstance &Compiler) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+ std::unique_ptr<clang::tidy::utils::IncludeInserter> IncludeInserter;
+ const std::vector<std::string> StringLikeClasses;
+ const utils::IncludeSorter::IncludeStyle IncludeStyle;
+ const std::string AbseilStringsMatchHeader;
+};
+
+} // namespace abseil
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRINGFINDSTARTSWITHCHECK_H
diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py
index ab0c883..2f1a301 100755
--- a/clang-tidy/add_new_check.py
+++ b/clang-tidy/add_new_check.py
@@ -9,12 +9,13 @@
#
#===------------------------------------------------------------------------===#
+from __future__ import print_function
+
import argparse
import os
import re
import sys
-
# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
# and 'False' if the entry already existed.
def adapt_cmake(module_path, check_name_camel):
@@ -30,7 +31,7 @@
return False
print('Updating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
cpp_found = False
file_added = False
for line in lines:
@@ -50,7 +51,7 @@
check_name_dashes = module + '-' + check_name
filename = os.path.join(module_path, check_name_camel) + '.h'
print('Creating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_'
+ check_name_camel.upper() + '_H')
f.write('//===--- ')
@@ -103,7 +104,7 @@
def write_implementation(module_path, module, check_name_camel):
filename = os.path.join(module_path, check_name_camel) + '.cpp'
print('Creating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
f.write('//===--- ')
f.write(os.path.basename(filename))
f.write(' - clang-tidy')
@@ -152,14 +153,15 @@
# Modifies the module to include the new check.
def adapt_module(module_path, module, check_name, check_name_camel):
- modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
- os.listdir(module_path))[0]
+ modulecpp = list(filter(
+ lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
+ os.listdir(module_path)))[0]
filename = os.path.join(module_path, modulecpp)
with open(filename, 'r') as f:
lines = f.readlines()
print('Updating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
header_added = False
header_found = False
check_added = False
@@ -199,7 +201,7 @@
lines = f.readlines()
print('Updating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
note_added = False
header_found = False
@@ -211,8 +213,8 @@
elif header_found:
if not line.startswith('----'):
f.write("""
-- New `%s
- <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_ check
+- New :doc:`%s
+ <clang-tidy/checks/%s>` check
FIXME: add release notes.
""" % (check_name_dashes, check_name_dashes))
@@ -227,7 +229,7 @@
filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
check_name_dashes + '.' + test_extension))
print('Creating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
// FIXME: Add something that triggers the check here.
@@ -251,8 +253,8 @@
filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
with open(filename, 'r') as f:
lines = f.readlines()
- doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst',
- os.listdir(docs_dir))
+ doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst',
+ os.listdir(docs_dir)))
doc_files.sort()
def format_link(doc_file):
@@ -275,7 +277,7 @@
checks = map(format_link, doc_files)
print('Updating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
for line in lines:
f.write(line)
if line.startswith('.. toctree::'):
@@ -289,7 +291,7 @@
filename = os.path.normpath(os.path.join(
module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
print('Creating %s...' % filename)
- with open(filename, 'wb') as f:
+ with open(filename, 'w') as f:
f.write(""".. title:: clang-tidy - %(check_name_dashes)s
%(check_name_dashes)s
@@ -333,7 +335,7 @@
return
if not args.module or not args.check:
- print 'Module and check must be specified.'
+ print('Module and check must be specified.')
parser.print_usage()
return
@@ -341,8 +343,8 @@
check_name = args.check
if check_name.startswith(module):
- print 'Check name "%s" must not start with the module "%s". Exiting.' % (
- check_name, module)
+ print('Check name "%s" must not start with the module "%s". Exiting.' % (
+ check_name, module))
return
check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
check_name.split('-'))) + 'Check'
diff --git a/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tidy/bugprone/BugproneTidyModule.cpp
index ff35641..14f07dc 100644
--- a/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -22,11 +22,14 @@
#include "IncorrectRoundingsCheck.h"
#include "IntegerDivisionCheck.h"
#include "LambdaFunctionNameCheck.h"
+#include "MacroParenthesesCheck.h"
#include "MacroRepeatedSideEffectsCheck.h"
#include "MisplacedOperatorInStrlenInAllocCheck.h"
#include "MisplacedWideningCastCheck.h"
#include "MoveForwardingReferenceCheck.h"
#include "MultipleStatementMacroCheck.h"
+#include "SizeofContainerCheck.h"
+#include "SizeofExpressionCheck.h"
#include "StringConstructorCheck.h"
#include "StringIntegerAssignmentCheck.h"
#include "StringLiteralWithEmbeddedNulCheck.h"
@@ -39,6 +42,8 @@
#include "ThrowKeywordMissingCheck.h"
#include "UndefinedMemoryManipulationCheck.h"
#include "UndelegatedConstructorCheck.h"
+#include "UnusedRaiiCheck.h"
+#include "UnusedReturnValueCheck.h"
#include "UseAfterMoveCheck.h"
#include "VirtualNearMissCheck.h"
@@ -73,6 +78,8 @@
"bugprone-integer-division");
CheckFactories.registerCheck<LambdaFunctionNameCheck>(
"bugprone-lambda-function-name");
+ CheckFactories.registerCheck<MacroParenthesesCheck>(
+ "bugprone-macro-parentheses");
CheckFactories.registerCheck<MacroRepeatedSideEffectsCheck>(
"bugprone-macro-repeated-side-effects");
CheckFactories.registerCheck<MisplacedOperatorInStrlenInAllocCheck>(
@@ -83,6 +90,10 @@
"bugprone-move-forwarding-reference");
CheckFactories.registerCheck<MultipleStatementMacroCheck>(
"bugprone-multiple-statement-macro");
+ CheckFactories.registerCheck<SizeofContainerCheck>(
+ "bugprone-sizeof-container");
+ CheckFactories.registerCheck<SizeofExpressionCheck>(
+ "bugprone-sizeof-expression");
CheckFactories.registerCheck<StringConstructorCheck>(
"bugprone-string-constructor");
CheckFactories.registerCheck<StringIntegerAssignmentCheck>(
@@ -107,6 +118,10 @@
"bugprone-undefined-memory-manipulation");
CheckFactories.registerCheck<UndelegatedConstructorCheck>(
"bugprone-undelegated-constructor");
+ CheckFactories.registerCheck<UnusedRaiiCheck>(
+ "bugprone-unused-raii");
+ CheckFactories.registerCheck<UnusedReturnValueCheck>(
+ "bugprone-unused-return-value");
CheckFactories.registerCheck<UseAfterMoveCheck>(
"bugprone-use-after-move");
CheckFactories.registerCheck<VirtualNearMissCheck>(
diff --git a/clang-tidy/bugprone/CMakeLists.txt b/clang-tidy/bugprone/CMakeLists.txt
index a34d3fd..4624f60 100644
--- a/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tidy/bugprone/CMakeLists.txt
@@ -14,11 +14,14 @@
IncorrectRoundingsCheck.cpp
IntegerDivisionCheck.cpp
LambdaFunctionNameCheck.cpp
+ MacroParenthesesCheck.cpp
MacroRepeatedSideEffectsCheck.cpp
MisplacedOperatorInStrlenInAllocCheck.cpp
MisplacedWideningCastCheck.cpp
MoveForwardingReferenceCheck.cpp
MultipleStatementMacroCheck.cpp
+ SizeofContainerCheck.cpp
+ SizeofExpressionCheck.cpp
StringConstructorCheck.cpp
StringIntegerAssignmentCheck.cpp
StringLiteralWithEmbeddedNulCheck.cpp
@@ -31,6 +34,8 @@
ThrowKeywordMissingCheck.cpp
UndefinedMemoryManipulationCheck.cpp
UndelegatedConstructorCheck.cpp
+ UnusedRaiiCheck.cpp
+ UnusedReturnValueCheck.cpp
UseAfterMoveCheck.cpp
VirtualNearMissCheck.cpp
diff --git a/clang-tidy/bugprone/IncorrectRoundingsCheck.h b/clang-tidy/bugprone/IncorrectRoundingsCheck.h
index 9a88e21..b1886fd 100644
--- a/clang-tidy/bugprone/IncorrectRoundingsCheck.h
+++ b/clang-tidy/bugprone/IncorrectRoundingsCheck.h
@@ -1,4 +1,4 @@
-//===--- IncorrectRoundingsCheckCheck.h - clang-tidy -----------------*- C++ -*-===//
+//===--- IncorrectRoundingsCheck.h - clang-tidy -----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
diff --git a/clang-tidy/misc/MacroParenthesesCheck.cpp b/clang-tidy/bugprone/MacroParenthesesCheck.cpp
similarity index 99%
rename from clang-tidy/misc/MacroParenthesesCheck.cpp
rename to clang-tidy/bugprone/MacroParenthesesCheck.cpp
index 59fe772..6846bc2 100644
--- a/clang-tidy/misc/MacroParenthesesCheck.cpp
+++ b/clang-tidy/bugprone/MacroParenthesesCheck.cpp
@@ -14,7 +14,7 @@
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
namespace {
class MacroParenthesesPPCallbacks : public PPCallbacks {
@@ -255,6 +255,6 @@
&Compiler.getPreprocessor(), this));
}
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/misc/MacroParenthesesCheck.h b/clang-tidy/bugprone/MacroParenthesesCheck.h
similarity index 82%
rename from clang-tidy/misc/MacroParenthesesCheck.h
rename to clang-tidy/bugprone/MacroParenthesesCheck.h
index e398fc6..383a6cc 100644
--- a/clang-tidy/misc/MacroParenthesesCheck.h
+++ b/clang-tidy/bugprone/MacroParenthesesCheck.h
@@ -7,14 +7,14 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROPARENTHESESCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROPARENTHESESCHECK_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
/// Finds macros that can have unexpected behaviour due to missing parentheses.
///
@@ -36,8 +36,8 @@
void registerPPCallbacks(CompilerInstance &Compiler) override;
};
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROPARENTHESESCHECK_H
diff --git a/clang-tidy/misc/SizeofContainerCheck.cpp b/clang-tidy/bugprone/SizeofContainerCheck.cpp
similarity index 97%
rename from clang-tidy/misc/SizeofContainerCheck.cpp
rename to clang-tidy/bugprone/SizeofContainerCheck.cpp
index de4e8ad..b4a019e 100644
--- a/clang-tidy/misc/SizeofContainerCheck.cpp
+++ b/clang-tidy/bugprone/SizeofContainerCheck.cpp
@@ -15,7 +15,7 @@
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
void SizeofContainerCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
@@ -44,6 +44,6 @@
"container; did you mean .size()?");
}
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/misc/SizeofContainerCheck.h b/clang-tidy/bugprone/SizeofContainerCheck.h
similarity index 72%
rename from clang-tidy/misc/SizeofContainerCheck.h
rename to clang-tidy/bugprone/SizeofContainerCheck.h
index ed13ca5..76b82b0 100644
--- a/clang-tidy/misc/SizeofContainerCheck.h
+++ b/clang-tidy/bugprone/SizeofContainerCheck.h
@@ -7,20 +7,20 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFCONTAINERCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFCONTAINERCHECK_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
/// Find usages of sizeof on expressions of STL container types. Most likely the
/// user wanted to use `.size()` instead.
///
/// For the user-facing documentation see:
-/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-container.html
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-sizeof-container.html
class SizeofContainerCheck : public ClangTidyCheck {
public:
SizeofContainerCheck(StringRef Name, ClangTidyContext *Context)
@@ -29,8 +29,8 @@
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFCONTAINERCHECK_H
diff --git a/clang-tidy/misc/SizeofExpressionCheck.cpp b/clang-tidy/bugprone/SizeofExpressionCheck.cpp
similarity index 99%
rename from clang-tidy/misc/SizeofExpressionCheck.cpp
rename to clang-tidy/bugprone/SizeofExpressionCheck.cpp
index 5b8e7e7..d389ff1 100644
--- a/clang-tidy/misc/SizeofExpressionCheck.cpp
+++ b/clang-tidy/bugprone/SizeofExpressionCheck.cpp
@@ -16,7 +16,7 @@
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
namespace {
@@ -260,6 +260,6 @@
}
}
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/misc/SizeofExpressionCheck.h b/clang-tidy/bugprone/SizeofExpressionCheck.h
similarity index 73%
rename from clang-tidy/misc/SizeofExpressionCheck.h
rename to clang-tidy/bugprone/SizeofExpressionCheck.h
index d2ba9da..2811b5a 100644
--- a/clang-tidy/misc/SizeofExpressionCheck.h
+++ b/clang-tidy/bugprone/SizeofExpressionCheck.h
@@ -7,19 +7,19 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFEXPRESSIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFEXPRESSIONCHECK_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
/// Find suspicious usages of sizeof expression.
///
/// For the user-facing documentation see:
-/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-expression.html
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-sizeof-expression.html
class SizeofExpressionCheck : public ClangTidyCheck {
public:
SizeofExpressionCheck(StringRef Name, ClangTidyContext *Context);
@@ -33,8 +33,8 @@
const bool WarnOnSizeOfCompareToConstant;
};
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFEXPRESSIONCHECK_H
diff --git a/clang-tidy/misc/UnusedRAIICheck.cpp b/clang-tidy/bugprone/UnusedRaiiCheck.cpp
similarity index 91%
rename from clang-tidy/misc/UnusedRAIICheck.cpp
rename to clang-tidy/bugprone/UnusedRaiiCheck.cpp
index e1acfe9..e2882f3 100644
--- a/clang-tidy/misc/UnusedRAIICheck.cpp
+++ b/clang-tidy/bugprone/UnusedRaiiCheck.cpp
@@ -1,4 +1,4 @@
-//===--- UnusedRAIICheck.cpp - clang-tidy ---------------------------------===//
+//===--- UnusedRaiiCheck.cpp - clang-tidy ---------------------------------===//
//
// The LLVM Compiler Infrastructure
//
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
-#include "UnusedRAIICheck.h"
+#include "UnusedRaiiCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/Lex/Lexer.h"
@@ -15,7 +15,7 @@
namespace clang {
namespace tidy {
-namespace misc {
+namespace bugprone {
namespace {
AST_MATCHER(CXXRecordDecl, hasNonTrivialDestructor) {
@@ -24,7 +24,7 @@
}
} // namespace
-void UnusedRAIICheck::registerMatchers(MatchFinder *Finder) {
+void UnusedRaiiCheck::registerMatchers(MatchFinder *Finder) {
// Only register the matchers for C++; the functionality currently does not
// provide any benefit to other languages, despite being benign.
if (!getLangOpts().CPlusPlus)
@@ -47,7 +47,7 @@
this);
}
-void UnusedRAIICheck::check(const MatchFinder::MatchResult &Result) {
+void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) {
const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
// We ignore code expanded from macros to reduce the number of false
@@ -89,6 +89,6 @@
Replacement);
}
-} // namespace misc
+} // namespace bugprone
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/bugprone/UnusedRaiiCheck.h b/clang-tidy/bugprone/UnusedRaiiCheck.h
new file mode 100644
index 0000000..34190ec
--- /dev/null
+++ b/clang-tidy/bugprone/UnusedRaiiCheck.h
@@ -0,0 +1,35 @@
+//===--- UnusedRaiiCheck.h - clang-tidy -------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRAIICHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRAIICHECK_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// Finds temporaries that look like RAII objects.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-unused-raii.html
+class UnusedRaiiCheck : public ClangTidyCheck {
+public:
+ UnusedRaiiCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRAIICHECK_H
diff --git a/clang-tidy/bugprone/UnusedReturnValueCheck.cpp b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
new file mode 100644
index 0000000..198d0db
--- /dev/null
+++ b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
@@ -0,0 +1,82 @@
+//===--- UnusedReturnValueCheck.cpp - clang-tidy---------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "UnusedReturnValueCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+using namespace clang::ast_matchers::internal;
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ CheckedFunctions(Options.get("CheckedFunctions", "::std::async;"
+ "::std::launder;"
+ "::std::remove;"
+ "::std::remove_if;"
+ "::std::unique")) {}
+
+void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "CheckedFunctions", CheckedFunctions);
+}
+
+void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
+ auto FunVec = utils::options::parseStringList(CheckedFunctions);
+ auto MatchedCallExpr = expr(ignoringImplicit(ignoringParenImpCasts(
+ callExpr(
+ callee(functionDecl(
+ // Don't match void overloads of checked functions.
+ unless(returns(voidType())), hasAnyName(std::vector<StringRef>(
+ FunVec.begin(), FunVec.end())))))
+ .bind("match"))));
+
+ auto UnusedInCompoundStmt =
+ compoundStmt(forEach(MatchedCallExpr),
+ // The checker can't currently differentiate between the
+ // return statement and other statements inside GNU statement
+ // expressions, so disable the checker inside them to avoid
+ // false positives.
+ unless(hasParent(stmtExpr())));
+ auto UnusedInIfStmt =
+ ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
+ auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
+ auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
+ auto UnusedInForStmt =
+ forStmt(eachOf(hasLoopInit(MatchedCallExpr),
+ hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
+ auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
+ auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));
+
+ Finder->addMatcher(
+ stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
+ UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
+ UnusedInCaseStmt)),
+ this);
+}
+
+void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) {
+ if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>("match")) {
+ diag(Matched->getLocStart(),
+ "the value returned by this function should be used")
+ << Matched->getSourceRange();
+ diag(Matched->getLocStart(),
+ "cast the expression to void to silence this warning",
+ DiagnosticIDs::Note);
+ }
+}
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/bugprone/UnusedReturnValueCheck.h b/clang-tidy/bugprone/UnusedReturnValueCheck.h
new file mode 100644
index 0000000..9475f56
--- /dev/null
+++ b/clang-tidy/bugprone/UnusedReturnValueCheck.h
@@ -0,0 +1,39 @@
+//===--- UnusedReturnValueCheck.h - clang-tidy-------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRETURNVALUECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRETURNVALUECHECK_H
+
+#include "../ClangTidy.h"
+#include <string>
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// Detects function calls where the return value is unused.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-unused-return-value.html
+class UnusedReturnValueCheck : public ClangTidyCheck {
+public:
+ UnusedReturnValueCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ std::string CheckedFunctions;
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRETURNVALUECHECK_H
diff --git a/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp
index 791651f..aa91ff0 100644
--- a/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp
+++ b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp
@@ -19,6 +19,11 @@
namespace objc {
void AvoidThrowingObjCExceptionCheck::registerMatchers(MatchFinder *Finder) {
+ // this check should only be applied to ObjC sources.
+ if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
+ return;
+ }
+
Finder->addMatcher(objcThrowStmt().bind("throwStmt"), this);
Finder->addMatcher(
objcMessageExpr(anyOf(hasSelector("raise:format:"),
diff --git a/clang-tidy/hicpp/CMakeLists.txt b/clang-tidy/hicpp/CMakeLists.txt
index 0e37b3e..eeccf62 100644
--- a/clang-tidy/hicpp/CMakeLists.txt
+++ b/clang-tidy/hicpp/CMakeLists.txt
@@ -2,6 +2,7 @@
add_clang_library(clangTidyHICPPModule
ExceptionBaseclassCheck.cpp
+ MultiwayPathsCoveredCheck.cpp
NoAssemblerCheck.cpp
HICPPTidyModule.cpp
SignedBitwiseCheck.cpp
diff --git a/clang-tidy/hicpp/HICPPTidyModule.cpp b/clang-tidy/hicpp/HICPPTidyModule.cpp
index d8e9a20..d2b9fc6 100644
--- a/clang-tidy/hicpp/HICPPTidyModule.cpp
+++ b/clang-tidy/hicpp/HICPPTidyModule.cpp
@@ -36,6 +36,7 @@
#include "../readability/FunctionSizeCheck.h"
#include "../readability/IdentifierNamingCheck.h"
#include "ExceptionBaseclassCheck.h"
+#include "MultiwayPathsCoveredCheck.h"
#include "NoAssemblerCheck.h"
#include "SignedBitwiseCheck.h"
@@ -54,6 +55,8 @@
"hicpp-deprecated-headers");
CheckFactories.registerCheck<ExceptionBaseclassCheck>(
"hicpp-exception-baseclass");
+ CheckFactories.registerCheck<MultiwayPathsCoveredCheck>(
+ "hicpp-multiway-paths-covered");
CheckFactories.registerCheck<SignedBitwiseCheck>("hicpp-signed-bitwise");
CheckFactories.registerCheck<google::ExplicitConstructorCheck>(
"hicpp-explicit-conversions");
diff --git a/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp
new file mode 100644
index 0000000..68ae54e
--- /dev/null
+++ b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp
@@ -0,0 +1,181 @@
+//===--- MultiwayPathsCoveredCheck.cpp - clang-tidy------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MultiwayPathsCoveredCheck.h"
+#include "clang/AST/ASTContext.h"
+
+#include <limits>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace hicpp {
+
+void MultiwayPathsCoveredCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "WarnOnMissingElse", WarnOnMissingElse);
+}
+
+void MultiwayPathsCoveredCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ switchStmt(
+ hasCondition(allOf(
+ // Match on switch statements that have either a bit-field or
+ // an integer condition. The ordering in 'anyOf()' is
+ // important because the last condition is the most general.
+ anyOf(ignoringImpCasts(memberExpr(hasDeclaration(
+ fieldDecl(isBitField()).bind("bitfield")))),
+ ignoringImpCasts(declRefExpr().bind("non-enum-condition"))),
+ // 'unless()' must be the last match here and must be bound,
+ // otherwise the matcher does not work correctly, because it
+ // will not explicitly ignore enum conditions.
+ unless(ignoringImpCasts(
+ declRefExpr(hasType(enumType())).bind("enum-condition"))))))
+ .bind("switch"),
+ this);
+
+ // This option is noisy, therefore matching is configurable.
+ if (WarnOnMissingElse) {
+ Finder->addMatcher(
+ ifStmt(allOf(hasParent(ifStmt()), unless(hasElse(anything()))))
+ .bind("else-if"),
+ this);
+ }
+}
+
+static std::pair<std::size_t, bool> countCaseLabels(const SwitchStmt *Switch) {
+ std::size_t CaseCount = 0;
+ bool HasDefault = false;
+
+ const SwitchCase *CurrentCase = Switch->getSwitchCaseList();
+ while (CurrentCase) {
+ ++CaseCount;
+ if (isa<DefaultStmt>(CurrentCase))
+ HasDefault = true;
+
+ CurrentCase = CurrentCase->getNextSwitchCase();
+ }
+
+ return std::make_pair(CaseCount, HasDefault);
+}
+
+/// This function calculate 2 ** Bits and returns
+/// numeric_limits<std::size_t>::max() if an overflow occured.
+static std::size_t twoPow(std::size_t Bits) {
+ return Bits >= std::numeric_limits<std::size_t>::digits
+ ? std::numeric_limits<std::size_t>::max()
+ : static_cast<size_t>(1) << Bits;
+}
+
+/// Get the number of possible values that can be switched on for the type T.
+///
+/// \return - 0 if bitcount could not be determined
+/// - numeric_limits<std::size_t>::max() when overflow appeared due to
+/// more than 64 bits type size.
+static std::size_t getNumberOfPossibleValues(QualType T,
+ const ASTContext &Context) {
+ // `isBooleanType` must come first because `bool` is an integral type as well
+ // and would not return 2 as result.
+ if (T->isBooleanType())
+ return 2;
+ else if (T->isIntegralType(Context))
+ return twoPow(Context.getTypeSize(T));
+ else
+ return 1;
+}
+
+void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) {
+ if (const auto *ElseIfWithoutElse =
+ Result.Nodes.getNodeAs<IfStmt>("else-if")) {
+ diag(ElseIfWithoutElse->getLocStart(),
+ "potentially uncovered codepath; add an ending else statement");
+ return;
+ }
+ const auto *Switch = Result.Nodes.getNodeAs<SwitchStmt>("switch");
+ std::size_t SwitchCaseCount;
+ bool SwitchHasDefault;
+ std::tie(SwitchCaseCount, SwitchHasDefault) = countCaseLabels(Switch);
+
+ // Checks the sanity of 'switch' statements that actually do define
+ // a default branch but might be degenerated by having no or only one case.
+ if (SwitchHasDefault) {
+ handleSwitchWithDefault(Switch, SwitchCaseCount);
+ return;
+ }
+ // Checks all 'switch' statements that do not define a default label.
+ // Here the heavy lifting happens.
+ if (!SwitchHasDefault && SwitchCaseCount > 0) {
+ handleSwitchWithoutDefault(Switch, SwitchCaseCount, Result);
+ return;
+ }
+ // Warns for degenerated 'switch' statements that neither define a case nor
+ // a default label.
+ // FIXME: Evaluate, if emitting a fix-it to simplify that statement is
+ // reasonable.
+ if (!SwitchHasDefault && SwitchCaseCount == 0) {
+ diag(Switch->getLocStart(),
+ "switch statement without labels has no effect");
+ return;
+ }
+ llvm_unreachable("matched a case, that was not explicitly handled");
+}
+
+void MultiwayPathsCoveredCheck::handleSwitchWithDefault(
+ const SwitchStmt *Switch, std::size_t CaseCount) {
+ assert(CaseCount > 0 && "Switch statement with supposedly one default "
+ "branch did not contain any case labels");
+ if (CaseCount == 1 || CaseCount == 2)
+ diag(Switch->getLocStart(),
+ CaseCount == 1
+ ? "degenerated switch with default label only"
+ : "switch could be better written as an if/else statement");
+}
+
+void MultiwayPathsCoveredCheck::handleSwitchWithoutDefault(
+ const SwitchStmt *Switch, std::size_t CaseCount,
+ const MatchFinder::MatchResult &Result) {
+ // The matcher only works because some nodes are explicitly matched and
+ // bound but ignored. This is necessary to build the excluding logic for
+ // enums and 'switch' statements without a 'default' branch.
+ assert(!Result.Nodes.getNodeAs<DeclRefExpr>("enum-condition") &&
+ "switch over enum is handled by warnings already, explicitly ignoring "
+ "them");
+ // Determine the number of case labels. Because 'default' is not present
+ // and duplicating case labels is not allowed this number represents
+ // the number of codepaths. It can be directly compared to 'MaxPathsPossible'
+ // to see if some cases are missing.
+ // CaseCount == 0 is caught in DegenerateSwitch. Necessary because the
+ // matcher used for here does not match on degenerate 'switch'.
+ assert(CaseCount > 0 && "Switch statement without any case found. This case "
+ "should be excluded by the matcher and is handled "
+ "separatly.");
+ std::size_t MaxPathsPossible = [&]() {
+ if (const auto *GeneralCondition =
+ Result.Nodes.getNodeAs<DeclRefExpr>("non-enum-condition")) {
+ return getNumberOfPossibleValues(GeneralCondition->getType(),
+ *Result.Context);
+ }
+ if (const auto *BitfieldDecl =
+ Result.Nodes.getNodeAs<FieldDecl>("bitfield")) {
+ return twoPow(BitfieldDecl->getBitWidthValue(*Result.Context));
+ }
+
+ return static_cast<std::size_t>(0);
+ }();
+
+ // FIXME: Transform the 'switch' into an 'if' for CaseCount == 1.
+ if (CaseCount < MaxPathsPossible)
+ diag(Switch->getLocStart(),
+ CaseCount == 1 ? "switch with only one case; use an if statement"
+ : "potential uncovered code path; add a default label");
+}
+} // namespace hicpp
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h
new file mode 100644
index 0000000..498dad6
--- /dev/null
+++ b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h
@@ -0,0 +1,51 @@
+//===--- MultiwayPathsCoveredCheck.h - clang-tidy----------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_MULTIWAY_PATHS_COVERED_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_MULTIWAY_PATHS_COVERED_H
+
+#include "../ClangTidy.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include <iostream>
+
+namespace clang {
+namespace tidy {
+namespace hicpp {
+
+/// Find occasions where not all codepaths are explicitly covered in code.
+/// This includes 'switch' without a 'default'-branch and 'if'-'else if'-chains
+/// without a final 'else'-branch.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/hicpp-multiway-paths-covered.html
+class MultiwayPathsCoveredCheck : public ClangTidyCheck {
+public:
+ MultiwayPathsCoveredCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ WarnOnMissingElse(Options.get("WarnOnMissingElse", 0)) {}
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ void handleSwitchWithDefault(const SwitchStmt *Switch, std::size_t CaseCount);
+ void handleSwitchWithoutDefault(
+ const SwitchStmt *Switch, std::size_t CaseCount,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+ /// This option can be configured to warn on missing 'else' branches in an
+ /// 'if-else if' chain. The default is false because this option might be
+ /// noisy on some code bases.
+ const bool WarnOnMissingElse;
+};
+
+} // namespace hicpp
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_MULTIWAY_PATHS_COVERED_H
diff --git a/clang-tidy/misc/CMakeLists.txt b/clang-tidy/misc/CMakeLists.txt
index 30b8efe..3806398 100644
--- a/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tidy/misc/CMakeLists.txt
@@ -4,19 +4,15 @@
MisplacedConstCheck.cpp
UnconventionalAssignOperatorCheck.cpp
DefinitionsInHeadersCheck.cpp
- MacroParenthesesCheck.cpp
MiscTidyModule.cpp
NewDeleteOverloadsCheck.cpp
NonCopyableObjects.cpp
RedundantExpressionCheck.cpp
- SizeofContainerCheck.cpp
- SizeofExpressionCheck.cpp
StaticAssertCheck.cpp
ThrowByValueCatchByReferenceCheck.cpp
UniqueptrResetReleaseCheck.cpp
UnusedAliasDeclsCheck.cpp
UnusedParametersCheck.cpp
- UnusedRAIICheck.cpp
UnusedUsingDeclsCheck.cpp
LINK_LIBS
diff --git a/clang-tidy/misc/MiscTidyModule.cpp b/clang-tidy/misc/MiscTidyModule.cpp
index 56e6492..241689a 100644
--- a/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tidy/misc/MiscTidyModule.cpp
@@ -11,20 +11,16 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "DefinitionsInHeadersCheck.h"
-#include "MacroParenthesesCheck.h"
#include "MisplacedConstCheck.h"
#include "NewDeleteOverloadsCheck.h"
#include "NonCopyableObjects.h"
#include "RedundantExpressionCheck.h"
-#include "SizeofContainerCheck.h"
-#include "SizeofExpressionCheck.h"
#include "StaticAssertCheck.h"
#include "ThrowByValueCatchByReferenceCheck.h"
#include "UnconventionalAssignOperatorCheck.h"
#include "UniqueptrResetReleaseCheck.h"
#include "UnusedAliasDeclsCheck.h"
#include "UnusedParametersCheck.h"
-#include "UnusedRAIICheck.h"
#include "UnusedUsingDeclsCheck.h"
namespace clang {
@@ -39,17 +35,12 @@
"misc-unconventional-assign-operator");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
- CheckFactories.registerCheck<MacroParenthesesCheck>(
- "misc-macro-parentheses");
CheckFactories.registerCheck<NewDeleteOverloadsCheck>(
"misc-new-delete-overloads");
CheckFactories.registerCheck<NonCopyableObjectsCheck>(
"misc-non-copyable-objects");
CheckFactories.registerCheck<RedundantExpressionCheck>(
"misc-redundant-expression");
- CheckFactories.registerCheck<SizeofContainerCheck>("misc-sizeof-container");
- CheckFactories.registerCheck<SizeofExpressionCheck>(
- "misc-sizeof-expression");
CheckFactories.registerCheck<StaticAssertCheck>("misc-static-assert");
CheckFactories.registerCheck<ThrowByValueCatchByReferenceCheck>(
"misc-throw-by-value-catch-by-reference");
@@ -59,7 +50,6 @@
"misc-unused-alias-decls");
CheckFactories.registerCheck<UnusedParametersCheck>(
"misc-unused-parameters");
- CheckFactories.registerCheck<UnusedRAIICheck>("misc-unused-raii");
CheckFactories.registerCheck<UnusedUsingDeclsCheck>(
"misc-unused-using-decls");
}
diff --git a/clang-tidy/misc/UnusedRAIICheck.h b/clang-tidy/misc/UnusedRAIICheck.h
deleted file mode 100644
index 40f44e3..0000000
--- a/clang-tidy/misc/UnusedRAIICheck.h
+++ /dev/null
@@ -1,35 +0,0 @@
-//===--- UnusedRAIICheck.h - clang-tidy -------------------------*- C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H
-
-#include "../ClangTidy.h"
-
-namespace clang {
-namespace tidy {
-namespace misc {
-
-/// Finds temporaries that look like RAII objects.
-///
-/// For the user-facing documentation see:
-/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-raii.html
-class UnusedRAIICheck : public ClangTidyCheck {
-public:
- UnusedRAIICheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
- void registerMatchers(ast_matchers::MatchFinder *Finder) override;
- void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
-};
-
-} // namespace misc
-} // namespace tidy
-} // namespace clang
-
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H
diff --git a/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tidy/modernize/MakeSmartPtrCheck.cpp
index fa071b8..8deaa83 100644
--- a/clang-tidy/modernize/MakeSmartPtrCheck.cpp
+++ b/clang-tidy/modernize/MakeSmartPtrCheck.cpp
@@ -61,8 +61,13 @@
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
}
+bool MakeSmartPtrCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return LangOpts.CPlusPlus11;
+}
+
void MakeSmartPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) {
- if (getLangOpts().CPlusPlus11) {
+ if (isLanguageVersionSupported(getLangOpts())) {
Inserter.reset(new utils::IncludeInserter(
Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle));
Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
@@ -70,7 +75,7 @@
}
void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
- if (!getLangOpts().CPlusPlus11)
+ if (!isLanguageVersionSupported(getLangOpts()))
return;
// Calling make_smart_ptr from within a member function of a type with a
diff --git a/clang-tidy/modernize/MakeSmartPtrCheck.h b/clang-tidy/modernize/MakeSmartPtrCheck.h
index 8282154..6622482 100644
--- a/clang-tidy/modernize/MakeSmartPtrCheck.h
+++ b/clang-tidy/modernize/MakeSmartPtrCheck.h
@@ -40,6 +40,9 @@
/// in this class.
virtual SmartPtrTypeMatcher getSmartPointerTypeMatcher() const = 0;
+ /// Returns whether the C++ version is compatible with current check.
+ virtual bool isLanguageVersionSupported(const LangOptions &LangOpts) const;
+
static const char PointerType[];
static const char ConstructorCall[];
static const char ResetCall[];
diff --git a/clang-tidy/modernize/MakeUniqueCheck.cpp b/clang-tidy/modernize/MakeUniqueCheck.cpp
index 4a9b306..3ebbb07 100644
--- a/clang-tidy/modernize/MakeUniqueCheck.cpp
+++ b/clang-tidy/modernize/MakeUniqueCheck.cpp
@@ -17,7 +17,8 @@
MakeUniqueCheck::MakeUniqueCheck(StringRef Name,
clang::tidy::ClangTidyContext *Context)
- : MakeSmartPtrCheck(Name, Context, "std::make_unique") {}
+ : MakeSmartPtrCheck(Name, Context, "std::make_unique"),
+ RequireCPlusPlus14(Options.get("MakeSmartPtrFunction", "").empty()) {}
MakeUniqueCheck::SmartPtrTypeMatcher
MakeUniqueCheck::getSmartPointerTypeMatcher() const {
@@ -36,6 +37,11 @@
equalsBoundNode(PointerType))))))))))))))));
}
+bool MakeUniqueCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return RequireCPlusPlus14 ? LangOpts.CPlusPlus14 : LangOpts.CPlusPlus11;
+}
+
} // namespace modernize
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/modernize/MakeUniqueCheck.h b/clang-tidy/modernize/MakeUniqueCheck.h
index 15fbd55..587b41e 100644
--- a/clang-tidy/modernize/MakeUniqueCheck.h
+++ b/clang-tidy/modernize/MakeUniqueCheck.h
@@ -31,6 +31,11 @@
protected:
SmartPtrTypeMatcher getSmartPointerTypeMatcher() const override;
+
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+
+private:
+ const bool RequireCPlusPlus14;
};
} // namespace modernize
diff --git a/clang-tidy/objc/AvoidNSErrorInitCheck.cpp b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
index d92414c..86c4656 100644
--- a/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
+++ b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
@@ -18,6 +18,10 @@
namespace objc {
void AvoidNSErrorInitCheck::registerMatchers(MatchFinder *Finder) {
+ // this check should only be applied to ObjC sources.
+ if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
+ return;
+ }
Finder->addMatcher(objcMessageExpr(hasSelector("init"),
hasReceiverType(asString("NSError *")))
.bind("nserrorInit"),
diff --git a/clang-tidy/objc/ForbiddenSubclassingCheck.cpp b/clang-tidy/objc/ForbiddenSubclassingCheck.cpp
index a8d79f5..e78cb99 100644
--- a/clang-tidy/objc/ForbiddenSubclassingCheck.cpp
+++ b/clang-tidy/objc/ForbiddenSubclassingCheck.cpp
@@ -77,6 +77,10 @@
}
void ForbiddenSubclassingCheck::registerMatchers(MatchFinder *Finder) {
+ // this check should only be applied to ObjC sources.
+ if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
+ return;
+ }
Finder->addMatcher(
objcInterfaceDecl(
isSubclassOf(
diff --git a/clang-tidy/objc/PropertyDeclarationCheck.cpp b/clang-tidy/objc/PropertyDeclarationCheck.cpp
index 700c5e6..7a3bbaf 100644
--- a/clang-tidy/objc/PropertyDeclarationCheck.cpp
+++ b/clang-tidy/objc/PropertyDeclarationCheck.cpp
@@ -170,6 +170,10 @@
EscapedAcronyms() {}
void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
+ // this check should only be applied to ObjC sources.
+ if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
+ return;
+ }
if (IncludeDefaultAcronyms) {
EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
SpecialAcronyms.size());
diff --git a/clang-tidy/plugin/CMakeLists.txt b/clang-tidy/plugin/CMakeLists.txt
index 995c086..1979383 100644
--- a/clang-tidy/plugin/CMakeLists.txt
+++ b/clang-tidy/plugin/CMakeLists.txt
@@ -9,6 +9,7 @@
clangSema
clangTidy
clangTidyAndroidModule
+ clangTidyAbseilModule
clangTidyBoostModule
clangTidyCERTModule
clangTidyCppCoreGuidelinesModule
@@ -19,6 +20,7 @@
clangTidyMPIModule
clangTidyObjCModule
clangTidyPerformanceModule
+ clangTidyPortabilityModule
clangTidyReadabilityModule
clangTooling
)
diff --git a/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tidy/plugin/ClangTidyPlugin.cpp
index 25c13c7..dc56772 100644
--- a/clang-tidy/plugin/ClangTidyPlugin.cpp
+++ b/clang-tidy/plugin/ClangTidyPlugin.cpp
@@ -118,6 +118,11 @@
static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination =
PerformanceModuleAnchorSource;
+// This anchor is used to force the linker to link the PortabilityModule.
+extern volatile int PortabilityModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination =
+ PortabilityModuleAnchorSource;
+
// This anchor is used to force the linker to link the ReadabilityModule.
extern volatile int ReadabilityModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination =
diff --git a/clang-tidy/portability/CMakeLists.txt b/clang-tidy/portability/CMakeLists.txt
new file mode 100644
index 0000000..0420a18
--- /dev/null
+++ b/clang-tidy/portability/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_library(clangTidyPortabilityModule
+ PortabilityTidyModule.cpp
+ SIMDIntrinsicsCheck.cpp
+
+ LINK_LIBS
+ clangAST
+ clangASTMatchers
+ clangBasic
+ clangLex
+ clangTidy
+ clangTidyUtils
+ clangTooling
+ )
diff --git a/clang-tidy/portability/PortabilityTidyModule.cpp b/clang-tidy/portability/PortabilityTidyModule.cpp
new file mode 100644
index 0000000..013cbcf
--- /dev/null
+++ b/clang-tidy/portability/PortabilityTidyModule.cpp
@@ -0,0 +1,38 @@
+//===--- PortabilityTidyModule.cpp - clang-tidy ---------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../ClangTidy.h"
+#include "../ClangTidyModule.h"
+#include "../ClangTidyModuleRegistry.h"
+#include "SIMDIntrinsicsCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace portability {
+
+class PortabilityModule : public ClangTidyModule {
+public:
+ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<SIMDIntrinsicsCheck>(
+ "portability-simd-intrinsics");
+ }
+};
+
+// Register the PortabilityModule using this statically initialized variable.
+static ClangTidyModuleRegistry::Add<PortabilityModule>
+ X("portability-module", "Adds portability-related checks.");
+
+} // namespace portability
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the PortabilityModule.
+volatile int PortabilityModuleAnchorSource = 0;
+
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/readability/SIMDIntrinsicsCheck.cpp b/clang-tidy/portability/SIMDIntrinsicsCheck.cpp
similarity index 82%
rename from clang-tidy/readability/SIMDIntrinsicsCheck.cpp
rename to clang-tidy/portability/SIMDIntrinsicsCheck.cpp
index 123e76d..d5434cc 100644
--- a/clang-tidy/readability/SIMDIntrinsicsCheck.cpp
+++ b/clang-tidy/portability/SIMDIntrinsicsCheck.cpp
@@ -18,7 +18,7 @@
namespace clang {
namespace tidy {
-namespace readability {
+namespace portability {
namespace {
@@ -84,17 +84,21 @@
SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name,
ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context), Suggest(Options.get("Suggest", 0) != 0) {}
+ : ClangTidyCheck(Name, Context), Std(Options.get("Std", "")),
+ Suggest(Options.get("Suggest", 0) != 0) {}
void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "Std", "");
Options.store(Opts, "Suggest", 0);
}
void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus11)
return;
+ // If Std is not specified, infer it from the language options.
// libcxx implementation backports it to C++11 std::experimental::simd.
- Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental";
+ if (Std.empty())
+ Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental";
Finder->addMatcher(callExpr(callee(functionDecl(allOf(
matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"),
@@ -116,20 +120,23 @@
llvm::Triple::ArchType Arch =
Result.Context->getTargetInfo().getTriple().getArch();
+ // We warn or suggest if this SIMD intrinsic function has a std::simd
+ // replacement.
switch (Arch) {
- default:
- break;
- case llvm::Triple::ppc:
- case llvm::Triple::ppc64:
- case llvm::Triple::ppc64le:
- New = TrySuggestPPC(Old);
- break;
- case llvm::Triple::x86:
- case llvm::Triple::x86_64:
- New = TrySuggestX86(Old);
- break;
+ default:
+ break;
+ case llvm::Triple::ppc:
+ case llvm::Triple::ppc64:
+ case llvm::Triple::ppc64le:
+ New = TrySuggestPPC(Old);
+ break;
+ case llvm::Triple::x86:
+ case llvm::Triple::x86_64:
+ New = TrySuggestX86(Old);
+ break;
}
+ // We have found a std::simd replacement.
if (!New.empty()) {
std::string Message;
// If Suggest is true, give a P0214 alternative, otherwise point it out it
@@ -137,7 +144,8 @@
if (Suggest) {
Message = (Twine("'") + Old + "' can be replaced by " + New).str();
Message = llvm::Regex("\\$std").sub(Std, Message);
- Message = llvm::Regex("\\$simd").sub(Std.str() + "::simd", Message);
+ Message =
+ llvm::Regex("\\$simd").sub((Std.str() + "::simd").str(), Message);
} else {
Message = (Twine("'") + Old + "' is a non-portable " +
llvm::Triple::getArchTypeName(Arch) + " intrinsic function")
@@ -147,6 +155,6 @@
}
}
-} // namespace readability
+} // namespace portability
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/readability/SIMDIntrinsicsCheck.h b/clang-tidy/portability/SIMDIntrinsicsCheck.h
similarity index 86%
rename from clang-tidy/readability/SIMDIntrinsicsCheck.h
rename to clang-tidy/portability/SIMDIntrinsicsCheck.h
index 8c36cd7..ebcc855 100644
--- a/clang-tidy/readability/SIMDIntrinsicsCheck.h
+++ b/clang-tidy/portability/SIMDIntrinsicsCheck.h
@@ -12,14 +12,16 @@
#include "../ClangTidy.h"
+#include "llvm/ADT/SmallString.h"
+
namespace clang {
namespace tidy {
-namespace readability {
+namespace portability {
/// Find SIMD intrinsics calls and suggest std::experimental::simd alternatives.
///
/// For the user-facing documentation see:
-/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html
+/// http://clang.llvm.org/extra/clang-tidy/checks/portability-simd-intrinsics.html
class SIMDIntrinsicsCheck : public ClangTidyCheck {
public:
SIMDIntrinsicsCheck(StringRef Name, ClangTidyContext *Context);
@@ -29,11 +31,11 @@
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
+ llvm::SmallString<32> Std;
const bool Suggest;
- StringRef Std;
};
-} // namespace readability
+} // namespace portability
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/readability/CMakeLists.txt b/clang-tidy/readability/CMakeLists.txt
index d750626..95769ee 100644
--- a/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tidy/readability/CMakeLists.txt
@@ -24,7 +24,6 @@
RedundantStringCStrCheck.cpp
RedundantSmartptrGetCheck.cpp
RedundantStringInitCheck.cpp
- SIMDIntrinsicsCheck.cpp
SimplifyBooleanExprCheck.cpp
StaticAccessedThroughInstanceCheck.cpp
StaticDefinitionInAnonymousNamespaceCheck.cpp
diff --git a/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tidy/readability/ReadabilityTidyModule.cpp
index 619a448..f39e4a1 100644
--- a/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -31,7 +31,6 @@
#include "RedundantSmartptrGetCheck.h"
#include "RedundantStringCStrCheck.h"
#include "RedundantStringInitCheck.h"
-#include "SIMDIntrinsicsCheck.h"
#include "SimplifyBooleanExprCheck.h"
#include "StaticAccessedThroughInstanceCheck.h"
#include "StaticDefinitionInAnonymousNamespaceCheck.h"
@@ -93,8 +92,6 @@
"readability-redundant-string-cstr");
CheckFactories.registerCheck<RedundantStringInitCheck>(
"readability-redundant-string-init");
- CheckFactories.registerCheck<SIMDIntrinsicsCheck>(
- "readability-simd-intrinsics");
CheckFactories.registerCheck<SimplifyBooleanExprCheck>(
"readability-simplify-boolean-expr");
CheckFactories.registerCheck<UniqueptrDeleteReleaseCheck>(
diff --git a/clang-tidy/rename_check.py b/clang-tidy/rename_check.py
index 49798f7..53a5ff9 100755
--- a/clang-tidy/rename_check.py
+++ b/clang-tidy/rename_check.py
@@ -171,8 +171,8 @@
elif header_found:
if not line.startswith('----'):
f.write("""
-- The '%s' check was renamed to `%s
- <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_
+- The '%s' check was renamed to :doc:`%s
+ <clang-tidy/checks/%s>`
""" % (old_check_name, new_check_name, new_check_name))
note_added = True
diff --git a/clang-tidy/tool/CMakeLists.txt b/clang-tidy/tool/CMakeLists.txt
index 07f2671..a3ec4ae 100644
--- a/clang-tidy/tool/CMakeLists.txt
+++ b/clang-tidy/tool/CMakeLists.txt
@@ -18,6 +18,7 @@
clangBasic
clangTidy
clangTidyAndroidModule
+ clangTidyAbseilModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
@@ -31,7 +32,9 @@
clangTidyMPIModule
clangTidyObjCModule
clangTidyPerformanceModule
+ clangTidyPortabilityModule
clangTidyReadabilityModule
+ clangTidyZirconModule
clangTooling
clangToolingCore
)
diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp
index e362b32..8f0c420 100644
--- a/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tidy/tool/ClangTidyMain.cpp
@@ -424,13 +424,13 @@
if (EnabledChecks.empty()) {
llvm::errs() << "Error: no checks enabled.\n";
llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
- return 0;
+ return 1;
}
if (PathList.empty()) {
llvm::errs() << "Error: no input files specified.\n";
llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
- return 0;
+ return 1;
}
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS(
VfsOverlay.empty() ? vfs::getRealFileSystem()
@@ -499,6 +499,11 @@
static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination =
CERTModuleAnchorSource;
+// This anchor is used to force the linker to link the AbseilModule.
+extern volatile int AbseilModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination =
+ AbseilModuleAnchorSource;
+
// This anchor is used to force the linker to link the BoostModule.
extern volatile int BoostModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination =
@@ -519,7 +524,7 @@
static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
CppCoreGuidelinesModuleAnchorSource;
-// This anchor is used to force the linker to link the GoogleModule.
+// This anchor is used to force the linker to link the FuchsiaModule.
extern volatile int FuchsiaModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination =
FuchsiaModuleAnchorSource;
@@ -554,6 +559,11 @@
static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination =
PerformanceModuleAnchorSource;
+// This anchor is used to force the linker to link the PortabilityModule.
+extern volatile int PortabilityModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination =
+ PortabilityModuleAnchorSource;
+
// This anchor is used to force the linker to link the ReadabilityModule.
extern volatile int ReadabilityModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination =
@@ -569,6 +579,11 @@
static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination =
HICPPModuleAnchorSource;
+// This anchor is used to force the linker to link the ZirconModule.
+extern volatile int ZirconModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination =
+ ZirconModuleAnchorSource;
+
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/tool/run-clang-tidy.py b/clang-tidy/tool/run-clang-tidy.py
index 379ad71..ce46c0e 100755
--- a/clang-tidy/tool/run-clang-tidy.py
+++ b/clang-tidy/tool/run-clang-tidy.py
@@ -75,7 +75,8 @@
def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
- header_filter, extra_arg, extra_arg_before, quiet):
+ header_filter, extra_arg, extra_arg_before, quiet,
+ config):
"""Gets a command line for clang-tidy."""
start = [clang_tidy_binary]
if header_filter is not None:
@@ -99,6 +100,8 @@
start.append('-p=' + build_path)
if quiet:
start.append('-quiet')
+ if config:
+ start.append('-config=' + config)
start.append(f)
return start
@@ -150,16 +153,18 @@
subprocess.call(invocation)
-def run_tidy(args, tmpdir, build_path, queue):
+def run_tidy(args, tmpdir, build_path, queue, failed_files):
"""Takes filenames out of queue and runs clang-tidy on them."""
while True:
name = queue.get()
invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks,
tmpdir, build_path, args.header_filter,
args.extra_arg, args.extra_arg_before,
- args.quiet)
+ args.quiet, args.config)
sys.stdout.write(' '.join(invocation) + '\n')
- subprocess.call(invocation)
+ return_code = subprocess.call(invocation)
+ if return_code != 0:
+ failed_files.append(name)
queue.task_done()
@@ -177,6 +182,14 @@
parser.add_argument('-checks', default=None,
help='checks filter, when not specified, use clang-tidy '
'default')
+ parser.add_argument('-config', default=None,
+ help='Specifies a configuration in YAML/JSON format: '
+ ' -config="{Checks: \'*\', '
+ ' CheckOptions: [{key: x, '
+ ' value: y}]}" '
+ 'When the value is empty, clang-tidy will '
+ 'attempt to find a file named .clang-tidy for '
+ 'each source file in its parent directories.')
parser.add_argument('-header-filter', default=None,
help='regular expression matching the names of the '
'headers to output diagnostics from. Diagnostics from '
@@ -244,12 +257,15 @@
# Build up a big regexy filter from all command line arguments.
file_name_re = re.compile('|'.join(args.files))
+ return_code = 0
try:
# Spin up a bunch of tidy-launching threads.
task_queue = queue.Queue(max_task)
+ # List of files with a non-zero return code.
+ failed_files = []
for _ in range(max_task):
t = threading.Thread(target=run_tidy,
- args=(args, tmpdir, build_path, task_queue))
+ args=(args, tmpdir, build_path, task_queue, failed_files))
t.daemon = True
t.start()
@@ -260,6 +276,8 @@
# Wait for all threads to be done.
task_queue.join()
+ if len(failed_files):
+ return_code = 1
except KeyboardInterrupt:
# This is a sad hack. Unfortunately subprocess goes
@@ -269,7 +287,6 @@
shutil.rmtree(tmpdir)
os.kill(0, 9)
- return_code = 0
if args.export_fixes:
print('Writing fixes to ' + args.export_fixes + ' ...')
try:
diff --git a/clang-tidy/zircon/CMakeLists.txt b/clang-tidy/zircon/CMakeLists.txt
new file mode 100644
index 0000000..7aa7cd3
--- /dev/null
+++ b/clang-tidy/zircon/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_library(clangTidyZirconModule
+ TemporaryObjectsCheck.cpp
+ ZirconTidyModule.cpp
+
+ LINK_LIBS
+ clangAST
+ clangASTMatchers
+ clangBasic
+ clangLex
+ clangTidy
+ clangTidyUtils
+ )
diff --git a/clang-tidy/zircon/TemporaryObjectsCheck.cpp b/clang-tidy/zircon/TemporaryObjectsCheck.cpp
new file mode 100644
index 0000000..5fff67b
--- /dev/null
+++ b/clang-tidy/zircon/TemporaryObjectsCheck.cpp
@@ -0,0 +1,60 @@
+//===--- TemporaryObjectsCheck.cpp - clang-tidy----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "TemporaryObjectsCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace zircon {
+
+AST_MATCHER_P(CXXRecordDecl, matchesAnyName, ArrayRef<std::string>, Names) {
+ std::string QualifiedName = Node.getQualifiedNameAsString();
+ return llvm::any_of(Names,
+ [&](StringRef Name) { return QualifiedName == Name; });
+}
+
+void TemporaryObjectsCheck::registerMatchers(MatchFinder *Finder) {
+ // Matcher for default constructors.
+ Finder->addMatcher(
+ cxxTemporaryObjectExpr(hasDeclaration(cxxConstructorDecl(hasParent(
+ cxxRecordDecl(matchesAnyName(Names))))))
+ .bind("temps"),
+ this);
+
+ // Matcher for user-defined constructors.
+ Finder->addMatcher(
+ cxxConstructExpr(allOf(hasParent(cxxFunctionalCastExpr()),
+ hasDeclaration(cxxConstructorDecl(hasParent(
+ cxxRecordDecl(matchesAnyName(Names)))))))
+ .bind("temps"),
+ this);
+}
+
+void TemporaryObjectsCheck::check(const MatchFinder::MatchResult &Result) {
+ if (const auto *D = Result.Nodes.getNodeAs<CXXConstructExpr>("temps"))
+ diag(D->getLocation(),
+ "creating a temporary object of type %q0 is prohibited")
+ << D->getConstructor()->getParent();
+}
+
+void TemporaryObjectsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "Names", utils::options::serializeStringList(Names));
+}
+
+} // namespace zircon
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/zircon/TemporaryObjectsCheck.h b/clang-tidy/zircon/TemporaryObjectsCheck.h
new file mode 100644
index 0000000..302ef72
--- /dev/null
+++ b/clang-tidy/zircon/TemporaryObjectsCheck.h
@@ -0,0 +1,42 @@
+//===--- TemporaryObjectsCheck.h - clang-tidy------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ZIRCON_TEMPORARYOBJECTSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ZIRCON_TEMPORARYOBJECTSCHECK_H
+
+#include "../ClangTidy.h"
+#include "../utils/OptionsUtils.h"
+
+namespace clang {
+namespace tidy {
+namespace zircon {
+
+/// Construction of specific temporary objects in the Zircon kernel is
+/// discouraged.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/zircon-temporary-objects.html
+class TemporaryObjectsCheck : public ClangTidyCheck {
+public:
+ TemporaryObjectsCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ Names(utils::options::parseStringList(Options.get("Names", ""))) {}
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ std::vector<std::string> Names;
+};
+
+} // namespace zircon
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ZIRCON_TEMPORARYOBJECTSCHECK_H
diff --git a/clang-tidy/zircon/ZirconTidyModule.cpp b/clang-tidy/zircon/ZirconTidyModule.cpp
new file mode 100644
index 0000000..3e53c23
--- /dev/null
+++ b/clang-tidy/zircon/ZirconTidyModule.cpp
@@ -0,0 +1,40 @@
+//===--- ZirconTidyModule.cpp - clang-tidy---------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../ClangTidy.h"
+#include "../ClangTidyModule.h"
+#include "../ClangTidyModuleRegistry.h"
+#include "TemporaryObjectsCheck.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace zircon {
+
+/// This module is for Zircon-specific checks.
+class ZirconModule : public ClangTidyModule {
+public:
+ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<TemporaryObjectsCheck>(
+ "zircon-temporary-objects");
+ }
+};
+
+// Register the ZirconTidyModule using this statically initialized variable.
+static ClangTidyModuleRegistry::Add<ZirconModule>
+ X("zircon-module", "Adds Zircon kernel checks.");
+} // namespace zircon
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the ZirconModule.
+volatile int ZirconModuleAnchorSource = 0;
+
+} // namespace tidy
+} // namespace clang
diff --git a/clangd/AST.cpp b/clangd/AST.cpp
new file mode 100644
index 0000000..5d549c3
--- /dev/null
+++ b/clangd/AST.cpp
@@ -0,0 +1,42 @@
+//===--- AST.cpp - Utility AST functions -----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "AST.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/Basic/SourceManager.h"
+
+namespace clang {
+namespace clangd {
+using namespace llvm;
+
+SourceLocation findNameLoc(const clang::Decl* D) {
+ const auto& SM = D->getASTContext().getSourceManager();
+ // FIXME: Revisit the strategy, the heuristic is limitted when handling
+ // macros, we should use the location where the whole definition occurs.
+ SourceLocation SpellingLoc = SM.getSpellingLoc(D->getLocation());
+ if (D->getLocation().isMacroID()) {
+ std::string PrintLoc = SpellingLoc.printToString(SM);
+ if (llvm::StringRef(PrintLoc).startswith("<scratch") ||
+ llvm::StringRef(PrintLoc).startswith("<command line>")) {
+ // We use the expansion location for the following symbols, as spelling
+ // locations of these symbols are not interesting to us:
+ // * symbols formed via macro concatenation, the spelling location will
+ // be "<scratch space>"
+ // * symbols controlled and defined by a compile command-line option
+ // `-DName=foo`, the spelling location will be "<command line>".
+ SpellingLoc = SM.getExpansionRange(D->getLocation()).first;
+ }
+ }
+ return SpellingLoc;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/AST.h b/clangd/AST.h
new file mode 100644
index 0000000..b8cf074
--- /dev/null
+++ b/clangd/AST.h
@@ -0,0 +1,34 @@
+//===--- AST.h - Utility AST functions -------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Various code that examines C++ source code using AST.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_
+
+#include "clang/Basic/SourceLocation.h"
+
+namespace clang {
+class SourceManager;
+class Decl;
+
+namespace clangd {
+
+/// Find the identifier source location of the given D.
+///
+/// The returned location is usually the spelling location where the name of the
+/// decl occurs in the code.
+SourceLocation findNameLoc(const clang::Decl* D);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_
diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt
index d12a6b3..6da1766 100644
--- a/clangd/CMakeLists.txt
+++ b/clangd/CMakeLists.txt
@@ -3,6 +3,7 @@
)
add_clang_library(clangDaemon
+ AST.cpp
ClangdLSPServer.cpp
ClangdServer.cpp
ClangdUnit.cpp
@@ -11,6 +12,7 @@
CompileArgsCache.cpp
Compiler.cpp
Context.cpp
+ Diagnostics.cpp
DraftStore.cpp
FuzzyMatch.cpp
GlobalCompilationDatabase.cpp
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index 539ee17..61dbd66 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -8,9 +8,11 @@
//===---------------------------------------------------------------------===//
#include "ClangdLSPServer.h"
+#include "Diagnostics.h"
#include "JSONRPCDispatcher.h"
#include "SourceCode.h"
#include "URI.h"
+#include "llvm/Support/Errc.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
@@ -98,7 +100,7 @@
reply(json::obj{
{{"capabilities",
json::obj{
- {"textDocumentSync", 1},
+ {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
{"documentFormattingProvider", true},
{"documentRangeFormattingProvider", true},
{"documentOnTypeFormattingProvider",
@@ -141,21 +143,34 @@
if (Params.metadata && !Params.metadata->extraFlags.empty())
CDB.setExtraFlagsForFile(Params.textDocument.uri.file(),
std::move(Params.metadata->extraFlags));
- Server.addDocument(Params.textDocument.uri.file(), Params.textDocument.text,
- WantDiagnostics::Yes);
+
+ PathRef File = Params.textDocument.uri.file();
+ std::string &Contents = Params.textDocument.text;
+
+ DraftMgr.addDraft(File, Contents);
+ Server.addDocument(File, Contents, WantDiagnostics::Yes);
}
void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
- if (Params.contentChanges.size() != 1)
- return replyError(ErrorCode::InvalidParams,
- "can only apply one change at a time");
auto WantDiags = WantDiagnostics::Auto;
if (Params.wantDiagnostics.hasValue())
WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
: WantDiagnostics::No;
- // We only support full syncing right now.
- Server.addDocument(Params.textDocument.uri.file(),
- Params.contentChanges[0].text, WantDiags);
+
+ PathRef File = Params.textDocument.uri.file();
+ llvm::Expected<std::string> Contents =
+ DraftMgr.updateDraft(File, Params.contentChanges);
+ if (!Contents) {
+ // If this fails, we are most likely going to be not in sync anymore with
+ // the client. It is better to remove the draft and let further operations
+ // fail rather than giving wrong results.
+ DraftMgr.removeDraft(File);
+ Server.removeDocument(File);
+ log(llvm::toString(Contents.takeError()));
+ return;
+ }
+
+ Server.addDocument(File, *Contents, WantDiags);
}
void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
@@ -187,7 +202,7 @@
} else if (Params.command ==
ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) {
auto &FileURI = Params.includeInsertion->textDocument.uri;
- auto Code = Server.getDocument(FileURI.file());
+ auto Code = DraftMgr.getDraft(FileURI.file());
if (!Code)
return replyError(ErrorCode::InvalidParams,
("command " +
@@ -232,7 +247,7 @@
void ClangdLSPServer::onRename(RenameParams &Params) {
Path File = Params.textDocument.uri.file();
- llvm::Optional<std::string> Code = Server.getDocument(File);
+ llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
if (!Code)
return replyError(ErrorCode::InvalidParams,
"onRename called for non-added file");
@@ -253,13 +268,15 @@
}
void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
- Server.removeDocument(Params.textDocument.uri.file());
+ PathRef File = Params.textDocument.uri.file();
+ DraftMgr.removeDraft(File);
+ Server.removeDocument(File);
}
void ClangdLSPServer::onDocumentOnTypeFormatting(
DocumentOnTypeFormattingParams &Params) {
auto File = Params.textDocument.uri.file();
- auto Code = Server.getDocument(File);
+ auto Code = DraftMgr.getDraft(File);
if (!Code)
return replyError(ErrorCode::InvalidParams,
"onDocumentOnTypeFormatting called for non-added file");
@@ -275,7 +292,7 @@
void ClangdLSPServer::onDocumentRangeFormatting(
DocumentRangeFormattingParams &Params) {
auto File = Params.textDocument.uri.file();
- auto Code = Server.getDocument(File);
+ auto Code = DraftMgr.getDraft(File);
if (!Code)
return replyError(ErrorCode::InvalidParams,
"onDocumentRangeFormatting called for non-added file");
@@ -290,7 +307,7 @@
void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
auto File = Params.textDocument.uri.file();
- auto Code = Server.getDocument(File);
+ auto Code = DraftMgr.getDraft(File);
if (!Code)
return replyError(ErrorCode::InvalidParams,
"onDocumentFormatting called for non-added file");
@@ -306,19 +323,19 @@
void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
- auto Code = Server.getDocument(Params.textDocument.uri.file());
+ auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
if (!Code)
return replyError(ErrorCode::InvalidParams,
"onCodeAction called for non-added file");
json::ary Commands;
for (Diagnostic &D : Params.context.diagnostics) {
- auto Edits = getFixIts(Params.textDocument.uri.file(), D);
- if (!Edits.empty()) {
+ for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
WorkspaceEdit WE;
+ std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end());
WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
Commands.push_back(json::obj{
- {"title", llvm::formatv("Apply FixIt {0}", D.message)},
+ {"title", llvm::formatv("Apply fix: {0}", F.Message)},
{"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
{"arguments", {WE}},
});
@@ -329,28 +346,33 @@
void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
- [](Tagged<CompletionList> List) { reply(List.Value); });
+ [](llvm::Expected<CompletionList> List) {
+ if (!List)
+ return replyError(ErrorCode::InvalidParams,
+ llvm::toString(List.takeError()));
+ reply(*List);
+ });
}
void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
- [](llvm::Expected<Tagged<SignatureHelp>> SignatureHelp) {
+ [](llvm::Expected<SignatureHelp> SignatureHelp) {
if (!SignatureHelp)
return replyError(
ErrorCode::InvalidParams,
llvm::toString(SignatureHelp.takeError()));
- reply(SignatureHelp->Value);
+ reply(*SignatureHelp);
});
}
void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Server.findDefinitions(
Params.textDocument.uri.file(), Params.position,
- [](llvm::Expected<Tagged<std::vector<Location>>> Items) {
+ [](llvm::Expected<std::vector<Location>> Items) {
if (!Items)
return replyError(ErrorCode::InvalidParams,
llvm::toString(Items.takeError()));
- reply(json::ary(Items->Value));
+ reply(json::ary(*Items));
});
}
@@ -362,24 +384,24 @@
void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Server.findDocumentHighlights(
Params.textDocument.uri.file(), Params.position,
- [](llvm::Expected<Tagged<std::vector<DocumentHighlight>>> Highlights) {
+ [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
if (!Highlights)
return replyError(ErrorCode::InternalError,
llvm::toString(Highlights.takeError()));
- reply(json::ary(Highlights->Value));
+ reply(json::ary(*Highlights));
});
}
void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
Server.findHover(Params.textDocument.uri.file(), Params.position,
- [](llvm::Expected<Tagged<Hover>> H) {
+ [](llvm::Expected<Hover> H) {
if (!H) {
replyError(ErrorCode::InternalError,
llvm::toString(H.takeError()));
return;
}
- reply(H->Value);
+ reply(*H);
});
}
@@ -391,7 +413,7 @@
// Compilation database change.
if (Settings.compilationDatabasePath.hasValue()) {
CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
- Server.reparseOpenedFiles();
+ reparseOpenedFiles();
}
}
@@ -409,7 +431,7 @@
JSONRPCDispatcher Dispatcher([](const json::Expr &Params) {
replyError(ErrorCode::MethodNotFound, "method not found");
});
- registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
+ registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
// Run the Language Server loop.
runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
@@ -421,8 +443,8 @@
return ShutdownRequestReceived;
}
-std::vector<TextEdit> ClangdLSPServer::getFixIts(StringRef File,
- const clangd::Diagnostic &D) {
+std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
+ const clangd::Diagnostic &D) {
std::lock_guard<std::mutex> Lock(FixItsMutex);
auto DiagToFixItsIter = FixItsMap.find(File);
if (DiagToFixItsIter == FixItsMap.end())
@@ -436,22 +458,23 @@
return FixItsIter->second;
}
-void ClangdLSPServer::onDiagnosticsReady(
- PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
+void ClangdLSPServer::onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) {
json::ary DiagnosticsJSON;
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
- for (auto &DiagWithFixes : Diagnostics.Value) {
- auto Diag = DiagWithFixes.Diag;
- DiagnosticsJSON.push_back(json::obj{
- {"range", Diag.range},
- {"severity", Diag.severity},
- {"message", Diag.message},
+ for (auto &Diag : Diagnostics) {
+ toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
+ DiagnosticsJSON.push_back(json::obj{
+ {"range", Diag.range},
+ {"severity", Diag.severity},
+ {"message", Diag.message},
+ });
+
+ auto &FixItsForDiagnostic = LocalFixIts[Diag];
+ std::copy(Fixes.begin(), Fixes.end(),
+ std::back_inserter(FixItsForDiagnostic));
});
- // We convert to Replacements to become independent of the SourceManager.
- auto &FixItsForDiagnostic = LocalFixIts[Diag];
- std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
- std::back_inserter(FixItsForDiagnostic));
}
// Cache FixIts
@@ -472,3 +495,10 @@
}},
});
}
+
+void ClangdLSPServer::reparseOpenedFiles() {
+ for (const Path &FilePath : DraftMgr.getActiveFiles())
+ Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath),
+ WantDiagnostics::Auto,
+ /*SkipCache=*/true);
+}
diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h
index 30e1746..82cf16f 100644
--- a/clangd/ClangdLSPServer.h
+++ b/clangd/ClangdLSPServer.h
@@ -11,6 +11,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H
#include "ClangdServer.h"
+#include "DraftStore.h"
#include "GlobalCompilationDatabase.h"
#include "Path.h"
#include "Protocol.h"
@@ -46,9 +47,7 @@
private:
// Implement DiagnosticsConsumer.
- virtual void
- onDiagnosticsReady(PathRef File,
- Tagged<std::vector<DiagWithFixIts>> Diagnostics) override;
+ void onDiagnosticsReady(PathRef File, std::vector<Diag> Diagnostics) override;
// Implement ProtocolCallbacks.
void onInitialize(InitializeParams &Params) override;
@@ -74,7 +73,12 @@
void onHover(TextDocumentPositionParams &Params) override;
void onChangeConfiguration(DidChangeConfigurationParams &Params) override;
- std::vector<TextEdit> getFixIts(StringRef File, const clangd::Diagnostic &D);
+ std::vector<Fix> getFixes(StringRef File, const clangd::Diagnostic &D);
+
+ /// Forces a reparse of all currently opened files. As a result, this method
+ /// may be very expensive. This method is normally called when the
+ /// compilation database is changed.
+ void reparseOpenedFiles();
JSONOutput &Out;
/// Used to indicate that the 'shutdown' request was received from the
@@ -87,8 +91,7 @@
bool IsDone = false;
std::mutex FixItsMutex;
- typedef std::map<clangd::Diagnostic, std::vector<TextEdit>,
- LSPDiagnosticCompare>
+ typedef std::map<clangd::Diagnostic, std::vector<Fix>, LSPDiagnosticCompare>
DiagnosticToReplacementMap;
/// Caches FixIts per file and diagnostics
llvm::StringMap<DiagnosticToReplacementMap> FixItsMap;
@@ -99,6 +102,10 @@
RealFileSystemProvider FSProvider;
/// Options used for code completion
clangd::CodeCompleteOptions CCOpts;
+
+ // Store of the current versions of the open documents.
+ DraftStore DraftMgr;
+
// Server must be the last member of the class to allow its destructor to exit
// the worker thread that may otherwise run an async callback on partially
// destructed instance of ClangdLSPServer.
diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp
index 53e3539..dbb3ce4 100644
--- a/clangd/ClangdServer.cpp
+++ b/clangd/ClangdServer.cpp
@@ -65,9 +65,8 @@
} // namespace
-Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
-RealFileSystemProvider::getTaggedFileSystem(PathRef File) {
- return make_tagged(vfs::getRealFileSystem(), VFSTag());
+IntrusiveRefCntPtr<vfs::FileSystem> RealFileSystemProvider::getFileSystem() {
+ return vfs::getRealFileSystem();
}
ClangdServer::Options ClangdServer::optsForTest() {
@@ -117,115 +116,87 @@
}
void ClangdServer::addDocument(PathRef File, StringRef Contents,
- WantDiagnostics WantDiags) {
- DocVersion Version = DraftMgr.updateDraft(File, Contents);
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()},
- WantDiags, std::move(TaggedFS));
+ WantDiagnostics WantDiags, bool SkipCache) {
+ if (SkipCache)
+ CompileArgs.invalidate(File);
+
+ DocVersion Version = ++InternalVersion[File];
+ ParseInputs Inputs = {CompileArgs.getCompileCommand(File),
+ FSProvider.getFileSystem(), Contents.str()};
+
+ Path FileStr = File.str();
+ WorkScheduler.update(File, std::move(Inputs), WantDiags,
+ [this, FileStr, Version](std::vector<Diag> Diags) {
+ consumeDiagnostics(FileStr, Version, std::move(Diags));
+ });
}
void ClangdServer::removeDocument(PathRef File) {
- DraftMgr.removeDraft(File);
+ ++InternalVersion[File];
CompileArgs.invalidate(File);
WorkScheduler.remove(File);
}
-void ClangdServer::forceReparse(PathRef File) {
- auto FileContents = DraftMgr.getDraft(File);
- assert(FileContents.Draft &&
- "forceReparse() was called for non-added document");
-
- // forceReparse promises to request new compilation flags from CDB, so we
- // remove any cahced flags.
- CompileArgs.invalidate(File);
-
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- scheduleReparseAndDiags(File, std::move(FileContents), WantDiagnostics::Yes,
- std::move(TaggedFS));
-}
-
-void ClangdServer::codeComplete(
- PathRef File, Position Pos, const clangd::CodeCompleteOptions &Opts,
- UniqueFunction<void(Tagged<CompletionList>)> Callback,
- IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
- using CallbackType = UniqueFunction<void(Tagged<CompletionList>)>;
-
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- if (UsedFS)
- *UsedFS = TaggedFS.Value;
-
+void ClangdServer::codeComplete(PathRef File, Position Pos,
+ const clangd::CodeCompleteOptions &Opts,
+ Callback<CompletionList> CB) {
// Copy completion options for passing them to async task handler.
auto CodeCompleteOpts = Opts;
if (!CodeCompleteOpts.Index) // Respect overridden index.
CodeCompleteOpts.Index = Index;
- VersionedDraft Latest = DraftMgr.getDraft(File);
- // FIXME(sammccall): return error for consistency?
- assert(Latest.Draft && "codeComplete called for non-added document");
-
// Copy PCHs to avoid accessing this->PCHs concurrently
std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs;
- auto Task = [PCHs, Pos, TaggedFS, CodeCompleteOpts](
- std::string Contents, Path File, CallbackType Callback,
- llvm::Expected<InputsAndPreamble> IP) {
- assert(IP && "error when trying to read preamble for codeComplete");
+ auto FS = FSProvider.getFileSystem();
+ auto Task = [PCHs, Pos, FS,
+ CodeCompleteOpts](Path File, Callback<CompletionList> CB,
+ llvm::Expected<InputsAndPreamble> IP) {
+ if (!IP)
+ return CB(IP.takeError());
+
auto PreambleData = IP->Preamble;
- auto &Command = IP->Inputs.CompileCommand;
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CompletionList Result = clangd::codeComplete(
- File, Command, PreambleData ? &PreambleData->Preamble : nullptr,
- Contents, Pos, TaggedFS.Value, PCHs, CodeCompleteOpts);
-
- Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag)));
+ File, IP->Command, PreambleData ? &PreambleData->Preamble : nullptr,
+ IP->Contents, Pos, FS, PCHs, CodeCompleteOpts);
+ CB(std::move(Result));
};
- WorkScheduler.runWithPreamble(
- "CodeComplete", File,
- Bind(Task, std::move(*Latest.Draft), File.str(), std::move(Callback)));
+ WorkScheduler.runWithPreamble("CodeComplete", File,
+ Bind(Task, File.str(), std::move(CB)));
}
-void ClangdServer::signatureHelp(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<SignatureHelp>>)> Callback,
- IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- if (UsedFS)
- *UsedFS = TaggedFS.Value;
-
- VersionedDraft Latest = DraftMgr.getDraft(File);
- if (!Latest.Draft)
- return Callback(llvm::make_error<llvm::StringError>(
- "signatureHelp is called for non-added document",
- llvm::errc::invalid_argument));
+void ClangdServer::signatureHelp(PathRef File, Position Pos,
+ Callback<SignatureHelp> CB) {
auto PCHs = this->PCHs;
- auto Action = [Pos, TaggedFS, PCHs](std::string Contents, Path File,
- decltype(Callback) Callback,
- llvm::Expected<InputsAndPreamble> IP) {
+ auto FS = FSProvider.getFileSystem();
+ auto Action = [Pos, FS, PCHs](Path File, Callback<SignatureHelp> CB,
+ llvm::Expected<InputsAndPreamble> IP) {
if (!IP)
- return Callback(IP.takeError());
+ return CB(IP.takeError());
auto PreambleData = IP->Preamble;
- auto &Command = IP->Inputs.CompileCommand;
- Callback(make_tagged(
- clangd::signatureHelp(File, Command,
- PreambleData ? &PreambleData->Preamble : nullptr,
- Contents, Pos, TaggedFS.Value, PCHs),
- TaggedFS.Tag));
+ CB(clangd::signatureHelp(File, IP->Command,
+ PreambleData ? &PreambleData->Preamble : nullptr,
+ IP->Contents, Pos, FS, PCHs));
};
- WorkScheduler.runWithPreamble(
- "SignatureHelp", File,
- Bind(Action, std::move(*Latest.Draft), File.str(), std::move(Callback)));
+ WorkScheduler.runWithPreamble("SignatureHelp", File,
+ Bind(Action, File.str(), std::move(CB)));
}
llvm::Expected<tooling::Replacements>
ClangdServer::formatRange(StringRef Code, PathRef File, Range Rng) {
- size_t Begin = positionToOffset(Code, Rng.start);
- size_t Len = positionToOffset(Code, Rng.end) - Begin;
- return formatCode(Code, File, {tooling::Range(Begin, Len)});
+ llvm::Expected<size_t> Begin = positionToOffset(Code, Rng.start);
+ if (!Begin)
+ return Begin.takeError();
+ llvm::Expected<size_t> End = positionToOffset(Code, Rng.end);
+ if (!End)
+ return End.takeError();
+ return formatCode(Code, File, {tooling::Range(*Begin, *End - *Begin)});
}
llvm::Expected<tooling::Replacements> ClangdServer::formatFile(StringRef Code,
@@ -238,24 +209,24 @@
ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) {
// Look for the previous opening brace from the character position and
// format starting from there.
- size_t CursorPos = positionToOffset(Code, Pos);
- size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
+ llvm::Expected<size_t> CursorPos = positionToOffset(Code, Pos);
+ if (!CursorPos)
+ return CursorPos.takeError();
+ size_t PreviousLBracePos = StringRef(Code).find_last_of('{', *CursorPos);
if (PreviousLBracePos == StringRef::npos)
- PreviousLBracePos = CursorPos;
- size_t Len = CursorPos - PreviousLBracePos;
+ PreviousLBracePos = *CursorPos;
+ size_t Len = *CursorPos - PreviousLBracePos;
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
}
-void ClangdServer::rename(
- PathRef File, Position Pos, llvm::StringRef NewName,
- UniqueFunction<void(Expected<std::vector<tooling::Replacement>>)>
- Callback) {
+void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
+ Callback<std::vector<tooling::Replacement>> CB) {
auto Action = [Pos](Path File, std::string NewName,
- decltype(Callback) Callback,
+ Callback<std::vector<tooling::Replacement>> CB,
Expected<InputsAndAST> InpAST) {
if (!InpAST)
- return Callback(InpAST.takeError());
+ return CB(InpAST.takeError());
auto &AST = InpAST->AST;
RefactoringResultCollector ResultCollector;
@@ -263,7 +234,7 @@
const FileEntry *FE =
SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
if (!FE)
- return Callback(llvm::make_error<llvm::StringError>(
+ return CB(llvm::make_error<llvm::StringError>(
"rename called for non-added document",
llvm::errc::invalid_argument));
SourceLocation SourceLocationBeg =
@@ -274,13 +245,13 @@
auto Rename = clang::tooling::RenameOccurrences::initiate(
Context, SourceRange(SourceLocationBeg), NewName);
if (!Rename)
- return Callback(Rename.takeError());
+ return CB(Rename.takeError());
Rename->invoke(ResultCollector, Context);
assert(ResultCollector.Result.hasValue());
if (!ResultCollector.Result.getValue())
- return Callback(ResultCollector.Result->takeError());
+ return CB(ResultCollector.Result->takeError());
std::vector<tooling::Replacement> Replacements;
for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
@@ -299,12 +270,11 @@
Replacements.push_back(Rep);
}
}
- return Callback(Replacements);
+ return CB(std::move(Replacements));
};
WorkScheduler.runWithAST(
- "Rename", File,
- Bind(Action, File.str(), NewName.str(), std::move(Callback)));
+ "Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB)));
}
/// Creates a `HeaderFile` from \p Header which can be either a URI or a literal
@@ -335,9 +305,9 @@
if (!ResolvedPreferred)
return ResolvedPreferred.takeError();
tooling::CompileCommand CompileCommand = CompileArgs.getCompileCommand(File);
- auto Include = calculateIncludePath(
- File, Code, *ResolvedOrginal, *ResolvedPreferred, CompileCommand,
- FSProvider.getTaggedFileSystem(File).Value);
+ auto Include =
+ calculateIncludePath(File, Code, *ResolvedOrginal, *ResolvedPreferred,
+ CompileCommand, FSProvider.getFileSystem());
if (!Include)
return Include.takeError();
if (Include->empty())
@@ -353,20 +323,13 @@
// Replacement with offset UINT_MAX and length 0 will be treated as include
// insertion.
tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, "#include " + ToInclude);
- auto Replaces = format::cleanupAroundReplacements(
- Code, tooling::Replacements(R), *Style);
+ auto Replaces =
+ format::cleanupAroundReplacements(Code, tooling::Replacements(R), *Style);
if (!Replaces)
return Replaces;
return formatReplacements(Code, *Replaces, *Style);
}
-llvm::Optional<std::string> ClangdServer::getDocument(PathRef File) {
- auto Latest = DraftMgr.getDraft(File);
- if (!Latest.Draft)
- return llvm::None;
- return std::move(*Latest.Draft);
-}
-
void ClangdServer::dumpAST(PathRef File,
UniqueFunction<void(std::string)> Callback) {
auto Action = [](decltype(Callback) Callback,
@@ -387,21 +350,17 @@
WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback)));
}
-void ClangdServer::findDefinitions(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<std::vector<Location>>>)>
- Callback) {
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
- llvm::Expected<InputsAndAST> InpAST) {
+void ClangdServer::findDefinitions(PathRef File, Position Pos,
+ Callback<std::vector<Location>> CB) {
+ auto FS = FSProvider.getFileSystem();
+ auto Action = [Pos, FS](Callback<std::vector<Location>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
- return Callback(InpAST.takeError());
- auto Result = clangd::findDefinitions(InpAST->AST, Pos);
- Callback(make_tagged(std::move(Result), TaggedFS.Tag));
+ return CB(InpAST.takeError());
+ CB(clangd::findDefinitions(InpAST->AST, Pos));
};
- WorkScheduler.runWithAST("Definitions", File,
- Bind(Action, std::move(Callback)));
+ WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB)));
}
llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
@@ -444,7 +403,7 @@
SmallString<128> NewPath = StringRef(Path);
// Instance of vfs::FileSystem, used for file existence checks.
- auto FS = FSProvider.getTaggedFileSystem(Path).Value;
+ auto FS = FSProvider.getFileSystem();
// Loop through switched extension candidates.
for (StringRef NewExt : NewExts) {
@@ -467,9 +426,8 @@
ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
ArrayRef<tooling::Range> Ranges) {
// Call clang-format.
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
- auto Style =
- format::getStyle("file", File, "LLVM", Code, TaggedFS.Value.get());
+ auto FS = FSProvider.getFileSystem();
+ auto Style = format::getStyle("file", File, "LLVM", Code, FS.get());
if (!Style)
return Style.takeError();
@@ -486,89 +444,47 @@
}
void ClangdServer::findDocumentHighlights(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
- Callback) {
- auto FileContents = DraftMgr.getDraft(File);
- if (!FileContents.Draft)
- return Callback(llvm::make_error<llvm::StringError>(
- "findDocumentHighlights called on non-added file",
- llvm::errc::invalid_argument));
+ PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
-
- auto Action = [TaggedFS, Pos](decltype(Callback) Callback,
- llvm::Expected<InputsAndAST> InpAST) {
+ auto FS = FSProvider.getFileSystem();
+ auto Action = [FS, Pos](Callback<std::vector<DocumentHighlight>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
- return Callback(InpAST.takeError());
- auto Result = clangd::findDocumentHighlights(InpAST->AST, Pos);
- Callback(make_tagged(std::move(Result), TaggedFS.Tag));
+ return CB(InpAST.takeError());
+ CB(clangd::findDocumentHighlights(InpAST->AST, Pos));
};
- WorkScheduler.runWithAST("Highlights", File,
- Bind(Action, std::move(Callback)));
+ WorkScheduler.runWithAST("Highlights", File, Bind(Action, std::move(CB)));
}
-void ClangdServer::findHover(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback) {
- Hover FinalHover;
- auto FileContents = DraftMgr.getDraft(File);
- if (!FileContents.Draft)
- return Callback(llvm::make_error<llvm::StringError>(
- "findHover called on non-added file", llvm::errc::invalid_argument));
-
- auto TaggedFS = FSProvider.getTaggedFileSystem(File);
-
- auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
- llvm::Expected<InputsAndAST> InpAST) {
+void ClangdServer::findHover(PathRef File, Position Pos, Callback<Hover> CB) {
+ auto FS = FSProvider.getFileSystem();
+ auto Action = [Pos, FS](Callback<Hover> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
- return Callback(InpAST.takeError());
-
- Hover Result = clangd::getHover(InpAST->AST, Pos);
- Callback(make_tagged(std::move(Result), TaggedFS.Tag));
+ return CB(InpAST.takeError());
+ CB(clangd::getHover(InpAST->AST, Pos));
};
- WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(Callback)));
+ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
}
-void ClangdServer::scheduleReparseAndDiags(
- PathRef File, VersionedDraft Contents, WantDiagnostics WantDiags,
- Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
- tooling::CompileCommand Command = CompileArgs.getCompileCommand(File);
+void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version,
+ std::vector<Diag> Diags) {
+ // We need to serialize access to resulting diagnostics to avoid calling
+ // `onDiagnosticsReady` in the wrong order.
+ std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
+ DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[File];
- DocVersion Version = Contents.Version;
- Path FileStr = File.str();
- VFSTag Tag = std::move(TaggedFS.Tag);
+ // FIXME(ibiryukov): get rid of '<' comparison here. In the current
+ // implementation diagnostics will not be reported after version counters'
+ // overflow. This should not happen in practice, since DocVersion is a
+ // 64-bit unsigned integer.
+ if (Version < LastReportedDiagsVersion)
+ return;
+ LastReportedDiagsVersion = Version;
- auto Callback = [this, Version, FileStr,
- Tag](std::vector<DiagWithFixIts> Diags) {
- // We need to serialize access to resulting diagnostics to avoid calling
- // `onDiagnosticsReady` in the wrong order.
- std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
- DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr];
- // FIXME(ibiryukov): get rid of '<' comparison here. In the current
- // implementation diagnostics will not be reported after version counters'
- // overflow. This should not happen in practice, since DocVersion is a
- // 64-bit unsigned integer.
- if (Version < LastReportedDiagsVersion)
- return;
- LastReportedDiagsVersion = Version;
-
- DiagConsumer.onDiagnosticsReady(
- FileStr, make_tagged(std::move(Diags), std::move(Tag)));
- };
-
- WorkScheduler.update(File,
- ParseInputs{std::move(Command),
- std::move(TaggedFS.Value),
- std::move(*Contents.Draft)},
- WantDiags, std::move(Callback));
-}
-
-void ClangdServer::reparseOpenedFiles() {
- for (const Path &FilePath : DraftMgr.getActiveFiles())
- forceReparse(FilePath);
+ DiagConsumer.onDiagnosticsReady(File, std::move(Diags));
}
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h
index 5e10c24..34af4b9 100644
--- a/clangd/ClangdServer.h
+++ b/clangd/ClangdServer.h
@@ -13,7 +13,6 @@
#include "ClangdUnit.h"
#include "CodeComplete.h"
#include "CompileArgsCache.h"
-#include "DraftStore.h"
#include "Function.h"
#include "GlobalCompilationDatabase.h"
#include "Protocol.h"
@@ -35,64 +34,29 @@
namespace clangd {
-/// A tag supplied by the FileSytemProvider.
-typedef std::string VFSTag;
-
-/// A value of an arbitrary type and VFSTag that was supplied by the
-/// FileSystemProvider when this value was computed.
-template <class T> class Tagged {
-public:
- // MSVC requires future<> arguments to be default-constructible.
- Tagged() = default;
-
- template <class U>
- Tagged(U &&Value, VFSTag Tag)
- : Value(std::forward<U>(Value)), Tag(std::move(Tag)) {}
-
- template <class U>
- Tagged(const Tagged<U> &Other) : Value(Other.Value), Tag(Other.Tag) {}
-
- template <class U>
- Tagged(Tagged<U> &&Other)
- : Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {}
-
- T Value = T();
- VFSTag Tag = VFSTag();
-};
-
-template <class T>
-Tagged<typename std::decay<T>::type> make_tagged(T &&Value, VFSTag Tag) {
- return Tagged<typename std::decay<T>::type>(std::forward<T>(Value), Tag);
-}
-
class DiagnosticsConsumer {
public:
virtual ~DiagnosticsConsumer() = default;
/// Called by ClangdServer when \p Diagnostics for \p File are ready.
- virtual void
- onDiagnosticsReady(PathRef File,
- Tagged<std::vector<DiagWithFixIts>> Diagnostics) = 0;
+ virtual void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) = 0;
};
class FileSystemProvider {
public:
virtual ~FileSystemProvider() = default;
/// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing.
- /// Name of the file that will be parsed is passed in \p File.
- ///
- /// \return A filesystem that will be used for all file accesses in clangd.
- /// A Tag returned by this method will be propagated to all results of clangd
- /// that will use this filesystem.
- virtual Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
- getTaggedFileSystem(PathRef File) = 0;
+ /// Context::current() will be the context passed to the clang entrypoint,
+ /// such as addDocument(), and will also be propagated to result callbacks.
+ /// Embedders may use this to isolate filesystem accesses.
+ virtual IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() = 0;
};
class RealFileSystemProvider : public FileSystemProvider {
public:
- /// \return getRealFileSystem() tagged with default tag, i.e. VFSTag()
- Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
- getTaggedFileSystem(PathRef File) override;
+ /// Returns getRealFileSystem().
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override;
};
/// Provides API to manage ASTs for a collection of C++ files and request
@@ -138,10 +102,6 @@
/// those arguments for subsequent reparses. However, ClangdServer will check
/// if compilation arguments changed on calls to forceReparse().
///
- /// FSProvider provides a vfs::FileSystem for each parsing request. Results of
- /// code completion and diagnostics also include a tag, that \p FSProvider
- /// returns along with the vfs::FileSystem.
- ///
/// After each parsing request finishes, ClangdServer reports diagnostics to
/// \p DiagConsumer. Note that a callback to \p DiagConsumer happens on a
/// worker thread. Therefore, instances of \p DiagConsumer must properly
@@ -156,30 +116,19 @@
/// \p File is already tracked. Also schedules parsing of the AST for it on a
/// separate thread. When the parsing is complete, DiagConsumer passed in
/// constructor will receive onDiagnosticsReady callback.
+ /// When \p SkipCache is true, compile commands will always be requested from
+ /// compilation database even if they were cached in previous invocations.
void addDocument(PathRef File, StringRef Contents,
- WantDiagnostics WD = WantDiagnostics::Auto);
+ WantDiagnostics WD = WantDiagnostics::Auto,
+ bool SkipCache = false);
/// Remove \p File from list of tracked files, schedule a request to free
/// resources associated with it.
void removeDocument(PathRef File);
- /// Force \p File to be reparsed using the latest contents.
- /// Will also check if CompileCommand, provided by GlobalCompilationDatabase
- /// for \p File has changed. If it has, will remove currently stored Preamble
- /// and AST and rebuild them from scratch.
- void forceReparse(PathRef File);
-
- /// Calls forceReparse() on all currently opened files.
- /// As a result, this method may be very expensive.
- /// This method is normally called when the compilation database is changed.
- void reparseOpenedFiles();
-
/// Run code completion for \p File at \p Pos.
/// Request is processed asynchronously.
///
- /// The current draft for \p File will be used. If \p UsedFS is non-null, it
- /// will be overwritten by vfs::FileSystem used for completion.
- ///
/// This method should only be called for currently tracked files. However, it
/// is safe to call removeDocument for \p File after this method returns, even
/// while returned future is not yet ready.
@@ -187,39 +136,26 @@
/// when codeComplete results become available.
void codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
- UniqueFunction<void(Tagged<CompletionList>)> Callback,
- IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
+ Callback<CompletionList> CB);
- /// Provide signature help for \p File at \p Pos. If \p OverridenContents is
- /// not None, they will used only for signature help, i.e. no diagnostics
- /// update will be scheduled and a draft for \p File will not be updated. If
- /// If \p UsedFS is non-null, it will be overwritten by vfs::FileSystem used
- /// for signature help. This method should only be called for tracked files.
- void signatureHelp(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<SignatureHelp>>)> Callback,
- IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
+ /// Provide signature help for \p File at \p Pos. This method should only be
+ /// called for tracked files.
+ void signatureHelp(PathRef File, Position Pos, Callback<SignatureHelp> CB);
/// Get definition of symbol at a specified \p Line and \p Column in \p File.
- void findDefinitions(
- PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<std::vector<Location>>>)>
- Callback);
+ void findDefinitions(PathRef File, Position Pos,
+ Callback<std::vector<Location>> CB);
/// Helper function that returns a path to the corresponding source file when
/// given a header file and vice versa.
llvm::Optional<Path> switchSourceHeader(PathRef Path);
/// Get document highlights for a given position.
- void findDocumentHighlights(
- PathRef File, Position Pos,
- UniqueFunction<
- void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
- Callback);
+ void findDocumentHighlights(PathRef File, Position Pos,
+ Callback<std::vector<DocumentHighlight>> CB);
/// Get code hover for a given position.
- void findHover(PathRef File, Position Pos,
- UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback);
+ void findHover(PathRef File, Position Pos, Callback<Hover> CB);
/// Run formatting for \p Rng inside \p File with content \p Code.
llvm::Expected<tooling::Replacements> formatRange(StringRef Code,
@@ -237,8 +173,7 @@
/// Rename all occurrences of the symbol at the \p Pos in \p File to
/// \p NewName.
void rename(PathRef File, Position Pos, llvm::StringRef NewName,
- UniqueFunction<void(Expected<std::vector<tooling::Replacement>>)>
- Callback);
+ Callback<std::vector<tooling::Replacement>> CB);
/// Inserts a new #include into \p File, if it's not present in \p Code.
///
@@ -256,12 +191,6 @@
StringRef DeclaringHeader,
StringRef InsertedHeader);
- /// Gets current document contents for \p File. Returns None if \p File is not
- /// currently tracked.
- /// FIXME(ibiryukov): This function is here to allow offset-to-Position
- /// conversions in outside code, maybe there's a way to get rid of it.
- llvm::Optional<std::string> getDocument(PathRef File);
-
/// Only for testing purposes.
/// Waits until all requests to worker thread are finished and dumps AST for
/// \p File. \p File must be in the list of added documents.
@@ -290,15 +219,18 @@
formatCode(llvm::StringRef Code, PathRef File,
ArrayRef<tooling::Range> Ranges);
- void
- scheduleReparseAndDiags(PathRef File, VersionedDraft Contents,
- WantDiagnostics WD,
- Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS);
+ typedef uint64_t DocVersion;
+
+ void consumeDiagnostics(PathRef File, DocVersion Version,
+ std::vector<Diag> Diags);
CompileArgsCache CompileArgs;
DiagnosticsConsumer &DiagConsumer;
FileSystemProvider &FSProvider;
- DraftStore DraftMgr;
+
+ /// Used to synchronize diagnostic responses for added and removed files.
+ llvm::StringMap<DocVersion> InternalVersion;
+
// The index used to look up symbols. This could be:
// - null (all index functionality is optional)
// - the dynamic index owned by ClangdServer (FileIdx)
diff --git a/clangd/ClangdUnit.cpp b/clangd/ClangdUnit.cpp
index 4ad95c4..b20ca8e 100644
--- a/clangd/ClangdUnit.cpp
+++ b/clangd/ClangdUnit.cpp
@@ -9,7 +9,9 @@
#include "ClangdUnit.h"
#include "Compiler.h"
+#include "Diagnostics.h"
#include "Logger.h"
+#include "SourceCode.h"
#include "Trace.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
@@ -81,22 +83,6 @@
std::vector<const Decl *> TopLevelDecls;
};
-// Converts a half-open clang source range to an LSP range.
-// Note that clang also uses closed source ranges, which this can't handle!
-Range toRange(CharSourceRange R, const SourceManager &M) {
- // Clang is 1-based, LSP uses 0-based indexes.
- Position Begin;
- Begin.line = static_cast<int>(M.getSpellingLineNumber(R.getBegin())) - 1;
- Begin.character =
- static_cast<int>(M.getSpellingColumnNumber(R.getBegin())) - 1;
-
- Position End;
- End.line = static_cast<int>(M.getSpellingLineNumber(R.getEnd())) - 1;
- End.character = static_cast<int>(M.getSpellingColumnNumber(R.getEnd())) - 1;
-
- return {Begin, End};
-}
-
class InclusionLocationsCollector : public PPCallbacks {
public:
InclusionLocationsCollector(SourceManager &SourceMgr,
@@ -115,7 +101,7 @@
if (SourceMgr.isInMainFile(SR.getBegin())) {
// Only inclusion directives in the main file make sense. The user cannot
// select directives not in the main file.
- IncLocations.emplace_back(toRange(FilenameRange, SourceMgr),
+ IncLocations.emplace_back(halfOpenToRange(SourceMgr, FilenameRange),
File->tryGetRealPathName());
}
}
@@ -170,113 +156,6 @@
SourceManager *SourceMgr = nullptr;
};
-/// Convert from clang diagnostic level to LSP severity.
-static int getSeverity(DiagnosticsEngine::Level L) {
- switch (L) {
- case DiagnosticsEngine::Remark:
- return 4;
- case DiagnosticsEngine::Note:
- return 3;
- case DiagnosticsEngine::Warning:
- return 2;
- case DiagnosticsEngine::Fatal:
- case DiagnosticsEngine::Error:
- return 1;
- case DiagnosticsEngine::Ignored:
- return 0;
- }
- llvm_unreachable("Unknown diagnostic level!");
-}
-
-// Checks whether a location is within a half-open range.
-// Note that clang also uses closed source ranges, which this can't handle!
-bool locationInRange(SourceLocation L, CharSourceRange R,
- const SourceManager &M) {
- assert(R.isCharRange());
- if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
- M.getFileID(R.getBegin()) != M.getFileID(L))
- return false;
- return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
-}
-
-// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
-// LSP needs a single range.
-Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
- auto &M = D.getSourceManager();
- auto Loc = M.getFileLoc(D.getLocation());
- // Accept the first range that contains the location.
- for (const auto &CR : D.getRanges()) {
- auto R = Lexer::makeFileCharRange(CR, M, L);
- if (locationInRange(Loc, R, M))
- return toRange(R, M);
- }
- // The range may be given as a fixit hint instead.
- for (const auto &F : D.getFixItHints()) {
- auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
- if (locationInRange(Loc, R, M))
- return toRange(R, M);
- }
- // If no suitable range is found, just use the token at the location.
- auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L);
- if (!R.isValid()) // Fall back to location only, let the editor deal with it.
- R = CharSourceRange::getCharRange(Loc);
- return toRange(R, M);
-}
-
-TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
- const LangOptions &L) {
- TextEdit Result;
- Result.range = toRange(Lexer::makeFileCharRange(FixIt.RemoveRange, M, L), M);
- Result.newText = FixIt.CodeToInsert;
- return Result;
-}
-
-llvm::Optional<DiagWithFixIts> toClangdDiag(const clang::Diagnostic &D,
- DiagnosticsEngine::Level Level,
- const LangOptions &LangOpts) {
- if (!D.hasSourceManager() || !D.getLocation().isValid() ||
- !D.getSourceManager().isInMainFile(D.getLocation())) {
- IgnoreDiagnostics::log(Level, D);
- return llvm::None;
- }
-
- SmallString<64> Message;
- D.FormatDiagnostic(Message);
-
- DiagWithFixIts Result;
- Result.Diag.range = diagnosticRange(D, LangOpts);
- Result.Diag.severity = getSeverity(Level);
- Result.Diag.message = Message.str();
- for (const FixItHint &Fix : D.getFixItHints())
- Result.FixIts.push_back(toTextEdit(Fix, D.getSourceManager(), LangOpts));
- return std::move(Result);
-}
-
-class StoreDiagsConsumer : public DiagnosticConsumer {
-public:
- StoreDiagsConsumer(std::vector<DiagWithFixIts> &Output) : Output(Output) {}
-
- // Track language options in case we need to expand token ranges.
- void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override {
- LangOpts = Opts;
- }
-
- void EndSourceFile() override { LangOpts = llvm::None; }
-
- void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
- const clang::Diagnostic &Info) override {
- DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
-
- if (LangOpts)
- if (auto D = toClangdDiag(Info, DiagLevel, *LangOpts))
- Output.push_back(std::move(*D));
- }
-
-private:
- std::vector<DiagWithFixIts> &Output;
- llvm::Optional<LangOptions> LangOpts;
-};
-
} // namespace
void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
@@ -289,14 +168,13 @@
std::unique_ptr<llvm::MemoryBuffer> Buffer,
std::shared_ptr<PCHContainerOperations> PCHs,
IntrusiveRefCntPtr<vfs::FileSystem> VFS) {
- std::vector<DiagWithFixIts> ASTDiags;
- StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags);
-
const PrecompiledPreamble *PreamblePCH =
Preamble ? &Preamble->Preamble : nullptr;
- auto Clang = prepareCompilerInstance(
- std::move(CI), PreamblePCH, std::move(Buffer), std::move(PCHs),
- std::move(VFS), /*ref*/ UnitDiagsConsumer);
+
+ StoreDiags ASTDiags;
+ auto Clang =
+ prepareCompilerInstance(std::move(CI), PreamblePCH, std::move(Buffer),
+ std::move(PCHs), std::move(VFS), ASTDiags);
if (!Clang)
return llvm::None;
@@ -328,10 +206,12 @@
// UnitDiagsConsumer is local, we can not store it in CompilerInstance that
// has a longer lifetime.
Clang->getDiagnostics().setClient(new IgnoreDiagnostics);
+ // CompilerInstance won't run this callback, do it directly.
+ ASTDiags.EndSourceFile();
std::vector<const Decl *> ParsedDecls = Action->takeTopLevelDecls();
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
- std::move(ParsedDecls), std::move(ASTDiags),
+ std::move(ParsedDecls), ASTDiags.take(),
std::move(IncLocations));
}
@@ -399,14 +279,12 @@
return TopLevelDecls;
}
-const std::vector<DiagWithFixIts> &ParsedAST::getDiagnostics() const {
- return Diags;
-}
+const std::vector<Diag> &ParsedAST::getDiagnostics() const { return Diags; }
std::size_t ParsedAST::getUsedBytes() const {
auto &AST = getASTContext();
// FIXME(ibiryukov): we do not account for the dynamically allocated part of
- // SmallVector<FixIt> inside each Diag.
+ // Message and Fixes inside each diagnostic.
return AST.getASTAllocatedMemory() + AST.getSideTableAllocatedMemory() +
::getUsedBytes(TopLevelDecls) + ::getUsedBytes(Diags);
}
@@ -417,7 +295,7 @@
PreambleData::PreambleData(PrecompiledPreamble Preamble,
std::vector<serialization::DeclID> TopLevelDeclIDs,
- std::vector<DiagWithFixIts> Diags,
+ std::vector<Diag> Diags,
InclusionLocations IncLocations)
: Preamble(std::move(Preamble)),
TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)),
@@ -427,8 +305,7 @@
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
std::vector<const Decl *> TopLevelDecls,
- std::vector<DiagWithFixIts> Diags,
- InclusionLocations IncLocations)
+ std::vector<Diag> Diags, InclusionLocations IncLocations)
: Preamble(std::move(Preamble)), Clang(std::move(Clang)),
Action(std::move(Action)), Diags(std::move(Diags)),
TopLevelDecls(std::move(TopLevelDecls)), PreambleDeclsDeserialized(false),
@@ -445,8 +322,7 @@
log("Created CppFile for " + FileName);
}
-llvm::Optional<std::vector<DiagWithFixIts>>
-CppFile::rebuild(ParseInputs &&Inputs) {
+llvm::Optional<std::vector<Diag>> CppFile::rebuild(ParseInputs &&Inputs) {
log("Rebuilding file " + FileName + " with command [" +
Inputs.CompileCommand.Directory + "] " +
llvm::join(Inputs.CompileCommand.CommandLine, " "));
@@ -500,12 +376,11 @@
std::move(ContentsBuffer), PCHs, Inputs.FS);
}
- std::vector<DiagWithFixIts> Diagnostics;
+ std::vector<Diag> Diagnostics;
if (NewAST) {
// Collect diagnostics from both the preamble and the AST.
if (NewPreamble)
- Diagnostics.insert(Diagnostics.begin(), NewPreamble->Diags.begin(),
- NewPreamble->Diags.end());
+ Diagnostics = NewPreamble->Diags;
Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(),
NewAST->getDiagnostics().end());
}
@@ -557,11 +432,10 @@
trace::Span Tracer("Preamble");
SPAN_ATTACH(Tracer, "File", FileName);
- std::vector<DiagWithFixIts> PreambleDiags;
- StoreDiagsConsumer PreambleDiagnosticsConsumer(/*ref*/ PreambleDiags);
+ StoreDiags PreambleDiagnostics;
IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine =
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(),
- &PreambleDiagnosticsConsumer, false);
+ &PreambleDiagnostics, false);
// Skip function bodies when building the preamble to speed up building
// the preamble and make it smaller.
@@ -584,7 +458,7 @@
return std::make_shared<PreambleData>(
std::move(*BuiltPreamble),
SerializedDeclsCollector.takeTopLevelDeclIDs(),
- std::move(PreambleDiags),
+ PreambleDiagnostics.take(),
SerializedDeclsCollector.takeInclusionLocations());
} else {
log("Could not build a preamble for file " + Twine(FileName));
diff --git a/clangd/ClangdUnit.h b/clangd/ClangdUnit.h
index df29290..b3535b5 100644
--- a/clangd/ClangdUnit.h
+++ b/clangd/ClangdUnit.h
@@ -10,6 +10,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
+#include "Diagnostics.h"
#include "Function.h"
#include "Path.h"
#include "Protocol.h"
@@ -39,24 +40,17 @@
namespace clangd {
-/// A diagnostic with its FixIts.
-struct DiagWithFixIts {
- clangd::Diagnostic Diag;
- llvm::SmallVector<TextEdit, 1> FixIts;
-};
-
using InclusionLocations = std::vector<std::pair<Range, Path>>;
// Stores Preamble and associated data.
struct PreambleData {
PreambleData(PrecompiledPreamble Preamble,
std::vector<serialization::DeclID> TopLevelDeclIDs,
- std::vector<DiagWithFixIts> Diags,
- InclusionLocations IncLocations);
+ std::vector<Diag> Diags, InclusionLocations IncLocations);
PrecompiledPreamble Preamble;
std::vector<serialization::DeclID> TopLevelDeclIDs;
- std::vector<DiagWithFixIts> Diags;
+ std::vector<Diag> Diags;
InclusionLocations IncLocations;
};
@@ -96,7 +90,7 @@
/// this call might be expensive.
ArrayRef<const Decl *> getTopLevelDecls();
- const std::vector<DiagWithFixIts> &getDiagnostics() const;
+ const std::vector<Diag> &getDiagnostics() const;
/// Returns the esitmated size of the AST and the accessory structures, in
/// bytes. Does not include the size of the preamble.
@@ -107,8 +101,8 @@
ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
- std::vector<const Decl *> TopLevelDecls,
- std::vector<DiagWithFixIts> Diags, InclusionLocations IncLocations);
+ std::vector<const Decl *> TopLevelDecls, std::vector<Diag> Diags,
+ InclusionLocations IncLocations);
private:
void ensurePreambleDeclsDeserialized();
@@ -125,7 +119,7 @@
std::unique_ptr<FrontendAction> Action;
// Data, stored after parsing.
- std::vector<DiagWithFixIts> Diags;
+ std::vector<Diag> Diags;
std::vector<const Decl *> TopLevelDecls;
bool PreambleDeclsDeserialized;
InclusionLocations IncLocations;
@@ -143,7 +137,7 @@
/// Rebuild the AST and the preamble.
/// Returns a list of diagnostics or llvm::None, if an error occured.
- llvm::Optional<std::vector<DiagWithFixIts>> rebuild(ParseInputs &&Inputs);
+ llvm::Optional<std::vector<Diag>> rebuild(ParseInputs &&Inputs);
/// Returns the last built preamble.
const std::shared_ptr<const PreambleData> &getPreamble() const;
/// Returns the last built AST.
diff --git a/clangd/CodeComplete.cpp b/clangd/CodeComplete.cpp
index 6edc076..8753645 100644
--- a/clangd/CodeComplete.cpp
+++ b/clangd/CodeComplete.cpp
@@ -450,8 +450,15 @@
void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
CodeCompletionResult *InResults,
unsigned NumResults) override final {
+ if (CCSema) {
+ log(llvm::formatv(
+ "Multiple code complete callbacks (parser backtracked?). "
+ "Dropping results from context {0}, keeping results from {1}.",
+ getCompletionKindString(this->CCContext.getKind()),
+ getCompletionKindString(Context.getKind())));
+ return;
+ }
// Record the completion context.
- assert(!CCSema && "ProcessCodeCompleteResults called multiple times!");
CCSema = &S;
CCContext = Context;
diff --git a/clangd/Diagnostics.cpp b/clangd/Diagnostics.cpp
new file mode 100644
index 0000000..e497823
--- /dev/null
+++ b/clangd/Diagnostics.cpp
@@ -0,0 +1,364 @@
+//===--- Diagnostics.cpp ----------------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+
+#include "Diagnostics.h"
+#include "Compiler.h"
+#include "Logger.h"
+#include "SourceCode.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/Support/Capacity.h"
+#include "llvm/Support/Path.h"
+#include <algorithm>
+
+namespace clang {
+namespace clangd {
+
+namespace {
+
+bool mentionsMainFile(const Diag &D) {
+ if (D.InsideMainFile)
+ return true;
+ // Fixes are always in the main file.
+ if (!D.Fixes.empty())
+ return true;
+ for (auto &N : D.Notes) {
+ if (N.InsideMainFile)
+ return true;
+ }
+ return false;
+}
+
+// Checks whether a location is within a half-open range.
+// Note that clang also uses closed source ranges, which this can't handle!
+bool locationInRange(SourceLocation L, CharSourceRange R,
+ const SourceManager &M) {
+ assert(R.isCharRange());
+ if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
+ M.getFileID(R.getBegin()) != M.getFileID(L))
+ return false;
+ return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
+}
+
+// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
+// LSP needs a single range.
+Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
+ auto &M = D.getSourceManager();
+ auto Loc = M.getFileLoc(D.getLocation());
+ // Accept the first range that contains the location.
+ for (const auto &CR : D.getRanges()) {
+ auto R = Lexer::makeFileCharRange(CR, M, L);
+ if (locationInRange(Loc, R, M))
+ return halfOpenToRange(M, R);
+ }
+ // The range may be given as a fixit hint instead.
+ for (const auto &F : D.getFixItHints()) {
+ auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
+ if (locationInRange(Loc, R, M))
+ return halfOpenToRange(M, R);
+ }
+ // If no suitable range is found, just use the token at the location.
+ auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L);
+ if (!R.isValid()) // Fall back to location only, let the editor deal with it.
+ R = CharSourceRange::getCharRange(Loc);
+ return halfOpenToRange(M, R);
+}
+
+TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
+ const LangOptions &L) {
+ TextEdit Result;
+ Result.range =
+ halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));
+ Result.newText = FixIt.CodeToInsert;
+ return Result;
+}
+
+bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) {
+ return Loc.isValid() && M.isInMainFile(Loc);
+}
+
+bool isInsideMainFile(const clang::Diagnostic &D) {
+ if (!D.hasSourceManager())
+ return false;
+
+ return isInsideMainFile(D.getLocation(), D.getSourceManager());
+}
+
+bool isNote(DiagnosticsEngine::Level L) {
+ return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
+}
+
+llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
+ switch (Lvl) {
+ case DiagnosticsEngine::Ignored:
+ return "ignored";
+ case DiagnosticsEngine::Note:
+ return "note";
+ case DiagnosticsEngine::Remark:
+ return "remark";
+ case DiagnosticsEngine::Warning:
+ return "warning";
+ case DiagnosticsEngine::Error:
+ return "error";
+ case DiagnosticsEngine::Fatal:
+ return "fatal error";
+ }
+ llvm_unreachable("unhandled DiagnosticsEngine::Level");
+}
+
+/// Prints a single diagnostic in a clang-like manner, the output includes
+/// location, severity and error message. An example of the output message is:
+///
+/// main.cpp:12:23: error: undeclared identifier
+///
+/// For main file we only print the basename and for all other files we print
+/// the filename on a separate line to provide a slightly more readable output
+/// in the editors:
+///
+/// dir1/dir2/dir3/../../dir4/header.h:12:23
+/// error: undeclared identifier
+void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
+ if (D.InsideMainFile) {
+ // Paths to main files are often taken from compile_command.json, where they
+ // are typically absolute. To reduce noise we print only basename for them,
+ // it should not be confusing and saves space.
+ OS << llvm::sys::path::filename(D.File) << ":";
+ } else {
+ OS << D.File << ":";
+ }
+ // Note +1 to line and character. clangd::Range is zero-based, but when
+ // printing for users we want one-based indexes.
+ auto Pos = D.Range.start;
+ OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":";
+ // The non-main-file paths are often too long, putting them on a separate
+ // line improves readability.
+ if (D.InsideMainFile)
+ OS << " ";
+ else
+ OS << "\n";
+ OS << diagLeveltoString(D.Severity) << ": " << D.Message;
+}
+
+/// Returns a message sent to LSP for the main diagnostic in \p D.
+/// The message includes all the notes with their corresponding locations.
+/// However, notes with fix-its are excluded as those usually only contain a
+/// fix-it message and just add noise if included in the message for diagnostic.
+/// Example output:
+///
+/// no matching function for call to 'foo'
+///
+/// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
+///
+/// dir1/dir2/dir3/../../dir4/header.h:12:23
+/// note: candidate function not viable: requires 3 arguments
+std::string mainMessage(const Diag &D) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ OS << D.Message;
+ for (auto &Note : D.Notes) {
+ OS << "\n\n";
+ printDiag(OS, Note);
+ }
+ OS.flush();
+ return Result;
+}
+
+/// Returns a message sent to LSP for the note of the main diagnostic.
+/// The message includes the main diagnostic to provide the necessary context
+/// for the user to understand the note.
+std::string noteMessage(const Diag &Main, const DiagBase &Note) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ OS << Note.Message;
+ OS << "\n\n";
+ printDiag(OS, Main);
+ OS.flush();
+ return Result;
+}
+} // namespace
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) {
+ if (!D.InsideMainFile)
+ OS << "[in " << D.File << "] ";
+ return OS << D.Message;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) {
+ OS << F.Message << " {";
+ const char *Sep = "";
+ for (const auto &Edit : F.Edits) {
+ OS << Sep << Edit;
+ Sep = ", ";
+ }
+ return OS << "}";
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) {
+ OS << static_cast<const DiagBase &>(D);
+ if (!D.Notes.empty()) {
+ OS << ", notes: {";
+ const char *Sep = "";
+ for (auto &Note : D.Notes) {
+ OS << Sep << Note;
+ Sep = ", ";
+ }
+ OS << "}";
+ }
+ if (!D.Fixes.empty()) {
+ OS << ", fixes: {";
+ const char *Sep = "";
+ for (auto &Fix : D.Fixes) {
+ OS << Sep << Fix;
+ Sep = ", ";
+ }
+ }
+ return OS;
+}
+
+void toLSPDiags(
+ const Diag &D,
+ llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) {
+ auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic {
+ clangd::Diagnostic Res;
+ Res.range = D.Range;
+ Res.severity = getSeverity(D.Severity);
+ return Res;
+ };
+
+ {
+ clangd::Diagnostic Main = FillBasicFields(D);
+ Main.message = mainMessage(D);
+ OutFn(std::move(Main), D.Fixes);
+ }
+
+ for (auto &Note : D.Notes) {
+ if (!Note.InsideMainFile)
+ continue;
+ clangd::Diagnostic Res = FillBasicFields(Note);
+ Res.message = noteMessage(D, Note);
+ OutFn(std::move(Res), llvm::ArrayRef<Fix>());
+ }
+}
+
+int getSeverity(DiagnosticsEngine::Level L) {
+ switch (L) {
+ case DiagnosticsEngine::Remark:
+ return 4;
+ case DiagnosticsEngine::Note:
+ return 3;
+ case DiagnosticsEngine::Warning:
+ return 2;
+ case DiagnosticsEngine::Fatal:
+ case DiagnosticsEngine::Error:
+ return 1;
+ case DiagnosticsEngine::Ignored:
+ return 0;
+ }
+ llvm_unreachable("Unknown diagnostic level!");
+}
+
+std::vector<Diag> StoreDiags::take() { return std::move(Output); }
+
+void StoreDiags::BeginSourceFile(const LangOptions &Opts,
+ const Preprocessor *) {
+ LangOpts = Opts;
+}
+
+void StoreDiags::EndSourceFile() {
+ flushLastDiag();
+ LangOpts = llvm::None;
+}
+
+void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
+ const clang::Diagnostic &Info) {
+ DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
+
+ if (!LangOpts || !Info.hasSourceManager()) {
+ IgnoreDiagnostics::log(DiagLevel, Info);
+ return;
+ }
+
+ bool InsideMainFile = isInsideMainFile(Info);
+
+ auto FillDiagBase = [&](DiagBase &D) {
+ D.Range = diagnosticRange(Info, *LangOpts);
+ llvm::SmallString<64> Message;
+ Info.FormatDiagnostic(Message);
+ D.Message = Message.str();
+ D.InsideMainFile = InsideMainFile;
+ D.File = Info.getSourceManager().getFilename(Info.getLocation());
+ D.Severity = DiagLevel;
+ return D;
+ };
+
+ auto AddFix = [&]() -> bool {
+ assert(!Info.getFixItHints().empty() &&
+ "diagnostic does not have attached fix-its");
+ if (!InsideMainFile)
+ return false;
+
+ llvm::SmallVector<TextEdit, 1> Edits;
+ for (auto &FixIt : Info.getFixItHints()) {
+ if (!isInsideMainFile(FixIt.RemoveRange.getBegin(),
+ Info.getSourceManager()))
+ return false;
+ Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts));
+ }
+
+ llvm::SmallString<64> Message;
+ Info.FormatDiagnostic(Message);
+ LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)});
+ return true;
+ };
+
+ if (!isNote(DiagLevel)) {
+ // Handle the new main diagnostic.
+ flushLastDiag();
+
+ LastDiag = Diag();
+ FillDiagBase(*LastDiag);
+
+ if (!Info.getFixItHints().empty())
+ AddFix();
+ } else {
+ // Handle a note to an existing diagnostic.
+ if (!LastDiag) {
+ assert(false && "Adding a note without main diagnostic");
+ IgnoreDiagnostics::log(DiagLevel, Info);
+ return;
+ }
+
+ if (!Info.getFixItHints().empty()) {
+ // A clang note with fix-it is not a separate diagnostic in clangd. We
+ // attach it as a Fix to the main diagnostic instead.
+ if (!AddFix())
+ IgnoreDiagnostics::log(DiagLevel, Info);
+ } else {
+ // A clang note without fix-its corresponds to clangd::Note.
+ Note N;
+ FillDiagBase(N);
+
+ LastDiag->Notes.push_back(std::move(N));
+ }
+ }
+}
+
+void StoreDiags::flushLastDiag() {
+ if (!LastDiag)
+ return;
+ if (mentionsMainFile(*LastDiag))
+ Output.push_back(std::move(*LastDiag));
+ else
+ log(Twine("Dropped diagnostic outside main file:") + LastDiag->File + ":" +
+ LastDiag->Message);
+ LastDiag.reset();
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/Diagnostics.h b/clangd/Diagnostics.h
new file mode 100644
index 0000000..b2174f6
--- /dev/null
+++ b/clangd/Diagnostics.h
@@ -0,0 +1,103 @@
+//===--- Diagnostics.h ------------------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIAGNOSTICS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIAGNOSTICS_H
+
+#include "Path.h"
+#include "Protocol.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LangOptions.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringSet.h"
+#include <cassert>
+#include <string>
+
+namespace clang {
+namespace clangd {
+
+/// Contains basic information about a diagnostic.
+struct DiagBase {
+ std::string Message;
+ // Intended to be used only in error messages.
+ // May be relative, absolute or even artifically constructed.
+ std::string File;
+ clangd::Range Range;
+ DiagnosticsEngine::Level Severity = DiagnosticsEngine::Note;
+ // Since File is only descriptive, we store a separate flag to distinguish
+ // diags from the main file.
+ bool InsideMainFile = false;
+};
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D);
+
+/// Represents a single fix-it that editor can apply to fix the error.
+struct Fix {
+ /// Message for the fix-it.
+ std::string Message;
+ /// TextEdits from clang's fix-its. Must be non-empty.
+ llvm::SmallVector<TextEdit, 1> Edits;
+};
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F);
+
+/// Represents a note for the diagnostic. Severity of notes can only be 'note'
+/// or 'remark'.
+struct Note : DiagBase {};
+
+/// A top-level diagnostic that may have Notes and Fixes.
+struct Diag : DiagBase {
+ /// Elaborate on the problem, usually pointing to a related piece of code.
+ std::vector<Note> Notes;
+ /// *Alternative* fixes for this diagnostic, one should be chosen.
+ std::vector<Fix> Fixes;
+};
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D);
+
+/// Conversion to LSP diagnostics. Formats the error message of each diagnostic
+/// to include all its notes. Notes inside main file are also provided as
+/// separate diagnostics with their corresponding fixits. Notes outside main
+/// file do not have a corresponding LSP diagnostic, but can still be included
+/// as part of their main diagnostic's message.
+void toLSPDiags(
+ const Diag &D,
+ llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn);
+
+/// Convert from clang diagnostic level to LSP severity.
+int getSeverity(DiagnosticsEngine::Level L);
+
+/// StoreDiags collects the diagnostics that can later be reported by
+/// clangd. It groups all notes for a diagnostic into a single Diag
+/// and filters out diagnostics that don't mention the main file (i.e. neither
+/// the diag itself nor its notes are in the main file).
+class StoreDiags : public DiagnosticConsumer {
+public:
+ std::vector<Diag> take();
+
+ void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override;
+ void EndSourceFile() override;
+ void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
+ const clang::Diagnostic &Info) override;
+
+private:
+ bool shouldIgnore(DiagnosticsEngine::Level DiagLevel,
+ const clang::Diagnostic &Info);
+
+ void flushLastDiag();
+
+ std::vector<Diag> Output;
+ llvm::Optional<LangOptions> LangOpts;
+ llvm::Optional<Diag> LastDiag;
+ /// Is any diag or note from LastDiag in the main file?
+ bool LastDiagMentionsMainFile = false;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clangd/DraftStore.cpp b/clangd/DraftStore.cpp
index 4bda859..8e1cf88 100644
--- a/clangd/DraftStore.cpp
+++ b/clangd/DraftStore.cpp
@@ -8,16 +8,19 @@
//===----------------------------------------------------------------------===//
#include "DraftStore.h"
+#include "SourceCode.h"
+#include "llvm/Support/Errc.h"
using namespace clang;
using namespace clang::clangd;
-VersionedDraft DraftStore::getDraft(PathRef File) const {
+llvm::Optional<std::string> DraftStore::getDraft(PathRef File) const {
std::lock_guard<std::mutex> Lock(Mutex);
auto It = Drafts.find(File);
if (It == Drafts.end())
- return {0, llvm::None};
+ return llvm::None;
+
return It->second;
}
@@ -26,35 +29,79 @@
std::vector<Path> ResultVector;
for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++)
- if (DraftIt->second.Draft)
- ResultVector.push_back(DraftIt->getKey());
+ ResultVector.push_back(DraftIt->getKey());
return ResultVector;
}
-DocVersion DraftStore::getVersion(PathRef File) const {
+void DraftStore::addDraft(PathRef File, StringRef Contents) {
std::lock_guard<std::mutex> Lock(Mutex);
- auto It = Drafts.find(File);
- if (It == Drafts.end())
- return 0;
- return It->second.Version;
+ Drafts[File] = Contents;
}
-DocVersion DraftStore::updateDraft(PathRef File, StringRef Contents) {
+llvm::Expected<std::string> DraftStore::updateDraft(
+ PathRef File, llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) {
std::lock_guard<std::mutex> Lock(Mutex);
- auto &Entry = Drafts[File];
- DocVersion NewVersion = ++Entry.Version;
- Entry.Draft = Contents;
- return NewVersion;
+ auto EntryIt = Drafts.find(File);
+ if (EntryIt == Drafts.end()) {
+ return llvm::make_error<llvm::StringError>(
+ "Trying to do incremental update on non-added document: " + File,
+ llvm::errc::invalid_argument);
+ }
+
+ std::string Contents = EntryIt->second;
+
+ for (const TextDocumentContentChangeEvent &Change : Changes) {
+ if (!Change.range) {
+ Contents = Change.text;
+ continue;
+ }
+
+ const Position &Start = Change.range->start;
+ llvm::Expected<size_t> StartIndex =
+ positionToOffset(Contents, Start, false);
+ if (!StartIndex)
+ return StartIndex.takeError();
+
+ const Position &End = Change.range->end;
+ llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false);
+ if (!EndIndex)
+ return EndIndex.takeError();
+
+ if (*EndIndex < *StartIndex)
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv(
+ "Range's end position ({0}) is before start position ({1})", End,
+ Start),
+ llvm::errc::invalid_argument);
+
+ if (Change.rangeLength &&
+ (ssize_t)(*EndIndex - *StartIndex) != *Change.rangeLength)
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Change's rangeLength ({0}) doesn't match the "
+ "computed range length ({1}).",
+ *Change.rangeLength, *EndIndex - *StartIndex),
+ llvm::errc::invalid_argument);
+
+ std::string NewContents;
+ NewContents.reserve(*StartIndex + Change.text.length() +
+ (Contents.length() - *EndIndex));
+
+ NewContents = Contents.substr(0, *StartIndex);
+ NewContents += Change.text;
+ NewContents += Contents.substr(*EndIndex);
+
+ Contents = std::move(NewContents);
+ }
+
+ EntryIt->second = Contents;
+ return Contents;
}
-DocVersion DraftStore::removeDraft(PathRef File) {
+void DraftStore::removeDraft(PathRef File) {
std::lock_guard<std::mutex> Lock(Mutex);
- auto &Entry = Drafts[File];
- DocVersion NewVersion = ++Entry.Version;
- Entry.Draft = llvm::None;
- return NewVersion;
+ Drafts.erase(File);
}
diff --git a/clangd/DraftStore.h b/clangd/DraftStore.h
index 4757768..90a2d2c 100644
--- a/clangd/DraftStore.h
+++ b/clangd/DraftStore.h
@@ -11,9 +11,9 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DRAFTSTORE_H
#include "Path.h"
+#include "Protocol.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/StringMap.h"
-#include <cstdint>
#include <mutex>
#include <string>
#include <vector>
@@ -21,45 +21,37 @@
namespace clang {
namespace clangd {
-/// Using unsigned int type here to avoid undefined behaviour on overflow.
-typedef uint64_t DocVersion;
-
-/// Document draft with a version of this draft.
-struct VersionedDraft {
- DocVersion Version;
- /// If the value of the field is None, draft is now deleted
- llvm::Optional<std::string> Draft;
-};
-
/// A thread-safe container for files opened in a workspace, addressed by
-/// filenames. The contents are owned by the DraftStore. Versions are mantained
-/// for the all added documents, including removed ones. The document version is
-/// incremented on each update and removal of the document.
+/// filenames. The contents are owned by the DraftStore. This class supports
+/// both whole and incremental updates of the documents.
class DraftStore {
public:
- /// \return version and contents of the stored document.
- /// For untracked files, a (0, None) pair is returned.
- VersionedDraft getDraft(PathRef File) const;
+ /// \return Contents of the stored document.
+ /// For untracked files, a llvm::None is returned.
+ llvm::Optional<std::string> getDraft(PathRef File) const;
- /// \return List of names of active drafts in this store. Drafts that were
- /// removed (which still have an entry in the Drafts map) are not returned by
- /// this function.
+ /// \return List of names of the drafts in this store.
std::vector<Path> getActiveFiles() const;
- /// \return version of the tracked document.
- /// For untracked files, 0 is returned.
- DocVersion getVersion(PathRef File) const;
-
/// Replace contents of the draft for \p File with \p Contents.
- /// \return The new version of the draft for \p File.
- DocVersion updateDraft(PathRef File, StringRef Contents);
- /// Remove the contents of the draft
- /// \return The new version of the draft for \p File.
- DocVersion removeDraft(PathRef File);
+ void addDraft(PathRef File, StringRef Contents);
+
+ /// Update the contents of the draft for \p File based on \p Changes.
+ /// If a position in \p Changes is invalid (e.g. out-of-range), the
+ /// draft is not modified.
+ ///
+ /// \return The new version of the draft for \p File, or an error if the
+ /// changes couldn't be applied.
+ llvm::Expected<std::string>
+ updateDraft(PathRef File,
+ llvm::ArrayRef<TextDocumentContentChangeEvent> Changes);
+
+ /// Remove the draft from the store.
+ void removeDraft(PathRef File);
private:
mutable std::mutex Mutex;
- llvm::StringMap<VersionedDraft> Drafts;
+ llvm::StringMap<std::string> Drafts;
};
} // namespace clangd
diff --git a/clangd/Function.h b/clangd/Function.h
index de3ed5a..975134b 100644
--- a/clangd/Function.h
+++ b/clangd/Function.h
@@ -15,6 +15,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H
#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
#include <cassert>
#include <memory>
#include <tuple>
@@ -27,6 +28,9 @@
/// A move-only type-erasing function wrapper. Similar to `std::function`, but
/// allows to store move-only callables.
template <class> class UniqueFunction;
+/// A Callback<T> is a void function that accepts Expected<T>.
+/// This is accepted by ClangdServer functions that logically return T.
+template <typename T> using Callback = UniqueFunction<void(llvm::Expected<T>)>;
template <class Ret, class... Args> class UniqueFunction<Ret(Args...)> {
public:
diff --git a/clangd/JSONExpr.cpp b/clangd/JSONExpr.cpp
index a6e6a55..3f87c28 100644
--- a/clangd/JSONExpr.cpp
+++ b/clangd/JSONExpr.cpp
@@ -113,7 +113,7 @@
++P;
}
- // On invalid syntax, parseX() functions return false and and set Err.
+ // On invalid syntax, parseX() functions return false and set Err.
bool parseNumber(char First, double &Out);
bool parseString(std::string &Out);
bool parseUnicode(std::string &Out);
diff --git a/clangd/Protocol.cpp b/clangd/Protocol.cpp
index 8f2f8c6..566104b 100644
--- a/clangd/Protocol.cpp
+++ b/clangd/Protocol.cpp
@@ -248,7 +248,8 @@
bool fromJSON(const json::Expr &Params, TextDocumentContentChangeEvent &R) {
json::ObjectMapper O(Params);
- return O && O.map("text", R.text);
+ return O && O.map("range", R.range) && O.map("rangeLength", R.rangeLength) &&
+ O.map("text", R.text);
}
bool fromJSON(const json::Expr &Params, FormattingOptions &R) {
diff --git a/clangd/Protocol.h b/clangd/Protocol.h
index 968ebfc..77cf8d2 100644
--- a/clangd/Protocol.h
+++ b/clangd/Protocol.h
@@ -119,6 +119,9 @@
friend bool operator==(const Range &LHS, const Range &RHS) {
return std::tie(LHS.start, LHS.end) == std::tie(RHS.start, RHS.end);
}
+ friend bool operator!=(const Range &LHS, const Range &RHS) {
+ return !(LHS == RHS);
+ }
friend bool operator<(const Range &LHS, const Range &RHS) {
return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end);
}
@@ -194,6 +197,20 @@
using ShutdownParams = NoParams;
using ExitParams = NoParams;
+/// Defines how the host (editor) should sync document changes to the language
+/// server.
+enum class TextDocumentSyncKind {
+ /// Documents should not be synced at all.
+ None = 0,
+
+ /// Documents are synced by always sending the full content of the document.
+ Full = 1,
+
+ /// Documents are synced by sending the full content on open. After that
+ /// only incremental updates to the document are send.
+ Incremental = 2,
+};
+
struct CompletionItemClientCapabilities {
/// Client supports snippets as insert text.
bool snippetSupport = false;
@@ -284,7 +301,13 @@
bool fromJSON(const json::Expr &, DidCloseTextDocumentParams &);
struct TextDocumentContentChangeEvent {
- /// The new text of the document.
+ /// The range of the document that changed.
+ llvm::Optional<Range> range;
+
+ /// The length of the range that got replaced.
+ llvm::Optional<int> rangeLength;
+
+ /// The new text of the range/document.
std::string text;
};
bool fromJSON(const json::Expr &, TextDocumentContentChangeEvent &);
@@ -410,13 +433,14 @@
/// The diagnostic's message.
std::string message;
};
+
/// A LSP-specific comparator used to find diagnostic in a container like
/// std:map.
/// We only use the required fields of Diagnostic to do the comparsion to avoid
/// any regression issues from LSP clients (e.g. VScode), see
/// https://git.io/vbr29
struct LSPDiagnosticCompare {
- bool operator()(const Diagnostic& LHS, const Diagnostic& RHS) const {
+ bool operator()(const Diagnostic &LHS, const Diagnostic &RHS) const {
return std::tie(LHS.range, LHS.message) < std::tie(RHS.range, RHS.message);
}
};
@@ -741,4 +765,14 @@
} // namespace clangd
} // namespace clang
+namespace llvm {
+template <> struct format_provider<clang::clangd::Position> {
+ static void format(const clang::clangd::Position &Pos, raw_ostream &OS,
+ StringRef Style) {
+ assert(Style.empty() && "style modifiers for this type are not supported");
+ OS << Pos;
+ }
+};
+} // namespace llvm
+
#endif
diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp
index 32cfd2f..f5b4669 100644
--- a/clangd/ProtocolHandlers.cpp
+++ b/clangd/ProtocolHandlers.cpp
@@ -38,16 +38,14 @@
}
JSONRPCDispatcher &Dispatcher;
- JSONOutput *Out;
ProtocolCallbacks *Callbacks;
};
} // namespace
void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
- JSONOutput &Out,
ProtocolCallbacks &Callbacks) {
- HandlerRegisterer Register{Dispatcher, &Out, &Callbacks};
+ HandlerRegisterer Register{Dispatcher, &Callbacks};
Register("initialize", &ProtocolCallbacks::onInitialize);
Register("shutdown", &ProtocolCallbacks::onShutdown);
diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h
index 7599ece..6ed062c 100644
--- a/clangd/ProtocolHandlers.h
+++ b/clangd/ProtocolHandlers.h
@@ -55,7 +55,7 @@
virtual void onChangeConfiguration(DidChangeConfigurationParams &Params) = 0;
};
-void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
+void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
ProtocolCallbacks &Callbacks);
} // namespace clangd
diff --git a/clangd/SourceCode.cpp b/clangd/SourceCode.cpp
index 4495b53..8e1db83 100644
--- a/clangd/SourceCode.cpp
+++ b/clangd/SourceCode.cpp
@@ -9,23 +9,43 @@
#include "SourceCode.h"
#include "clang/Basic/SourceManager.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
namespace clang {
namespace clangd {
using namespace llvm;
-size_t positionToOffset(StringRef Code, Position P) {
+llvm::Expected<size_t> positionToOffset(StringRef Code, Position P,
+ bool AllowColumnsBeyondLineLength) {
if (P.line < 0)
- return 0;
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Line value can't be negative ({0})", P.line),
+ llvm::errc::invalid_argument);
+ if (P.character < 0)
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Character value can't be negative ({0})", P.character),
+ llvm::errc::invalid_argument);
size_t StartOfLine = 0;
for (int I = 0; I != P.line; ++I) {
size_t NextNL = Code.find('\n', StartOfLine);
if (NextNL == StringRef::npos)
- return Code.size();
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Line value is out of range ({0})", P.line),
+ llvm::errc::invalid_argument);
StartOfLine = NextNL + 1;
}
+
+ size_t NextNL = Code.find('\n', StartOfLine);
+ if (NextNL == StringRef::npos)
+ NextNL = Code.size();
+
+ if (StartOfLine + P.character > NextNL && !AllowColumnsBeyondLineLength)
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Character value is out of range ({0})", P.character),
+ llvm::errc::invalid_argument);
// FIXME: officially P.character counts UTF-16 code units, not UTF-8 bytes!
- return std::min(Code.size(), StartOfLine + std::max(0, P.character));
+ return std::min(NextNL, StartOfLine + P.character);
}
Position offsetToPosition(StringRef Code, size_t Offset) {
@@ -48,5 +68,13 @@
return P;
}
+Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) {
+ // Clang is 1-based, LSP uses 0-based indexes.
+ Position Begin = sourceLocToPosition(SM, R.getBegin());
+ Position End = sourceLocToPosition(SM, R.getEnd());
+
+ return {Begin, End};
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/SourceCode.h b/clangd/SourceCode.h
index cd0578b..6a2efd9 100644
--- a/clangd/SourceCode.h
+++ b/clangd/SourceCode.h
@@ -22,14 +22,33 @@
namespace clangd {
/// Turn a [line, column] pair into an offset in Code.
-size_t positionToOffset(llvm::StringRef Code, Position P);
+///
+/// If the character value is greater than the line length, the behavior depends
+/// on AllowColumnsBeyondLineLength:
+///
+/// - if true: default back to the end of the line
+/// - if false: return an error
+///
+/// If the line number is greater than the number of lines in the document,
+/// always return an error.
+///
+/// The returned value is in the range [0, Code.size()].
+llvm::Expected<size_t>
+positionToOffset(llvm::StringRef Code, Position P,
+ bool AllowColumnsBeyondLineLength = true);
/// Turn an offset in Code into a [line, column] pair.
+/// FIXME: This should return an error if the offset is invalid.
Position offsetToPosition(llvm::StringRef Code, size_t Offset);
/// Turn a SourceLocation into a [line, column] pair.
+/// FIXME: This should return an error if the location is invalid.
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc);
+// Converts a half-open clang source range to an LSP range.
+// Note that clang also uses closed source ranges, which this can't handle!
+Range halfOpenToRange(const SourceManager &SM, CharSourceRange R);
+
} // namespace clangd
} // namespace clang
#endif
diff --git a/clangd/TUScheduler.cpp b/clangd/TUScheduler.cpp
index 5dad70a..51b13e2 100644
--- a/clangd/TUScheduler.cpp
+++ b/clangd/TUScheduler.cpp
@@ -85,7 +85,7 @@
~ASTWorker();
void update(ParseInputs Inputs, WantDiagnostics,
- UniqueFunction<void(std::vector<DiagWithFixIts>)> OnUpdated);
+ UniqueFunction<void(std::vector<Diag>)> OnUpdated);
void runWithAST(llvm::StringRef Name,
UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action);
bool blockUntilIdle(Deadline Timeout) const;
@@ -135,8 +135,8 @@
// Result of getUsedBytes() after the last rebuild or read of AST.
std::size_t LastASTSize; /* GUARDED_BY(Mutex) */
// Set to true to signal run() to finish processing.
- bool Done; /* GUARDED_BY(Mutex) */
- std::deque<Request> Requests; /* GUARDED_BY(Mutex) */
+ bool Done; /* GUARDED_BY(Mutex) */
+ std::deque<Request> Requests; /* GUARDED_BY(Mutex) */
mutable std::condition_variable RequestsCV;
};
@@ -210,9 +210,8 @@
#endif
}
-void ASTWorker::update(
- ParseInputs Inputs, WantDiagnostics WantDiags,
- UniqueFunction<void(std::vector<DiagWithFixIts>)> OnUpdated) {
+void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
+ UniqueFunction<void(std::vector<Diag>)> OnUpdated) {
auto Task = [=](decltype(OnUpdated) OnUpdated) mutable {
FileInputs = Inputs;
auto Diags = AST.rebuild(std::move(Inputs));
@@ -408,7 +407,8 @@
struct TUScheduler::FileData {
/// Latest inputs, passed to TUScheduler::update().
- ParseInputs Inputs;
+ std::string Contents;
+ tooling::CompileCommand Command;
ASTWorkerHandle Worker;
};
@@ -447,9 +447,9 @@
return true;
}
-void TUScheduler::update(
- PathRef File, ParseInputs Inputs, WantDiagnostics WantDiags,
- UniqueFunction<void(std::vector<DiagWithFixIts>)> OnUpdated) {
+void TUScheduler::update(PathRef File, ParseInputs Inputs,
+ WantDiagnostics WantDiags,
+ UniqueFunction<void(std::vector<Diag>)> OnUpdated) {
std::unique_ptr<FileData> &FD = Files[File];
if (!FD) {
// Create a new worker to process the AST-related tasks.
@@ -457,9 +457,11 @@
File, WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier,
CppFile(File, StorePreamblesInMemory, PCHOps, ASTCallback),
UpdateDebounce);
- FD = std::unique_ptr<FileData>(new FileData{Inputs, std::move(Worker)});
+ FD = std::unique_ptr<FileData>(new FileData{
+ Inputs.Contents, Inputs.CompileCommand, std::move(Worker)});
} else {
- FD->Inputs = Inputs;
+ FD->Contents = Inputs.Contents;
+ FD->Command = Inputs.CompileCommand;
}
FD->Worker->update(std::move(Inputs), WantDiags, std::move(OnUpdated));
}
@@ -501,26 +503,28 @@
SPAN_ATTACH(Tracer, "file", File);
std::shared_ptr<const PreambleData> Preamble =
It->second->Worker->getPossiblyStalePreamble();
- Action(InputsAndPreamble{It->second->Inputs, Preamble.get()});
+ Action(InputsAndPreamble{It->second->Contents, It->second->Command,
+ Preamble.get()});
return;
}
- ParseInputs InputsCopy = It->second->Inputs;
std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
- auto Task = [InputsCopy, Worker, this](std::string Name, std::string File,
- Context Ctx,
- decltype(Action) Action) mutable {
+ auto Task = [Worker, this](std::string Name, std::string File,
+ std::string Contents,
+ tooling::CompileCommand Command, Context Ctx,
+ decltype(Action) Action) mutable {
std::lock_guard<Semaphore> BarrierLock(Barrier);
WithContext Guard(std::move(Ctx));
trace::Span Tracer(Name);
SPAN_ATTACH(Tracer, "file", File);
std::shared_ptr<const PreambleData> Preamble =
Worker->getPossiblyStalePreamble();
- Action(InputsAndPreamble{InputsCopy, Preamble.get()});
+ Action(InputsAndPreamble{Contents, Command, Preamble.get()});
};
PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File),
Bind(Task, std::string(Name), std::string(File),
+ It->second->Contents, It->second->Command,
Context::current().clone(), std::move(Action)));
}
diff --git a/clangd/TUScheduler.h b/clangd/TUScheduler.h
index 828db6e..aea669d 100644
--- a/clangd/TUScheduler.h
+++ b/clangd/TUScheduler.h
@@ -29,7 +29,8 @@
};
struct InputsAndPreamble {
- const ParseInputs &Inputs;
+ llvm::StringRef Contents;
+ const tooling::CompileCommand &Command;
const PreambleData *Preamble;
};
@@ -63,7 +64,7 @@
/// \p File was not part of it before.
/// FIXME(ibiryukov): remove the callback from this function.
void update(PathRef File, ParseInputs Inputs, WantDiagnostics WD,
- UniqueFunction<void(std::vector<DiagWithFixIts>)> OnUpdated);
+ UniqueFunction<void(std::vector<Diag>)> OnUpdated);
/// Remove \p File from the list of tracked files and schedule removal of its
/// resources.
@@ -76,16 +77,19 @@
/// If an error occurs during processing, it is forwarded to the \p Action
/// callback.
void runWithAST(llvm::StringRef Name, PathRef File,
- UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action);
+ Callback<InputsAndAST> Action);
- /// Schedule an async read of the Preamble. Preamble passed to \p Action may
- /// be built for any version of the file, callers must not rely on it being
- /// consistent with the current version of the file.
+ /// Schedule an async read of the Preamble.
+ /// The preamble may be stale, generated from an older version of the file.
+ /// Reading from locations in the preamble may cause the files to be re-read.
+ /// This gives callers two options:
+ /// - validate that the preamble is still valid, and only use it in this case
+ /// - accept that preamble contents may be outdated, and try to avoid reading
+ /// source code from headers.
/// If an error occurs during processing, it is forwarded to the \p Action
/// callback.
- void runWithPreamble(
- llvm::StringRef Name, PathRef File,
- UniqueFunction<void(llvm::Expected<InputsAndPreamble>)> Action);
+ void runWithPreamble(llvm::StringRef Name, PathRef File,
+ Callback<InputsAndPreamble> Action);
/// Wait until there are no scheduled or running tasks.
/// Mostly useful for synchronizing tests.
diff --git a/clangd/XRefs.cpp b/clangd/XRefs.cpp
index 6cc27ec..b502efe 100644
--- a/clangd/XRefs.cpp
+++ b/clangd/XRefs.cpp
@@ -7,6 +7,7 @@
//
//===---------------------------------------------------------------------===//
#include "XRefs.h"
+#include "AST.h"
#include "Logger.h"
#include "SourceCode.h"
#include "URI.h"
@@ -126,6 +127,16 @@
MacroInfo *MacroInf = MacroDef.getMacroInfo();
if (MacroInf) {
MacroInfos.push_back(MacroDecl{IdentifierInfo->getName(), MacroInf});
+ // Clear all collected delcarations if this is a macro search.
+ //
+ // In theory, there should be no declarataions being collected when we
+ // search a source location that refers to a macro.
+ // The occurrence location returned by `handleDeclOccurence` is
+ // limited (FID, Offset are from expansion location), we will collect
+ // all declarations inside the macro.
+ //
+ // FIXME: Avoid adding decls from inside macros in handlDeclOccurence.
+ Decls.clear();
}
}
}
@@ -133,7 +144,7 @@
};
llvm::Optional<Location>
-getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
+makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
const LangOptions &LangOpts = AST.getASTContext().getLangOpts();
SourceLocation LocStart = ValSourceRange.getBegin();
@@ -174,6 +185,18 @@
SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE);
+ std::vector<Location> Result;
+ // Handle goto definition for #include.
+ for (auto &IncludeLoc : AST.getInclusionLocations()) {
+ Range R = IncludeLoc.first;
+ Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg);
+
+ if (R.contains(Pos))
+ Result.push_back(Location{URIForFile{IncludeLoc.second}, {}});
+ }
+ if (!Result.empty())
+ return Result;
+
auto DeclMacrosFinder = std::make_shared<DeclarationAndMacrosFinder>(
llvm::errs(), SourceLocationBeg, AST.getASTContext(),
AST.getPreprocessor());
@@ -187,31 +210,21 @@
std::vector<const Decl *> Decls = DeclMacrosFinder->takeDecls();
std::vector<MacroDecl> MacroInfos = DeclMacrosFinder->takeMacroInfos();
- std::vector<Location> Result;
- for (auto Item : Decls) {
- auto L = getDeclarationLocation(AST, Item->getSourceRange());
+ for (auto D : Decls) {
+ auto Loc = findNameLoc(D);
+ auto L = makeLocation(AST, SourceRange(Loc, Loc));
if (L)
Result.push_back(*L);
}
for (auto Item : MacroInfos) {
- SourceRange SR(Item.Info->getDefinitionLoc(),
- Item.Info->getDefinitionEndLoc());
- auto L = getDeclarationLocation(AST, SR);
+ auto Loc = Item.Info->getDefinitionLoc();
+ auto L = makeLocation(AST, SourceRange(Loc, Loc));
if (L)
Result.push_back(*L);
}
- /// Process targets for paths inside #include directive.
- for (auto &IncludeLoc : AST.getInclusionLocations()) {
- Range R = IncludeLoc.first;
- Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg);
-
- if (R.contains(Pos))
- Result.push_back(Location{URIForFile{IncludeLoc.second}, {}});
- }
-
return Result;
}
diff --git a/clangd/clients/clangd-vscode/.gitignore b/clangd/clients/clangd-vscode/.gitignore
index 8e5962e..1294fe2 100644
--- a/clangd/clients/clangd-vscode/.gitignore
+++ b/clangd/clients/clangd-vscode/.gitignore
@@ -1,2 +1,3 @@
out
-node_modules
\ No newline at end of file
+node_modules
+package-lock.json
diff --git a/clangd/clients/clangd-vscode/package.json b/clangd/clients/clangd-vscode/package.json
index a25cf45..18d8bc5 100644
--- a/clangd/clients/clangd-vscode/package.json
+++ b/clangd/clients/clangd-vscode/package.json
@@ -2,7 +2,7 @@
"name": "vscode-clangd",
"displayName": "vscode-clangd",
"description": "Clang Language Server",
- "version": "0.0.4",
+ "version": "0.0.5",
"publisher": "llvm-vs-code-extensions",
"homepage": "https://clang.llvm.org/extra/clangd.html",
"engines": {
diff --git a/clangd/clients/clangd-vscode/src/extension.ts b/clangd/clients/clangd-vscode/src/extension.ts
index 3f9e95f..a126a19 100644
--- a/clangd/clients/clangd-vscode/src/extension.ts
+++ b/clangd/clients/clangd-vscode/src/extension.ts
@@ -1,12 +1,13 @@
import * as vscode from 'vscode';
import * as vscodelc from 'vscode-languageclient';
+import { realpathSync } from 'fs';
/**
* Method to get workspace configuration option
* @param option name of the option (e.g. for clangd.path should be path)
* @param defaultValue default value to return if option is not set
*/
-function getConfig<T>(option: string, defaultValue?: any) : T {
+function getConfig<T>(option: string, defaultValue?: any): T {
const config = vscode.workspace.getConfiguration('clangd');
return config.get<T>(option, defaultValue);
}
@@ -24,18 +25,29 @@
};
const traceFile = getConfig<string>('trace');
if (!!traceFile) {
- const trace = {CLANGD_TRACE : traceFile};
- clangd.options = {env : {...process.env, ...trace}};
+ const trace = { CLANGD_TRACE: traceFile };
+ clangd.options = { env: { ...process.env, ...trace } };
}
const serverOptions: vscodelc.ServerOptions = clangd;
const filePattern: string = '**/*.{' +
- ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'].join() + '}';
+ ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'].join() + '}';
const clientOptions: vscodelc.LanguageClientOptions = {
// Register the server for C/C++ files
- documentSelector: [{scheme: 'file', pattern: filePattern}],
+ documentSelector: [{ scheme: 'file', pattern: filePattern }],
synchronize: !syncFileEvents ? undefined : {
fileEvents: vscode.workspace.createFileSystemWatcher(filePattern)
+ },
+ // Resolve symlinks for all files provided by clangd.
+ // This is a workaround for a bazel + clangd issue - bazel produces a symlink tree to build in,
+ // and when navigating to the included file, clangd passes its path inside the symlink tree
+ // rather than its filesystem path.
+ // FIXME: remove this once clangd knows enough about bazel to resolve the
+ // symlinks where needed (or if this causes problems for other workflows).
+ uriConverters: {
+ code2Protocol: (value: vscode.Uri) => value.toString(),
+ protocol2Code: (value: string) =>
+ vscode.Uri.file(realpathSync(vscode.Uri.parse(value).fsPath))
}
};
diff --git a/clangd/fuzzer/ClangdFuzzer.cpp b/clangd/fuzzer/ClangdFuzzer.cpp
index 266da4e..3321222 100644
--- a/clangd/fuzzer/ClangdFuzzer.cpp
+++ b/clangd/fuzzer/ClangdFuzzer.cpp
@@ -14,6 +14,7 @@
//===----------------------------------------------------------------------===//
#include "ClangdLSPServer.h"
+#include "ClangdServer.h"
#include "CodeComplete.h"
#include <sstream>
@@ -21,12 +22,10 @@
clang::clangd::JSONOutput Out(llvm::nulls(), llvm::nulls(), nullptr);
clang::clangd::CodeCompleteOptions CCOpts;
CCOpts.EnableSnippets = false;
+ clang::clangd::ClangdServer::Options Opts;
// Initialize and run ClangdLSPServer.
- clang::clangd::ClangdLSPServer LSPServer(
- Out, clang::clangd::getDefaultAsyncThreadsCount(),
- /*StorePreamblesInMemory=*/false, CCOpts, llvm::None, llvm::None,
- /*BuildDynamicSymbolIndex=*/false);
+ clang::clangd::ClangdLSPServer LSPServer(Out, CCOpts, llvm::None, Opts);
std::istringstream In(std::string(reinterpret_cast<char *>(data), size));
LSPServer.run(In);
diff --git a/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
index b4f7f0b..7d62cf1 100644
--- a/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
+++ b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
@@ -99,6 +99,7 @@
auto CollectorOpts = SymbolCollector::Options();
CollectorOpts.FallbackDir = AssumedHeaderDir;
CollectorOpts.CollectIncludePath = true;
+ CollectorOpts.CountReferences = true;
auto Includes = llvm::make_unique<CanonicalIncludes>();
addSystemHeadersMapping(Includes.get());
CollectorOpts.Includes = Includes.get();
diff --git a/clangd/index/FileIndex.cpp b/clangd/index/FileIndex.cpp
index 4f1f10a..2cf8b12 100644
--- a/clangd/index/FileIndex.cpp
+++ b/clangd/index/FileIndex.cpp
@@ -26,12 +26,14 @@
// AST at this point, but we also need preprocessor callbacks (e.g.
// CommentHandler for IWYU pragma) to canonicalize includes.
CollectorOpts.CollectIncludePath = false;
+ CollectorOpts.CountReferences = false;
auto Collector = std::make_shared<SymbolCollector>(std::move(CollectorOpts));
Collector->setPreprocessor(std::move(PP));
index::IndexingOptions IndexOpts;
+ // We only need declarations, because we don't count references.
IndexOpts.SystemSymbolFilter =
- index::IndexingOptions::SystemSymbolFilterKind::All;
+ index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly;
IndexOpts.IndexFunctionLocals = false;
index::indexTopLevelDecls(Ctx, Decls, Collector, IndexOpts);
@@ -91,5 +93,11 @@
return Index.fuzzyFind(Req, Callback);
}
+void FileIndex::lookup(
+ const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const {
+ Index.lookup(Req, Callback);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/index/FileIndex.h b/clangd/index/FileIndex.h
index cbb4755..d6d3631 100644
--- a/clangd/index/FileIndex.h
+++ b/clangd/index/FileIndex.h
@@ -63,6 +63,9 @@
fuzzyFind(const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override;
+ void lookup(const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const override;
+
private:
FileSymbols FSymbols;
MemIndex Index;
diff --git a/clangd/index/Index.h b/clangd/index/Index.h
index 629db53..42f23ce 100644
--- a/clangd/index/Index.h
+++ b/clangd/index/Index.h
@@ -1,4 +1,4 @@
-//===--- Symbol.h -----------------------------------------------*- C++-*-===//
+//===--- Index.h ------------------------------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
@@ -56,16 +56,20 @@
}
private:
+ static constexpr unsigned HashByteLength = 20;
+
friend llvm::hash_code hash_value(const SymbolID &ID) {
// We already have a good hash, just return the first bytes.
- static_assert(sizeof(size_t) <= 20, "size_t longer than SHA1!");
- return *reinterpret_cast<const size_t *>(ID.HashValue.data());
+ static_assert(sizeof(size_t) <= HashByteLength, "size_t longer than SHA1!");
+ size_t Result;
+ memcpy(&Result, ID.HashValue.data(), sizeof(size_t));
+ return llvm::hash_code(Result);
}
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const SymbolID &ID);
friend void operator>>(llvm::StringRef Str, SymbolID &ID);
- std::array<uint8_t, 20> HashValue;
+ std::array<uint8_t, HashByteLength> HashValue;
};
// Write SymbolID into the given stream. SymbolID is encoded as a 40-bytes
@@ -131,6 +135,9 @@
// * For non-inline functions, the canonical declaration typically appears
// in the ".h" file corresponding to the definition.
SymbolLocation CanonicalDeclaration;
+ // The number of translation units that reference this symbol from their main
+ // file. This number is only meaningful if aggregated in an index.
+ unsigned References = 0;
/// A brief description of the symbol that can be displayed in the completion
/// candidate list. For example, "Foo(X x, Y y) const" is a labal for a
@@ -245,6 +252,10 @@
size_t MaxCandidateCount = UINT_MAX;
};
+struct LookupRequest {
+ llvm::DenseSet<SymbolID> IDs;
+};
+
/// \brief Interface for symbol indexes that can be used for searching or
/// matching symbols among a set of symbols based on names or unique IDs.
class SymbolIndex {
@@ -260,8 +271,14 @@
fuzzyFind(const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const = 0;
+ /// Looks up symbols with any of the given symbol IDs and applies \p Callback
+ /// on each matched symbol.
+ /// The returned symbol must be deep-copied if it's used outside Callback.
+ virtual void
+ lookup(const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const = 0;
+
// FIXME: add interfaces for more index use cases:
- // - Symbol getSymbolInfo(SymbolID);
// - getAllOccurrences(SymbolID);
};
diff --git a/clangd/index/MemIndex.cpp b/clangd/index/MemIndex.cpp
index df7dbc8..6a4d307 100644
--- a/clangd/index/MemIndex.cpp
+++ b/clangd/index/MemIndex.cpp
@@ -60,6 +60,15 @@
return More;
}
+void MemIndex::lookup(const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const {
+ for (const auto &ID : Req.IDs) {
+ auto I = Index.find(ID);
+ if (I != Index.end())
+ Callback(*I->second);
+ }
+}
+
std::unique_ptr<SymbolIndex> MemIndex::build(SymbolSlab Slab) {
struct Snapshot {
SymbolSlab Slab;
diff --git a/clangd/index/MemIndex.h b/clangd/index/MemIndex.h
index e2f7f0e..3147a6c 100644
--- a/clangd/index/MemIndex.h
+++ b/clangd/index/MemIndex.h
@@ -31,6 +31,10 @@
fuzzyFind(const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override;
+ virtual void
+ lookup(const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const override;
+
private:
std::shared_ptr<std::vector<const Symbol *>> Symbols;
// Index is a set of symbols that are deduplicated by symbol IDs.
diff --git a/clangd/index/Merge.cpp b/clangd/index/Merge.cpp
index 57c62c5..41d5345 100644
--- a/clangd/index/Merge.cpp
+++ b/clangd/index/Merge.cpp
@@ -52,6 +52,28 @@
return More;
}
+ void
+ lookup(const LookupRequest &Req,
+ llvm::function_ref<void(const Symbol &)> Callback) const override {
+ SymbolSlab::Builder B;
+
+ Dynamic->lookup(Req, [&](const Symbol &S) { B.insert(S); });
+
+ auto RemainingIDs = Req.IDs;
+ Symbol::Details Scratch;
+ Static->lookup(Req, [&](const Symbol &S) {
+ const Symbol *Sym = B.find(S.ID);
+ RemainingIDs.erase(S.ID);
+ if (!Sym)
+ Callback(S);
+ else
+ Callback(mergeSymbol(*Sym, S, &Scratch));
+ });
+ for (const auto &ID : RemainingIDs)
+ if (const Symbol *Sym = B.find(ID))
+ Callback(*Sym);
+ }
+
private:
const SymbolIndex *Dynamic, *Static;
};
@@ -73,6 +95,7 @@
S.Definition = O.Definition;
if (!S.CanonicalDeclaration)
S.CanonicalDeclaration = O.CanonicalDeclaration;
+ S.References += O.References;
if (S.CompletionLabel == "")
S.CompletionLabel = O.CompletionLabel;
if (S.CompletionFilterText == "")
diff --git a/clangd/index/SymbolCollector.cpp b/clangd/index/SymbolCollector.cpp
index fe994d0..c595e56 100644
--- a/clangd/index/SymbolCollector.cpp
+++ b/clangd/index/SymbolCollector.cpp
@@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "SymbolCollector.h"
+#include "../AST.h"
#include "../CodeCompletionStrings.h"
#include "../Logger.h"
#include "../URI.h"
@@ -115,10 +116,16 @@
// * enum constants in unscoped enum decl (e.g. "red" in "enum {red};")
auto InTopLevelScope = hasDeclContext(
anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl()));
+ // Don't index template specializations.
+ auto IsSpecialization =
+ anyOf(functionDecl(isExplicitTemplateSpecialization()),
+ cxxRecordDecl(isExplicitTemplateSpecialization()),
+ varDecl(isExplicitTemplateSpecialization()));
if (match(decl(allOf(unless(isExpansionInMainFile()),
anyOf(InTopLevelScope,
hasDeclContext(enumDecl(InTopLevelScope,
- unless(isScoped())))))),
+ unless(isScoped())))),
+ unless(IsSpecialization))),
*ND, *ASTCtx)
.empty())
return true;
@@ -178,30 +185,16 @@
llvm::Optional<SymbolLocation> getSymbolLocation(
const NamedDecl &D, SourceManager &SM, const SymbolCollector::Options &Opts,
const clang::LangOptions &LangOpts, std::string &FileURIStorage) {
- SourceLocation SpellingLoc = SM.getSpellingLoc(D.getLocation());
- if (D.getLocation().isMacroID()) {
- std::string PrintLoc = SpellingLoc.printToString(SM);
- if (llvm::StringRef(PrintLoc).startswith("<scratch") ||
- llvm::StringRef(PrintLoc).startswith("<command line>")) {
- // We use the expansion location for the following symbols, as spelling
- // locations of these symbols are not interesting to us:
- // * symbols formed via macro concatenation, the spelling location will
- // be "<scratch space>"
- // * symbols controlled and defined by a compile command-line option
- // `-DName=foo`, the spelling location will be "<command line>".
- SpellingLoc = SM.getExpansionRange(D.getLocation()).first;
- }
- }
-
- auto U = toURI(SM, SM.getFilename(SpellingLoc), Opts);
+ SourceLocation NameLoc = findNameLoc(&D);
+ auto U = toURI(SM, SM.getFilename(NameLoc), Opts);
if (!U)
return llvm::None;
FileURIStorage = std::move(*U);
SymbolLocation Result;
Result.FileURI = FileURIStorage;
- Result.StartOffset = SM.getFileOffset(SpellingLoc);
+ Result.StartOffset = SM.getFileOffset(NameLoc);
Result.EndOffset = Result.StartOffset + clang::Lexer::MeasureTokenLength(
- SpellingLoc, SM, LangOpts);
+ NameLoc, SM, LangOpts);
return std::move(Result);
}
@@ -209,7 +202,7 @@
// in a header file, in which case clangd would prefer to use ND as a canonical
// declaration.
// FIXME: handle symbol types that are not TagDecl (e.g. functions), if using
-// the the first seen declaration as canonical declaration is not a good enough
+// the first seen declaration as canonical declaration is not a good enough
// heuristic.
bool isPreferredDeclaration(const NamedDecl &ND, index::SymbolRoleSet Roles) {
using namespace clang::ast_matchers;
@@ -235,39 +228,60 @@
ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset,
index::IndexDataConsumer::ASTNodeInfo ASTNode) {
assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set.");
+ assert(CompletionAllocator && CompletionTUInfo);
+ const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D);
+ if (!ND)
+ return true;
- // FIXME: collect all symbol references.
+ // Mark D as referenced if this is a reference coming from the main file.
+ // D may not be an interesting symbol, but it's cheaper to check at the end.
+ if (Opts.CountReferences &&
+ (Roles & static_cast<unsigned>(index::SymbolRole::Reference)) &&
+ ASTCtx->getSourceManager().getMainFileID() == FID)
+ ReferencedDecls.insert(ND);
+
+ // Don't continue indexing if this is a mere reference.
if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
return true;
+ if (shouldFilterDecl(ND, ASTCtx, Opts))
+ return true;
- assert(CompletionAllocator && CompletionTUInfo);
+ llvm::SmallString<128> USR;
+ if (index::generateUSRForDecl(ND, USR))
+ return true;
+ SymbolID ID(USR);
- if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D)) {
- if (shouldFilterDecl(ND, ASTCtx, Opts))
- return true;
- llvm::SmallString<128> USR;
- if (index::generateUSRForDecl(ND, USR))
- return true;
+ const NamedDecl &OriginalDecl = *cast<NamedDecl>(ASTNode.OrigD);
+ const Symbol *BasicSymbol = Symbols.find(ID);
+ if (!BasicSymbol) // Regardless of role, ND is the canonical declaration.
+ BasicSymbol = addDeclaration(*ND, std::move(ID));
+ else if (isPreferredDeclaration(OriginalDecl, Roles))
+ // If OriginalDecl is preferred, replace the existing canonical
+ // declaration (e.g. a class forward declaration). There should be at most
+ // one duplicate as we expect to see only one preferred declaration per
+ // TU, because in practice they are definitions.
+ BasicSymbol = addDeclaration(OriginalDecl, std::move(ID));
- const NamedDecl &OriginalDecl = *cast<NamedDecl>(ASTNode.OrigD);
- auto ID = SymbolID(USR);
- const Symbol *BasicSymbol = Symbols.find(ID);
- if (!BasicSymbol) // Regardless of role, ND is the canonical declaration.
- BasicSymbol = addDeclaration(*ND, std::move(ID));
- else if (isPreferredDeclaration(OriginalDecl, Roles))
- // If OriginalDecl is preferred, replace the existing canonical
- // declaration (e.g. a class forward declaration). There should be at most
- // one duplicate as we expect to see only one preferred declaration per
- // TU, because in practice they are definitions.
- BasicSymbol = addDeclaration(OriginalDecl, std::move(ID));
-
- if (Roles & static_cast<unsigned>(index::SymbolRole::Definition))
- addDefinition(OriginalDecl, *BasicSymbol);
- }
+ if (Roles & static_cast<unsigned>(index::SymbolRole::Definition))
+ addDefinition(OriginalDecl, *BasicSymbol);
return true;
}
+void SymbolCollector::finish() {
+ // At the end of the TU, add 1 to the refcount of the ReferencedDecls.
+ for (const auto *ND : ReferencedDecls) {
+ llvm::SmallString<128> USR;
+ if (!index::generateUSRForDecl(ND, USR))
+ if (const auto *S = Symbols.find(SymbolID(USR))) {
+ Symbol Inc = *S;
+ ++Inc.References;
+ Symbols.insert(Inc);
+ }
+ }
+ ReferencedDecls.clear();
+}
+
const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
SymbolID ID) {
auto &SM = ND.getASTContext().getSourceManager();
diff --git a/clangd/index/SymbolCollector.h b/clangd/index/SymbolCollector.h
index c18f74a..7237e39 100644
--- a/clangd/index/SymbolCollector.h
+++ b/clangd/index/SymbolCollector.h
@@ -45,6 +45,8 @@
/// If set, this is used to map symbol #include path to a potentially
/// different #include path.
const CanonicalIncludes *Includes = nullptr;
+ // Populate the Symbol.References field.
+ bool CountReferences = false;
};
SymbolCollector(Options Opts);
@@ -63,6 +65,8 @@
SymbolSlab takeSymbols() { return std::move(Symbols).build(); }
+ void finish() override;
+
private:
const Symbol *addDeclaration(const NamedDecl &, SymbolID);
void addDefinition(const NamedDecl &, const Symbol &DeclSymbol);
@@ -74,6 +78,8 @@
std::shared_ptr<GlobalCodeCompletionAllocator> CompletionAllocator;
std::unique_ptr<CodeCompletionTUInfo> CompletionTUInfo;
Options Opts;
+ // Decls referenced from the current TU, flushed on finish().
+ llvm::DenseSet<const NamedDecl *> ReferencedDecls;
};
} // namespace clangd
diff --git a/clangd/index/SymbolYAML.cpp b/clangd/index/SymbolYAML.cpp
index 379cb33..307b0af 100644
--- a/clangd/index/SymbolYAML.cpp
+++ b/clangd/index/SymbolYAML.cpp
@@ -100,6 +100,7 @@
IO.mapOptional("CanonicalDeclaration", Sym.CanonicalDeclaration,
SymbolLocation());
IO.mapOptional("Definition", Sym.Definition, SymbolLocation());
+ IO.mapOptional("References", Sym.References, 0u);
IO.mapRequired("CompletionLabel", Sym.CompletionLabel);
IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText);
IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText);
diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst
index ef7e74e..e71e616 100644
--- a/docs/ReleaseNotes.rst
+++ b/docs/ReleaseNotes.rst
@@ -57,96 +57,136 @@
Improvements to clang-tidy
--------------------------
-- New `bugprone-throw-keyword-missing
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-throw-keyword-missing.html>`_ check
+- New module `abseil` for checks related to the `Abseil <https://abseil.io>`_
+ library.
+
+- New module ``portability``.
+
+- New module ``zircon`` for checks related to Fuchsia's Zircon kernel.
+
+- New :doc:`bugprone-throw-keyword-missing
+ <clang-tidy/checks/bugprone-throw-keyword-missing>` check
Diagnoses when a temporary object that appears to be an exception is
constructed but not thrown.
-- New `cppcoreguidelines-avoid-goto
- <http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-avoid-goto.html>`_ check
+- New :doc:`bugprone-unused-return-value
+ <clang-tidy/checks/bugprone-unused-return-value>` check
+
+ Warns on unused function return values.
+
+- New :doc:`cppcoreguidelines-avoid-goto
+ <clang-tidy/checks/cppcoreguidelines-avoid-goto>` check
The usage of ``goto`` for control flow is error prone and should be replaced
with looping constructs. Every backward jump is rejected. Forward jumps are
only allowed in nested loops.
-- New `fuchsia-multiple-inheritance
- <http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-multiple-inheritance.html>`_ check
+- New :doc:`fuchsia-multiple-inheritance
+ <clang-tidy/checks/fuchsia-multiple-inheritance>` check
Warns if a class inherits from multiple classes that are not pure virtual.
-- New `fuchsia-statically-constructed-objects
- <http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-statically-constructed-objects.html>`_ check
+- New :doc:`abseil-string-find-startswith
+ <clang-tidy/checks/abseil-string-find-startswith>` check
+
+ Checks whether a ``std::string::find()`` result is compared with 0, and
+ suggests replacing with ``absl::StartsWith()``.
+
+- New :doc:`fuchsia-statically-constructed-objects
+ <clang-tidy/checks/fuchsia-statically-constructed-objects>` check
Warns if global, non-trivial objects with static storage are constructed,
unless the object is statically initialized with a ``constexpr`` constructor
or has no explicit constructor.
-
-- New `fuchsia-trailing-return
- <http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-trailing-return.html>`_ check
- Functions that have trailing returns are disallowed, except for those
- using ``decltype`` specifiers and lambda with otherwise unutterable
+- New :doc:`fuchsia-trailing-return
+ <clang-tidy/checks/fuchsia-trailing-return>` check
+
+ Functions that have trailing returns are disallowed, except for those
+ using ``decltype`` specifiers and lambda with otherwise unutterable
return types.
-- New `modernize-use-uncaught-exceptions
- <http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-uncaught-exceptions.html>`_ check
+- New :doc:`hicpp-multiway-paths-covered
+ <clang-tidy/checks/hicpp-multiway-paths-covered>` check
+
+ Checks on ``switch`` and ``if`` - ``else if`` constructs that do not cover all possible code paths.
+
+- New :doc:`modernize-use-uncaught-exceptions
+ <clang-tidy/checks/modernize-use-uncaught-exceptions>` check
Finds and replaces deprecated uses of ``std::uncaught_exception`` to
``std::uncaught_exceptions``.
-- New `readability-simd-intrinsics
- <http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html>`_ check
+- New :doc:`portability-simd-intrinsics
+ <clang-tidy/checks/portability-simd-intrinsics>` check
- Warns if SIMD intrinsics are used which can be replaced by
+ Warns or suggests alternatives if SIMD intrinsics are used which can be replaced by
``std::experimental::simd`` operations.
-- New alias `hicpp-avoid-goto
- <http://clang.llvm.org/extra/clang-tidy/checks/hicpp-avoid-goto.html>`_ to
- `cppcoreguidelines-avoid-goto <http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-avoid-goto.html>`_
+- New :doc:`zircon-temporary-objects
+ <clang-tidy/checks/zircon-temporary-objects>` check
+
+ Warns on construction of specific temporary objects in the Zircon kernel.
+
+- New alias :doc:`hicpp-avoid-goto
+ <clang-tidy/checks/hicpp-avoid-goto>` to :doc:`cppcoreguidelines-avoid-goto
+ <clang-tidy/checks/cppcoreguidelines-avoid-goto>`
added.
-- The 'misc-forwarding-reference-overload' check was renamed to `bugprone-forwarding-reference-overload
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-forwarding-reference-overload.html>`_
+- The 'misc-forwarding-reference-overload' check was renamed to :doc:`bugprone-forwarding-reference-overload
+ <clang-tidy/checks/bugprone-forwarding-reference-overload>`
-- The 'misc-incorrect-roundings' check was renamed to `bugprone-incorrect-roundings
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-incorrect-roundings.html>`_
+- The 'misc-incorrect-roundings' check was renamed to :doc:`bugprone-incorrect-roundings
+ <clang-tidy/checks/bugprone-incorrect-roundings>`
-- The 'misc-lambda-function-name' check was renamed to `bugprone-lambda-function-name
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-lambda-function-name.html>`_
+- The 'misc-lambda-function-name' check was renamed to :doc:`bugprone-lambda-function-name
+ <clang-tidy/checks/bugprone-lambda-function-name>`
-- The 'misc-macro-repeated-side-effects' check was renamed to `bugprone-macro-repeated-side-effects
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-macro-repeated-side-effects.html>`_
+- The 'misc-macro-parentheses' check was renamed to :doc:`bugprone-macro-parentheses
+ <clang-tidy/checks/bugprone-macro-parentheses>`
-- The 'misc-misplaced-widening-cast' check was renamed to `bugprone-misplaced-widening-cast
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-misplaced-widening-cast.html>`_
+- The 'misc-macro-repeated-side-effects' check was renamed to :doc:`bugprone-macro-repeated-side-effects
+ <clang-tidy/checks/bugprone-macro-repeated-side-effects>`
-- The 'misc-string-compare' check was renamed to `readability-string-compare
- <http://clang.llvm.org/extra/clang-tidy/checks/readability-string-compare.html>`_
+- The 'misc-misplaced-widening-cast' check was renamed to :doc:`bugprone-misplaced-widening-cast
+ <clang-tidy/checks/bugprone-misplaced-widening-cast>`
-- The 'misc-string-integer-assignment' check was renamed to `bugprone-string-integer-assignment
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-string-integer-assignment.html>`_
+- The 'misc-sizeof-container' check was renamed to :doc:`bugprone-sizeof-container
+ <clang-tidy/checks/bugprone-sizeof-container>`
-- The 'misc-string-literal-with-embedded-nul' check was renamed to `bugprone-string-literal-with-embedded-nul
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-string-literal-with-embedded-nul.html>`_
+- The 'misc-sizeof-expression' check was renamed to :doc:`bugprone-sizeof-expression
+ <clang-tidy/checks/bugprone-sizeof-expression>`
-- The 'misc-suspicious-enum-usage' check was renamed to `bugprone-suspicious-enum-usage
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-suspicious-enum-usage.html>`_
+- The 'misc-string-compare' check was renamed to :doc:`readability-string-compare
+ <clang-tidy/checks/readability-string-compare>`
-- The 'misc-suspicious-missing-comma' check was renamed to `bugprone-suspicious-missing-comma
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-suspicious-missing-comma.html>`_
+- The 'misc-string-integer-assignment' check was renamed to :doc:`bugprone-string-integer-assignment
+ <clang-tidy/checks/bugprone-string-integer-assignment>`
-- The 'misc-suspicious-semicolon' check was renamed to `bugprone-suspicious-semicolon
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-suspicious-semicolon.html>`_
+- The 'misc-string-literal-with-embedded-nul' check was renamed to :doc:`bugprone-string-literal-with-embedded-nul
+ <clang-tidy/checks/bugprone-string-literal-with-embedded-nul>`
-- The 'misc-suspicious-string-compare' check was renamed to `bugprone-suspicious-string-compare
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-suspicious-string-compare.html>`_
+- The 'misc-suspicious-enum-usage' check was renamed to :doc:`bugprone-suspicious-enum-usage
+ <clang-tidy/checks/bugprone-suspicious-enum-usage>`
-- The 'misc-swapped-arguments' check was renamed to `bugprone-swapped-arguments
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-swapped-arguments.html>`_
+- The 'misc-suspicious-missing-comma' check was renamed to :doc:`bugprone-suspicious-missing-comma
+ <clang-tidy/checks/bugprone-suspicious-missing-comma>`
-- The 'misc-undelegated-constructor' check was renamed to `bugprone-undelegated-constructor
- <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-undelegated-constructor.html>`_
+- The 'misc-suspicious-semicolon' check was renamed to :doc:`bugprone-suspicious-semicolon
+ <clang-tidy/checks/bugprone-suspicious-semicolon>`
+
+- The 'misc-suspicious-string-compare' check was renamed to :doc:`bugprone-suspicious-string-compare
+ <clang-tidy/checks/bugprone-suspicious-string-compare>`
+
+- The 'misc-swapped-arguments' check was renamed to :doc:`bugprone-swapped-arguments
+ <clang-tidy/checks/bugprone-swapped-arguments>`
+
+- The 'misc-undelegated-constructor' check was renamed to :doc:`bugprone-undelegated-constructor
+ <clang-tidy/checks/bugprone-undelegated-constructor>`
+
+- The 'misc-unused-raii' check was renamed to :doc:`bugprone-unused-raii
+ <clang-tidy/checks/bugprone-unused-raii>`
Improvements to include-fixer
-----------------------------
diff --git a/docs/clang-doc.rst b/docs/clang-doc.rst
new file mode 100644
index 0000000..f891b71
--- /dev/null
+++ b/docs/clang-doc.rst
@@ -0,0 +1,65 @@
+===================
+Clang-Doc
+===================
+
+.. contents::
+
+.. toctree::
+ :maxdepth: 1
+
+:program:`clang-doc` is a tool for generating C and C++ documenation from
+source code and comments.
+
+The tool is in a very early development stage, so you might encounter bugs and
+crashes. Submitting reports with information about how to reproduce the issue
+to `the LLVM bugtracker <https://llvm.org/bugs>`_ will definitely help the
+project. If you have any ideas or suggestions, please to put a feature request
+there.
+
+Use
+=====
+
+:program:`clang-doc` is a `LibTooling
+<http://clang.llvm.org/docs/LibTooling.html>`_-based tool, and so requires a
+compile command database for your project (for an example of how to do this
+see `How To Setup Tooling For LLVM
+<http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html>`_).
+
+The tool can be used on a single file or multiple files as defined in
+the compile commands database:
+
+.. code-block:: console
+
+ $ clang-doc /path/to/file.cpp -p /path/to/compile/commands
+
+This generates an intermediate representation of the declarations and their
+associated information in the specified TUs, serialized to LLVM bitcode.
+
+As currently implemented, the tool is only able to parse TUs that can be
+stored in-memory. Future additions will extend the current framework to use
+map-reduce frameworks to allow for use with large codebases.
+
+:program:`clang-doc` offers the following options:
+
+.. code-block:: console
+
+ $ clang-doc --help
+ USAGE: clang-doc [options] <source0> [... <sourceN>]
+
+ OPTIONS:
+
+ Generic Options:
+
+ -help - Display available options (-help-hidden for more)
+ -help-list - Display list of available options (-help-list-hidden for more)
+ -version - Display the version of this program
+
+ clang-doc options:
+
+ -doxygen - Use only doxygen-style comments to generate docs.
+ -dump - Dump intermediate results to bitcode file.
+ -extra-arg=<string> - Additional argument to append to the compiler command line
+ -extra-arg-before=<string> - Additional argument to prepend to the compiler command line
+ -omit-filenames - Omit filenames in output.
+ -output=<string> - Directory for outputting generated files.
+ -p=<string> - Build path
diff --git a/docs/clang-tidy/checks/abseil-string-find-startswith.rst b/docs/clang-tidy/checks/abseil-string-find-startswith.rst
new file mode 100644
index 0000000..43f35ac
--- /dev/null
+++ b/docs/clang-tidy/checks/abseil-string-find-startswith.rst
@@ -0,0 +1,41 @@
+.. title:: clang-tidy - abseil-string-find-startswith
+
+abseil-string-find-startswith
+=============================
+
+Checks whether a ``std::string::find()`` result is compared with 0, and
+suggests replacing with ``absl::StartsWith()``. This is both a readability and
+performance issue.
+
+.. code-block:: c++
+
+ string s = "...";
+ if (s.find("Hello World") == 0) { /* do something */ }
+
+becomes
+
+
+.. code-block:: c++
+
+ string s = "...";
+ if (absl::StartsWith(s, "Hello World")) { /* do something */ }
+
+
+Options
+-------
+
+.. option:: StringLikeClasses
+
+ Semicolon-separated list of names of string-like classes. By default only
+ ``std::basic_string`` is considered. The list of methods to considered is
+ fixed.
+
+.. option:: IncludeStyle
+
+ A string specifying which include-style is used, `llvm` or `google`. Default
+ is `llvm`.
+
+.. option:: AbseilStringsMatchHeader
+
+ The location of Abseil's ``strings/match.h``. Defaults to
+ ``absl/strings/match.h``.
diff --git a/docs/clang-tidy/checks/misc-macro-parentheses.rst b/docs/clang-tidy/checks/bugprone-macro-parentheses.rst
similarity index 85%
rename from docs/clang-tidy/checks/misc-macro-parentheses.rst
rename to docs/clang-tidy/checks/bugprone-macro-parentheses.rst
index 6120170..f827080 100644
--- a/docs/clang-tidy/checks/misc-macro-parentheses.rst
+++ b/docs/clang-tidy/checks/bugprone-macro-parentheses.rst
@@ -1,7 +1,7 @@
-.. title:: clang-tidy - misc-macro-parentheses
+.. title:: clang-tidy - bugprone-macro-parentheses
-misc-macro-parentheses
-======================
+bugprone-macro-parentheses
+==========================
Finds macros that can have unexpected behaviour due to missing parentheses.
diff --git a/docs/clang-tidy/checks/misc-sizeof-container.rst b/docs/clang-tidy/checks/bugprone-sizeof-container.rst
similarity index 88%
rename from docs/clang-tidy/checks/misc-sizeof-container.rst
rename to docs/clang-tidy/checks/bugprone-sizeof-container.rst
index 9ee4440..fb2f0b2 100644
--- a/docs/clang-tidy/checks/misc-sizeof-container.rst
+++ b/docs/clang-tidy/checks/bugprone-sizeof-container.rst
@@ -1,7 +1,7 @@
-.. title:: clang-tidy - misc-sizeof-container
+.. title:: clang-tidy - bugprone-sizeof-container
-misc-sizeof-container
-=====================
+bugprone-sizeof-container
+=========================
The check finds usages of ``sizeof`` on expressions of STL container types. Most
likely the user wanted to use ``.size()`` instead.
diff --git a/docs/clang-tidy/checks/misc-sizeof-expression.rst b/docs/clang-tidy/checks/bugprone-sizeof-expression.rst
similarity index 97%
rename from docs/clang-tidy/checks/misc-sizeof-expression.rst
rename to docs/clang-tidy/checks/bugprone-sizeof-expression.rst
index d3e3790..8190c00 100644
--- a/docs/clang-tidy/checks/misc-sizeof-expression.rst
+++ b/docs/clang-tidy/checks/bugprone-sizeof-expression.rst
@@ -1,7 +1,7 @@
-.. title:: clang-tidy - misc-sizeof-expression
+.. title:: clang-tidy - bugprone-sizeof-expression
-misc-sizeof-expression
-======================
+bugprone-sizeof-expression
+==========================
The check finds usages of ``sizeof`` expressions which are most likely errors.
diff --git a/docs/clang-tidy/checks/misc-unused-raii.rst b/docs/clang-tidy/checks/bugprone-unused-raii.rst
similarity index 89%
rename from docs/clang-tidy/checks/misc-unused-raii.rst
rename to docs/clang-tidy/checks/bugprone-unused-raii.rst
index d081702..f1987c5 100644
--- a/docs/clang-tidy/checks/misc-unused-raii.rst
+++ b/docs/clang-tidy/checks/bugprone-unused-raii.rst
@@ -1,7 +1,7 @@
-.. title:: clang-tidy - misc-unused-raii
+.. title:: clang-tidy - bugprone-unused-raii
-misc-unused-raii
-================
+bugprone-unused-raii
+====================
Finds temporaries that look like RAII objects.
diff --git a/docs/clang-tidy/checks/bugprone-unused-return-value.rst b/docs/clang-tidy/checks/bugprone-unused-return-value.rst
new file mode 100644
index 0000000..83b243f
--- /dev/null
+++ b/docs/clang-tidy/checks/bugprone-unused-return-value.rst
@@ -0,0 +1,24 @@
+.. title:: clang-tidy - bugprone-unused-return-value
+
+bugprone-unused-return-value
+============================
+
+Warns on unused function return values. The checked funtions can be configured.
+
+Options
+-------
+
+.. option:: CheckedFunctions
+
+ Semicolon-separated list of functions to check. Defaults to
+ ``::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique``.
+ This means that the calls to following functions are checked by default:
+
+ - ``std::async()``. Not using the return value makes the call synchronous.
+ - ``std::launder()``. Not using the return value usually means that the
+ function interface was misunderstood by the programmer. Only the returned
+ pointer is "laundered", not the argument.
+ - ``std::remove()``, ``std::remove_if()`` and ``std::unique()``. The returned
+ iterator indicates the boundary between elements to keep and elements to be
+ removed. Not using the return value means that the information about which
+ elements to remove is lost.
diff --git a/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst b/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst
new file mode 100644
index 0000000..2cace63
--- /dev/null
+++ b/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst
@@ -0,0 +1,96 @@
+.. title:: clang-tidy - hicpp-multiway-paths-covered
+
+hicpp-multiway-paths-covered
+============================
+
+This check discovers situations where code paths are not fully-covered.
+It furthermore suggests using ``if`` instead of ``switch`` if the code will be more clear.
+The `rule 6.1.2 <http://www.codingstandard.com/rule/6-1-2-explicitly-cover-all-paths-through-multi-way-selection-statements/>`_
+and `rule 6.1.4 <http://www.codingstandard.com/rule/6-1-4-ensure-that-a-switch-statement-has-at-least-two-case-labels-distinct-from-the-default-label/>`_
+of the High Integrity C++ Coding Standard are enforced.
+
+``if-else if`` chains that miss a final ``else`` branch might lead to unexpected
+program execution and be the result of a logical error.
+If the missing ``else`` branch is intended you can leave it empty with a clarifying
+comment.
+This warning can be noisy on some code bases, so it is disabled by default.
+
+.. code-block:: c++
+
+ void f1() {
+ int i = determineTheNumber();
+
+ if(i > 0) {
+ // Some Calculation
+ } else if (i < 0) {
+ // Precondition violated or something else.
+ }
+ // ...
+ }
+
+Similar arguments hold for ``switch`` statements which do not cover all possible code paths.
+
+.. code-block:: c++
+
+ // The missing default branch might be a logical error. It can be kept empty
+ // if there is nothing to do, making it explicit.
+ void f2(int i) {
+ switch (i) {
+ case 0: // something
+ break;
+ case 1: // something else
+ break;
+ }
+ // All other numbers?
+ }
+
+ // Violates this rule as well, but already emits a compiler warning (-Wswitch).
+ enum Color { Red, Green, Blue, Yellow };
+ void f3(enum Color c) {
+ switch (c) {
+ case Red: // We can't drive for now.
+ break;
+ case Green: // We are allowed to drive.
+ break;
+ }
+ // Other cases missing
+ }
+
+
+The `rule 6.1.4 <http://www.codingstandard.com/rule/6-1-4-ensure-that-a-switch-statement-has-at-least-two-case-labels-distinct-from-the-default-label/>`_
+requires every ``switch`` statement to have at least two ``case`` labels other than a `default` label.
+Otherwise, the ``switch`` could be better expressed with an ``if`` statement.
+Degenerated ``switch`` statements without any labels are caught as well.
+
+.. code-block:: c++
+
+ // Degenerated switch that could be better written as `if`
+ int i = 42;
+ switch(i) {
+ case 1: // do something here
+ default: // do somethe else here
+ }
+
+ // Should rather be the following:
+ if (i == 1) {
+ // do something here
+ }
+ else {
+ // do something here
+ }
+
+
+.. code-block:: c++
+
+ // A completly degenerated switch will be diagnosed.
+ int i = 42;
+ switch(i) {}
+
+
+Options
+-------
+
+.. option:: WarnOnMissingElse
+
+ Boolean flag that activates a warning for missing ``else`` branches.
+ Default is `0`.
diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst
index a186b4b..cb1ef44 100644
--- a/docs/clang-tidy/checks/list.rst
+++ b/docs/clang-tidy/checks/list.rst
@@ -4,6 +4,7 @@
=================
.. toctree::
+ abseil-string-find-startswith
android-cloexec-accept
android-cloexec-accept4
android-cloexec-creat
@@ -29,11 +30,14 @@
bugprone-incorrect-roundings
bugprone-integer-division
bugprone-lambda-function-name
+ bugprone-macro-parentheses
bugprone-macro-repeated-side-effects
bugprone-misplaced-operator-in-strlen-in-alloc
bugprone-misplaced-widening-cast
bugprone-move-forwarding-reference
bugprone-multiple-statement-macro
+ bugprone-sizeof-container
+ bugprone-sizeof-expression
bugprone-string-constructor
bugprone-string-integer-assignment
bugprone-string-literal-with-embedded-nul
@@ -46,6 +50,8 @@
bugprone-throw-keyword-missing
bugprone-undefined-memory-manipulation
bugprone-undelegated-constructor
+ bugprone-unused-raii
+ bugprone-unused-return-value
bugprone-use-after-move
bugprone-virtual-near-miss
cert-dcl03-c (redirects to misc-static-assert) <cert-dcl03-c>
@@ -116,6 +122,7 @@
hicpp-invalid-access-moved (redirects to bugprone-use-after-move) <hicpp-invalid-access-moved>
hicpp-member-init (redirects to cppcoreguidelines-pro-type-member-init) <hicpp-member-init>
hicpp-move-const-arg (redirects to performance-move-const-arg) <hicpp-move-const-arg>
+ hicpp-multiway-paths-covered
hicpp-named-parameter (redirects to readability-named-parameter) <hicpp-named-parameter>
hicpp-new-delete-operators (redirects to misc-new-delete-overloads) <hicpp-new-delete-operators>
hicpp-no-array-decay (redirects to cppcoreguidelines-pro-bounds-array-to-pointer-decay) <hicpp-no-array-decay>
@@ -139,20 +146,16 @@
llvm-namespace-comment
llvm-twine-local
misc-definitions-in-headers
- misc-macro-parentheses
misc-misplaced-const
misc-new-delete-overloads
misc-non-copyable-objects
misc-redundant-expression
- misc-sizeof-container
- misc-sizeof-expression
misc-static-assert
misc-throw-by-value-catch-by-reference
misc-unconventional-assign-operator
misc-uniqueptr-reset-release
misc-unused-alias-decls
misc-unused-parameters
- misc-unused-raii
misc-unused-using-decls
modernize-avoid-bind
modernize-deprecated-headers
@@ -197,6 +200,7 @@
performance-type-promotion-in-math-fn
performance-unnecessary-copy-initialization
performance-unnecessary-value-param
+ portability-simd-intrinsics
readability-avoid-const-params-in-decls
readability-braces-around-statements
readability-container-size-empty
@@ -218,9 +222,9 @@
readability-redundant-smartptr-get
readability-redundant-string-cstr
readability-redundant-string-init
- readability-simd-intrinsics
readability-simplify-boolean-expr
readability-static-accessed-through-instance
readability-static-definition-in-anonymous-namespace
readability-string-compare
readability-uniqueptr-delete-release
+ zircon-temporary-objects
diff --git a/docs/clang-tidy/checks/readability-simd-intrinsics.rst b/docs/clang-tidy/checks/portability-simd-intrinsics.rst
similarity index 84%
rename from docs/clang-tidy/checks/readability-simd-intrinsics.rst
rename to docs/clang-tidy/checks/portability-simd-intrinsics.rst
index 49d442c..2cd9d9f 100644
--- a/docs/clang-tidy/checks/readability-simd-intrinsics.rst
+++ b/docs/clang-tidy/checks/portability-simd-intrinsics.rst
@@ -1,6 +1,6 @@
-.. title:: clang-tidy - readability-simd-intrinsics
+.. title:: clang-tidy - portability-simd-intrinsics
-readability-simd-intrinsics
+portability-simd-intrinsics
===========================
Finds SIMD intrinsics calls and suggests ``std::experimental::simd`` (`P0214`_)
@@ -41,4 +41,9 @@
`P0214`_ alternatives, otherwise it only points out the intrinsic function is
non-portable.
+.. option:: Std
+
+ The namespace used to suggest `P0214`_ alternatives. If not specified, `std::`
+ for `-std=c++2a` and `std::experimental::` for `-std=c++11`.
+
.. _P0214: http://wg21.link/p0214
diff --git a/docs/clang-tidy/checks/zircon-temporary-objects.rst b/docs/clang-tidy/checks/zircon-temporary-objects.rst
new file mode 100644
index 0000000..7491f77
--- /dev/null
+++ b/docs/clang-tidy/checks/zircon-temporary-objects.rst
@@ -0,0 +1,53 @@
+.. title:: clang-tidy - zircon-temporary-objects
+
+zircon-temporary-objects
+========================
+
+Warns on construction of specific temporary objects in the Zircon kernel.
+If the object should be flagged, If the object should be flagged, the fully
+qualified type name must be explicitly passed to the check.
+
+For example, given the list of classes "Foo" and "NS::Bar", all of the
+following will trigger the warning:
+
+.. code-block:: c++
+
+ Foo();
+ Foo F = Foo();
+ func(Foo());
+
+ namespace NS {
+
+ Bar();
+
+ }
+
+With the same list, the following will not trigger the warning:
+
+.. code-block:: c++
+
+ Foo F; // Non-temporary construction okay
+ Foo F(param); // Non-temporary construction okay
+ Foo *F = new Foo(); // New construction okay
+
+ Bar(); // Not NS::Bar, so okay
+ NS::Bar B; // Non-temporary construction okay
+
+Note that objects must be explicitly specified in order to be flagged,
+and so objects that inherit a specified object will not be flagged.
+
+This check matches temporary objects without regard for inheritance and so a
+prohibited base class type does not similarly prohibit derived class types.
+
+.. code-block:: c++
+
+ class Derived : Foo {} // Derived is not explicitly disallowed
+ Derived(); // and so temporary construction is okay
+
+Options
+-------
+
+.. option:: Names
+
+ A semi-colon-separated list of fully-qualified names of C++ classes that
+ should not be constructed as temporaries. Default is empty.
diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst
index 35292d0..dc601ec 100644
--- a/docs/clang-tidy/index.rst
+++ b/docs/clang-tidy/index.rst
@@ -71,8 +71,11 @@
``mpi-`` Checks related to MPI (Message Passing Interface).
``objc-`` Checks related to Objective-C coding conventions.
``performance-`` Checks that target performance-related issues.
+``portability-`` Checks that target portability-related issues that don't
+ relate to any particular coding style.
``readability-`` Checks that target readability-related issues that don't
relate to any particular coding style.
+``zircon-`` Checks related to Zircon kernel coding conventions.
====================== =========================================================
Clang diagnostics are treated in a similar way as check diagnostics. Clang
diff --git a/docs/clangd.rst b/docs/clangd.rst
index c70cf7c..8218e3c 100644
--- a/docs/clangd.rst
+++ b/docs/clangd.rst
@@ -75,7 +75,7 @@
+-------------------------------------+------------+----------+
| Rename | Yes | Yes |
+-------------------------------------+------------+----------+
-| Source hover | Yes | No |
+| Source hover | Yes | Yes |
+-------------------------------------+------------+----------+
| Find References | Yes | No |
+-------------------------------------+------------+----------+
diff --git a/docs/index.rst b/docs/index.rst
index 1dc2f93..8e6beb3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,6 +26,7 @@
pp-trace
clang-rename
clangd
+ clang-doc
Doxygen Documentation
diff --git a/modularize/Modularize.cpp b/modularize/Modularize.cpp
index e5f19de..83f2340 100644
--- a/modularize/Modularize.cpp
+++ b/modularize/Modularize.cpp
@@ -825,7 +825,7 @@
// No go if we have no header list file.
if (ListFileNames.size() == 0) {
cl::PrintHelpMessage();
- return 0;
+ return 1;
}
std::unique_ptr<ModularizeUtilities> ModUtil;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 26d3405..fafe6c0 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -37,10 +37,14 @@
# For the clang-apply-replacements test that uses clang-rename.
clang-rename
+ # For the clang-doc tests that emit bitcode files.
+ llvm-bcanalyzer
+
# Individual tools we test.
clang-apply-replacements
clang-change-namespace
clangd
+ clang-doc
clang-include-fixer
clang-move
clang-query
diff --git a/test/clang-doc/mapper-class-in-class.cpp b/test/clang-doc/mapper-class-in-class.cpp
new file mode 100644
index 0000000..909f00d
--- /dev/null
+++ b/test/clang-doc/mapper-class-in-class.cpp
@@ -0,0 +1,35 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/641AB4A3D36399954ACDE29C7A8833032BF40472.bc --dump | FileCheck %s --check-prefix CHECK-X-Y
+// RUN: llvm-bcanalyzer %t/docs/bc/CA7C7935730B5EACD25F080E9C83FA087CCDC75E.bc --dump | FileCheck %s --check-prefix CHECK-X
+
+class X {
+ class Y {};
+};
+
+// CHECK-X: <BLOCKINFO_BLOCK/>
+// CHECK-X-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-X-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-X-NEXT: </VersionBlock>
+// CHECK-X-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-X-NEXT: <USR abbrevid=4 op0=20 op1=202 op2=124 op3=121 op4=53 op5=115 op6=11 op7=94 op8=172 op9=210 op10=95 op11=8 op12=14 op13=156 op14=131 op15=250 op16=8 op17=124 op18=205 op19=199 op20=94/>
+ // CHECK-X-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
+ // CHECK-X-NEXT: <DefLocation abbrevid=7 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-X-NEXT: <TagType abbrevid=9 op0=3/>
+// CHECK-X-NEXT: </RecordBlock>
+
+
+// CHECK-X-Y: <BLOCKINFO_BLOCK/>
+// CHECK-X-Y-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-X-Y-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-X-Y-NEXT: </VersionBlock>
+// CHECK-X-Y-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-X-Y-NEXT: <USR abbrevid=4 op0=20 op1=100 op2=26 op3=180 op4=163 op5=211 op6=99 op7=153 op8=149 op9=74 op10=205 op11=226 op12=156 op13=122 op14=136 op15=51 op16=3 op17=43 op18=244 op19=4 op20=114/>
+ // CHECK-X-Y-NEXT: <Name abbrevid=5 op0=1/> blob data = 'Y'
+ // CHECK-X-Y-NEXT: <Namespace abbrevid=6 op0=1 op1=40/> blob data = 'CA7C7935730B5EACD25F080E9C83FA087CCDC75E'
+ // CHECK-X-Y-NEXT: <DefLocation abbrevid=7 op0=10 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-X-Y-NEXT: <TagType abbrevid=9 op0=3/>
+// CHECK-X-Y-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-class-in-function.cpp b/test/clang-doc/mapper-class-in-function.cpp
new file mode 100644
index 0000000..e572c07
--- /dev/null
+++ b/test/clang-doc/mapper-class-in-function.cpp
@@ -0,0 +1,41 @@
+// This test requires Linux due to the system-dependent USR for the
+// inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/B6AC4C5C9F2EA3F2B3ECE1A33D349F4EE502B24E.bc --dump | FileCheck %s --check-prefix CHECK-H
+// RUN: llvm-bcanalyzer %t/docs/bc/01A95F3F73F53281B3E50109A577FD2493159365.bc --dump | FileCheck %s --check-prefix CHECK-H-I
+
+void H() {
+ class I {};
+}
+
+// CHECK-H: <BLOCKINFO_BLOCK/>
+// CHECK-H-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-H-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-H-NEXT: </VersionBlock>
+// CHECK-H-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-H-NEXT: <USR abbrevid=4 op0=20 op1=182 op2=172 op3=76 op4=92 op5=159 op6=46 op7=163 op8=242 op9=179 op10=236 op11=225 op12=163 op13=61 op14=52 op15=159 op16=78 op17=229 op18=2 op19=178 op20=78/>
+ // CHECK-H-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
+ // CHECK-H-NEXT: <DefLocation abbrevid=7 op0=12 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-H-NEXT: <TypeBlock NumWords=4 BlockCodeSize=4>
+ // CHECK-H-NEXT: <Type abbrevid=4 op0=4 op1=4/> blob data = 'void'
+ // CHECK-H-NEXT: </TypeBlock>
+// CHECK-H-NEXT: </FunctionBlock>
+
+// CHECK-H-I: <BLOCKINFO_BLOCK/>
+// CHECK-H-I-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-H-I-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-H-I-NEXT: </VersionBlock>
+// CHECK-H-I-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-H-I-NEXT: <USR abbrevid=4 op0=20 op1=1 op2=169 op3=95 op4=63 op5=115 op6=245 op7=50 op8=129 op9=179 op10=229 op11=1 op12=9 op13=165 op14=119 op15=253 op16=36 op17=147 op18=21 op19=147 op20=101/>
+ // CHECK-H-I-NEXT: <Name abbrevid=5 op0=1/> blob data = 'I'
+ // CHECK-H-I-NEXT: <Namespace abbrevid=6 op0=2 op1=40/> blob data = 'B6AC4C5C9F2EA3F2B3ECE1A33D349F4EE502B24E'
+ // CHECK-H-I-NEXT: <DefLocation abbrevid=7 op0=13 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-H-I-NEXT: <TagType abbrevid=9 op0=3/>
+// CHECK-H-I-NEXT: </RecordBlock>
+
+
diff --git a/test/clang-doc/mapper-class.cpp b/test/clang-doc/mapper-class.cpp
new file mode 100644
index 0000000..7c0965c
--- /dev/null
+++ b/test/clang-doc/mapper-class.cpp
@@ -0,0 +1,19 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/289584A8E0FF4178A794622A547AA622503967A1.bc --dump | FileCheck %s
+
+class E {};
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=40 op2=149 op3=132 op4=168 op5=224 op6=255 op7=65 op8=120 op9=167 op10=148 op11=98 op12=42 op13=84 op14=122 op15=166 op16=34 op17=80 op18=57 op19=103 op20=161/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+ // CHECK-NEXT: <DefLocation abbrevid=7 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-NEXT: <TagType abbrevid=9 op0=3/>
+// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-comments.cpp b/test/clang-doc/mapper-comments.cpp
new file mode 100644
index 0000000..91620ca
--- /dev/null
+++ b/test/clang-doc/mapper-comments.cpp
@@ -0,0 +1,172 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/7574630614A535710E5A6ABCFFF98BCA2D06A4CA.bc --dump | FileCheck %s
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul> class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+///
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return int
+int F(int I, int J);
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=117 op2=116 op3=99 op4=6 op5=20 op6=165 op7=53 op8=113 op9=14 op10=90 op11=106 op12=188 op13=255 op14=249 op15=139 op16=202 op17=45 op18=6 op19=164 op20=202/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+ // CHECK-NEXT: <CommentBlock NumWords=351 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'FullComment'
+ // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=31 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=5/> blob data = 'brief'
+ // CHECK-NEXT: <CommentBlock NumWords=19 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=11 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=19/> blob data = ' Brief description.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=37 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=26/> blob data = ' Extended description that'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=14 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=30/> blob data = ' continues onto the next line.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=83 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=14/> blob data = ' class="test">'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'li'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=9/> blob data = ' Testing.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=17/> blob data = 'HTMLEndTagComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
+ // CHECK-NEXT: <SelfClosing abbrevid=10 op0=1/>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=32 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=20/> blob data = 'VerbatimBlockComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=8/> blob data = 'verbatim'
+ // CHECK-NEXT: <CloseName abbrevid=9 op0=11/> blob data = 'endverbatim'
+ // CHECK-NEXT: <CommentBlock NumWords=16 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=24/> blob data = 'VerbatimBlockLineComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=27/> blob data = ' The description continues.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=39 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
+ // CHECK-NEXT: <Direction abbrevid=7 op0=5/> blob data = '[out]'
+ // CHECK-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'I'
+ // CHECK-NEXT: <Explicit abbrevid=11 op0=1/>
+ // CHECK-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=38 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
+ // CHECK-NEXT: <Direction abbrevid=7 op0=4/> blob data = '[in]'
+ // CHECK-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'J'
+ // CHECK-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <CommentBlock NumWords=27 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
+ // CHECK-NEXT: <Name abbrevid=6 op0=6/> blob data = 'return'
+ // CHECK-NEXT: <CommentBlock NumWords=15 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+ // CHECK-NEXT: <CommentBlock NumWords=7 BlockCodeSize=4>
+ // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+ // CHECK-NEXT: <Text abbrevid=5 op0=4/> blob data = ' int'
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: </CommentBlock>
+ // CHECK-NEXT: <Location abbrevid=8 op0=24 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-NEXT: <TypeBlock NumWords=4 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: </TypeBlock>
+ // CHECK-NEXT: <FieldTypeBlock NumWords=6 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'I'
+ // CHECK-NEXT: </FieldTypeBlock>
+ // CHECK-NEXT: <FieldTypeBlock NumWords=6 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'J'
+ // CHECK-NEXT: </FieldTypeBlock>
+// CHECK-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-enum.cpp b/test/clang-doc/mapper-enum.cpp
new file mode 100644
index 0000000..a4db097
--- /dev/null
+++ b/test/clang-doc/mapper-enum.cpp
@@ -0,0 +1,36 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/FC07BD34D5E77782C263FA944447929EA8753740.bc --dump | FileCheck %s --check-prefix CHECK-B
+// RUN: llvm-bcanalyzer %t/docs/bc/020E6C32A700C3170C009FCCD41671EDDBEAF575.bc --dump | FileCheck %s --check-prefix CHECK-C
+
+enum B { X, Y };
+
+// CHECK-B: <BLOCKINFO_BLOCK/>
+// CHECK-B-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-B-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-B-NEXT: </VersionBlock>
+// CHECK-B-NEXT: <EnumBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-B-NEXT: <USR abbrevid=4 op0=20 op1=252 op2=7 op3=189 op4=52 op5=213 op6=231 op7=119 op8=130 op9=194 op10=99 op11=250 op12=148 op13=68 op14=71 op15=146 op16=158 op17=168 op18=117 op19=55 op20=64/>
+ // CHECK-B-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+ // CHECK-B-NEXT: <DefLocation abbrevid=7 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-B-NEXT: <Member abbrevid=9 op0=1/> blob data = 'X'
+ // CHECK-B-NEXT: <Member abbrevid=9 op0=1/> blob data = 'Y'
+// CHECK-B-NEXT: </EnumBlock>
+
+enum class C { A, B };
+
+// CHECK-C: <BLOCKINFO_BLOCK/>
+// CHECK-C-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-C-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-C-NEXT: </VersionBlock>
+// CHECK-C-NEXT: <EnumBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-C-NEXT: <USR abbrevid=4 op0=20 op1=2 op2=14 op3=108 op4=50 op5=167 op6=0 op7=195 op8=23 op9=12 op10=0 op11=159 op12=204 op13=212 op14=22 op15=113 op16=237 op17=219 op18=234 op19=245 op20=117/>
+ // CHECK-C-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
+ // CHECK-C-NEXT: <DefLocation abbrevid=7 op0=23 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-C-NEXT: <Scoped abbrevid=10 op0=1/>
+ // CHECK-C-NEXT: <Member abbrevid=9 op0=1/> blob data = 'A'
+ // CHECK-C-NEXT: <Member abbrevid=9 op0=1/> blob data = 'B'
+// CHECK-C-NEXT: </EnumBlock>
diff --git a/test/clang-doc/mapper-function.cpp b/test/clang-doc/mapper-function.cpp
new file mode 100644
index 0000000..07a6ecf
--- /dev/null
+++ b/test/clang-doc/mapper-function.cpp
@@ -0,0 +1,25 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/A44B32CC3C087C9AF75DAF50DE193E85E7B2C16B.bc --dump | FileCheck %s
+
+int F(int param) { return param; }
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=164 op2=75 op3=50 op4=204 op5=60 op6=8 op7=124 op8=154 op9=247 op10=93 op11=175 op12=80 op13=222 op14=25 op15=62 op16=133 op17=231 op18=178 op19=193 op20=107/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+ // CHECK-NEXT: <DefLocation abbrevid=7 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-NEXT: <TypeBlock NumWords=4 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: </TypeBlock>
+ // CHECK-NEXT: <FieldTypeBlock NumWords=7 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=5/> blob data = 'param'
+ // CHECK-NEXT: </FieldTypeBlock>
+// CHECK-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-method.cpp b/test/clang-doc/mapper-method.cpp
new file mode 100644
index 0000000..7d16d7c
--- /dev/null
+++ b/test/clang-doc/mapper-method.cpp
@@ -0,0 +1,43 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/F0F9FC65FC90F54F690144A7AFB15DFC3D69B6E6.bc --dump | FileCheck %s --check-prefix CHECK-G-F
+// RUN: llvm-bcanalyzer %t/docs/bc/4202E8BF0ECB12AE354C8499C52725B0EE30AED5.bc --dump | FileCheck %s --check-prefix CHECK-G
+
+class G {
+public:
+ int Method(int param) { return param; }
+};
+
+// CHECK-G: <BLOCKINFO_BLOCK/>
+// CHECK-G-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-G-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-G-NEXT: </VersionBlock>
+// CHECK-G-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-G-NEXT: <USR abbrevid=4 op0=20 op1=66 op2=2 op3=232 op4=191 op5=14 op6=203 op7=18 op8=174 op9=53 op10=76 op11=132 op12=153 op13=197 op14=39 op15=37 op16=176 op17=238 op18=48 op19=174 op20=213/>
+ // CHECK-G-NEXT: <Name abbrevid=5 op0=1/> blob data = 'G'
+ // CHECK-G-NEXT: <DefLocation abbrevid=7 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-G-NEXT: <TagType abbrevid=9 op0=3/>
+// CHECK-G-NEXT: </RecordBlock>
+
+// CHECK-G-F: <BLOCKINFO_BLOCK/>
+// CHECK-G-F-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-G-F-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-G-F-NEXT: </VersionBlock>
+// CHECK-G-F-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-G-F-NEXT: <USR abbrevid=4 op0=20 op1=240 op2=249 op3=252 op4=101 op5=252 op6=144 op7=245 op8=79 op9=105 op10=1 op11=68 op12=167 op13=175 op14=177 op15=93 op16=252 op17=61 op18=105 op19=182 op20=230/>
+ // CHECK-G-F-NEXT: <Name abbrevid=5 op0=6/> blob data = 'Method'
+ // CHECK-G-F-NEXT: <Namespace abbrevid=6 op0=1 op1=40/> blob data = '4202E8BF0ECB12AE354C8499C52725B0EE30AED5'
+ // CHECK-G-F-NEXT: <IsMethod abbrevid=11 op0=1/>
+ // CHECK-G-F-NEXT: <DefLocation abbrevid=7 op0=11 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-G-F-NEXT: <Parent abbrevid=9 op0=1 op1=40/> blob data = '4202E8BF0ECB12AE354C8499C52725B0EE30AED5'
+ // CHECK-G-F-NEXT: <TypeBlock NumWords=4 BlockCodeSize=4>
+ // CHECK-G-F-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-G-F-NEXT: </TypeBlock>
+ // CHECK-G-F-NEXT: <FieldTypeBlock NumWords=7 BlockCodeSize=4>
+ // CHECK-G-F-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-G-F-NEXT: <Name abbrevid=5 op0=5/> blob data = 'param'
+ // CHECK-G-F-NEXT: </FieldTypeBlock>
+// CHECK-G-F-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-namespace.cpp b/test/clang-doc/mapper-namespace.cpp
new file mode 100644
index 0000000..e46dfda
--- /dev/null
+++ b/test/clang-doc/mapper-namespace.cpp
@@ -0,0 +1,17 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/8D042EFFC98B373450BC6B5B90A330C25A150E9C.bc --dump | FileCheck %s
+
+namespace A {}
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <NamespaceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=141 op2=4 op3=46 op4=255 op5=201 op6=139 op7=55 op8=52 op9=80 op10=188 op11=107 op12=91 op13=144 op14=163 op15=48 op16=194 op17=90 op18=21 op19=14 op20=156/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/mapper-struct.cpp b/test/clang-doc/mapper-struct.cpp
new file mode 100644
index 0000000..f13dd60
--- /dev/null
+++ b/test/clang-doc/mapper-struct.cpp
@@ -0,0 +1,23 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/06B5F6A19BA9F6A832E127C9968282B94619B210.bc --dump | FileCheck %s
+
+struct C { int i; };
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=6 op2=181 op3=246 op4=161 op5=155 op6=169 op7=246 op8=168 op9=50 op10=225 op11=39 op12=201 op13=150 op14=130 op15=130 op16=185 op17=70 op18=25 op19=178 op20=16/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
+ // CHECK-NEXT: <DefLocation abbrevid=7 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-NEXT: <MemberTypeBlock NumWords=6 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=4/> blob data = 'C::i'
+ // CHECK-NEXT: <Access abbrevid=6 op0=3/>
+ // CHECK-NEXT: </MemberTypeBlock>
+// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-union.cpp b/test/clang-doc/mapper-union.cpp
new file mode 100644
index 0000000..33b0aa9
--- /dev/null
+++ b/test/clang-doc/mapper-union.cpp
@@ -0,0 +1,29 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
+// RUN: llvm-bcanalyzer %t/docs/bc/0B8A6B938B939B77C6325CCCC8AA3E938BF9E2E8.bc --dump | FileCheck %s
+
+union D { int X; int Y; };
+
+// CHECK: <BLOCKINFO_BLOCK/>
+// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+ // CHECK-NEXT: <Version abbrevid=4 op0=1/>
+// CHECK-NEXT: </VersionBlock>
+// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
+ // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=11 op2=138 op3=107 op4=147 op5=139 op6=147 op7=155 op8=119 op9=198 op10=50 op11=92 op12=204 op13=200 op14=170 op15=62 op16=147 op17=139 op18=249 op19=226 op20=232/>
+ // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
+ // CHECK-NEXT: <DefLocation abbrevid=7 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
+ // CHECK-NEXT: <TagType abbrevid=9 op0=2/>
+ // CHECK-NEXT: <MemberTypeBlock NumWords=6 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=4/> blob data = 'D::X'
+ // CHECK-NEXT: <Access abbrevid=6 op0=3/>
+ // CHECK-NEXT: </MemberTypeBlock>
+ // CHECK-NEXT: <MemberTypeBlock NumWords=6 BlockCodeSize=4>
+ // CHECK-NEXT: <Type abbrevid=4 op0=4 op1=3/> blob data = 'int'
+ // CHECK-NEXT: <Name abbrevid=5 op0=4/> blob data = 'D::Y'
+ // CHECK-NEXT: <Access abbrevid=6 op0=3/>
+ // CHECK-NEXT: </MemberTypeBlock>
+// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-tidy/abseil-string-find-startswith.cpp b/test/clang-tidy/abseil-string-find-startswith.cpp
new file mode 100644
index 0000000..194e795
--- /dev/null
+++ b/test/clang-tidy/abseil-string-find-startswith.cpp
@@ -0,0 +1,55 @@
+// RUN: %check_clang_tidy %s abseil-string-find-startswith %t
+
+namespace std {
+template <typename T> class allocator {};
+template <typename T> class char_traits {};
+template <typename C, typename T = std::char_traits<C>,
+ typename A = std::allocator<C>>
+struct basic_string {
+ basic_string();
+ basic_string(const basic_string &);
+ basic_string(const C *, const A &a = A());
+ ~basic_string();
+ int find(basic_string<C> s, int pos = 0);
+ int find(const char *s, int pos = 0);
+};
+typedef basic_string<char> string;
+typedef basic_string<wchar_t> wstring;
+} // namespace std
+
+std::string foo(std::string);
+std::string bar();
+
+#define A_MACRO(x, y) ((x) == (y))
+
+void tests(std::string s) {
+ s.find("a") == 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith instead of find() == 0 [abseil-string-find-startswith]
+ // CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s, "a");{{$}}
+
+ s.find(s) == 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s, s);{{$}}
+
+ s.find("aaa") != 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, "aaa");{{$}}
+
+ s.find(foo(foo(bar()))) != 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, foo(foo(bar())));{{$}}
+
+ if (s.find("....") == 0) { /* do something */ }
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}if (absl::StartsWith(s, "....")) { /* do something */ }{{$}}
+
+ 0 != s.find("a");
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, "a");{{$}}
+
+ // expressions that don't trigger the check are here.
+ A_MACRO(s.find("a"), 0);
+ s.find("a", 1) == 0;
+ s.find("a", 1) == 1;
+ s.find("a") == 1;
+}
diff --git a/test/clang-tidy/misc-macro-parentheses-cmdline.cpp b/test/clang-tidy/bugprone-macro-parentheses-cmdline.cpp
similarity index 79%
rename from test/clang-tidy/misc-macro-parentheses-cmdline.cpp
rename to test/clang-tidy/bugprone-macro-parentheses-cmdline.cpp
index f7e8088..9ff757d 100644
--- a/test/clang-tidy/misc-macro-parentheses-cmdline.cpp
+++ b/test/clang-tidy/bugprone-macro-parentheses-cmdline.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s misc-macro-parentheses %t -- -- -DVAL=0+0
+// RUN: %check_clang_tidy %s bugprone-macro-parentheses %t -- -- -DVAL=0+0
// The previous command-line is producing warnings and fixes with the source
// locations from a virtual buffer. VAL is replaced by '0+0'.
diff --git a/test/clang-tidy/misc-macro-parentheses.cpp b/test/clang-tidy/bugprone-macro-parentheses.cpp
similarity index 82%
rename from test/clang-tidy/misc-macro-parentheses.cpp
rename to test/clang-tidy/bugprone-macro-parentheses.cpp
index 11d7c7b..2cc45e8 100644
--- a/test/clang-tidy/misc-macro-parentheses.cpp
+++ b/test/clang-tidy/bugprone-macro-parentheses.cpp
@@ -1,15 +1,15 @@
-// RUN: %check_clang_tidy %s misc-macro-parentheses %t
+// RUN: %check_clang_tidy %s bugprone-macro-parentheses %t
#define BAD1 -1
-// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: macro replacement list should be enclosed in parentheses [misc-macro-parentheses]
+// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: macro replacement list should be enclosed in parentheses [bugprone-macro-parentheses]
#define BAD2 1+2
-// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: macro replacement list should be enclosed in parentheses [misc-macro-parentheses]
+// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: macro replacement list should be enclosed in parentheses [bugprone-macro-parentheses]
#define BAD3(A) (A+1)
-// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: macro argument should be enclosed in parentheses [misc-macro-parentheses]
+// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
#define BAD4(x) ((unsigned char)(x & 0xff))
-// CHECK-MESSAGES: :[[@LINE-1]]:44: warning: macro argument should be enclosed in parentheses [misc-macro-parentheses]
+// CHECK-MESSAGES: :[[@LINE-1]]:44: warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
#define BAD5(X) A*B=(C*)X+2
-// CHECK-MESSAGES: :[[@LINE-1]]:35: warning: macro argument should be enclosed in parentheses [misc-macro-parentheses]
+// CHECK-MESSAGES: :[[@LINE-1]]:35: warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
#define GOOD1 1
#define GOOD2 (1+2)
diff --git a/test/clang-tidy/misc-sizeof-container.cpp b/test/clang-tidy/bugprone-sizeof-container.cpp
similarity index 91%
rename from test/clang-tidy/misc-sizeof-container.cpp
rename to test/clang-tidy/bugprone-sizeof-container.cpp
index 472587e..27798d6 100644
--- a/test/clang-tidy/misc-sizeof-container.cpp
+++ b/test/clang-tidy/bugprone-sizeof-container.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s misc-sizeof-container %t -- -- -std=c++11 -target x86_64-unknown-unknown
+// RUN: %check_clang_tidy %s bugprone-sizeof-container %t -- -- -std=c++11 -target x86_64-unknown-unknown
namespace std {
@@ -64,7 +64,7 @@
std::vector<int> v;
int a = 42 + sizeof(s1);
-// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: sizeof() doesn't return the size of the container; did you mean .size()? [misc-sizeof-container]
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: sizeof() doesn't return the size of the container; did you mean .size()? [bugprone-sizeof-container]
a = 123 * sizeof(s2);
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: sizeof() doesn't return the size
a = 45 + sizeof(s2 + "asdf");
diff --git a/test/clang-tidy/misc-sizeof-expression.cpp b/test/clang-tidy/bugprone-sizeof-expression.cpp
similarity index 99%
rename from test/clang-tidy/misc-sizeof-expression.cpp
rename to test/clang-tidy/bugprone-sizeof-expression.cpp
index f9d798d..1a14f68 100644
--- a/test/clang-tidy/misc-sizeof-expression.cpp
+++ b/test/clang-tidy/bugprone-sizeof-expression.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s misc-sizeof-expression %t
+// RUN: %check_clang_tidy %s bugprone-sizeof-expression %t
class C {
int size() { return sizeof(this); }
diff --git a/test/clang-tidy/misc-unused-raii.cpp b/test/clang-tidy/bugprone-unused-raii.cpp
similarity index 95%
rename from test/clang-tidy/misc-unused-raii.cpp
rename to test/clang-tidy/bugprone-unused-raii.cpp
index f22746c..91ade52 100644
--- a/test/clang-tidy/misc-unused-raii.cpp
+++ b/test/clang-tidy/bugprone-unused-raii.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s misc-unused-raii %t
+// RUN: %check_clang_tidy %s bugprone-unused-raii %t
struct Foo {
Foo();
diff --git a/test/clang-tidy/bugprone-unused-return-value-custom.cpp b/test/clang-tidy/bugprone-unused-return-value-custom.cpp
new file mode 100644
index 0000000..4f498dd
--- /dev/null
+++ b/test/clang-tidy/bugprone-unused-return-value-custom.cpp
@@ -0,0 +1,82 @@
+// RUN: %check_clang_tidy %s bugprone-unused-return-value %t \
+// RUN: -config='{CheckOptions: \
+// RUN: [{key: bugprone-unused-return-value.CheckedFunctions, \
+// RUN: value: "::fun;::ns::Outer::Inner::memFun;::ns::Type::staticFun"}]}' \
+// RUN: --
+
+namespace std {
+
+template <typename T>
+T *launder(T *);
+
+} // namespace std
+
+namespace ns {
+
+struct Outer {
+ struct Inner {
+ bool memFun();
+ };
+};
+
+using AliasName = Outer;
+
+struct Derived : public Outer::Inner {};
+
+struct Retval {
+ int *P;
+ Retval() { P = new int; }
+ ~Retval() { delete P; }
+};
+
+struct Type {
+ Retval memFun();
+ static Retval staticFun();
+};
+
+} // namespace ns
+
+int fun();
+void fun(int);
+
+void warning() {
+ fun();
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ (fun());
+ // CHECK-MESSAGES: [[@LINE-1]]:4: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ ns::Outer::Inner ObjA1;
+ ObjA1.memFun();
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ ns::AliasName::Inner ObjA2;
+ ObjA2.memFun();
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ ns::Derived ObjA3;
+ ObjA3.memFun();
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ ns::Type::staticFun();
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+}
+
+void noWarning() {
+ auto R1 = fun();
+
+ ns::Outer::Inner ObjB1;
+ auto R2 = ObjB1.memFun();
+
+ auto R3 = ns::Type::staticFun();
+
+ // test calling a void overload of a checked function
+ fun(5);
+
+ // test discarding return value of functions that are not configured to be checked
+ int I = 1;
+ std::launder(&I);
+
+ ns::Type ObjB2;
+ ObjB2.memFun();
+}
diff --git a/test/clang-tidy/bugprone-unused-return-value.cpp b/test/clang-tidy/bugprone-unused-return-value.cpp
new file mode 100644
index 0000000..de9184d
--- /dev/null
+++ b/test/clang-tidy/bugprone-unused-return-value.cpp
@@ -0,0 +1,166 @@
+// RUN: %check_clang_tidy %s bugprone-unused-return-value %t -- -- -fexceptions
+
+namespace std {
+
+struct future {};
+
+enum class launch {
+ async,
+ deferred
+};
+
+template <typename Function, typename... Args>
+future async(Function &&, Args &&...);
+
+template <typename Function, typename... Args>
+future async(launch, Function &&, Args &&...);
+
+template <typename ForwardIt, typename T>
+ForwardIt remove(ForwardIt, ForwardIt, const T &);
+
+template <typename ForwardIt, typename UnaryPredicate>
+ForwardIt remove_if(ForwardIt, ForwardIt, UnaryPredicate);
+
+template <typename ForwardIt>
+ForwardIt unique(ForwardIt, ForwardIt);
+
+// the check should be able to match std lib calls even if the functions are
+// declared inside inline namespaces
+inline namespace v1 {
+
+template <typename T>
+T *launder(T *);
+
+} // namespace v1
+} // namespace std
+
+struct Foo {
+ void f();
+};
+
+int increment(int i) {
+ return i + 1;
+}
+
+void useFuture(const std::future &fut);
+
+void warning() {
+ std::async(increment, 42);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ std::async(std::launch::async, increment, 42);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ Foo F;
+ std::launder(&F);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ std::remove_if(nullptr, nullptr, nullptr);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ std::unique(nullptr, nullptr);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ // test discarding return values inside different kinds of statements
+
+ auto Lambda = [] { std::remove(nullptr, nullptr, 1); };
+ // CHECK-MESSAGES: [[@LINE-1]]:22: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ if (true)
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ else if (true)
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ else
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ while (true)
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ do
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ while (true);
+
+ for (;;)
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ for (std::remove(nullptr, nullptr, 1);;)
+ // CHECK-MESSAGES: [[@LINE-1]]:8: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ ;
+
+ for (;; std::remove(nullptr, nullptr, 1))
+ // CHECK-MESSAGES: [[@LINE-1]]:11: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ ;
+
+ for (auto C : "foo")
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+
+ switch (1) {
+ case 1:
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ break;
+ default:
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ break;
+ }
+
+ try {
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ } catch (...) {
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should be used [bugprone-unused-return-value]
+ }
+}
+
+void noWarning() {
+ auto AsyncRetval1 = std::async(increment, 42);
+ auto AsyncRetval2 = std::async(std::launch::async, increment, 42);
+
+ Foo FNoWarning;
+ auto LaunderRetval = std::launder(&FNoWarning);
+
+ auto RemoveRetval = std::remove(nullptr, nullptr, 1);
+
+ auto RemoveIfRetval = std::remove_if(nullptr, nullptr, nullptr);
+
+ auto UniqueRetval = std::unique(nullptr, nullptr);
+
+ // test using the return value in different kinds of expressions
+ useFuture(std::async(increment, 42));
+ std::launder(&FNoWarning)->f();
+ delete std::launder(&FNoWarning);
+
+ if (std::launder(&FNoWarning))
+ ;
+ for (; std::launder(&FNoWarning);)
+ ;
+ while (std::launder(&FNoWarning))
+ ;
+ do
+ ;
+ while (std::launder(&FNoWarning));
+ switch (std::unique(1, 1))
+ ;
+
+ // cast to void should allow ignoring the return value
+ (void)std::async(increment, 42);
+
+ // test discarding return value of functions that are not configured to be checked
+ increment(1);
+
+ // test that the check is disabled inside GNU statement expressions
+ ({ std::async(increment, 42); });
+ auto StmtExprRetval = ({ std::async(increment, 42); });
+}
diff --git a/test/clang-tidy/hicpp-multiway-paths-covered-else.cpp b/test/clang-tidy/hicpp-multiway-paths-covered-else.cpp
new file mode 100644
index 0000000..34820b5
--- /dev/null
+++ b/test/clang-tidy/hicpp-multiway-paths-covered-else.cpp
@@ -0,0 +1,57 @@
+// RUN: %check_clang_tidy %s hicpp-multiway-paths-covered %t \
+// RUN: -config='{CheckOptions: \
+// RUN: [{key: hicpp-multiway-paths-covered.WarnOnMissingElse, value: 1}]}'\
+// RUN: --
+
+enum OS { Mac,
+ Windows,
+ Linux };
+
+void problematic_if(int i, enum OS os) {
+ if (i > 0) {
+ return;
+ } else if (i < 0) {
+ // CHECK-MESSAGES: [[@LINE-1]]:10: warning: potentially uncovered codepath; add an ending else statement
+ return;
+ }
+
+ // Could be considered as false positive because all paths are covered logically.
+ // I still think this is valid since the possibility of a final 'everything else'
+ // codepath is expected from if-else if.
+ if (i > 0) {
+ return;
+ } else if (i <= 0) {
+ // CHECK-MESSAGES: [[@LINE-1]]:10: warning: potentially uncovered codepath; add an ending else statement
+ return;
+ }
+
+ // Test if nesting of if-else chains does get caught as well.
+ if (os == Mac) {
+ return;
+ } else if (os == Linux) {
+ // These checks are kind of degenerated, but the check will not try to solve
+ // if logically all paths are covered, which is more the area of the static analyzer.
+ if (true) {
+ return;
+ } else if (false) {
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: potentially uncovered codepath; add an ending else statement
+ return;
+ }
+ return;
+ } else {
+ /* unreachable */
+ if (true) // check if the parent would match here as well
+ return;
+ // No warning for simple if statements, since it is common to just test one condition
+ // and ignore the opposite.
+ }
+
+ // Ok, because all paths are covered
+ if (i > 0) {
+ return;
+ } else if (i < 0) {
+ return;
+ } else {
+ /* error, maybe precondition failed */
+ }
+}
diff --git a/test/clang-tidy/hicpp-multiway-paths-covered.cpp b/test/clang-tidy/hicpp-multiway-paths-covered.cpp
new file mode 100644
index 0000000..15a3407
--- /dev/null
+++ b/test/clang-tidy/hicpp-multiway-paths-covered.cpp
@@ -0,0 +1,468 @@
+// RUN: %check_clang_tidy %s hicpp-multiway-paths-covered %t
+
+enum OS { Mac,
+ Windows,
+ Linux };
+
+struct Bitfields {
+ unsigned UInt : 3;
+ int SInt : 1;
+};
+
+int return_integer() { return 42; }
+
+void bad_switch(int i) {
+ switch (i) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: switch with only one case; use an if statement
+ case 0:
+ break;
+ }
+ // No default in this switch
+ switch (i) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 0:
+ break;
+ case 1:
+ break;
+ case 2:
+ break;
+ }
+
+ // degenerate, maybe even warning
+ switch (i) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: switch statement without labels has no effect
+ }
+
+ switch (int j = return_integer()) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 0:
+ case 1:
+ case 2:
+ break;
+ }
+
+ // Degenerated, only default case.
+ switch (i) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: degenerated switch with default label only
+ default:
+ break;
+ }
+
+ // Degenerated, only one case label and default case -> Better as if-stmt.
+ switch (i) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: switch could be better written as an if/else statement
+ case 0:
+ break;
+ default:
+ break;
+ }
+
+ unsigned long long BigNumber = 0;
+ switch (BigNumber) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 0:
+ case 1:
+ break;
+ }
+
+ const int &IntRef = i;
+ switch (IntRef) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 0:
+ case 1:
+ break;
+ }
+
+ char C = 'A';
+ switch (C) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 'A':
+ break;
+ case 'B':
+ break;
+ }
+
+ Bitfields Bf;
+ // UInt has 3 bits size.
+ switch (Bf.UInt) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: potential uncovered code path; add a default label
+ case 0:
+ case 1:
+ break;
+ }
+ // All paths explicitly covered.
+ switch (Bf.UInt) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ break;
+ }
+ // SInt has 1 bit size, so this is somewhat degenerated.
+ switch (Bf.SInt) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: switch with only one case; use an if statement
+ case 0:
+ break;
+ }
+ // All paths explicitly covered.
+ switch (Bf.SInt) {
+ case 0:
+ case 1:
+ break;
+ }
+
+ bool Flag = false;
+ switch (Flag) {
+ // CHECK-MESSAGES:[[@LINE-1]]:3: warning: switch with only one case; use an if statement
+ case true:
+ break;
+ }
+
+ switch (Flag) {
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: degenerated switch with default label only
+ default:
+ break;
+ }
+
+ // This `switch` will create a frontend warning from '-Wswitch-bool' but is
+ // ok for this check.
+ switch (Flag) {
+ case true:
+ break;
+ case false:
+ break;
+ }
+}
+
+void unproblematic_switch(unsigned char c) {
+ //
+ switch (c) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ case 58:
+ case 59:
+ case 60:
+ case 61:
+ case 62:
+ case 63:
+ case 64:
+ case 65:
+ case 66:
+ case 67:
+ case 68:
+ case 69:
+ case 70:
+ case 71:
+ case 72:
+ case 73:
+ case 74:
+ case 75:
+ case 76:
+ case 77:
+ case 78:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ case 84:
+ case 85:
+ case 86:
+ case 87:
+ case 88:
+ case 89:
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ case 108:
+ case 109:
+ case 110:
+ case 111:
+ case 112:
+ case 113:
+ case 114:
+ case 115:
+ case 116:
+ case 117:
+ case 118:
+ case 119:
+ case 120:
+ case 121:
+ case 122:
+ case 123:
+ case 124:
+ case 125:
+ case 126:
+ case 127:
+ case 128:
+ case 129:
+ case 130:
+ case 131:
+ case 132:
+ case 133:
+ case 134:
+ case 135:
+ case 136:
+ case 137:
+ case 138:
+ case 139:
+ case 140:
+ case 141:
+ case 142:
+ case 143:
+ case 144:
+ case 145:
+ case 146:
+ case 147:
+ case 148:
+ case 149:
+ case 150:
+ case 151:
+ case 152:
+ case 153:
+ case 154:
+ case 155:
+ case 156:
+ case 157:
+ case 158:
+ case 159:
+ case 160:
+ case 161:
+ case 162:
+ case 163:
+ case 164:
+ case 165:
+ case 166:
+ case 167:
+ case 168:
+ case 169:
+ case 170:
+ case 171:
+ case 172:
+ case 173:
+ case 174:
+ case 175:
+ case 176:
+ case 177:
+ case 178:
+ case 179:
+ case 180:
+ case 181:
+ case 182:
+ case 183:
+ case 184:
+ case 185:
+ case 186:
+ case 187:
+ case 188:
+ case 189:
+ case 190:
+ case 191:
+ case 192:
+ case 193:
+ case 194:
+ case 195:
+ case 196:
+ case 197:
+ case 198:
+ case 199:
+ case 200:
+ case 201:
+ case 202:
+ case 203:
+ case 204:
+ case 205:
+ case 206:
+ case 207:
+ case 208:
+ case 209:
+ case 210:
+ case 211:
+ case 212:
+ case 213:
+ case 214:
+ case 215:
+ case 216:
+ case 217:
+ case 218:
+ case 219:
+ case 220:
+ case 221:
+ case 222:
+ case 223:
+ case 224:
+ case 225:
+ case 226:
+ case 227:
+ case 228:
+ case 229:
+ case 230:
+ case 231:
+ case 232:
+ case 233:
+ case 234:
+ case 235:
+ case 236:
+ case 237:
+ case 238:
+ case 239:
+ case 240:
+ case 241:
+ case 242:
+ case 243:
+ case 244:
+ case 245:
+ case 246:
+ case 247:
+ case 248:
+ case 249:
+ case 250:
+ case 251:
+ case 252:
+ case 253:
+ case 254:
+ case 255:
+ break;
+ }
+
+ // Some paths are covered by the switch and a default case is present.
+ switch (c) {
+ case 1:
+ case 2:
+ case 3:
+ default:
+ break;
+ }
+}
+
+OS return_enumerator() {
+ return Linux;
+}
+
+// Enumpaths are already covered by a warning, this is just to ensure, that there is
+// no interference or false positives.
+// -Wswitch warns about uncovered enum paths and each here described case is already
+// covered.
+void switch_enums(OS os) {
+ switch (os) {
+ case Linux:
+ break;
+ }
+
+ switch (OS another_os = return_enumerator()) {
+ case Linux:
+ break;
+ }
+
+ switch (os) {
+ }
+}
+
+/// All of these cases will not emit a warning per default, but with explicit activation.
+/// Covered in extra test file.
+void problematic_if(int i, enum OS os) {
+ if (i > 0) {
+ return;
+ } else if (i < 0) {
+ return;
+ }
+
+ if (os == Mac) {
+ return;
+ } else if (os == Linux) {
+ if (true) {
+ return;
+ } else if (false) {
+ return;
+ }
+ return;
+ } else {
+ /* unreachable */
+ if (true) // check if the parent would match here as well
+ return;
+ }
+
+ // Ok, because all paths are covered
+ if (i > 0) {
+ return;
+ } else if (i < 0) {
+ return;
+ } else {
+ /* error, maybe precondition failed */
+ }
+}
diff --git a/test/clang-tidy/hicpp-no-assembler-msvc.cpp b/test/clang-tidy/hicpp-no-assembler-msvc.cpp
index f89e92b..d29a9e9 100644
--- a/test/clang-tidy/hicpp-no-assembler-msvc.cpp
+++ b/test/clang-tidy/hicpp-no-assembler-msvc.cpp
@@ -1,4 +1,6 @@
// REQUIRES: system-windows
+// FIXME: Re-enable test on windows (PR36855)
+// UNSUPPORTED: system-windows
// RUN: %check_clang_tidy %s hicpp-no-assembler %t
void f() {
diff --git a/test/clang-tidy/modernize-make-unique-cxx11.cpp b/test/clang-tidy/modernize-make-unique-cxx11.cpp
new file mode 100644
index 0000000..89beb08
--- /dev/null
+++ b/test/clang-tidy/modernize-make-unique-cxx11.cpp
@@ -0,0 +1,10 @@
+// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++11 \
+// RUN: -I%S/Inputs/modernize-smart-ptr
+
+#include "unique_ptr.h"
+// CHECK-FIXES: #include "unique_ptr.h"
+
+void f() {
+ auto my_ptr = std::unique_ptr<int>(new int(1));
+ // CHECK-FIXES: auto my_ptr = std::unique_ptr<int>(new int(1));
+}
diff --git a/test/clang-tidy/modernize-make-unique-cxx14.cpp b/test/clang-tidy/modernize-make-unique-cxx14.cpp
new file mode 100644
index 0000000..cd35f75
--- /dev/null
+++ b/test/clang-tidy/modernize-make-unique-cxx14.cpp
@@ -0,0 +1,11 @@
+// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++14 \
+// RUN: -I%S/Inputs/modernize-smart-ptr
+
+#include "unique_ptr.h"
+// CHECK-FIXES: #include <memory>
+
+void f() {
+ auto my_ptr = std::unique_ptr<int>(new int(1));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use std::make_unique instead
+ // CHECK-FIXES: auto my_ptr = std::make_unique<int>(1);
+}
diff --git a/test/clang-tidy/modernize-make-unique-macros.cpp b/test/clang-tidy/modernize-make-unique-macros.cpp
index e3e5479..117a45c 100644
--- a/test/clang-tidy/modernize-make-unique-macros.cpp
+++ b/test/clang-tidy/modernize-make-unique-macros.cpp
@@ -1,6 +1,6 @@
// RUN: %check_clang_tidy %s modernize-make-unique %t -- \
// RUN: -config="{CheckOptions: [{key: modernize-make-unique.IgnoreMacros, value: 0}]}" \
-// RUN: -- -std=c++11 -I%S/Inputs/modernize-smart-ptr
+// RUN: -- -std=c++14 -I%S/Inputs/modernize-smart-ptr
#include "unique_ptr.h"
diff --git a/test/clang-tidy/modernize-make-unique.cpp b/test/clang-tidy/modernize-make-unique.cpp
index 1e06d38..9685203 100644
--- a/test/clang-tidy/modernize-make-unique.cpp
+++ b/test/clang-tidy/modernize-make-unique.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++11 \
+// RUN: %check_clang_tidy %s modernize-make-unique %t -- -- -std=c++14 \
// RUN: -I%S/Inputs/modernize-smart-ptr
#include "unique_ptr.h"
diff --git a/test/clang-tidy/portability-simd-intrinsics-ppc.cpp b/test/clang-tidy/portability-simd-intrinsics-ppc.cpp
new file mode 100644
index 0000000..be0ef76
--- /dev/null
+++ b/test/clang-tidy/portability-simd-intrinsics-ppc.cpp
@@ -0,0 +1,13 @@
+// RUN: %check_clang_tidy %s portability-simd-intrinsics %t -- \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \
+// RUN: ]}' -- -target ppc64le -maltivec -std=c++11
+
+vector int vec_add(vector int, vector int);
+
+void PPC() {
+ vector int i0, i1;
+
+ vec_add(i0, i1);
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics]
+}
diff --git a/test/clang-tidy/readability-simd-intrinsics-x86.cpp b/test/clang-tidy/portability-simd-intrinsics-x86.cpp
similarity index 72%
rename from test/clang-tidy/readability-simd-intrinsics-x86.cpp
rename to test/clang-tidy/portability-simd-intrinsics-x86.cpp
index 8ab3556..1fb2289 100644
--- a/test/clang-tidy/readability-simd-intrinsics-x86.cpp
+++ b/test/clang-tidy/portability-simd-intrinsics-x86.cpp
@@ -1,6 +1,6 @@
-// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \
+// RUN: %check_clang_tidy %s portability-simd-intrinsics %t -- \
// RUN: -config='{CheckOptions: [ \
-// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \
+// RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \
// RUN: ]}' -- -target x86_64 -std=c++11
typedef long long __m128i __attribute__((vector_size(16)));
@@ -17,7 +17,7 @@
__m256 d0;
_mm_add_epi32(i0, i1);
-// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics]
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics]
d0 = _mm256_load_pd(0);
_mm256_store_pd(0, d0);
diff --git a/test/clang-tidy/readability-simd-intrinsics-ppc.cpp b/test/clang-tidy/readability-simd-intrinsics-ppc.cpp
deleted file mode 100644
index 387384b..0000000
--- a/test/clang-tidy/readability-simd-intrinsics-ppc.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \
-// RUN: -config='{CheckOptions: [ \
-// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \
-// RUN: ]}' -- -target ppc64le -maltivec -std=c++11
-
-vector int vec_add(vector int, vector int);
-
-void PPC() {
- vector int i0, i1;
-
- vec_add(i0, i1);
-// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics]
-}
diff --git a/test/clang-tidy/run-clang-tidy.cpp b/test/clang-tidy/run-clang-tidy.cpp
new file mode 100644
index 0000000..28e6d75
--- /dev/null
+++ b/test/clang-tidy/run-clang-tidy.cpp
@@ -0,0 +1,14 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "[{\"directory\":\".\",\"command\":\"clang++ -c %/t/test.cpp\",\"file\":\"%/t/test.cpp\"}]" | sed -e 's/\\/\\\\/g' > %t/compile_commands.json
+// RUN: echo "Checks: '-*,modernize-use-auto'" > %t/.clang-tidy
+// RUN: echo "WarningsAsErrors: '*'" >> %t/.clang-tidy
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: cd "%t"
+// RUN: not %run_clang_tidy "%t/test.cpp"
+
+int main()
+{
+ int* x = new int();
+ delete x;
+}
diff --git a/test/clang-tidy/select-checks.cpp b/test/clang-tidy/select-checks.cpp
index 12d05b4..791def7 100644
--- a/test/clang-tidy/select-checks.cpp
+++ b/test/clang-tidy/select-checks.cpp
@@ -1,5 +1,5 @@
// RUN: clang-tidy %s -checks='-*,llvm-namespace-*' -- 2>&1 | FileCheck -implicit-check-not='{{warning:|error:}}' %s
-// RUN: clang-tidy %s -checks='-*,an-unknown-check' -- 2>&1 | FileCheck -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK2 %s
+// RUN: not clang-tidy %s -checks='-*,an-unknown-check' -- 2>&1 | FileCheck -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK2 %s
// CHECK2: Error: no checks enabled.
diff --git a/test/clang-tidy/zircon-temporary-objects.cpp b/test/clang-tidy/zircon-temporary-objects.cpp
new file mode 100644
index 0000000..b29b482
--- /dev/null
+++ b/test/clang-tidy/zircon-temporary-objects.cpp
@@ -0,0 +1,109 @@
+// RUN: %check_clang_tidy %s zircon-temporary-objects %t -- \
+// RUN: -config="{CheckOptions: [{key: zircon-temporary-objects.Names, value: 'Foo;NS::Bar'}]}" \
+// RUN: -header-filter=.* \
+// RUN: -- -std=c++11
+
+// Should flag instances of Foo, NS::Bar.
+
+class Foo {
+public:
+ Foo() = default;
+ Foo(int Val) : Val(Val){};
+
+private:
+ int Val;
+};
+
+namespace NS {
+
+class Bar {
+public:
+ Bar() = default;
+ Bar(int Val) : Val(Val){};
+
+private:
+ int Val;
+};
+
+} // namespace NS
+
+class Bar {
+public:
+ Bar() = default;
+ Bar(int Val) : Val(Val){};
+
+private:
+ int Val;
+};
+
+int func(Foo F) { return 1; };
+
+int main() {
+ Foo F;
+ Foo *F2 = new Foo();
+ new Foo();
+ Foo();
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'Foo' is prohibited
+ Foo F3 = Foo();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: creating a temporary object of type 'Foo' is prohibited
+
+ Bar();
+ NS::Bar();
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'NS::Bar' is prohibited
+
+ int A = func(Foo());
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: creating a temporary object of type 'Foo' is prohibited
+
+ Foo F4(0);
+ Foo *F5 = new Foo(0);
+ new Foo(0);
+ Foo(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'Foo' is prohibited
+ Foo F6 = Foo(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: creating a temporary object of type 'Foo' is prohibited
+
+ Bar(0);
+ NS::Bar(0);
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'NS::Bar' is prohibited
+
+ int B = func(Foo(0));
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: creating a temporary object of type 'Foo' is prohibited
+}
+
+namespace NS {
+
+void f() {
+ Bar();
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'NS::Bar' is prohibited
+ Bar(0);
+// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: creating a temporary object of type 'NS::Bar' is prohibited
+}
+
+} // namespace NS
+
+template <typename Ty>
+Ty make_ty() { return Ty(); }
+// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: creating a temporary object of type 'Foo' is prohibited
+// CHECK-MESSAGES: :[[@LINE-2]]:23: warning: creating a temporary object of type 'NS::Bar' is prohibited
+
+void ty_func() {
+ make_ty<Bar>();
+ make_ty<NS::Bar>();
+ make_ty<Foo>();
+}
+
+// Inheriting the disallowed class does not trigger the check.
+
+class Bingo : NS::Bar {}; // Not explicitly disallowed
+
+void f2() {
+ Bingo();
+}
+
+template <typename Ty>
+class Quux : Ty {};
+
+void f3() {
+ Quux<NS::Bar>();
+ Quux<Bar>();
+}
diff --git a/test/clangd/diagnostics.test b/test/clangd/diagnostics.test
index 7c8150e..afd1277 100644
--- a/test/clangd/diagnostics.test
+++ b/test/clangd/diagnostics.test
@@ -18,20 +18,6 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "severity": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "change return type to 'int'",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 4,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
diff --git a/test/clangd/execute-command.test b/test/clangd/execute-command.test
index ffd8971..9686d04 100644
--- a/test/clangd/execute-command.test
+++ b/test/clangd/execute-command.test
@@ -18,34 +18,6 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "severity": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 35,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 34,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 35,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 34,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
diff --git a/test/clangd/extra-flags.test b/test/clangd/extra-flags.test
index 13d1d9e..23b2c65 100644
--- a/test/clangd/extra-flags.test
+++ b/test/clangd/extra-flags.test
@@ -18,20 +18,6 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "severity": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 19,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 18,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
@@ -54,20 +40,6 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "severity": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 19,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 18,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
diff --git a/test/clangd/fixits.test b/test/clangd/fixits.test
index 6087bc8..f8eb20c 100644
--- a/test/clangd/fixits.test
+++ b/test/clangd/fixits.test
@@ -18,40 +18,12 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "severity": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 35,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 34,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
-# CHECK-NEXT: },
-# CHECK-NEXT: {
-# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison",
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 35,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 34,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "severity": 3
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
# CHECK-NEXT: }
---
-{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
+{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"}]}}}
# CHECK: "id": 2,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
@@ -91,7 +63,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "command": "clangd.applyFix",
-# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning"
+# CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "arguments": [
@@ -116,11 +88,11 @@
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "command": "clangd.applyFix",
-# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison"
+# CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison"
# CHECK-NEXT: }
# CHECK-NEXT: ]
---
-{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
+{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"}]}}}
# Make sure unused "code" and "source" fields ignored gracefully
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
@@ -161,7 +133,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "command": "clangd.applyFix",
-# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning"
+# CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "arguments": [
@@ -186,7 +158,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "command": "clangd.applyFix",
-# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison"
+# CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison"
# CHECK-NEXT: }
# CHECK-NEXT: ]
---
diff --git a/test/clangd/initialize-params-invalid.test b/test/clangd/initialize-params-invalid.test
index da921cb..d98e110 100644
--- a/test/clangd/initialize-params-invalid.test
+++ b/test/clangd/initialize-params-invalid.test
@@ -36,7 +36,7 @@
# CHECK-NEXT: ","
# CHECK-NEXT: ]
# CHECK-NEXT: },
-# CHECK-NEXT: "textDocumentSync": 1
+# CHECK-NEXT: "textDocumentSync": 2
# CHECK-NEXT: }
# CHECK-NEXT: }
---
diff --git a/test/clangd/initialize-params.test b/test/clangd/initialize-params.test
index a4de1ee..d3bf136 100644
--- a/test/clangd/initialize-params.test
+++ b/test/clangd/initialize-params.test
@@ -36,7 +36,7 @@
# CHECK-NEXT: ","
# CHECK-NEXT: ]
# CHECK-NEXT: },
-# CHECK-NEXT: "textDocumentSync": 1
+# CHECK-NEXT: "textDocumentSync": 2
# CHECK-NEXT: }
# CHECK-NEXT: }
---
diff --git a/test/clangd/textdocument-didchange-fail.test b/test/clangd/textdocument-didchange-fail.test
new file mode 100644
index 0000000..3bd01e9
--- /dev/null
+++ b/test/clangd/textdocument-didchange-fail.test
@@ -0,0 +1,37 @@
+# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
+# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int main() {}\n"}}}
+---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":6}}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+---
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp"},"contentChanges":[{"range":{"start":{"line":100,"character":0},"end":{"line":100,"character":0}},"text": "foo"}]}}
+---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":6}}}
+# CHECK: "error": {
+# CHECK-NEXT: "code": -32602,
+# CHECK-NEXT: "message": "trying to get AST for non-added document"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0"
+# CHECK-NEXT:}
+---
+{"jsonrpc":"2.0","id":4,"method":"shutdown"}
diff --git a/test/clangd/xrefs.test b/test/clangd/xrefs.test
index 6e0e21f..f2e17c4 100644
--- a/test/clangd/xrefs.test
+++ b/test/clangd/xrefs.test
@@ -10,11 +10,11 @@
# CHECK-NEXT: {
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "character": 5,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "character": 4,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
diff --git a/test/lit.cfg b/test/lit.cfg
index 3bffbdb..4b4718a 100644
--- a/test/lit.cfg
+++ b/test/lit.cfg
@@ -129,6 +129,11 @@
config.substitutions.append(
('%clang_tidy_diff',
'%s %s' % (config.python_executable, clang_tidy_diff)) )
+ run_clang_tidy = os.path.join(
+ config.test_source_root, "..", "clang-tidy", "tool", "run-clang-tidy.py")
+ config.substitutions.append(
+ ('%run_clang_tidy',
+ '%s %s' % (config.python_executable, run_clang_tidy)) )
else:
# exclude the clang-tidy test directory
config.excludes.append('clang-tidy')
diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in
index 86e9617..948e469 100644
--- a/test/lit.site.cfg.in
+++ b/test/lit.site.cfg.in
@@ -23,5 +23,7 @@
key, = e.args
lit_config.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key,key))
+@LIT_SITE_CFG_IN_FOOTER@
+
# Let the main config do the real work.
lit_config.load_config(config, "@CLANG_TOOLS_SOURCE_DIR@/test/lit.cfg")
diff --git a/test/modularize/NoProblemsNamespace.modularize b/test/modularize/NoProblemsNamespace.modularize
index 1c3f78d..93a4fea 100644
--- a/test/modularize/NoProblemsNamespace.modularize
+++ b/test/modularize/NoProblemsNamespace.modularize
@@ -1,3 +1,3 @@
-# RUN: modularize -block-check-header-list-only
+# RUN: modularize -block-check-header-list-only %s
Inputs/IncludeInNamespace.h
diff --git a/unittests/change-namespace/ChangeNamespaceTests.cpp b/unittests/change-namespace/ChangeNamespaceTests.cpp
index 440a3fe..917f7b0 100644
--- a/unittests/change-namespace/ChangeNamespaceTests.cpp
+++ b/unittests/change-namespace/ChangeNamespaceTests.cpp
@@ -850,22 +850,58 @@
TEST_F(ChangeNamespaceTest, UsingShadowDeclInGlobal) {
std::string Code = "namespace glob {\n"
"class Glob {};\n"
+ "void GFunc() {}\n"
"}\n"
"using glob::Glob;\n"
+ "using glob::GFunc;\n"
"namespace na {\n"
"namespace nb {\n"
- "void f() { Glob g; }\n"
+ "void f() { Glob g; GFunc(); }\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "namespace glob {\n"
"class Glob {};\n"
+ "void GFunc() {}\n"
"}\n"
"using glob::Glob;\n"
+ "using glob::GFunc;\n"
"\n"
"namespace x {\n"
"namespace y {\n"
- "void f() { Glob g; }\n"
+ "void f() { Glob g; GFunc(); }\n"
+ "} // namespace y\n"
+ "} // namespace x\n";
+ EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
+}
+
+TEST_F(ChangeNamespaceTest, UsingShadowDeclsInAnonymousNamespaces) {
+ std::string Code = "namespace util {\n"
+ "class Util {};\n"
+ "void func() {}\n"
+ "}\n"
+ "namespace na {\n"
+ "namespace nb {\n"
+ "namespace {\n"
+ "using ::util::Util;\n"
+ "using ::util::func;\n"
+ "void f() { Util u; func(); }\n"
+ "}\n"
+ "} // namespace nb\n"
+ "} // namespace na\n";
+
+ std::string Expected = "namespace util {\n"
+ "class Util {};\n"
+ "void func() {}\n"
+ "} // namespace util\n"
+ "\n"
+ "namespace x {\n"
+ "namespace y {\n"
+ "namespace {\n"
+ "using ::util::Util;\n"
+ "using ::util::func;\n"
+ "void f() { Util u; func(); }\n"
+ "}\n"
"} // namespace y\n"
"} // namespace x\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
diff --git a/unittests/clangd/Annotations.cpp b/unittests/clangd/Annotations.cpp
index 92d1cb1..fc9c4cb 100644
--- a/unittests/clangd/Annotations.cpp
+++ b/unittests/clangd/Annotations.cpp
@@ -86,7 +86,11 @@
std::pair<std::size_t, std::size_t>
Annotations::offsetRange(llvm::StringRef Name) const {
auto R = range(Name);
- return {positionToOffset(Code, R.start), positionToOffset(Code, R.end)};
+ llvm::Expected<size_t> Start = positionToOffset(Code, R.start);
+ llvm::Expected<size_t> End = positionToOffset(Code, R.end);
+ assert(Start);
+ assert(End);
+ return {*Start, *End};
}
} // namespace clangd
diff --git a/unittests/clangd/CMakeLists.txt b/unittests/clangd/CMakeLists.txt
index 6017868..af2e114 100644
--- a/unittests/clangd/CMakeLists.txt
+++ b/unittests/clangd/CMakeLists.txt
@@ -15,6 +15,7 @@
CodeCompleteTests.cpp
CodeCompletionStringsTests.cpp
ContextTests.cpp
+ DraftStoreTests.cpp
FileIndexTests.cpp
FuzzyMatchTests.cpp
HeadersTests.cpp
@@ -43,4 +44,5 @@
clangTooling
clangToolingCore
LLVMSupport
+ LLVMTestingSupport
)
diff --git a/unittests/clangd/ClangdTests.cpp b/unittests/clangd/ClangdTests.cpp
index 1102d2c..b0e56ba 100644
--- a/unittests/clangd/ClangdTests.cpp
+++ b/unittests/clangd/ClangdTests.cpp
@@ -42,12 +42,10 @@
namespace {
-static bool diagsContainErrors(ArrayRef<DiagWithFixIts> Diagnostics) {
- for (const auto &DiagAndFixIts : Diagnostics) {
- // FIXME: severities returned by clangd should have a descriptive
- // diagnostic severity enum
- const int ErrorSeverity = 1;
- if (DiagAndFixIts.Diag.severity == ErrorSeverity)
+bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
+ for (auto D : Diagnostics) {
+ if (D.Severity == DiagnosticsEngine::Error ||
+ D.Severity == DiagnosticsEngine::Fatal)
return true;
}
return false;
@@ -55,14 +53,11 @@
class ErrorCheckingDiagConsumer : public DiagnosticsConsumer {
public:
- void
- onDiagnosticsReady(PathRef File,
- Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {
- bool HadError = diagsContainErrors(Diagnostics.Value);
-
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {
+ bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
HadErrorInLastDiags = HadError;
- LastVFSTag = Diagnostics.Tag;
}
bool hadErrorInLastDiags() {
@@ -70,22 +65,18 @@
return HadErrorInLastDiags;
}
- VFSTag lastVFSTag() { return LastVFSTag; }
-
private:
std::mutex Mutex;
bool HadErrorInLastDiags = false;
- VFSTag LastVFSTag = VFSTag();
};
/// For each file, record whether the last published diagnostics contained at
/// least one error.
class MultipleErrorCheckingDiagConsumer : public DiagnosticsConsumer {
public:
- void
- onDiagnosticsReady(PathRef File,
- Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {
- bool HadError = diagsContainErrors(Diagnostics.Value);
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {
+ bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
LastDiagsHadError[File] = HadError;
@@ -153,7 +144,6 @@
FS.Files[testPath(FileWithContents.first)] = FileWithContents.second;
auto SourceFilename = testPath(SourceFileRelPath);
- FS.ExpectedFile = SourceFilename;
Server.addDocument(SourceFilename, SourceContents);
auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
@@ -212,7 +202,6 @@
FS.Files[testPath("foo.h")] = "int a;";
FS.Files[FooCpp] = SourceContents;
- FS.ExpectedFile = FooCpp;
Server.addDocument(FooCpp, SourceContents);
auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
@@ -249,7 +238,6 @@
FS.Files[FooH] = "int a;";
FS.Files[FooCpp] = SourceContents;
- FS.ExpectedFile = FooCpp;
Server.addDocument(FooCpp, SourceContents);
auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
@@ -257,13 +245,13 @@
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
FS.Files[FooH] = "";
- Server.forceReparse(FooCpp);
+ Server.addDocument(FooCpp, SourceContents);
auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
FS.Files[FooH] = "int a;";
- Server.forceReparse(FooCpp);
+ Server.addDocument(FooCpp, SourceContents);
auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
@@ -272,29 +260,33 @@
EXPECT_NE(DumpParse1, DumpParseDifferent);
}
-TEST_F(ClangdVFSTest, CheckVersions) {
- MockFSProvider FS;
- ErrorCheckingDiagConsumer DiagConsumer;
+TEST_F(ClangdVFSTest, PropagatesContexts) {
+ static Key<int> Secret;
+ struct FSProvider : public FileSystemProvider {
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
+ Got = Context::current().getExisting(Secret);
+ return buildTestFS({});
+ }
+ int Got;
+ } FS;
+ struct DiagConsumer : public DiagnosticsConsumer {
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {
+ Got = Context::current().getExisting(Secret);
+ }
+ int Got;
+ } DiagConsumer;
MockCompilationDatabase CDB;
+
+ // Verify that the context is plumbed to the FS provider and diagnostics.
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
-
- auto FooCpp = testPath("foo.cpp");
- const auto SourceContents = "int a;";
- FS.Files[FooCpp] = SourceContents;
- FS.ExpectedFile = FooCpp;
-
- // Use default completion options.
- clangd::CodeCompleteOptions CCOpts;
-
- FS.Tag = "123";
- runAddDocument(Server, FooCpp, SourceContents);
- EXPECT_EQ(runCodeComplete(Server, FooCpp, Position(), CCOpts).Tag, FS.Tag);
- EXPECT_EQ(DiagConsumer.lastVFSTag(), FS.Tag);
-
- FS.Tag = "321";
- runAddDocument(Server, FooCpp, SourceContents);
- EXPECT_EQ(DiagConsumer.lastVFSTag(), FS.Tag);
- EXPECT_EQ(runCodeComplete(Server, FooCpp, Position(), CCOpts).Tag, FS.Tag);
+ {
+ WithContextValue Entrypoint(Secret, 42);
+ Server.addDocument(testPath("foo.cpp"), "void main(){}");
+ }
+ ASSERT_TRUE(Server.blockUntilIdleForTest());
+ EXPECT_EQ(FS.Got, 42);
+ EXPECT_EQ(DiagConsumer.Got, 42);
}
// Only enable this test on Unix
@@ -366,7 +358,6 @@
)cpp";
FS.Files[FooCpp] = "";
- FS.ExpectedFile = FooCpp;
// First parse files in C mode and check they produce errors.
CDB.ExtraClangFlags = {"-xc"};
@@ -377,15 +368,16 @@
// Now switch to C++ mode.
CDB.ExtraClangFlags = {"-xc++"};
- // Currently, addDocument never checks if CompileCommand has changed, so we
+ // By default addDocument does not check if CompileCommand has changed, so we
// expect to see the errors.
runAddDocument(Server, FooCpp, SourceContents1);
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
runAddDocument(Server, FooCpp, SourceContents2);
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
- // But forceReparse should reparse the file with proper flags.
- Server.forceReparse(FooCpp);
- ASSERT_TRUE(Server.blockUntilIdleForTest());
+ // Passing SkipCache=true will force addDocument to reparse the file with
+ // proper flags.
+ runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto,
+ /*SkipCache=*/true);
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
// Subsequent addDocument calls should finish without errors too.
runAddDocument(Server, FooCpp, SourceContents1);
@@ -409,7 +401,6 @@
int main() { return 0; }
)cpp";
FS.Files[FooCpp] = "";
- FS.ExpectedFile = FooCpp;
// Parse with define, we expect to see the errors.
CDB.ExtraClangFlags = {"-DWITH_ERROR"};
@@ -418,12 +409,14 @@
// Parse without the define, no errors should be produced.
CDB.ExtraClangFlags = {};
- // Currently, addDocument never checks if CompileCommand has changed, so we
+ // By default addDocument does not check if CompileCommand has changed, so we
// expect to see the errors.
runAddDocument(Server, FooCpp, SourceContents);
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
- // But forceReparse should reparse the file with proper flags.
- Server.forceReparse(FooCpp);
+ // Passing SkipCache=true will force addDocument to reparse the file with
+ // proper flags.
+ runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto,
+ /*SkipCache=*/true);
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
// Subsequent addDocument call should finish without errors too.
@@ -435,9 +428,9 @@
TEST_F(ClangdVFSTest, ReparseOpenedFiles) {
Annotations FooSource(R"cpp(
#ifdef MACRO
-$one[[static void bob() {}]]
+static void $one[[bob]]() {}
#else
-$two[[static void bob() {}]]
+static void $two[[bob]]() {}
#endif
int main () { bo^b (); return 0; }
@@ -478,14 +471,17 @@
auto Locations = runFindDefinitions(Server, FooCpp, FooSource.point());
EXPECT_TRUE(bool(Locations));
- EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp},
- FooSource.range("one")}));
+ EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
+ FooSource.range("one")}));
// Undefine MACRO, close baz.cpp.
CDB.ExtraClangFlags.clear();
DiagConsumer.clear();
Server.removeDocument(BazCpp);
- Server.reparseOpenedFiles();
+ Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto,
+ /*SkipCache=*/true);
+ Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto,
+ /*SkipCache=*/true);
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_THAT(DiagConsumer.filesWithDiags(),
@@ -493,8 +489,8 @@
Locations = runFindDefinitions(Server, FooCpp, FooSource.point());
EXPECT_TRUE(bool(Locations));
- EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp},
- FooSource.range("two")}));
+ EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
+ FooSource.range("two")}));
}
TEST_F(ClangdVFSTest, MemoryUsage) {
@@ -554,13 +550,13 @@
EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name"));
// FIXME: codeComplete and signatureHelp should also return errors when they
// can't parse the file.
- EXPECT_THAT(
- runCodeComplete(Server, FooCpp, Position(), clangd::CodeCompleteOptions())
- .Value.items,
- IsEmpty());
+ EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
+ clangd::CodeCompleteOptions()))
+ .items,
+ IsEmpty());
auto SigHelp = runSignatureHelp(Server, FooCpp, Position());
ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error";
- EXPECT_THAT(SigHelp->Value.signatures, IsEmpty());
+ EXPECT_THAT(SigHelp->signatures, IsEmpty());
}
class ClangdThreadingTest : public ClangdVFSTest {};
@@ -612,15 +608,14 @@
public:
TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
- void onDiagnosticsReady(
- PathRef File,
- Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {
StringRef FileIndexStr = llvm::sys::path::stem(File);
ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
unsigned long FileIndex = std::stoul(FileIndexStr.str());
- bool HadError = diagsContainErrors(Diagnostics.Value);
+ bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
if (HadError)
@@ -686,44 +681,31 @@
Stats.FileIsRemoved = true;
};
- auto UpdateStatsOnForceReparse = [&](unsigned FileIndex) {
- auto &Stats = ReqStats[FileIndex];
-
- if (Stats.LastContentsHadErrors)
- ++Stats.RequestsWithErrors;
- else
- ++Stats.RequestsWithoutErrors;
- };
-
- auto AddDocument = [&](unsigned FileIndex) {
+ auto AddDocument = [&](unsigned FileIndex, bool SkipCache) {
bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
Server.addDocument(FilePaths[FileIndex],
ShouldHaveErrors ? SourceContentsWithErrors
- : SourceContentsWithoutErrors);
+ : SourceContentsWithoutErrors,
+ WantDiagnostics::Auto, SkipCache);
UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
};
// Various requests that we would randomly run.
auto AddDocumentRequest = [&]() {
unsigned FileIndex = FileIndexDist(RandGen);
- AddDocument(FileIndex);
+ AddDocument(FileIndex, /*SkipCache=*/false);
};
auto ForceReparseRequest = [&]() {
unsigned FileIndex = FileIndexDist(RandGen);
- // Make sure we don't violate the ClangdServer's contract.
- if (ReqStats[FileIndex].FileIsRemoved)
- AddDocument(FileIndex);
-
- Server.forceReparse(FilePaths[FileIndex]);
- UpdateStatsOnForceReparse(FileIndex);
+ AddDocument(FileIndex, /*SkipCache=*/true);
};
auto RemoveDocumentRequest = [&]() {
unsigned FileIndex = FileIndexDist(RandGen);
// Make sure we don't violate the ClangdServer's contract.
if (ReqStats[FileIndex].FileIsRemoved)
- AddDocument(FileIndex);
+ AddDocument(FileIndex, /*SkipCache=*/false);
Server.removeDocument(FilePaths[FileIndex]);
UpdateStatsOnRemoveDocument(FileIndex);
@@ -733,7 +715,7 @@
unsigned FileIndex = FileIndexDist(RandGen);
// Make sure we don't violate the ClangdServer's contract.
if (ReqStats[FileIndex].FileIsRemoved)
- AddDocument(FileIndex);
+ AddDocument(FileIndex, /*SkipCache=*/false);
Position Pos;
Pos.line = LineDist(RandGen);
@@ -744,15 +726,15 @@
// requests as opposed to AddDocument/RemoveDocument, which are implicitly
// cancelled by any subsequent AddDocument/RemoveDocument request to the
// same file.
- runCodeComplete(Server, FilePaths[FileIndex], Pos,
- clangd::CodeCompleteOptions());
+ cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos,
+ clangd::CodeCompleteOptions()));
};
auto FindDefinitionsRequest = [&]() {
unsigned FileIndex = FileIndexDist(RandGen);
// Make sure we don't violate the ClangdServer's contract.
if (ReqStats[FileIndex].FileIsRemoved)
- AddDocument(FileIndex);
+ AddDocument(FileIndex, /*SkipCache=*/false);
Position Pos;
Pos.line = LineDist(RandGen);
@@ -881,8 +863,7 @@
NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
: StartSecondReparse(std::move(StartSecondReparse)) {}
- void onDiagnosticsReady(PathRef,
- Tagged<std::vector<DiagWithFixIts>>) override {
+ void onDiagnosticsReady(PathRef, std::vector<Diag>) override {
++Count;
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
ASSERT_TRUE(Lock.owns_lock())
@@ -945,8 +926,8 @@
auto FooCpp = testPath("foo.cpp");
const auto Code = R"cpp(
-#include "z.h"
#include "x.h"
+#include "z.h"
void f() {}
)cpp";
@@ -967,7 +948,7 @@
.contains((llvm::Twine("#include ") + Expected + "").str());
};
- EXPECT_TRUE(Inserted("\"y.h\"", /*Preferred=*/"","\"y.h\""));
+ EXPECT_TRUE(Inserted("\"y.h\"", /*Preferred=*/"", "\"y.h\""));
EXPECT_TRUE(Inserted("\"y.h\"", /*Preferred=*/"\"Y.h\"", "\"Y.h\""));
EXPECT_TRUE(Inserted("<string>", /*Preferred=*/"", "<string>"));
EXPECT_TRUE(Inserted("<string>", /*Preferred=*/"", "<string>"));
@@ -1000,8 +981,8 @@
auto Path = testPath("foo.cpp");
std::string Code = R"cpp(
-#include "y.h"
#include "x.h"
+#include "y.h"
void f( ) {}
)cpp";
diff --git a/unittests/clangd/ClangdUnitTests.cpp b/unittests/clangd/ClangdUnitTests.cpp
index ee4acbe..0e11082 100644
--- a/unittests/clangd/ClangdUnitTests.cpp
+++ b/unittests/clangd/ClangdUnitTests.cpp
@@ -7,8 +7,8 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangdUnit.h"
#include "Annotations.h"
+#include "ClangdUnit.h"
#include "TestFS.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
@@ -20,26 +20,24 @@
namespace clang {
namespace clangd {
using namespace llvm;
-void PrintTo(const DiagWithFixIts &D, std::ostream *O) {
- llvm::raw_os_ostream OS(*O);
- OS << D.Diag;
- if (!D.FixIts.empty()) {
- OS << " {";
- const char *Sep = "";
- for (const auto &F : D.FixIts) {
- OS << Sep << F;
- Sep = ", ";
- }
- OS << "}";
- }
-}
namespace {
using testing::ElementsAre;
+using testing::Field;
+using testing::IsEmpty;
+using testing::Pair;
+
+testing::Matcher<const Diag &> WithFix(testing::Matcher<Fix> FixMatcher) {
+ return Field(&Diag::Fixes, ElementsAre(FixMatcher));
+}
+
+testing::Matcher<const Diag &> WithNote(testing::Matcher<Note> NoteMatcher) {
+ return Field(&Diag::Notes, ElementsAre(NoteMatcher));
+}
// FIXME: this is duplicated with FileIndexTests. Share it.
-ParsedAST build(StringRef Code, std::vector<const char*> Flags = {}) {
- std::vector<const char*> Cmd = {"clang", "main.cpp"};
+ParsedAST build(StringRef Code, std::vector<const char *> Flags = {}) {
+ std::vector<const char *> Cmd = {"clang", "main.cpp"};
Cmd.insert(Cmd.begin() + 1, Flags.begin(), Flags.end());
auto CI = createInvocationFromCommandLine(Cmd);
auto Buf = MemoryBuffer::getMemBuffer(Code);
@@ -50,18 +48,56 @@
return std::move(*AST);
}
+std::vector<Diag> buildDiags(llvm::StringRef Code,
+ std::vector<const char *> Flags = {}) {
+ return build(Code, std::move(Flags)).getDiagnostics();
+}
+
MATCHER_P2(Diag, Range, Message,
- "Diagnostic at " + llvm::to_string(Range) + " = [" + Message + "]") {
- return arg.Diag.range == Range && arg.Diag.message == Message &&
- arg.FixIts.empty();
+ "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
+ return arg.Range == Range && arg.Message == Message;
}
MATCHER_P3(Fix, Range, Replacement, Message,
"Fix " + llvm::to_string(Range) + " => " +
testing::PrintToString(Replacement) + " = [" + Message + "]") {
- return arg.Diag.range == Range && arg.Diag.message == Message &&
- arg.FixIts.size() == 1 && arg.FixIts[0].range == Range &&
- arg.FixIts[0].newText == Replacement;
+ return arg.Message == Message && arg.Edits.size() == 1 &&
+ arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
+}
+
+MATCHER_P(EqualToLSPDiag, LSPDiag,
+ "LSP diagnostic " + llvm::to_string(LSPDiag)) {
+ return std::tie(arg.range, arg.severity, arg.message) ==
+ std::tie(LSPDiag.range, LSPDiag.severity, LSPDiag.message);
+}
+
+MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
+ if (arg.Message != Fix.Message)
+ return false;
+ if (arg.Edits.size() != Fix.Edits.size())
+ return false;
+ for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
+ if (arg.Edits[I].range != Fix.Edits[I].range ||
+ arg.Edits[I].newText != Fix.Edits[I].newText)
+ return false;
+ }
+ return true;
+}
+
+// Helper function to make tests shorter.
+Position pos(int line, int character) {
+ Position Res;
+ Res.line = line;
+ Res.character = character;
+ return Res;
+}
+
+/// Matches diagnostic that has exactly one fix with the same range and message
+/// as the diagnostic itself.
+testing::Matcher<const clangd::Diag &> DiagWithEqualFix(clangd::Range Range,
+ std::string Replacement,
+ std::string Message) {
+ return AllOf(Diag(Range, Message), WithFix(Fix(Range, Replacement, Message)));
}
TEST(DiagnosticsTest, DiagnosticRanges) {
@@ -75,35 +111,34 @@
$unk[[unknown]]();
}
)cpp");
- llvm::errs() << Test.code();
- EXPECT_THAT(
- build(Test.code()).getDiagnostics(),
- ElementsAre(
- // This range spans lines.
- Fix(Test.range("typo"), "foo",
- "use of undeclared identifier 'goo'; did you mean 'foo'?"),
- // This is a pretty normal range.
- Diag(Test.range("decl"), "'foo' declared here"),
- // This range is zero-width, and at the end of a line.
- Fix(Test.range("semicolon"), ";",
- "expected ';' after expression"),
- // This range isn't provided by clang, we expand to the token.
- Diag(Test.range("unk"),
- "use of undeclared identifier 'unknown'")));
+ llvm::errs() << Test.code();
+ EXPECT_THAT(
+ buildDiags(Test.code()),
+ ElementsAre(
+ // This range spans lines.
+ AllOf(DiagWithEqualFix(
+ Test.range("typo"), "foo",
+ "use of undeclared identifier 'goo'; did you mean 'foo'?"),
+ // This is a pretty normal range.
+ WithNote(Diag(Test.range("decl"), "'foo' declared here"))),
+ // This range is zero-width, and at the end of a line.
+ DiagWithEqualFix(Test.range("semicolon"), ";",
+ "expected ';' after expression"),
+ // This range isn't provided by clang, we expand to the token.
+ Diag(Test.range("unk"), "use of undeclared identifier 'unknown'")));
}
TEST(DiagnosticsTest, FlagsMatter) {
Annotations Test("[[void]] main() {}");
- EXPECT_THAT(
- build(Test.code()).getDiagnostics(),
- ElementsAre(Fix(Test.range(), "int", "'main' must return 'int'")));
+ EXPECT_THAT(buildDiags(Test.code()),
+ ElementsAre(DiagWithEqualFix(Test.range(), "int",
+ "'main' must return 'int'")));
// Same code built as C gets different diagnostics.
EXPECT_THAT(
- build(Test.code(), {"-x", "c"}).getDiagnostics(),
- ElementsAre(
- // FIXME: ideally this would be one diagnostic with a named FixIt.
+ buildDiags(Test.code(), {"-x", "c"}),
+ ElementsAre(AllOf(
Diag(Test.range(), "return type of 'main' is not 'int'"),
- Fix(Test.range(), "int", "change return type to 'int'")));
+ WithFix(Fix(Test.range(), "int", "change return type to 'int'")))));
}
TEST(DiagnosticsTest, Preprocessor) {
@@ -121,10 +156,73 @@
#endif
)cpp");
EXPECT_THAT(
- build(Test.code()).getDiagnostics(),
+ buildDiags(Test.code()),
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
}
+TEST(DiagnosticsTest, ToLSP) {
+ clangd::Diag D;
+ D.Message = "something terrible happened";
+ D.Range = {pos(1, 2), pos(3, 4)};
+ D.InsideMainFile = true;
+ D.Severity = DiagnosticsEngine::Error;
+ D.File = "foo/bar/main.cpp";
+
+ clangd::Note NoteInMain;
+ NoteInMain.Message = "declared somewhere in the main file";
+ NoteInMain.Range = {pos(5, 6), pos(7, 8)};
+ NoteInMain.Severity = DiagnosticsEngine::Remark;
+ NoteInMain.File = "../foo/bar/main.cpp";
+ NoteInMain.InsideMainFile = true;
+ D.Notes.push_back(NoteInMain);
+
+ clangd::Note NoteInHeader;
+ NoteInHeader.Message = "declared somewhere in the header file";
+ NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
+ NoteInHeader.Severity = DiagnosticsEngine::Note;
+ NoteInHeader.File = "../foo/baz/header.h";
+ NoteInHeader.InsideMainFile = false;
+ D.Notes.push_back(NoteInHeader);
+
+ clangd::Fix F;
+ F.Message = "do something";
+ D.Fixes.push_back(F);
+
+ auto MatchingLSP = [](const DiagBase &D, llvm::StringRef Message) {
+ clangd::Diagnostic Res;
+ Res.range = D.Range;
+ Res.severity = getSeverity(D.Severity);
+ Res.message = Message;
+ return Res;
+ };
+
+ // Diagnostics should turn into these:
+ clangd::Diagnostic MainLSP = MatchingLSP(D, R"(something terrible happened
+
+main.cpp:6:7: remark: declared somewhere in the main file
+
+../foo/baz/header.h:10:11:
+note: declared somewhere in the header file)");
+
+ clangd::Diagnostic NoteInMainLSP =
+ MatchingLSP(NoteInMain, R"(declared somewhere in the main file
+
+main.cpp:2:3: error: something terrible happened)");
+
+ // Transform dianostics and check the results.
+ std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
+ toLSPDiags(D, [&](clangd::Diagnostic LSPDiag,
+ llvm::ArrayRef<clangd::Fix> Fixes) {
+ LSPDiags.push_back({std::move(LSPDiag),
+ std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
+ });
+
+ EXPECT_THAT(
+ LSPDiags,
+ ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))),
+ Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty())));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/CodeCompleteTests.cpp b/unittests/clangd/CodeCompleteTests.cpp
index 7ffff71..48771a3 100644
--- a/unittests/clangd/CodeCompleteTests.cpp
+++ b/unittests/clangd/CodeCompleteTests.cpp
@@ -56,13 +56,13 @@
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
+using ::testing::Field;
using ::testing::Not;
using ::testing::UnorderedElementsAre;
-using ::testing::Field;
class IgnoreDiagnostics : public DiagnosticsConsumer {
- void onDiagnosticsReady(
- PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {}
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {}
};
// GMock helpers for matching completion items.
@@ -121,7 +121,8 @@
auto File = testPath("foo.cpp");
Annotations Test(Text);
runAddDocument(Server, File, Test.code());
- auto CompletionList = runCodeComplete(Server, File, Test.point(), Opts).Value;
+ auto CompletionList =
+ cantFail(runCodeComplete(Server, File, Test.point(), Opts));
// Sanity-check that filterText is valid.
EXPECT_THAT(CompletionList.items, Each(NameContainsFilter()));
return CompletionList;
@@ -319,11 +320,11 @@
};
// We used to test every combination of options, but that got too slow (2^N).
auto Flags = {
- &clangd::CodeCompleteOptions::IncludeMacros,
- &clangd::CodeCompleteOptions::IncludeBriefComments,
- &clangd::CodeCompleteOptions::EnableSnippets,
- &clangd::CodeCompleteOptions::IncludeCodePatterns,
- &clangd::CodeCompleteOptions::IncludeIneligibleResults,
+ &clangd::CodeCompleteOptions::IncludeMacros,
+ &clangd::CodeCompleteOptions::IncludeBriefComments,
+ &clangd::CodeCompleteOptions::EnableSnippets,
+ &clangd::CodeCompleteOptions::IncludeCodePatterns,
+ &clangd::CodeCompleteOptions::IncludeIneligibleResults,
};
// Test default options.
Test({});
@@ -536,18 +537,18 @@
auto I = memIndex({var("ns::index")});
Opts.Index = I.get();
- auto WithIndex = runCodeComplete(Server, File, Test.point(), Opts).Value;
+ auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts));
EXPECT_THAT(WithIndex.items,
UnorderedElementsAre(Named("local"), Named("index")));
auto ClassFromPreamble =
- runCodeComplete(Server, File, Test.point("2"), Opts).Value;
+ cantFail(runCodeComplete(Server, File, Test.point("2"), Opts));
EXPECT_THAT(ClassFromPreamble.items, Contains(Named("member")));
Opts.Index = nullptr;
- auto WithoutIndex = runCodeComplete(Server, File, Test.point(), Opts).Value;
+ auto WithoutIndex =
+ cantFail(runCodeComplete(Server, File, Test.point(), Opts));
EXPECT_THAT(WithoutIndex.items,
UnorderedElementsAre(Named("local"), Named("preamble")));
-
}
TEST(CompletionTest, DynamicIndexMultiFile) {
@@ -576,7 +577,7 @@
)cpp");
runAddDocument(Server, File, Test.code());
- auto Results = runCodeComplete(Server, File, Test.point(), {}).Value;
+ auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {}));
// "XYZ" and "foo" are not included in the file being completed but are still
// visible through the index.
EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
@@ -607,6 +608,43 @@
EXPECT_THAT(Results.items, Not(Contains(Labeled("clang::"))));
}
+TEST(CompletionTest, BacktrackCrashes) {
+ // Sema calls code completion callbacks twice in these cases.
+ auto Results = completions(R"cpp(
+ namespace ns {
+ struct FooBarBaz {};
+ } // namespace ns
+
+ int foo(ns::FooBar^
+ )cpp");
+
+ EXPECT_THAT(Results.items, ElementsAre(Labeled("FooBarBaz")));
+
+ // Check we don't crash in that case too.
+ completions(R"cpp(
+ struct FooBarBaz {};
+ void test() {
+ if (FooBarBaz * x^) {}
+ }
+)cpp");
+}
+
+TEST(CompletionTest, CompleteInExcludedPPBranch) {
+ auto Results = completions(R"cpp(
+ int bar(int param_in_bar) {
+ }
+
+ int foo(int param_in_foo) {
+#if 0
+ par^
+#endif
+ }
+)cpp");
+
+ EXPECT_THAT(Results.items, Contains(Labeled("param_in_foo")));
+ EXPECT_THAT(Results.items, Not(Contains(Labeled("param_in_bar"))));
+}
+
SignatureHelp signatures(StringRef Text) {
MockFSProvider FS;
MockCompilationDatabase CDB;
@@ -615,9 +653,7 @@
auto File = testPath("foo.cpp");
Annotations Test(Text);
runAddDocument(Server, File, Test.code());
- auto R = runSignatureHelp(Server, File, Test.point());
- assert(R);
- return R.get().Value;
+ return cantFail(runSignatureHelp(Server, File, Test.point()));
}
MATCHER_P(ParamsAre, P, "") {
@@ -690,6 +726,9 @@
return true;
}
+ void lookup(const LookupRequest &,
+ llvm::function_ref<void(const Symbol &)>) const override {}
+
const std::vector<FuzzyFindRequest> allRequests() const { return Requests; }
private:
diff --git a/unittests/clangd/DraftStoreTests.cpp b/unittests/clangd/DraftStoreTests.cpp
new file mode 100644
index 0000000..7443975
--- /dev/null
+++ b/unittests/clangd/DraftStoreTests.cpp
@@ -0,0 +1,350 @@
+//===-- DraftStoreTests.cpp -------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Annotations.h"
+#include "DraftStore.h"
+#include "SourceCode.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+struct IncrementalTestStep {
+ StringRef Src;
+ StringRef Contents;
+};
+
+int rangeLength(StringRef Code, const Range &Rng) {
+ llvm::Expected<size_t> Start = positionToOffset(Code, Rng.start);
+ llvm::Expected<size_t> End = positionToOffset(Code, Rng.end);
+ assert(Start);
+ assert(End);
+ return *End - *Start;
+}
+
+/// Send the changes one by one to updateDraft, verify the intermediate results.
+void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) {
+ DraftStore DS;
+ Annotations InitialSrc(Steps.front().Src);
+ constexpr llvm::StringLiteral Path("/hello.cpp");
+
+ // Set the initial content.
+ DS.addDraft(Path, InitialSrc.code());
+
+ for (size_t i = 1; i < Steps.size(); i++) {
+ Annotations SrcBefore(Steps[i - 1].Src);
+ Annotations SrcAfter(Steps[i].Src);
+ StringRef Contents = Steps[i - 1].Contents;
+ TextDocumentContentChangeEvent Event{
+ SrcBefore.range(),
+ rangeLength(SrcBefore.code(), SrcBefore.range()),
+ Contents.str(),
+ };
+
+ llvm::Expected<std::string> Result = DS.updateDraft(Path, {Event});
+ ASSERT_TRUE(!!Result);
+ EXPECT_EQ(*Result, SrcAfter.code());
+ EXPECT_EQ(*DS.getDraft(Path), SrcAfter.code());
+ }
+}
+
+/// Send all the changes at once to updateDraft, check only the final result.
+void allAtOnce(llvm::ArrayRef<IncrementalTestStep> Steps) {
+ DraftStore DS;
+ Annotations InitialSrc(Steps.front().Src);
+ Annotations FinalSrc(Steps.back().Src);
+ constexpr llvm::StringLiteral Path("/hello.cpp");
+ std::vector<TextDocumentContentChangeEvent> Changes;
+
+ for (size_t i = 0; i < Steps.size() - 1; i++) {
+ Annotations Src(Steps[i].Src);
+ StringRef Contents = Steps[i].Contents;
+
+ Changes.push_back({
+ Src.range(),
+ rangeLength(Src.code(), Src.range()),
+ Contents.str(),
+ });
+ }
+
+ // Set the initial content.
+ DS.addDraft(Path, InitialSrc.code());
+
+ llvm::Expected<std::string> Result = DS.updateDraft(Path, Changes);
+
+ ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError());
+ EXPECT_EQ(*Result, FinalSrc.code());
+ EXPECT_EQ(*DS.getDraft(Path), FinalSrc.code());
+}
+
+TEST(DraftStoreIncrementalUpdateTest, Simple) {
+ // clang-format off
+ IncrementalTestStep Steps[] =
+ {
+ // Replace a range
+ {
+R"cpp(static int
+hello[[World]]()
+{})cpp",
+ "Universe"
+ },
+ // Delete a range
+ {
+R"cpp(static int
+hello[[Universe]]()
+{})cpp",
+ ""
+ },
+ // Add a range
+ {
+R"cpp(static int
+hello[[]]()
+{})cpp",
+ "Monde"
+ },
+ {
+R"cpp(static int
+helloMonde()
+{})cpp",
+ ""
+ }
+ };
+ // clang-format on
+
+ stepByStep(Steps);
+ allAtOnce(Steps);
+}
+
+TEST(DraftStoreIncrementalUpdateTest, MultiLine) {
+ // clang-format off
+ IncrementalTestStep Steps[] =
+ {
+ // Replace a range
+ {
+R"cpp(static [[int
+helloWorld]]()
+{})cpp",
+R"cpp(char
+welcome)cpp"
+ },
+ // Delete a range
+ {
+R"cpp(static char[[
+welcome]]()
+{})cpp",
+ ""
+ },
+ // Add a range
+ {
+R"cpp(static char[[]]()
+{})cpp",
+ R"cpp(
+cookies)cpp"
+ },
+ // Replace the whole file
+ {
+R"cpp([[static char
+cookies()
+{}]])cpp",
+ R"cpp(#include <stdio.h>
+)cpp"
+ },
+ // Delete the whole file
+ {
+ R"cpp([[#include <stdio.h>
+]])cpp",
+ "",
+ },
+ // Add something to an empty file
+ {
+ "[[]]",
+ R"cpp(int main() {
+)cpp",
+ },
+ {
+ R"cpp(int main() {
+)cpp",
+ ""
+ }
+ };
+ // clang-format on
+
+ stepByStep(Steps);
+ allAtOnce(Steps);
+}
+
+TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 0;
+ Change.range->start.character = 0;
+ Change.range->end.line = 0;
+ Change.range->end.character = 2;
+ Change.rangeLength = 10;
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(
+ llvm::toString(Result.takeError()),
+ "Change's rangeLength (10) doesn't match the computed range length (2).");
+}
+
+TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 0;
+ Change.range->start.character = 5;
+ Change.range->end.line = 0;
+ Change.range->end.character = 3;
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Range's end position (0:3) is before start position (0:5)");
+}
+
+TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 0;
+ Change.range->start.character = 100;
+ Change.range->end.line = 0;
+ Change.range->end.character = 100;
+ Change.text = "foo";
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Character value is out of range (100)");
+}
+
+TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 0;
+ Change.range->start.character = 0;
+ Change.range->end.line = 0;
+ Change.range->end.character = 100;
+ Change.text = "foo";
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Character value is out of range (100)");
+}
+
+TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 100;
+ Change.range->start.character = 0;
+ Change.range->end.line = 100;
+ Change.range->end.character = 0;
+ Change.text = "foo";
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Line value is out of range (100)");
+}
+
+TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ DS.addDraft(File, "int main() {}\n");
+
+ TextDocumentContentChangeEvent Change;
+ Change.range.emplace();
+ Change.range->start.line = 0;
+ Change.range->start.character = 0;
+ Change.range->end.line = 100;
+ Change.range->end.character = 0;
+ Change.text = "foo";
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Line value is out of range (100)");
+}
+
+/// Check that if a valid change is followed by an invalid change, the original
+/// version of the document (prior to all changes) is kept.
+TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) {
+ DraftStore DS;
+ Path File = "foo.cpp";
+
+ StringRef OriginalContents = "int main() {}\n";
+ DS.addDraft(File, OriginalContents);
+
+ // The valid change
+ TextDocumentContentChangeEvent Change1;
+ Change1.range.emplace();
+ Change1.range->start.line = 0;
+ Change1.range->start.character = 0;
+ Change1.range->end.line = 0;
+ Change1.range->end.character = 0;
+ Change1.text = "Hello ";
+
+ // The invalid change
+ TextDocumentContentChangeEvent Change2;
+ Change2.range.emplace();
+ Change2.range->start.line = 0;
+ Change2.range->start.character = 5;
+ Change2.range->end.line = 0;
+ Change2.range->end.character = 100;
+ Change2.text = "something";
+
+ llvm::Expected<std::string> Result = DS.updateDraft(File, {Change1, Change2});
+
+ EXPECT_TRUE(!Result);
+ EXPECT_EQ(llvm::toString(Result.takeError()),
+ "Character value is out of range (100)");
+
+ llvm::Optional<std::string> Contents = DS.getDraft(File);
+ EXPECT_TRUE(Contents);
+ EXPECT_EQ(*Contents, OriginalContents);
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/unittests/clangd/HeadersTests.cpp b/unittests/clangd/HeadersTests.cpp
index 29452ef..9a5ca48 100644
--- a/unittests/clangd/HeadersTests.cpp
+++ b/unittests/clangd/HeadersTests.cpp
@@ -29,7 +29,7 @@
bool ExpectError = false) {
if (Preferred.empty())
Preferred = Original;
- auto VFS = FS.getTaggedFileSystem(MainFile).Value;
+ auto VFS = FS.getFileSystem();
auto Cmd = CDB.getCompileCommand(MainFile);
assert(static_cast<bool>(Cmd));
VFS->setCurrentWorkingDirectory(Cmd->Directory);
diff --git a/unittests/clangd/IndexTests.cpp b/unittests/clangd/IndexTests.cpp
index 20eb452..4d7c430 100644
--- a/unittests/clangd/IndexTests.cpp
+++ b/unittests/clangd/IndexTests.cpp
@@ -29,7 +29,7 @@
Sym.Scope = "";
} else {
Sym.Name = QName.substr(Pos + 2);
- Sym.Scope = QName.substr(0, Pos);
+ Sym.Scope = QName.substr(0, Pos + 2);
}
return Sym;
}
@@ -89,13 +89,16 @@
return generateSymbols(Names, WeakSymbols);
}
+std::string getQualifiedName(const Symbol &Sym) {
+ return (Sym.Scope + Sym.Name).str();
+}
+
std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req,
bool *Incomplete = nullptr) {
std::vector<std::string> Matches;
bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) {
- Matches.push_back(
- (Sym.Scope + (Sym.Scope.empty() ? "" : "::") + Sym.Name).str());
+ Matches.push_back(getQualifiedName(Sym));
});
if (Incomplete)
*Incomplete = IsIncomplete;
@@ -178,7 +181,7 @@
I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes = {"a"};
+ Req.Scopes = {"a::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2"));
}
@@ -187,7 +190,7 @@
I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes = {"a", "b"};
+ Req.Scopes = {"a::", "b::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3"));
}
@@ -196,7 +199,7 @@
I.build(generateSymbols({"a::y1", "a::b::y2"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes = {"a"};
+ Req.Scopes = {"a::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1"));
}
@@ -205,16 +208,60 @@
I.build(generateSymbols({"ns::ABC", "ns::abc"}));
FuzzyFindRequest Req;
Req.Query = "AB";
- Req.Scopes = {"ns"};
+ Req.Scopes = {"ns::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("ns::ABC", "ns::abc"));
}
-TEST(MergeTest, MergeIndex) {
+// Returns qualified names of symbols with any of IDs in the index.
+std::vector<std::string> lookup(const SymbolIndex &I,
+ llvm::ArrayRef<SymbolID> IDs) {
+ LookupRequest Req;
+ Req.IDs.insert(IDs.begin(), IDs.end());
+ std::vector<std::string> Results;
+ I.lookup(Req, [&](const Symbol &Sym) {
+ Results.push_back(getQualifiedName(Sym));
+ });
+ return Results;
+}
+
+TEST(MemIndexTest, Lookup) {
+ MemIndex I;
+ I.build(generateSymbols({"ns::abc", "ns::xyz"}));
+ EXPECT_THAT(lookup(I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc"));
+ EXPECT_THAT(lookup(I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}),
+ UnorderedElementsAre("ns::abc", "ns::xyz"));
+ EXPECT_THAT(lookup(I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}),
+ UnorderedElementsAre("ns::xyz"));
+ EXPECT_THAT(lookup(I, SymbolID("ns::nonono")), UnorderedElementsAre());
+}
+
+TEST(MergeIndexTest, Lookup) {
+ MemIndex I, J;
+ I.build(generateSymbols({"ns::A", "ns::B"}));
+ J.build(generateSymbols({"ns::B", "ns::C"}));
+ EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::A")),
+ UnorderedElementsAre("ns::A"));
+ EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::B")),
+ UnorderedElementsAre("ns::B"));
+ EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::C")),
+ UnorderedElementsAre("ns::C"));
+ EXPECT_THAT(
+ lookup(*mergeIndex(&I, &J), {SymbolID("ns::A"), SymbolID("ns::B")}),
+ UnorderedElementsAre("ns::A", "ns::B"));
+ EXPECT_THAT(
+ lookup(*mergeIndex(&I, &J), {SymbolID("ns::A"), SymbolID("ns::C")}),
+ UnorderedElementsAre("ns::A", "ns::C"));
+ EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::D")),
+ UnorderedElementsAre());
+ EXPECT_THAT(lookup(*mergeIndex(&I, &J), {}), UnorderedElementsAre());
+}
+
+TEST(MergeIndexTest, FuzzyFind) {
MemIndex I, J;
I.build(generateSymbols({"ns::A", "ns::B"}));
J.build(generateSymbols({"ns::B", "ns::C"}));
FuzzyFindRequest Req;
- Req.Scopes = {"ns"};
+ Req.Scopes = {"ns::"};
EXPECT_THAT(match(*mergeIndex(&I, &J), Req),
UnorderedElementsAre("ns::A", "ns::B", "ns::C"));
}
@@ -225,6 +272,8 @@
L.Name = R.Name = "Foo"; // same in both
L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs
R.CanonicalDeclaration.FileURI = "file:///right.h";
+ L.References = 1;
+ R.References = 2;
L.CompletionPlainInsertText = "f00"; // present in left only
R.CompletionSnippetInsertText = "f0{$1:0}"; // present in right only
Symbol::Details DetL, DetR;
@@ -238,6 +287,7 @@
Symbol M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.Name, "Foo");
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:///left.h");
+ EXPECT_EQ(M.References, 3u);
EXPECT_EQ(M.CompletionPlainInsertText, "f00");
EXPECT_EQ(M.CompletionSnippetInsertText, "f0{$1:0}");
ASSERT_TRUE(M.Detail);
diff --git a/unittests/clangd/SourceCodeTests.cpp b/unittests/clangd/SourceCodeTests.cpp
index 86c8a09..d787641 100644
--- a/unittests/clangd/SourceCodeTests.cpp
+++ b/unittests/clangd/SourceCodeTests.cpp
@@ -7,7 +7,9 @@
//
//===----------------------------------------------------------------------===//
#include "SourceCode.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/raw_os_ostream.h"
+#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -15,6 +17,9 @@
namespace clangd {
namespace {
+using llvm::Failed;
+using llvm::HasValue;
+
MATCHER_P2(Pos, Line, Col, "") {
return arg.line == Line && arg.character == Col;
}
@@ -33,30 +38,57 @@
TEST(SourceCodeTests, PositionToOffset) {
// line out of bounds
- EXPECT_EQ(0u, positionToOffset(File, position(-1, 2)));
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), Failed());
// first line
- EXPECT_EQ(0u, positionToOffset(File, position(0, -1))); // out of range
- EXPECT_EQ(0u, positionToOffset(File, position(0, 0))); // first character
- EXPECT_EQ(3u, positionToOffset(File, position(0, 3))); // middle character
- EXPECT_EQ(6u, positionToOffset(File, position(0, 6))); // last character
- EXPECT_EQ(7u, positionToOffset(File, position(0, 7))); // the newline itself
- EXPECT_EQ(8u, positionToOffset(File, position(0, 8))); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)),
+ Failed()); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)),
+ HasValue(0)); // first character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)),
+ HasValue(3)); // middle character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)),
+ HasValue(6)); // last character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)),
+ HasValue(7)); // the newline itself
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7), false),
+ HasValue(7));
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8)),
+ HasValue(7)); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false),
+ Failed()); // out of range
// middle line
- EXPECT_EQ(8u, positionToOffset(File, position(1, -1))); // out of range
- EXPECT_EQ(8u, positionToOffset(File, position(1, 0))); // first character
- EXPECT_EQ(11u, positionToOffset(File, position(1, 3))); // middle character
- EXPECT_EQ(14u, positionToOffset(File, position(1, 6))); // last character
- EXPECT_EQ(15u, positionToOffset(File, position(1, 7))); // the newline itself
- EXPECT_EQ(16u, positionToOffset(File, position(1, 8))); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)),
+ Failed()); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)),
+ HasValue(8)); // first character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)),
+ HasValue(11)); // middle character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3), false),
+ HasValue(11));
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 6)),
+ HasValue(14)); // last character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)),
+ HasValue(15)); // the newline itself
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)),
+ HasValue(15)); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false),
+ Failed()); // out of range
// last line
- EXPECT_EQ(16u, positionToOffset(File, position(2, -1))); // out of range
- EXPECT_EQ(16u, positionToOffset(File, position(2, 0))); // first character
- EXPECT_EQ(19u, positionToOffset(File, position(2, 3))); // middle character
- EXPECT_EQ(23u, positionToOffset(File, position(2, 7))); // last character
- EXPECT_EQ(24u, positionToOffset(File, position(2, 8))); // EOF
- EXPECT_EQ(24u, positionToOffset(File, position(2, 9))); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)),
+ Failed()); // out of range
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)),
+ HasValue(16)); // first character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 3)),
+ HasValue(19)); // middle character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 7)),
+ HasValue(23)); // last character
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)),
+ HasValue(24)); // EOF
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9), false),
+ Failed()); // out of range
// line out of bounds
- EXPECT_EQ(24u, positionToOffset(File, position(3, 1)));
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), Failed());
+ EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 1)), Failed());
}
TEST(SourceCodeTests, OffsetToPosition) {
diff --git a/unittests/clangd/SymbolCollectorTests.cpp b/unittests/clangd/SymbolCollectorTests.cpp
index 363fd00..76eace5 100644
--- a/unittests/clangd/SymbolCollectorTests.cpp
+++ b/unittests/clangd/SymbolCollectorTests.cpp
@@ -59,6 +59,7 @@
return arg.Definition.StartOffset == Offsets.first &&
arg.Definition.EndOffset == Offsets.second;
}
+MATCHER_P(Refs, R, "") { return int(arg.References) == R; }
namespace clang {
namespace clangd {
@@ -198,6 +199,19 @@
QName("foo::bar::v2"), QName("foo::baz")}));
}
+TEST_F(SymbolCollectorTest, Template) {
+ Annotations Header(R"(
+ // Template is indexed, specialization and instantiation is not.
+ template <class T> struct [[Tmpl]] {T x = 0;};
+ template <> struct Tmpl<int> {};
+ extern template struct Tmpl<float>;
+ template struct Tmpl<double>;
+ )");
+ runSymbolCollector(Header.code(), /*Main=*/"");
+ EXPECT_THAT(Symbols, UnorderedElementsAreArray({AllOf(
+ QName("Tmpl"), DeclRange(Header.offsetRange()))}));
+}
+
TEST_F(SymbolCollectorTest, Locations) {
Annotations Header(R"cpp(
// Declared in header, defined in main.
@@ -229,6 +243,31 @@
AllOf(QName("Z"), DeclRange(Header.offsetRange("zdecl")))));
}
+TEST_F(SymbolCollectorTest, References) {
+ const std::string Header = R"(
+ class W;
+ class X {};
+ class Y;
+ class Z {}; // not used anywhere
+ Y* y = nullptr; // used in header doesn't count
+ )";
+ const std::string Main = R"(
+ W* w = nullptr;
+ W* w2 = nullptr; // only one usage counts
+ X x();
+ class V;
+ V* v = nullptr; // Used, but not eligible for indexing.
+ class Y{}; // definition doesn't count as a reference
+ )";
+ CollectorOpts.CountReferences = true;
+ runSymbolCollector(Header, Main);
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(AllOf(QName("W"), Refs(1)),
+ AllOf(QName("X"), Refs(1)),
+ AllOf(QName("Y"), Refs(0)),
+ AllOf(QName("Z"), Refs(0)), QName("y")));
+}
+
TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) {
runSymbolCollector("class Foo {};", /*Main=*/"");
EXPECT_THAT(Symbols, UnorderedElementsAre(
diff --git a/unittests/clangd/SyncAPI.cpp b/unittests/clangd/SyncAPI.cpp
index 62b68ca..28393ee 100644
--- a/unittests/clangd/SyncAPI.cpp
+++ b/unittests/clangd/SyncAPI.cpp
@@ -11,8 +11,9 @@
namespace clang {
namespace clangd {
-void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents) {
- Server.addDocument(File, Contents);
+void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents,
+ WantDiagnostics WantDiags, bool SkipCache) {
+ Server.addDocument(File, Contents, WantDiags, SkipCache);
if (!Server.blockUntilIdleForTest())
llvm_unreachable("not idle after addDocument");
}
@@ -67,31 +68,31 @@
}
} // namespace
-Tagged<CompletionList> runCodeComplete(ClangdServer &Server, PathRef File,
- Position Pos,
- clangd::CodeCompleteOptions Opts) {
- llvm::Optional<Tagged<CompletionList>> Result;
+llvm::Expected<CompletionList>
+runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
+ clangd::CodeCompleteOptions Opts) {
+ llvm::Optional<llvm::Expected<CompletionList>> Result;
Server.codeComplete(File, Pos, Opts, capture(Result));
return std::move(*Result);
}
-llvm::Expected<Tagged<SignatureHelp>>
-runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos) {
- llvm::Optional<llvm::Expected<Tagged<SignatureHelp>>> Result;
+llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server,
+ PathRef File, Position Pos) {
+ llvm::Optional<llvm::Expected<SignatureHelp>> Result;
Server.signatureHelp(File, Pos, capture(Result));
return std::move(*Result);
}
-llvm::Expected<Tagged<std::vector<Location>>>
+llvm::Expected<std::vector<Location>>
runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos) {
- llvm::Optional<llvm::Expected<Tagged<std::vector<Location>>>> Result;
+ llvm::Optional<llvm::Expected<std::vector<Location>>> Result;
Server.findDefinitions(File, Pos, capture(Result));
return std::move(*Result);
}
-llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
+llvm::Expected<std::vector<DocumentHighlight>>
runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos) {
- llvm::Optional<llvm::Expected<Tagged<std::vector<DocumentHighlight>>>> Result;
+ llvm::Optional<llvm::Expected<std::vector<DocumentHighlight>>> Result;
Server.findDocumentHighlights(File, Pos, capture(Result));
return std::move(*Result);
}
diff --git a/unittests/clangd/SyncAPI.h b/unittests/clangd/SyncAPI.h
index 9bfe155..c0fcb93 100644
--- a/unittests/clangd/SyncAPI.h
+++ b/unittests/clangd/SyncAPI.h
@@ -19,19 +19,21 @@
namespace clangd {
// Calls addDocument and then blockUntilIdleForTest.
-void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents);
+void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents,
+ WantDiagnostics WantDiags = WantDiagnostics::Auto,
+ bool SkipCache = false);
-Tagged<CompletionList> runCodeComplete(ClangdServer &Server, PathRef File,
- Position Pos,
- clangd::CodeCompleteOptions Opts);
+llvm::Expected<CompletionList>
+runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
+ clangd::CodeCompleteOptions Opts);
-llvm::Expected<Tagged<SignatureHelp>>
-runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos);
+llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server,
+ PathRef File, Position Pos);
-llvm::Expected<Tagged<std::vector<Location>>>
+llvm::Expected<std::vector<Location>>
runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos);
-llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
+llvm::Expected<std::vector<DocumentHighlight>>
runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos);
llvm::Expected<std::vector<tooling::Replacement>>
diff --git a/unittests/clangd/TUSchedulerTests.cpp b/unittests/clangd/TUSchedulerTests.cpp
index 14588d6..2235e87 100644
--- a/unittests/clangd/TUSchedulerTests.cpp
+++ b/unittests/clangd/TUSchedulerTests.cpp
@@ -21,7 +21,7 @@
using ::testing::Pair;
using ::testing::Pointee;
-void ignoreUpdate(llvm::Optional<std::vector<DiagWithFixIts>>) {}
+void ignoreUpdate(llvm::Optional<std::vector<Diag>>) {}
void ignoreError(llvm::Error Err) {
handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {});
}
@@ -102,20 +102,20 @@
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero());
auto Path = testPath("foo.cpp");
S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes,
- [&](std::vector<DiagWithFixIts>) { Ready.wait(); });
+ [&](std::vector<Diag>) { Ready.wait(); });
S.update(Path, getInputs(Path, "request diags"), WantDiagnostics::Yes,
- [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });
+ [&](std::vector<Diag> Diags) { ++CallbackCount; });
S.update(Path, getInputs(Path, "auto (clobbered)"), WantDiagnostics::Auto,
- [&](std::vector<DiagWithFixIts> Diags) {
+ [&](std::vector<Diag> Diags) {
ADD_FAILURE() << "auto should have been cancelled by auto";
});
S.update(Path, getInputs(Path, "request no diags"), WantDiagnostics::No,
- [&](std::vector<DiagWithFixIts> Diags) {
+ [&](std::vector<Diag> Diags) {
ADD_FAILURE() << "no diags should not be called back";
});
S.update(Path, getInputs(Path, "auto (produces)"), WantDiagnostics::Auto,
- [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });
+ [&](std::vector<Diag> Diags) { ++CallbackCount; });
Ready.notify();
}
EXPECT_EQ(2, CallbackCount);
@@ -131,15 +131,15 @@
// FIXME: we could probably use timeouts lower than 1 second here.
auto Path = testPath("foo.cpp");
S.update(Path, getInputs(Path, "auto (debounced)"), WantDiagnostics::Auto,
- [&](std::vector<DiagWithFixIts> Diags) {
+ [&](std::vector<Diag> Diags) {
ADD_FAILURE() << "auto should have been debounced and canceled";
});
std::this_thread::sleep_for(std::chrono::milliseconds(200));
S.update(Path, getInputs(Path, "auto (timed out)"), WantDiagnostics::Auto,
- [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });
+ [&](std::vector<Diag> Diags) { ++CallbackCount; });
std::this_thread::sleep_for(std::chrono::seconds(2));
S.update(Path, getInputs(Path, "auto (shut down)"), WantDiagnostics::Auto,
- [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });
+ [&](std::vector<Diag> Diags) { ++CallbackCount; });
}
EXPECT_EQ(2, CallbackCount);
}
@@ -190,8 +190,8 @@
{
WithContextValue WithNonce(NonceKey, ++Nonce);
S.update(File, Inputs, WantDiagnostics::Auto,
- [Nonce, &Mut, &TotalUpdates](
- llvm::Optional<std::vector<DiagWithFixIts>> Diags) {
+ [Nonce, &Mut,
+ &TotalUpdates](llvm::Optional<std::vector<Diag>> Diags) {
EXPECT_THAT(Context::current().get(NonceKey),
Pointee(Nonce));
@@ -226,8 +226,7 @@
EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
ASSERT_TRUE((bool)Preamble);
- EXPECT_EQ(Preamble->Inputs.FS, Inputs.FS);
- EXPECT_EQ(Preamble->Inputs.Contents, Inputs.Contents);
+ EXPECT_EQ(Preamble->Contents, Inputs.Contents);
std::lock_guard<std::mutex> Lock(Mut);
++TotalPreambleReads;
diff --git a/unittests/clangd/TestFS.cpp b/unittests/clangd/TestFS.cpp
index 197c93f..1b9bb04 100644
--- a/unittests/clangd/TestFS.cpp
+++ b/unittests/clangd/TestFS.cpp
@@ -26,16 +26,6 @@
return MemFS;
}
-Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
-MockFSProvider::getTaggedFileSystem(PathRef File) {
- if (ExpectedFile) {
- EXPECT_EQ(*ExpectedFile, File);
- }
-
- auto FS = buildTestFS(Files);
- return make_tagged(FS, Tag);
-}
-
MockCompilationDatabase::MockCompilationDatabase(bool UseRelPaths)
: ExtraClangFlags({"-ffreestanding"}), UseRelPaths(UseRelPaths) {
// -ffreestanding avoids implicit stdc-predef.h.
diff --git a/unittests/clangd/TestFS.h b/unittests/clangd/TestFS.h
index b86adbe..bec8817 100644
--- a/unittests/clangd/TestFS.h
+++ b/unittests/clangd/TestFS.h
@@ -26,13 +26,12 @@
// A VFS provider that returns TestFSes containing a provided set of files.
class MockFSProvider : public FileSystemProvider {
public:
- Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
- getTaggedFileSystem(PathRef File) override;
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
+ return buildTestFS(Files);
+ }
- llvm::Optional<std::string> ExpectedFile;
// If relative paths are used, they are resolved with testPath().
llvm::StringMap<std::string> Files;
- VFSTag Tag = VFSTag();
};
// A Compilation database that returns a fixed set of compile flags.
diff --git a/unittests/clangd/TraceTests.cpp b/unittests/clangd/TraceTests.cpp
index 553997a..02fe7da 100644
--- a/unittests/clangd/TraceTests.cpp
+++ b/unittests/clangd/TraceTests.cpp
@@ -41,7 +41,7 @@
}
bool Match = true;
SmallString<32> Tmp;
- for (auto Prop : *M) {
+ for (auto &Prop : *M) {
auto *K = dyn_cast_or_null<yaml::ScalarNode>(Prop.getKey());
if (!K)
continue;
diff --git a/unittests/clangd/XRefsTests.cpp b/unittests/clangd/XRefsTests.cpp
index c2dd053..e112043 100644
--- a/unittests/clangd/XRefsTests.cpp
+++ b/unittests/clangd/XRefsTests.cpp
@@ -41,8 +41,8 @@
using testing::UnorderedElementsAreArray;
class IgnoreDiagnostics : public DiagnosticsConsumer {
- void onDiagnosticsReady(
- PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {}
+ void onDiagnosticsReady(PathRef File,
+ std::vector<Diag> Diagnostics) override {}
};
// FIXME: this is duplicated with FileIndexTests. Share it.
@@ -119,7 +119,7 @@
const char *Tests[] = {
R"cpp(// Local variable
int main() {
- [[int bonjour]];
+ int [[bonjour]];
^bonjour = 2;
int test1 = bonjour;
}
@@ -127,7 +127,7 @@
R"cpp(// Struct
namespace ns1 {
- [[struct MyClass {}]];
+ struct [[MyClass]] {};
} // namespace ns1
int main() {
ns1::My^Class* Params;
@@ -135,21 +135,21 @@
)cpp",
R"cpp(// Function definition via pointer
- [[int foo(int) {}]]
+ int [[foo]](int) {}
int main() {
auto *X = &^foo;
}
)cpp",
R"cpp(// Function declaration via call
- [[int foo(int)]];
+ int [[foo]](int);
int main() {
return ^foo(42);
}
)cpp",
R"cpp(// Field
- struct Foo { [[int x]]; };
+ struct Foo { int [[x]]; };
int main() {
Foo bar;
bar.^x;
@@ -158,27 +158,27 @@
R"cpp(// Field, member initializer
struct Foo {
- [[int x]];
+ int [[x]];
Foo() : ^x(0) {}
};
)cpp",
R"cpp(// Field, GNU old-style field designator
- struct Foo { [[int x]]; };
+ struct Foo { int [[x]]; };
int main() {
Foo bar = { ^x : 1 };
}
)cpp",
R"cpp(// Field, field designator
- struct Foo { [[int x]]; };
+ struct Foo { int [[x]]; };
int main() {
Foo bar = { .^x = 2 };
}
)cpp",
R"cpp(// Method call
- struct Foo { [[int x()]]; };
+ struct Foo { int [[x]](); };
int main() {
Foo bar;
bar.^x();
@@ -186,7 +186,7 @@
)cpp",
R"cpp(// Typedef
- [[typedef int Foo]];
+ typedef int [[Foo]];
int main() {
^Foo bar;
}
@@ -199,30 +199,51 @@
)cpp", */
R"cpp(// Namespace
- [[namespace ns {
+ namespace [[ns]] {
struct Foo { static void bar(); }
- }]] // namespace ns
+ } // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
R"cpp(// Macro
#define MACRO 0
- #define [[MACRO 1]]
+ #define [[MACRO]] 1
int main() { return ^MACRO; }
#define MACRO 2
#undef macro
)cpp",
+ R"cpp(// Macro
+ class TTT { public: int a; };
+ #define [[FF]](S) if (int b = S.a) {}
+ void f() {
+ TTT t;
+ F^F(t);
+ }
+ )cpp",
+
R"cpp(// Forward class declaration
class Foo;
- [[class Foo {}]];
+ class [[Foo]] {};
F^oo* foo();
)cpp",
R"cpp(// Function declaration
void foo();
void g() { f^oo(); }
- [[void foo() {}]]
+ void [[foo]]() {}
+ )cpp",
+
+ R"cpp(
+ #define FF(name) class name##_Test {};
+ [[FF]](my);
+ void f() { my^_Test a; }
+ )cpp",
+
+ R"cpp(
+ #define FF() class [[Test]] {};
+ FF();
+ void f() { T^est a; }
)cpp",
};
for (const char *Test : Tests) {
@@ -236,7 +257,7 @@
TEST(GoToDefinition, RelPathsInCompileCommand) {
Annotations SourceAnnotations(R"cpp(
-[[int foo]];
+int [[foo]];
int baz = f^oo;
)cpp");
@@ -254,9 +275,8 @@
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(
- Locations->Value,
- ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()}));
+ EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
+ SourceAnnotations.range()}));
}
TEST(Hover, All) {
@@ -584,7 +604,9 @@
auto FooH = testPath("foo.h");
auto FooHUri = URIForFile{FooH};
- const char *HeaderContents = R"cpp([[]]int a;)cpp";
+ const char *HeaderContents = R"cpp([[]]#pragma once
+ int a;
+ )cpp";
Annotations HeaderAnnotations(HeaderContents);
FS.Files[FooH] = HeaderAnnotations.code();
@@ -595,38 +617,38 @@
auto Locations =
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value,
+ EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include in preamble, last char.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("2"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value,
+ EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("3"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value,
+ EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include outside of preamble.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("6"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value,
+ EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test a few positions that do not result in Locations.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("4"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value, IsEmpty());
+ EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("5"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value, IsEmpty());
+ EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
- EXPECT_THAT(Locations->Value, IsEmpty());
+ EXPECT_THAT(*Locations, IsEmpty());
}
} // namespace