Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 104 additions & 3 deletions clang-tools-extra/clangd/Diagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,104 @@ std::string noteMessage(const Diag &Main, const DiagBase &Note,
return capitalize(std::move(Result));
}

// Tests if any two `TextEdit`s in `Edits` conflict. Two `TextEdit`s
// conflict if they have overlapping source ranges.
// NOTE: This function is inspired by clang::internal::anyConflict
bool anyConflict(const llvm::SmallVector<TextEdit, 1> &Edits) {
// A simple interval overlap detection algorithm. Sorts all ranges by their
// begin location then finds the first overlap in one pass.
llvm::SmallVector<const TextEdit *, 1> All; // a copy of `Edits`

for (const TextEdit &E : Edits)
All.push_back(&E);
std::sort(All.begin(), All.end(), [](const TextEdit *H1, const TextEdit *H2) {
return H1->range.start < H2->range.start;
});

const TextEdit *CurrHint = nullptr;

for (const TextEdit *Hint : All) {
if (!CurrHint || CurrHint->range.end < Hint->range.start) {
// Either to initialize `CurrHint` or `CurrHint` does not
// overlap with `Hint`:
CurrHint = Hint;
} else
// In case `Hint` overlaps the `CurrHint`, we found at least one
// conflict:
return true;
}
return false;
}

std::optional<Fix>
generateApplyAllFromOption(const llvm::StringRef Name,
llvm::ArrayRef<Diag *> AllDiagnostics) {
Fix ApplyAll;
for (auto *const Diag : AllDiagnostics) {
for (const auto &Fix : Diag->Fixes)
ApplyAll.Edits.insert(ApplyAll.Edits.end(), Fix.Edits.begin(),
Fix.Edits.end());
}
llvm::sort(ApplyAll.Edits);
ApplyAll.Edits.erase(
std::unique(ApplyAll.Edits.begin(), ApplyAll.Edits.end()),
ApplyAll.Edits.end());
// Skip diagnostic categories that don't have multiple fixes to apply or that
// have conflicting fixes to apply
if (ApplyAll.Edits.size() < 2U || anyConflict(ApplyAll.Edits)) {
return std::nullopt;
}
ApplyAll.Message = llvm::formatv("apply all '{0}' fixes", Name);
return ApplyAll;
}

std::optional<Fix>
generateApplyAllFixesOption(llvm::ArrayRef<Diag> AllDiagnostics) {
Fix ApplyAll;
for (auto const &Diag : AllDiagnostics) {
for (const auto &Fix : Diag.Fixes)
ApplyAll.Edits.insert(ApplyAll.Edits.end(), Fix.Edits.begin(),
Fix.Edits.end());
}
llvm::sort(ApplyAll.Edits);
ApplyAll.Edits.erase(
std::unique(ApplyAll.Edits.begin(), ApplyAll.Edits.end()),
ApplyAll.Edits.end());
// Skip diagnostics that don't have multiple fixes to apply or that have
// conflicting fixes to apply
if (ApplyAll.Edits.size() < 2U || anyConflict(ApplyAll.Edits)) {
return std::nullopt;
}
ApplyAll.Message = "apply all clangd fixes";
return ApplyAll;
}

void appendApplyAlls(std::vector<Diag> &AllDiagnostics) {
llvm::DenseMap<llvm::StringRef, std::vector<Diag *>> CategorizedFixes;

for (auto &Diag : AllDiagnostics) {
// Keep track of fixable diagnostics for generating "apply all fixes"
if (!Diag.Fixes.empty()) {
if (auto [It, DidEmplace] = CategorizedFixes.try_emplace(
Diag.Name, std::vector<struct Diag *>{&Diag});
!DidEmplace)
It->second.emplace_back(&Diag);
}
}

auto FixAllClangd = generateApplyAllFixesOption(AllDiagnostics);
for (const auto &[Name, DiagsForThisCategory] : CategorizedFixes) {
auto FixAllForCategory =
generateApplyAllFromOption(Name, DiagsForThisCategory);
for (auto *Diag : DiagsForThisCategory) {
if (DiagsForThisCategory.size() >= 2U && FixAllForCategory.has_value())
Diag->Fixes.emplace_back(*FixAllForCategory);
if (CategorizedFixes.size() >= 2U && FixAllClangd.has_value())
Diag->Fixes.emplace_back(*FixAllClangd);
}
}
}

void setTags(clangd::Diag &D) {
static const auto *DeprecatedDiags = new llvm::DenseSet<unsigned>{
diag::warn_access_decl_deprecated,
Expand Down Expand Up @@ -573,7 +671,8 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
// Do not forget to emit a pending diagnostic if there is one.
flushLastDiag();

// Fill in name/source now that we have all the context needed to map them.
// Fill in name/source now that we have all the context needed to map
// them.
for (auto &Diag : Output) {
if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
// Warnings controlled by -Wfoo are better recognized by that name.
Expand Down Expand Up @@ -617,6 +716,9 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
llvm::erase_if(Output, [&](const Diag &D) {
return !SeenDiags.emplace(D.Range, D.Message).second;
});

appendApplyAlls(Output);

return std::move(Output);
}

Expand Down Expand Up @@ -795,8 +897,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), std::move(Edits), {}});
LastDiag->Fixes.push_back(Fix{std::string(Message), std::move(Edits), {}});
return true;
};

Expand Down
22 changes: 13 additions & 9 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ inline bool operator==(const TextEdit &L, const TextEdit &R) {
return std::tie(L.newText, L.range, L.annotationId) ==
std::tie(R.newText, R.range, L.annotationId);
}
inline bool operator<(const TextEdit &L, const TextEdit &R) {
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 &);
Expand All @@ -281,7 +285,7 @@ struct TextDocumentEdit {
/// The text document to change.
VersionedTextDocumentIdentifier textDocument;

/// The edits to be applied.
/// The edits to be applied.
/// FIXME: support the AnnotatedTextEdit variant.
std::vector<TextEdit> edits;
};
Expand Down Expand Up @@ -557,7 +561,7 @@ struct ClientCapabilities {

/// The client supports versioned document changes for WorkspaceEdit.
bool DocumentChanges = false;

/// The client supports change annotations on text edits,
bool ChangeAnnotation = false;

Expand Down Expand Up @@ -1013,12 +1017,12 @@ struct WorkspaceEdit {
/// Versioned document edits.
///
/// If a client neither supports `documentChanges` nor
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
/// using the `changes` property are supported.
/// `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.
/// AnnotatedTextEdit.
std::map<std::string, ChangeAnnotation> changeAnnotations;
};
bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path);
Expand Down Expand Up @@ -1274,13 +1278,13 @@ enum class InsertTextFormat {
/// Additional details for a completion item label.
struct CompletionItemLabelDetails {
/// An optional string which is rendered less prominently directly after label
/// without any spacing. Should be used for function signatures or type
/// without any spacing. Should be used for function signatures or type
/// annotations.
std::string detail;

/// An optional string which is rendered less prominently after
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
/// names or file path.
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
/// names or file path.
std::string description;
};
llvm::json::Value toJSON(const CompletionItemLabelDetails &);
Expand Down
Loading
Loading