Skip to content

Commit 5594a67

Browse files
committed
[clangd] Add fix-all CodeActions
1 parent ed59d57 commit 5594a67

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
@@ -337,6 +337,72 @@ std::string noteMessage(const Diag &Main, const DiagBase &Note,
337337
return capitalize(std::move(Result));
338338
}
339339

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

576-
// Fill in name/source now that we have all the context needed to map them.
642+
// Fill in name/source now that we have all the context needed to map
643+
// them.
577644
for (auto &Diag : Output) {
578645
if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
579646
// Warnings controlled by -Wfoo are better recognized by that name.
@@ -617,6 +684,9 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
617684
llvm::erase_if(Output, [&](const Diag &D) {
618685
return !SeenDiags.emplace(D.Range, D.Message).second;
619686
});
687+
688+
appendApplyAlls(Output);
689+
620690
return std::move(Output);
621691
}
622692

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)