Skip to content

Commit 636e828

Browse files
committed
[clangd] Add fix-all CodeActions
1 parent 5ac2320 commit 636e828

File tree

3 files changed

+227
-67
lines changed

3 files changed

+227
-67
lines changed

clang-tools-extra/clangd/Diagnostics.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,72 @@ std::string noteMessage(const Diag &Main, const DiagBase &Note,
339339
return capitalize(std::move(Result));
340340
}
341341

342+
std::optional<Fix>
343+
generateApplyAllFromOption(const llvm::StringRef Name,
344+
llvm::ArrayRef<Diag *> AllDiagnostics) {
345+
Fix ApplyAll;
346+
for (auto *const Diag : AllDiagnostics) {
347+
for (const auto &Fix : Diag->Fixes)
348+
ApplyAll.Edits.insert(ApplyAll.Edits.end(), Fix.Edits.begin(),
349+
Fix.Edits.end());
350+
}
351+
llvm::sort(ApplyAll.Edits);
352+
ApplyAll.Edits.erase(
353+
std::unique(ApplyAll.Edits.begin(), ApplyAll.Edits.end()),
354+
ApplyAll.Edits.end());
355+
// Skip diagnostic categories that don't have multiple fixes to apply
356+
if (ApplyAll.Edits.size() < 2U) {
357+
return std::nullopt;
358+
}
359+
ApplyAll.Message = llvm::formatv("apply all '{0}' fixes", Name);
360+
return ApplyAll;
361+
}
362+
363+
std::optional<Fix>
364+
generateApplyAllFixesOption(llvm::ArrayRef<Diag> AllDiagnostics) {
365+
Fix ApplyAll;
366+
for (auto const &Diag : AllDiagnostics) {
367+
for (const auto &Fix : Diag.Fixes)
368+
ApplyAll.Edits.insert(ApplyAll.Edits.end(), Fix.Edits.begin(),
369+
Fix.Edits.end());
370+
}
371+
llvm::sort(ApplyAll.Edits);
372+
ApplyAll.Edits.erase(
373+
std::unique(ApplyAll.Edits.begin(), ApplyAll.Edits.end()),
374+
ApplyAll.Edits.end());
375+
if (ApplyAll.Edits.size() < 2U) {
376+
return std::nullopt;
377+
}
378+
ApplyAll.Message = "apply all clangd fixes";
379+
return ApplyAll;
380+
}
381+
382+
void appendApplyAlls(std::vector<Diag> &AllDiagnostics) {
383+
llvm::DenseMap<llvm::StringRef, std::vector<Diag *>> CategorizedFixes;
384+
385+
for (auto &Diag : AllDiagnostics) {
386+
// Keep track of fixable diagnostics for generating "apply all fixes"
387+
if (!Diag.Fixes.empty()) {
388+
if (auto [It, DidEmplace] = CategorizedFixes.try_emplace(
389+
Diag.Name, std::vector<struct Diag *>{&Diag});
390+
!DidEmplace)
391+
It->second.emplace_back(&Diag);
392+
}
393+
}
394+
395+
auto FixAllClangd = generateApplyAllFixesOption(AllDiagnostics);
396+
for (const auto &[Name, DiagsForThisCategory] : CategorizedFixes) {
397+
auto FixAllForCategory =
398+
generateApplyAllFromOption(Name, DiagsForThisCategory);
399+
for (auto *Diag : DiagsForThisCategory) {
400+
if (DiagsForThisCategory.size() >= 2U && FixAllForCategory.has_value())
401+
Diag->Fixes.emplace_back(*FixAllForCategory);
402+
if (CategorizedFixes.size() >= 2U && FixAllClangd.has_value())
403+
Diag->Fixes.emplace_back(*FixAllClangd);
404+
}
405+
}
406+
}
407+
342408
void setTags(clangd::Diag &D) {
343409
static const auto *DeprecatedDiags = new llvm::DenseSet<unsigned>{
344410
diag::warn_access_decl_deprecated,
@@ -575,7 +641,8 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
575641
// Do not forget to emit a pending diagnostic if there is one.
576642
flushLastDiag();
577643

578-
// Fill in name/source now that we have all the context needed to map them.
644+
// Fill in name/source now that we have all the context needed to map
645+
// them.
579646
for (auto &Diag : Output) {
580647
if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
581648
// Warnings controlled by -Wfoo are better recognized by that name.
@@ -619,6 +686,9 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
619686
llvm::erase_if(Output, [&](const Diag &D) {
620687
return !SeenDiags.emplace(D.Range, D.Message).second;
621688
});
689+
690+
appendApplyAlls(Output);
691+
622692
return std::move(Output);
623693
}
624694

clang-tools-extra/clangd/Protocol.h

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ inline bool operator==(const TextEdit &L, const TextEdit &R) {
257257
return std::tie(L.newText, L.range, L.annotationId) ==
258258
std::tie(R.newText, R.range, L.annotationId);
259259
}
260+
inline bool operator<(const TextEdit &L, const TextEdit &R) {
261+
return std::tie(L.newText, L.range, L.annotationId) <
262+
std::tie(R.newText, R.range, L.annotationId);
263+
}
260264
bool fromJSON(const llvm::json::Value &, TextEdit &, llvm::json::Path);
261265
llvm::json::Value toJSON(const TextEdit &);
262266
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
@@ -281,7 +285,7 @@ struct TextDocumentEdit {
281285
/// The text document to change.
282286
VersionedTextDocumentIdentifier textDocument;
283287

284-
/// The edits to be applied.
288+
/// The edits to be applied.
285289
/// FIXME: support the AnnotatedTextEdit variant.
286290
std::vector<TextEdit> edits;
287291
};
@@ -557,7 +561,7 @@ struct ClientCapabilities {
557561

558562
/// The client supports versioned document changes for WorkspaceEdit.
559563
bool DocumentChanges = false;
560-
564+
561565
/// The client supports change annotations on text edits,
562566
bool ChangeAnnotation = false;
563567

@@ -1013,12 +1017,12 @@ struct WorkspaceEdit {
10131017
/// Versioned document edits.
10141018
///
10151019
/// If a client neither supports `documentChanges` nor
1016-
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
1017-
/// using the `changes` property are supported.
1020+
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
1021+
/// using the `changes` property are supported.
10181022
std::optional<std::vector<TextDocumentEdit>> documentChanges;
1019-
1023+
10201024
/// A map of change annotations that can be referenced in
1021-
/// AnnotatedTextEdit.
1025+
/// AnnotatedTextEdit.
10221026
std::map<std::string, ChangeAnnotation> changeAnnotations;
10231027
};
10241028
bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path);
@@ -1274,13 +1278,13 @@ enum class InsertTextFormat {
12741278
/// Additional details for a completion item label.
12751279
struct CompletionItemLabelDetails {
12761280
/// An optional string which is rendered less prominently directly after label
1277-
/// without any spacing. Should be used for function signatures or type
1281+
/// without any spacing. Should be used for function signatures or type
12781282
/// annotations.
12791283
std::string detail;
12801284

12811285
/// An optional string which is rendered less prominently after
1282-
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
1283-
/// names or file path.
1286+
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
1287+
/// names or file path.
12841288
std::string description;
12851289
};
12861290
llvm::json::Value toJSON(const CompletionItemLabelDetails &);

0 commit comments

Comments
 (0)