[clang-tools-extra] 4b1cec0 - [clangd] Add batch fixes for include-cleaner diagnostics
Haojian Wu via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 27 03:10:33 PDT 2023
Author: Haojian Wu
Date: 2023-04-27T12:08:59+02:00
New Revision: 4b1cec065227db64f870a179a2d45cffa314641a
URL: https://github.com/llvm/llvm-project/commit/4b1cec065227db64f870a179a2d45cffa314641a
DIFF: https://github.com/llvm/llvm-project/commit/4b1cec065227db64f870a179a2d45cffa314641a.diff
LOG: [clangd] Add batch fixes for include-cleaner diagnostics
For each unused-include/missing-include diagnostic, we provide fix-all
alternative to them.
This patch also adds LSP ChangeAnnotation support.
Differential Revision: https://reviews.llvm.org/D147684
Added:
clang-tools-extra/clangd/test/Inputs/include-cleaner/all1.h
clang-tools-extra/clangd/test/Inputs/include-cleaner/all2.h
clang-tools-extra/clangd/test/Inputs/include-cleaner/bar.h
clang-tools-extra/clangd/test/Inputs/include-cleaner/foo.h
clang-tools-extra/clangd/test/include-cleaner-batch-fix.test
Modified:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/CodeComplete.cpp
clang-tools-extra/clangd/Diagnostics.cpp
clang-tools-extra/clangd/Diagnostics.h
clang-tools-extra/clangd/IncludeCleaner.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index bef6c4de131ed..26e239fa7bed6 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -99,19 +99,29 @@ CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File,
/// Convert from Fix to LSP CodeAction.
CodeAction toCodeAction(const Fix &F, const URIForFile &File,
const std::optional<int64_t> &Version,
- bool SupportsDocumentChanges) {
+ bool SupportsDocumentChanges,
+ bool SupportChangeAnnotation) {
CodeAction Action;
Action.title = F.Message;
Action.kind = std::string(CodeAction::QUICKFIX_KIND);
Action.edit.emplace();
if (!SupportsDocumentChanges) {
Action.edit->changes.emplace();
- (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()};
+ auto &Changes = (*Action.edit->changes)[File.uri()];
+ for (const auto &E : F.Edits)
+ Changes.push_back({E.range, E.newText, /*annotationId=*/""});
} else {
Action.edit->documentChanges.emplace();
- TextDocumentEdit& Edit = Action.edit->documentChanges->emplace_back();
+ TextDocumentEdit &Edit = Action.edit->documentChanges->emplace_back();
Edit.textDocument = VersionedTextDocumentIdentifier{{File}, Version};
- Edit.edits = {F.Edits.begin(), F.Edits.end()};
+ for (const auto &E : F.Edits)
+ Edit.edits.push_back(
+ {E.range, E.newText,
+ SupportChangeAnnotation ? E.annotationId : ""});
+ if (SupportChangeAnnotation) {
+ for (const auto &[AID, Annotation]: F.Annotations)
+ Action.edit->changeAnnotations[AID] = Annotation;
+ }
}
return Action;
}
@@ -509,6 +519,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
SupportsReferenceContainer = Params.capabilities.ReferenceContainer;
SupportFileStatus = Params.initializationOptions.FileStatus;
SupportsDocumentChanges = Params.capabilities.DocumentChanges;
+ SupportsChangeAnnotation = Params.capabilities.ChangeAnnotation;
HoverContentFormat = Params.capabilities.HoverContentFormat;
Opts.LineFoldingOnly = Params.capabilities.LineFoldingOnly;
SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp;
@@ -1742,7 +1753,8 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
for (const auto &Fix : Fixes)
CodeActions.push_back(toCodeAction(Fix, Notification.uri,
Notification.version,
- SupportsDocumentChanges));
+ SupportsDocumentChanges,
+ SupportsChangeAnnotation));
if (DiagOpts.EmbedFixesInDiagnostics) {
Diag.codeActions.emplace(CodeActions);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index e5e2104dcec2f..33c8d221f9bd4 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -277,6 +277,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
bool SupportsOffsetsInSignatureHelp = false;
/// Whether the client supports the versioned document changes.
bool SupportsDocumentChanges = false;
+ /// Whether the client supports change annotations on text edits.
+ bool SupportsChangeAnnotation = false;
+
std::mutex BackgroundIndexProgressMutex;
enum class BackgroundIndexProgress {
// Client doesn't support reporting progress. No transitions possible.
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 260c44b2064b5..bb01fa421b498 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -2242,7 +2242,7 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
}
LSP.sortText = sortText(Score.Total, FilterText);
LSP.filterText = FilterText;
- LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name};
+ LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name, ""};
// Merge continuous additionalTextEdits into main edit. The main motivation
// behind this is to help LSP clients, it seems most of them are confused when
// they are provided with additionalTextEdits that are consecutive to main
diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp
index eb6744f965267..d1726142c5ded 100644
--- a/clang-tools-extra/clangd/Diagnostics.cpp
+++ b/clang-tools-extra/clangd/Diagnostics.cpp
@@ -770,7 +770,7 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
if (Message.empty()) // either !SyntheticMessage, or we failed to make one.
Info.FormatDiagnostic(Message);
LastDiag->Fixes.push_back(
- Fix{std::string(Message.str()), std::move(Edits)});
+ Fix{std::string(Message.str()), std::move(Edits), {}});
return true;
};
diff --git a/clang-tools-extra/clangd/Diagnostics.h b/clang-tools-extra/clangd/Diagnostics.h
index 592bfd68bef4f..d4c0478c63a5c 100644
--- a/clang-tools-extra/clangd/Diagnostics.h
+++ b/clang-tools-extra/clangd/Diagnostics.h
@@ -83,6 +83,10 @@ struct Fix {
std::string Message;
/// TextEdits from clang's fix-its. Must be non-empty.
llvm::SmallVector<TextEdit, 1> Edits;
+
+ /// Annotations for the Edits.
+ llvm::SmallVector<std::pair<ChangeAnnotationIdentifier, ChangeAnnotation>>
+ Annotations;
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F);
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
index b88ad0ff5c5b5..d3754de689eb0 100644
--- a/clang-tools-extra/clangd/IncludeCleaner.cpp
+++ b/clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -45,6 +45,7 @@
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Casting.h"
@@ -435,6 +436,115 @@ IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST) {
return {std::move(UnusedIncludes), std::move(MissingIncludes)};
}
+Fix removeAllUnusedIncludes(llvm::ArrayRef<Diag> UnusedIncludes) {
+ assert(!UnusedIncludes.empty());
+
+ Fix RemoveAll;
+ RemoveAll.Message = "remove all unused includes";
+ for (const auto &Diag : UnusedIncludes) {
+ assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.");
+ RemoveAll.Edits.insert(RemoveAll.Edits.end(),
+ Diag.Fixes.front().Edits.begin(),
+ Diag.Fixes.front().Edits.end());
+ }
+
+ // TODO(hokein): emit a suitable text for the label.
+ ChangeAnnotation Annotation = {/*label=*/"",
+ /*needsConfirmation=*/true,
+ /*description=*/""};
+ static const ChangeAnnotationIdentifier RemoveAllUnusedID =
+ "RemoveAllUnusedIncludes";
+ for (unsigned I = 0; I < RemoveAll.Edits.size(); ++I) {
+ ChangeAnnotationIdentifier ID = RemoveAllUnusedID + std::to_string(I);
+ RemoveAll.Edits[I].annotationId = ID;
+ RemoveAll.Annotations.push_back({ID, Annotation});
+ }
+ return RemoveAll;
+}
+Fix addAllMissingIncludes(llvm::ArrayRef<Diag> MissingIncludeDiags) {
+ assert(!MissingIncludeDiags.empty());
+
+ Fix AddAllMissing;
+ AddAllMissing.Message = "add all missing includes";
+ // A map to deduplicate the edits with the same new text.
+ // newText (#include "my_missing_header.h") -> TextEdit.
+ llvm::StringMap<TextEdit> Edits;
+ for (const auto &Diag : MissingIncludeDiags) {
+ assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.");
+ for (const auto& Edit : Diag.Fixes.front().Edits) {
+ Edits.try_emplace(Edit.newText, Edit);
+ }
+ }
+ // FIXME(hokein): emit used symbol reference in the annotation.
+ ChangeAnnotation Annotation = {/*label=*/"",
+ /*needsConfirmation=*/true,
+ /*description=*/""};
+ static const ChangeAnnotationIdentifier AddAllMissingID =
+ "AddAllMissingIncludes";
+ unsigned I = 0;
+ for (auto &It : Edits) {
+ ChangeAnnotationIdentifier ID = AddAllMissingID + std::to_string(I++);
+ AddAllMissing.Edits.push_back(std::move(It.getValue()));
+ AddAllMissing.Edits.back().annotationId = ID;
+
+ AddAllMissing.Annotations.push_back({ID, Annotation});
+ }
+ return AddAllMissing;
+}
+Fix fixAll(const Fix& RemoveAllUnused, const Fix& AddAllMissing) {
+ Fix FixAll;
+ FixAll.Message = "fix all includes";
+
+ for (const auto &F : RemoveAllUnused.Edits)
+ FixAll.Edits.push_back(F);
+ for (const auto &F : AddAllMissing.Edits)
+ FixAll.Edits.push_back(F);
+
+ for (const auto& A : RemoveAllUnused.Annotations)
+ FixAll.Annotations.push_back(A);
+ for (const auto& A : AddAllMissing.Annotations)
+ FixAll.Annotations.push_back(A);
+ return FixAll;
+}
+
+std::vector<Diag> generateIncludeCleanerDiagnostic(
+ ParsedAST &AST, const IncludeCleanerFindings &Findings,
+ llvm::StringRef Code) {
+ std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics(
+ AST.tuPath(), Findings.UnusedIncludes, Code);
+ std::optional<Fix> RemoveAllUnused;;
+ if (UnusedIncludes.size() > 1)
+ RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes);
+
+ std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics(
+ AST, Findings.MissingIncludes, Code);
+ std::optional<Fix> AddAllMissing;
+ if (MissingIncludeDiags.size() > 1)
+ AddAllMissing = addAllMissingIncludes(MissingIncludeDiags);
+
+ std::optional<Fix> FixAll;
+ if (RemoveAllUnused && AddAllMissing)
+ FixAll = fixAll(*RemoveAllUnused, *AddAllMissing);
+
+ auto AddBatchFix = [](const std::optional<Fix> &F, clang::clangd::Diag *Out) {
+ if (!F) return;
+ Out->Fixes.push_back(*F);
+ };
+ for (auto &Diag : MissingIncludeDiags) {
+ AddBatchFix(AddAllMissing, &Diag);
+ AddBatchFix(FixAll, &Diag);
+ }
+ for (auto &Diag : UnusedIncludes) {
+ AddBatchFix(RemoveAllUnused, &Diag);
+ AddBatchFix(FixAll, &Diag);
+ }
+
+ auto Result = std::move(MissingIncludeDiags);
+ llvm::move(UnusedIncludes,
+ std::back_inserter(Result));
+ return Result;
+}
+
std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
llvm::StringRef Code) {
// Interaction is only polished for C/CPP.
@@ -450,13 +560,7 @@ std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
// will need include-cleaner results, call it once
Findings = computeIncludeCleanerFindings(AST);
}
-
- std::vector<Diag> Result = generateUnusedIncludeDiagnostics(
- AST.tuPath(), Findings.UnusedIncludes, Code);
- llvm::move(
- generateMissingIncludeDiagnostics(AST, Findings.MissingIncludes, Code),
- std::back_inserter(Result));
- return Result;
+ return generateIncludeCleanerDiagnostic(AST, Findings, Code);
}
std::optional<include_cleaner::Header>
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index ad851ca119178..465b5fc3d69cf 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -187,14 +187,34 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentItem &R,
bool fromJSON(const llvm::json::Value &Params, TextEdit &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
- return O && O.map("range", R.range) && O.map("newText", R.newText);
+ return O && O.map("range", R.range) && O.map("newText", R.newText) &&
+ O.mapOptional("annotationId", R.annotationId);
}
llvm::json::Value toJSON(const TextEdit &P) {
- return llvm::json::Object{
+ llvm::json::Object Result{
{"range", P.range},
{"newText", P.newText},
};
+ if (!P.annotationId.empty())
+ Result["annotationId"] = P.annotationId;
+ return Result;
+}
+
+bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.map("label", R.label) &&
+ O.map("needsConfirmation", R.needsConfirmation) &&
+ O.mapOptional("description", R.description);
+}
+llvm::json::Value toJSON(const ChangeAnnotation & CA) {
+ llvm::json::Object Result{{"label", CA.label}};
+ if (CA.needsConfirmation)
+ Result["needsConfirmation"] = *CA.needsConfirmation;
+ if (!CA.description.empty())
+ Result["description"] = CA.description;
+ return Result;
}
bool fromJSON(const llvm::json::Value &Params, TextDocumentEdit &R,
@@ -458,6 +478,10 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
if (auto *WorkspaceEdit = Workspace->getObject("workspaceEdit")) {
if (auto DocumentChanges = WorkspaceEdit->getBoolean("documentChanges"))
R.DocumentChanges = *DocumentChanges;
+ if (const auto& ChangeAnnotation =
+ WorkspaceEdit->getObject("changeAnnotationSupport")) {
+ R.ChangeAnnotation = true;
+ }
}
}
if (auto *Window = O->getObject("window")) {
@@ -733,7 +757,8 @@ bool fromJSON(const llvm::json::Value &Params, WorkspaceEdit &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("changes", R.changes) &&
- O.map("documentChanges", R.documentChanges);
+ O.map("documentChanges", R.documentChanges) &&
+ O.mapOptional("changeAnnotations", R.changeAnnotations);
}
bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R,
@@ -888,6 +913,12 @@ llvm::json::Value toJSON(const WorkspaceEdit &WE) {
}
if (WE.documentChanges)
Result["documentChanges"] = *WE.documentChanges;
+ if (!WE.changeAnnotations.empty()) {
+ llvm::json::Object ChangeAnnotations;
+ for (auto &Annotation : WE.changeAnnotations)
+ ChangeAnnotations[Annotation.first] = Annotation.second;
+ Result["changeAnnotations"] = std::move(ChangeAnnotations);
+ }
return Result;
}
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 508a863a32233..1d6fccff6ea4d 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -238,6 +238,8 @@ struct ReferenceLocation : Location {
llvm::json::Value toJSON(const ReferenceLocation &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ReferenceLocation &);
+using ChangeAnnotationIdentifier = std::string;
+// A combination of a LSP standard TextEdit and AnnotatedTextEdit.
struct TextEdit {
/// The range of the text document to be manipulated. To insert
/// text into a document create a range where start === end.
@@ -246,14 +248,35 @@ struct TextEdit {
/// The string to be inserted. For delete operations use an
/// empty string.
std::string newText;
+
+ /// The actual annotation identifier (optional)
+ /// If empty, then this field is nullopt.
+ ChangeAnnotationIdentifier annotationId = "";
};
inline bool operator==(const TextEdit &L, const TextEdit &R) {
- return std::tie(L.newText, L.range) == std::tie(R.newText, R.range);
+ return std::tie(L.newText, L.range, L.annotationId) ==
+ std::tie(R.newText, R.range, L.annotationId);
}
bool fromJSON(const llvm::json::Value &, TextEdit &, llvm::json::Path);
llvm::json::Value toJSON(const TextEdit &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
+struct ChangeAnnotation {
+ /// A human-readable string describing the actual change. The string
+ /// is rendered prominent in the user interface.
+ std::string label;
+
+ /// A flag which indicates that user confirmation is needed
+ /// before applying the change.
+ std::optional<bool> needsConfirmation;
+
+ /// A human-readable string which is rendered less prominent in
+ /// the user interface.
+ std::string description;
+};
+bool fromJSON(const llvm::json::Value &, ChangeAnnotation &, llvm::json::Path);
+llvm::json::Value toJSON(const ChangeAnnotation &);
+
struct TextDocumentEdit {
/// The text document to change.
VersionedTextDocumentIdentifier textDocument;
@@ -530,6 +553,9 @@ struct ClientCapabilities {
/// The client supports versioned document changes for WorkspaceEdit.
bool DocumentChanges = false;
+
+ /// The client supports change annotations on text edits,
+ bool ChangeAnnotation = false;
/// Whether the client supports the textDocument/inactiveRegions
/// notification. This is a clangd extension.
@@ -996,6 +1022,10 @@ struct WorkspaceEdit {
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
/// using the `changes` property are supported.
std::optional<std::vector<TextDocumentEdit>> documentChanges;
+
+ /// A map of change annotations that can be referenced in
+ /// AnnotatedTextEdit.
+ std::map<std::string, ChangeAnnotation> changeAnnotations;
};
bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path);
llvm::json::Value toJSON(const WorkspaceEdit &WE);
diff --git a/clang-tools-extra/clangd/test/Inputs/include-cleaner/all1.h b/clang-tools-extra/clangd/test/Inputs/include-cleaner/all1.h
new file mode 100644
index 0000000000000..c1ec51b8f24b9
--- /dev/null
+++ b/clang-tools-extra/clangd/test/Inputs/include-cleaner/all1.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include "bar.h"
+#include "foo.h"
diff --git a/clang-tools-extra/clangd/test/Inputs/include-cleaner/all2.h b/clang-tools-extra/clangd/test/Inputs/include-cleaner/all2.h
new file mode 100644
index 0000000000000..c1ec51b8f24b9
--- /dev/null
+++ b/clang-tools-extra/clangd/test/Inputs/include-cleaner/all2.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include "bar.h"
+#include "foo.h"
diff --git a/clang-tools-extra/clangd/test/Inputs/include-cleaner/bar.h b/clang-tools-extra/clangd/test/Inputs/include-cleaner/bar.h
new file mode 100644
index 0000000000000..f70dd9565fd48
--- /dev/null
+++ b/clang-tools-extra/clangd/test/Inputs/include-cleaner/bar.h
@@ -0,0 +1,2 @@
+#pragma once
+class Bar {};
diff --git a/clang-tools-extra/clangd/test/Inputs/include-cleaner/foo.h b/clang-tools-extra/clangd/test/Inputs/include-cleaner/foo.h
new file mode 100644
index 0000000000000..f35930c9fe80d
--- /dev/null
+++ b/clang-tools-extra/clangd/test/Inputs/include-cleaner/foo.h
@@ -0,0 +1,2 @@
+#pragma once
+class Foo {};
diff --git a/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test b/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test
new file mode 100644
index 0000000000000..b07da2a9b4df8
--- /dev/null
+++ b/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test
@@ -0,0 +1,487 @@
+# We specify a custom path in XDG_CONFIG_HOME, which only works on some systems.
+# UNSUPPORTED: system-windows
+# UNSUPPORTED: system-darwin
+
+# RUN: rm -rf %t
+# RUN: mkdir -p %t/clangd
+# RUN: cp -r %S/Inputs/include-cleaner %t/include
+# Create a config file enabling include-cleaner features.
+# RUN: echo $'Diagnostics:\n UnusedIncludes: Strict\n MissingIncludes: Strict' >> %t/clangd/config.yaml
+
+# RUN: env XDG_CONFIG_HOME=%t clangd -lit-test -enable-config --resource-dir=%t < %s | FileCheck -strict-whitespace %s
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"workspace":{"workspaceEdit":{"documentChanges":true, "changeAnnotationSupport":{"groupsOnLabel":true}}}},"trace":"off"}}
+---
+{
+ "jsonrpc": "2.0",
+ "method": "textDocument/didOpen",
+ "params": {
+ "textDocument": {
+ "uri": "test:///simple.cpp",
+ "languageId": "cpp",
+ "text": "#include \"all1.h\"\n#include \"all2.h\"\n Foo* foo; Bar* bar;"
+ }
+ }
+}
+# First, the diagnostic from the config file.
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [],
+
+# Then, diagnostic from the main cpp file.
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "code": "missing-includes",
+# CHECK-NEXT: "message": "No header providing \"Bar\" is directly included (fixes available)",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 14,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 11,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2,
+# CHECK-NEXT: "source": "clangd"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "code": "missing-includes",
+# CHECK-NEXT: "message": "No header providing \"Foo\" is directly included (fixes available)",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2,
+# CHECK-NEXT: "source": "clangd"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "code": "unused-includes",
+# CHECK-NEXT: "codeDescription": {
+# CHECK-NEXT: "href": "https://clangd.llvm.org/guides/include-cleaner"
+# CHECK-NEXT: },
+# CHECK-NEXT: "message": "Included header all1.h is not used directly (fixes available)",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 17,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2,
+# CHECK-NEXT: "source": "clangd",
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 1
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "code": "unused-includes",
+# CHECK-NEXT: "codeDescription": {
+# CHECK-NEXT: "href": "https://clangd.llvm.org/guides/include-cleaner"
+# CHECK-NEXT: },
+# CHECK-NEXT: "message": "Included header all2.h is not used directly (fixes available)",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 17,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2,
+# CHECK-NEXT: "source": "clangd",
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 1
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+---
+{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///simple.cpp"},"range":{"start":{"line":2,"character":1},"end":{"line":2,"character":4}},"context":{"diagnostics":[{"range":{"start": {"line": 2, "character": 1}, "end": {"line": 2, "character": 4}},"severity":2,"message":"No header providing \"Foo\" is directly included (fixes available)", "code": "missing-includes", "source": "clangd"}]}}}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "#include <foo.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: #include <foo.h>"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changeAnnotations": {
+# CHECK-NEXT: "AddAllMissingIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "AddAllMissingIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes0",
+# CHECK-NEXT: "newText": "#include <bar.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes1",
+# CHECK-NEXT: "newText": "#include <foo.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: add all missing includes"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changeAnnotations": {
+# CHECK-NEXT: "AddAllMissingIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "AddAllMissingIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "RemoveAllUnusedIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "RemoveAllUnusedIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes0",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes1",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes0",
+# CHECK-NEXT: "newText": "#include <bar.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes1",
+# CHECK-NEXT: "newText": "#include <foo.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: fix all includes"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+---
+{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///simple.cpp"},"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":17}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 17}},"severity":2,"message":"Included header all1.h is not used directly (fixes available)", "code": "unused-includes", "source": "clangd"}]}}}
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: remove #include directive"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changeAnnotations": {
+# CHECK-NEXT: "RemoveAllUnusedIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "RemoveAllUnusedIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes0",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes1",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: remove all unused includes"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changeAnnotations": {
+# CHECK-NEXT: "AddAllMissingIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "AddAllMissingIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "RemoveAllUnusedIncludes0": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: },
+# CHECK-NEXT: "RemoveAllUnusedIncludes1": {
+# CHECK-NEXT: "label": "",
+# CHECK-NEXT: "needsConfirmation": true
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentChanges": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "edits": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes0",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "RemoveAllUnusedIncludes1",
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes0",
+# CHECK-NEXT: "newText": "#include <bar.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "annotationId": "AddAllMissingIncludes1",
+# CHECK-NEXT: "newText": "#include <foo.h>\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "textDocument": {
+# CHECK-NEXT: "uri": "file://{{.*}}/simple.cpp",
+# CHECK-NEXT: "version": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply fix: fix all includes"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+---
+{"jsonrpc":"2.0","id":4,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
index 289767b0dec0f..552f4f4ecf886 100644
--- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -1908,11 +1908,11 @@ TEST(DiagnosticsTest, IncludeCleaner) {
auto AST = TU.build();
EXPECT_THAT(
*AST.getDiagnostics(),
- UnorderedElementsAre(AllOf(
- Diag(Test.range("diag"),
- "included header unused.h is not used directly"),
- withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
- withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
+ UnorderedElementsAre(
+ AllOf(Diag(Test.range("diag"),
+ "included header unused.h is not used directly"),
+ withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
+ withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
auto &Diag = AST.getDiagnostics()->front();
EXPECT_EQ(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name),
std::string("https://clangd.llvm.org/guides/include-cleaner"));
diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
index 32b7c5444e06f..a38c01b43270f 100644
--- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -45,8 +45,8 @@ using ::testing::Matcher;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
-Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
- return Field(&Diag::Fixes, ElementsAre(FixMatcher));
+Matcher<const Diag &> withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
+ return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
}
MATCHER_P2(Diag, Range, Message,
@@ -60,6 +60,8 @@ MATCHER_P3(Fix, Range, Replacement, Message,
return arg.Message == Message && arg.Edits.size() == 1 &&
arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
}
+MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
+
std::string guard(llvm::StringRef Code) {
return "#pragma once\n" + Code.str();
@@ -255,42 +257,51 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
UnorderedElementsAre(
AllOf(Diag(MainFile.range("b"),
"No header providing \"b\" is directly included"),
- withFix(Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
- "#include \"b.h\""))),
+ withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
+ "#include \"b.h\""),
+ FixMessage("add all missing includes")})),
AllOf(Diag(MainFile.range("bar"),
"No header providing \"ns::Bar\" is directly included"),
- withFix(Fix(MainFile.range("insert_d"),
- "#include \"dir/d.h\"\n", "#include \"dir/d.h\""))),
+ withFix({Fix(MainFile.range("insert_d"),
+ "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
+ FixMessage("add all missing includes")})),
AllOf(Diag(MainFile.range("f"),
"No header providing \"f\" is directly included"),
- withFix(Fix(MainFile.range("insert_f"), "#include <f.h>\n",
- "#include <f.h>"))),
+ withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
+ "#include <f.h>"),
+ FixMessage("add all missing includes")})),
AllOf(
Diag(MainFile.range("foobar"),
"No header providing \"foobar\" is directly included"),
- withFix(Fix(MainFile.range("insert_foobar"),
- "#include \"public.h\"\n", "#include \"public.h\""))),
+ withFix({Fix(MainFile.range("insert_foobar"),
+ "#include \"public.h\"\n", "#include \"public.h\""),
+ FixMessage("add all missing includes")})),
AllOf(
Diag(MainFile.range("vector"),
"No header providing \"std::vector\" is directly included"),
- withFix(Fix(MainFile.range("insert_vector"),
- "#include <vector>\n", "#include <vector>"))),
+ withFix({Fix(MainFile.range("insert_vector"),
+ "#include <vector>\n", "#include <vector>"),
+ FixMessage("add all missing includes"),})),
AllOf(Diag(MainFile.range("FOO"),
"No header providing \"FOO\" is directly included"),
- withFix(Fix(MainFile.range("insert_foo"),
- "#include \"foo.h\"\n", "#include \"foo.h\""))),
+ withFix({Fix(MainFile.range("insert_foo"),
+ "#include \"foo.h\"\n", "#include \"foo.h\""),
+ FixMessage("add all missing includes")})),
AllOf(Diag(MainFile.range("DEF"),
"No header providing \"Foo\" is directly included"),
- withFix(Fix(MainFile.range("insert_foo"),
- "#include \"foo.h\"\n", "#include \"foo.h\""))),
+ withFix({Fix(MainFile.range("insert_foo"),
+ "#include \"foo.h\"\n", "#include \"foo.h\""),
+ FixMessage("add all missing includes")})),
AllOf(Diag(MainFile.range("BAR"),
"No header providing \"BAR\" is directly included"),
- withFix(Fix(MainFile.range("insert_foo"),
- "#include \"foo.h\"\n", "#include \"foo.h\""))),
+ withFix({Fix(MainFile.range("insert_foo"),
+ "#include \"foo.h\"\n", "#include \"foo.h\""),
+ FixMessage("add all missing includes")})),
AllOf(Diag(MainFile.range("Foo"),
"No header providing \"Foo\" is directly included"),
- withFix(Fix(MainFile.range("insert_foo"),
- "#include \"foo.h\"\n", "#include \"foo.h\"")))));
+ withFix({Fix(MainFile.range("insert_foo"),
+ "#include \"foo.h\"\n", "#include \"foo.h\""),
+ FixMessage("add all missing includes")}))));
}
TEST(IncludeCleaner, IWYUPragmas) {
More information about the cfe-commits
mailing list