https://github.com/NSExceptional created https://github.com/llvm/llvm-project/pull/198073
## Motivation clangd matches diagnostics in `textDocument/codeAction`'s `context.diagnostics` against its fix-it cache by `(range, message)` ([`ClangdLSPServer.h` — `struct DiagKey`](https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clangd/ClangdLSPServer.h#L251-L260)). The header itself flags this as a FIXME: > The caching is a temporary solution to get corresponding clangd diagnostic > from a LSP diagnostic. Ideally, ClangdServer can generate an identifier for > each diagnostic, emit them via the LSP's data field (which was newly added in > LSP 3.16). The brittleness shows up in practice whenever an LSP client mutates the displayed message for its Problems panel — capitalizing, stripping clangd's `(fix available)` suffix, etc. — and then echoes that mutated diagnostic back on a code-action request. The (range, message) lookup misses and no quickfixes are returned, even though clangd has them cached. Concrete example: vscode-swift's `DiagnosticsManager` strips `(fix available)` for display. As a result, Objective-C `Add method implementation` quickfixes for `-Wincomplete-implementation` never appear in VS Code, despite clang emitting the FixIt and clangd surfacing it correctly. (Tracked at swiftlang/sourcekit-lsp#2186.) ## Change - Stamp a monotonically-increasing `clangdFixId` into `Diagnostic.data` for every published diagnostic that carries at least one Fix-It. - Maintain a parallel `DiagIdRefMap: file → id → DiagRef`. - In `getDiagRef`, look up by id first; fall back to the legacy `(range, message)` key when the client drops `data` on round-trip. Gating on `!Fixes.empty()` keeps the wire format unchanged for non-fixable diagnostics, which keeps the test churn limited to the tests that already check fix-bearing diagnostic output. ## Backwards compatibility `Diagnostic.data` is opaque per LSP spec — clients are expected to preserve it untouched between publish and request. The fallback path means clients that don't preserve it still behave exactly as before. The new `data` payload (one int64 per fix-bearing diagnostic) is small. ## Tests - New: [`fixits-codeaction-data-key.test`](clang-tools-extra/clangd/test/fixits-codeaction-data-key.test) — verifies the id-based lookup returns fix-its even when the client has mutated the diagnostic message, and a control with mutated message + no data returns `[]`. - Updated: every lit test that checks fix-bearing publish output now also expects the `data` field. - Local results on macOS arm64: 1383 unit tests passing, all touched lit tests passing. Marked draft to let CI exercise the build on the other platforms before promoting. >From 18d08d8060c87408b7530cb32af294c84ee2691f Mon Sep 17 00:00:00 2001 From: Tanner Bennett <[email protected]> Date: Sat, 16 May 2026 02:14:45 -0500 Subject: [PATCH] [clangd] Use Diagnostic.data for stable fix-it cache key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clangd's existing fix-it cache keys each cached diagnostic by `(range, message)`. The header has a long-standing FIXME noting this is brittle, and indeed it breaks down whenever an LSP client mutates the displayed message for the Problems panel — e.g. capitalizing the first letter or stripping clangd's "(fix available)" suffix — and then echoes that mutated diagnostic back in `context.diagnostics` on a textDocument/codeAction request. The (range, message) lookup misses and no fixes are returned, even though clangd has them cached. LSP 3.16 introduced `Diagnostic.data`, *"preserved between a textDocument/publishDiagnostics notification and textDocument/ codeAction request"*. Use it. This patch: - Assigns a monotonically-increasing `clangdFixId` to each published diagnostic that carries at least one Fix-It, stamped into `Diagnostic.data["clangdFixId"]`. - Maintains a parallel `DiagIdRefMap: file -> id -> DiagRef` keyed by that id. - In `getDiagRef`, looks up by id first; falls back to the legacy `(range, message)` key for clients that drop `data` on round-trip. Gating on `!Fixes.empty()` keeps the wire format unchanged for the common non-fixable diagnostic case and avoids churn in tests that don't deal with fix-its. Tests that *do* check fix-bearing publishDiagnostics output have been updated. A new lit test (`fixits-codeaction-data-key.test`) verifies that: - The id-based lookup returns fix-its even when the request's `context.diagnostics[0].message` has been mutated. - A control request with mutated message and no data returns `[]` (legacy behavior, confirming the new path is what's doing the work). Local: 1383 unit tests and the touched lit tests all pass. --- clang-tools-extra/clangd/ClangdLSPServer.cpp | 31 ++++++++++++++++ clang-tools-extra/clangd/ClangdLSPServer.h | 30 ++++++++++++---- .../clangd/test/diagnostic-category.test | 3 ++ .../clangd/test/diagnostics-no-tidy.test | 3 ++ .../test/did-change-configuration-params.test | 3 ++ .../clangd/test/execute-command.test | 3 ++ .../test/fixits-codeaction-data-key.test | 35 +++++++++++++++++++ .../fixits-codeaction-documentchanges.test | 3 ++ .../clangd/test/fixits-codeaction.test | 3 ++ .../test/fixits-command-documentchanges.test | 3 ++ .../clangd/test/fixits-command.test | 3 ++ .../test/fixits-embed-in-diagnostic.test | 3 ++ .../test/include-cleaner-batch-fix.test | 12 +++++++ 13 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 clang-tools-extra/clangd/test/fixits-codeaction-data-key.test diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 04f58ab6446d1..f038b4c285f73 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -936,6 +936,7 @@ void ClangdLSPServer::onDocumentDidClose( { std::lock_guard<std::mutex> Lock(DiagRefMutex); DiagRefMap.erase(File); + DiagIdRefMap.erase(File); } { std::lock_guard<std::mutex> HLock(SemanticTokensMutex); @@ -1770,6 +1771,22 @@ void ClangdLSPServer::profile(MemoryTree &MT) const { std::optional<ClangdServer::DiagRef> ClangdLSPServer::getDiagRef(StringRef File, const clangd::Diagnostic &D) { std::lock_guard<std::mutex> Lock(DiagRefMutex); + + // Prefer the id-based lookup. Robust against clients that mutate the + // displayed message (e.g. stripping clangd's "(fix available)" suffix). + if (auto Id = diagFixIdFromData(D)) { + auto IdIter = DiagIdRefMap.find(File); + if (IdIter != DiagIdRefMap.end()) { + auto FixItsIter = IdIter->second.find(*Id); + if (FixItsIter != IdIter->second.end()) + return FixItsIter->second; + } + // Fall through to legacy lookup — the id was missing from cache, which + // can happen if the file was reparsed between publish and codeAction. + } + + // Fallback: (range, message) key. Used for clients that drop the data + // field on round-trip, and as a backup when the id is unknown. auto DiagToDiagRefIter = DiagRefMap.find(File); if (DiagToDiagRefIter == DiagRefMap.end()) return std::nullopt; @@ -1811,6 +1828,7 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, Notification.version = decodeVersion(Version); Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File); DiagnosticToDiagRefMap LocalDiagMap; // Temporary storage + DiagIdToDiagRefMap LocalDiagIdMap; for (auto &Diag : Diagnostics) { toLSPDiags(Diag, Notification.uri, DiagOpts, [&](clangd::Diagnostic LSPDiag, llvm::ArrayRef<Fix> Fixes) { @@ -1824,6 +1842,18 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, if (LSPDiag.codeActions->size() == 1) LSPDiag.codeActions->front().isPreferred = true; } + // Stamp a unique id into Diagnostic.data only when there's + // at least one Fix-It associated. We only need to look this + // diagnostic up at codeAction time if it carries a fix, so + // gating keeps the wire format unchanged for the common + // non-fixable case and avoids churn in unrelated tests. + // The id keys an alternate cache so codeAction lookup stays + // robust even if the client mutates message text for display. + if (!Fixes.empty()) { + int64_t Id = NextDiagFixId.fetch_add(1); + LSPDiag.data[DiagDataIdKey] = Id; + LocalDiagIdMap[Id] = {Diag.Range, Diag.Message}; + } LocalDiagMap[toDiagKey(LSPDiag)] = {Diag.Range, Diag.Message}; Notification.diagnostics.push_back(std::move(LSPDiag)); }); @@ -1833,6 +1863,7 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, { std::lock_guard<std::mutex> Lock(DiagRefMutex); DiagRefMap[File] = std::move(LocalDiagMap); + DiagIdRefMap[File] = std::move(LocalDiagIdMap); } // Send a notification to the LSP client. diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 6ada3fd9e6e47..f62e17f9bb2bc 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -243,10 +243,18 @@ class ClangdLSPServer : private ClangdServer::Callbacks, /// Used to indicate the ClangdLSPServer is being destroyed. std::atomic<bool> IsBeingDestroyed = {false}; - // FIXME: The caching is a temporary solution to get corresponding clangd - // diagnostic from a LSP diagnostic. - // Ideally, ClangdServer can generate an identifier for each diagnostic, - // emit them via the LSP's data field (which was newly added in LSP 3.16). + // The cache maps each published LSP diagnostic back to the underlying + // clangd::Diag so we can look up its Fix-Its when the client sends a + // codeAction request later. + // + // Preferred path: each published Diagnostic gets a unique id stamped into + // its `data` field under the "clangdFixId" key. LSP spec (3.16+) preserves + // `data` between publishDiagnostics and codeAction's `context.diagnostics`, + // so we look up by id and the lookup is immune to clients that mutate + // `message` for display (e.g. capitalizing, stripping suffixes). + // + // Fallback path: legacy (range, message) key, retained for clients that + // drop `data` round-trip. std::mutex DiagRefMutex; struct DiagKey { clangd::Range Rng; @@ -255,15 +263,25 @@ class ClangdLSPServer : private ClangdServer::Callbacks, return std::tie(Rng, Message) < std::tie(Other.Rng, Other.Message); } }; + static constexpr llvm::StringLiteral DiagDataIdKey = "clangdFixId"; DiagKey toDiagKey(const clangd::Diagnostic &LSPDiag) { return {LSPDiag.range, LSPDiag.message}; } + /// Reads the clangdFixId from a diagnostic's `data` field, if present. + static std::optional<int64_t> + diagFixIdFromData(const clangd::Diagnostic &LSPDiag) { + return LSPDiag.data.getInteger(DiagDataIdKey); + } /// A map from LSP diagnostic to clangd-naive diagnostic. typedef std::map<DiagKey, ClangdServer::DiagRef> DiagnosticToDiagRefMap; + typedef std::map<int64_t, ClangdServer::DiagRef> DiagIdToDiagRefMap; /// Caches the mapping LSP and clangd-naive diagnostics per file. - llvm::StringMap<DiagnosticToDiagRefMap> - DiagRefMap; + /// Both maps are populated on every publish; lookup tries IdMap first. + llvm::StringMap<DiagnosticToDiagRefMap> DiagRefMap; + llvm::StringMap<DiagIdToDiagRefMap> DiagIdRefMap; + /// Monotonically-increasing fix-it id stamped into Diagnostic.data. + std::atomic<int64_t> NextDiagFixId = {1}; // Last semantic-tokens response, for incremental requests. std::mutex SemanticTokensMutex; diff --git a/clang-tools-extra/clangd/test/diagnostic-category.test b/clang-tools-extra/clangd/test/diagnostic-category.test index 654c2c0b46556..2bf845102927a 100644 --- a/clang-tools-extra/clangd/test/diagnostic-category.test +++ b/clang-tools-extra/clangd/test/diagnostic-category.test @@ -8,6 +8,9 @@ # CHECK-NEXT: { # CHECK-NEXT: "category": "Semantic Issue", # CHECK-NEXT: "code": "use_with_wrong_tag", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Use of 'Point' with tag type that does not match previous declaration (fix available)\n\nfoo.c:1:8: note: previous use is here", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/diagnostics-no-tidy.test b/clang-tools-extra/clangd/test/diagnostics-no-tidy.test index 9341275b6c213..8383ca59f457a 100644 --- a/clang-tools-extra/clangd/test/diagnostics-no-tidy.test +++ b/clang-tools-extra/clangd/test/diagnostics-no-tidy.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wmain-return-type", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Return type of 'main' is not 'int' (fix available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/did-change-configuration-params.test b/clang-tools-extra/clangd/test/did-change-configuration-params.test index 08c7b4bcb57ad..ea0516387989c 100644 --- a/clang-tools-extra/clangd/test/did-change-configuration-params.test +++ b/clang-tools-extra/clangd/test/did-change-configuration-params.test @@ -27,6 +27,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wuninitialized", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Variable 'i' is uninitialized when used here (fix available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/execute-command.test b/clang-tools-extra/clangd/test/execute-command.test index b4bd48b784f20..cf1bc8f9f04c1 100644 --- a/clang-tools-extra/clangd/test/execute-command.test +++ b/clang-tools-extra/clangd/test/execute-command.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wparentheses", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/fixits-codeaction-data-key.test b/clang-tools-extra/clangd/test/fixits-codeaction-data-key.test new file mode 100644 index 0000000000000..033de2da2af5b --- /dev/null +++ b/clang-tools-extra/clangd/test/fixits-codeaction-data-key.test @@ -0,0 +1,35 @@ +# Verifies that when an LSP client mutates the diagnostic message for display +# (e.g. stripping clangd's "(fixes available)" suffix) but preserves the +# `data` field per the LSP 3.16 spec, clangd still associates the diagnostic +# with its cached fix-its on textDocument/codeAction. +# +# clangd assigns fix-it ids monotonically from 1 on each process start, so the +# first published fix-bearing diagnostic gets clangdFixId 1. +# +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"codeAction":{"codeActionLiteralSupport":{}}}},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}} +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK: "clangdFixId": 1 +--- +# A code action request whose `context.diagnostics[0].message` has been +# mutated by the client (no "(fixes available)" suffix), but whose `data` +# carries the clangdFixId from the publishDiagnostics above. The id-based +# lookup should match and return the fix-its. +{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"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", "code": "-Wparentheses", "source": "clang", "data": {"clangdFixId": 1}}]}}} +# CHECK: "id": 2, +# CHECK: "kind": "quickfix", +# CHECK-NEXT: "title": "place parentheses around the assignment to silence this warning" +--- +# Negative control: same mutated message WITHOUT the data field — the legacy +# (range, message) lookup misses because the message no longer matches what +# clangd cached, so no fix-its are returned. +{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"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", "code": "-Wparentheses", "source": "clang"}]}}} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [] +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/test/fixits-codeaction-documentchanges.test b/clang-tools-extra/clangd/test/fixits-codeaction-documentchanges.test index 7a591319a7405..eb83573719c53 100644 --- a/clang-tools-extra/clangd/test/fixits-codeaction-documentchanges.test +++ b/clang-tools-extra/clangd/test/fixits-codeaction-documentchanges.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wparentheses", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/fixits-codeaction.test b/clang-tools-extra/clangd/test/fixits-codeaction.test index 75d0fb012ffc8..05fec1b8d912f 100644 --- a/clang-tools-extra/clangd/test/fixits-codeaction.test +++ b/clang-tools-extra/clangd/test/fixits-codeaction.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wparentheses", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/fixits-command-documentchanges.test b/clang-tools-extra/clangd/test/fixits-command-documentchanges.test index cd636c4df387a..1ef04d7938a0d 100644 --- a/clang-tools-extra/clangd/test/fixits-command-documentchanges.test +++ b/clang-tools-extra/clangd/test/fixits-command-documentchanges.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wparentheses", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/fixits-command.test b/clang-tools-extra/clangd/test/fixits-command.test index 62b5a6152d2cf..3c9a5c4afd04d 100644 --- a/clang-tools-extra/clangd/test/fixits-command.test +++ b/clang-tools-extra/clangd/test/fixits-command.test @@ -7,6 +7,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "-Wparentheses", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test b/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test index 26d3820d04322..878efdae3aba4 100644 --- a/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test +++ b/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test @@ -33,6 +33,9 @@ # CHECK-NEXT: "title": "change 'union' to 'struct'" # CHECK-NEXT: } # CHECK-NEXT: ], +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Use of 'Point' with tag type that does not match previous declaration (fix available)\n\nfoo.c:1:8: note: previous use is here", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test b/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test index 5a87a87e2f63a..1c70c55fec715 100644 --- a/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test +++ b/clang-tools-extra/clangd/test/include-cleaner-batch-fix.test @@ -39,6 +39,9 @@ # CHECK-NEXT: "codeDescription": { # CHECK-NEXT: "href": "{{.*}}" # CHECK-NEXT: }, +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "No header providing \"Foo\" is directly included (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { @@ -58,6 +61,9 @@ # CHECK-NEXT: "codeDescription": { # CHECK-NEXT: "href": "{{.*}}" # CHECK-NEXT: }, +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "No header providing \"Bar\" is directly included (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { @@ -77,6 +83,9 @@ # CHECK-NEXT: "codeDescription": { # CHECK-NEXT: "href": "{{.*}}" # CHECK-NEXT: }, +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Included header all1.h is not used directly (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { @@ -99,6 +108,9 @@ # CHECK-NEXT: "codeDescription": { # CHECK-NEXT: "href": "{{.*}}" # CHECK-NEXT: }, +# CHECK-NEXT: "data": { +# CHECK-NEXT: "clangdFixId": {{[0-9]+}} +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Included header all2.h is not used directly (fixes available)", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
