1 | //===--- ClangdServer.h - Main clangd server code ----------------*- C++-*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H |
10 | #define |
11 | |
12 | #include "CodeComplete.h" |
13 | #include "ConfigProvider.h" |
14 | #include "Diagnostics.h" |
15 | #include "DraftStore.h" |
16 | #include "FeatureModule.h" |
17 | #include "GlobalCompilationDatabase.h" |
18 | #include "Hover.h" |
19 | #include "Protocol.h" |
20 | #include "SemanticHighlighting.h" |
21 | #include "TUScheduler.h" |
22 | #include "XRefs.h" |
23 | #include "index/Background.h" |
24 | #include "index/FileIndex.h" |
25 | #include "index/Index.h" |
26 | #include "refactor/Rename.h" |
27 | #include "refactor/Tweak.h" |
28 | #include "support/Function.h" |
29 | #include "support/MemoryTree.h" |
30 | #include "support/Path.h" |
31 | #include "support/ThreadsafeFS.h" |
32 | #include "clang/Tooling/Core/Replacement.h" |
33 | #include "llvm/ADT/ArrayRef.h" |
34 | #include "llvm/ADT/FunctionExtras.h" |
35 | #include "llvm/ADT/StringRef.h" |
36 | #include <functional> |
37 | #include <memory> |
38 | #include <optional> |
39 | #include <string> |
40 | #include <tuple> |
41 | #include <vector> |
42 | |
43 | namespace clang { |
44 | namespace clangd { |
45 | /// Manages a collection of source files and derived data (ASTs, indexes), |
46 | /// and provides language-aware features such as code completion. |
47 | /// |
48 | /// The primary client is ClangdLSPServer which exposes these features via |
49 | /// the Language Server protocol. ClangdServer may also be embedded directly, |
50 | /// though its API is not stable over time. |
51 | /// |
52 | /// ClangdServer should be used from a single thread. Many potentially-slow |
53 | /// operations have asynchronous APIs and deliver their results on another |
54 | /// thread. |
55 | /// Such operations support cancellation: if the caller sets up a cancelable |
56 | /// context, many operations will notice cancellation and fail early. |
57 | /// (ClangdLSPServer uses this to implement $/cancelRequest). |
58 | class ClangdServer { |
59 | public: |
60 | /// Interface with hooks for users of ClangdServer to be notified of events. |
61 | class Callbacks { |
62 | public: |
63 | virtual ~Callbacks() = default; |
64 | |
65 | /// Called by ClangdServer when \p Diagnostics for \p File are ready. |
66 | /// These pushed diagnostics might correspond to an older version of the |
67 | /// file, they do not interfere with "pull-based" ClangdServer::diagnostics. |
68 | /// May be called concurrently for separate files, not for a single file. |
69 | virtual void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
70 | llvm::ArrayRef<Diag> Diagnostics) {} |
71 | /// Called whenever the file status is updated. |
72 | /// May be called concurrently for separate files, not for a single file. |
73 | virtual void onFileUpdated(PathRef File, const TUStatus &Status) {} |
74 | |
75 | /// Called when background indexing tasks are enqueued/started/completed. |
76 | /// Not called concurrently. |
77 | virtual void |
78 | onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) {} |
79 | |
80 | /// Called when the meaning of a source code may have changed without an |
81 | /// edit. Usually clients assume that responses to requests are valid until |
82 | /// they next edit the file. If they're invalidated at other times, we |
83 | /// should tell the client. In particular, when an asynchronous preamble |
84 | /// build finishes, we can provide more accurate semantic tokens, so we |
85 | /// should tell the client to refresh. |
86 | virtual void onSemanticsMaybeChanged(PathRef File) {} |
87 | |
88 | /// Called by ClangdServer when some \p InactiveRegions for \p File are |
89 | /// ready. |
90 | virtual void onInactiveRegionsReady(PathRef File, |
91 | std::vector<Range> InactiveRegions) {} |
92 | }; |
93 | /// Creates a context provider that loads and installs config. |
94 | /// Errors in loading config are reported as diagnostics via Callbacks. |
95 | /// (This is typically used as ClangdServer::Options::ContextProvider). |
96 | static std::function<Context(PathRef)> |
97 | createConfiguredContextProvider(const config::Provider *Provider, |
98 | ClangdServer::Callbacks *); |
99 | |
100 | struct Options { |
101 | /// To process requests asynchronously, ClangdServer spawns worker threads. |
102 | /// If this is zero, no threads are spawned. All work is done on the calling |
103 | /// thread, and callbacks are invoked before "async" functions return. |
104 | unsigned AsyncThreadsCount = getDefaultAsyncThreadsCount(); |
105 | |
106 | /// AST caching policy. The default is to keep up to 3 ASTs in memory. |
107 | ASTRetentionPolicy RetentionPolicy; |
108 | |
109 | /// Cached preambles are potentially large. If false, store them on disk. |
110 | bool StorePreamblesInMemory = true; |
111 | |
112 | /// This throttler controls which preambles may be built at a given time. |
113 | clangd::PreambleThrottler *PreambleThrottler = nullptr; |
114 | |
115 | /// If true, ClangdServer builds a dynamic in-memory index for symbols in |
116 | /// opened files and uses the index to augment code completion results. |
117 | bool BuildDynamicSymbolIndex = false; |
118 | /// If true, ClangdServer automatically indexes files in the current project |
119 | /// on background threads. The index is stored in the project root. |
120 | bool BackgroundIndex = false; |
121 | llvm::ThreadPriority BackgroundIndexPriority = llvm::ThreadPriority::Low; |
122 | |
123 | /// If set, use this index to augment code completion results. |
124 | SymbolIndex *StaticIndex = nullptr; |
125 | |
126 | /// If set, queried to derive a processing context for some work. |
127 | /// Usually used to inject Config (see createConfiguredContextProvider). |
128 | /// |
129 | /// When the provider is called, the active context will be that inherited |
130 | /// from the request (e.g. addDocument()), or from the ClangdServer |
131 | /// constructor if there is no such request (e.g. background indexing). |
132 | /// |
133 | /// The path is an absolute path of the file being processed. |
134 | /// If there is no particular file (e.g. project loading) then it is empty. |
135 | std::function<Context(PathRef)> ContextProvider; |
136 | |
137 | /// The Options provider to use when running clang-tidy. If null, clang-tidy |
138 | /// checks will be disabled. |
139 | TidyProviderRef ClangTidyProvider; |
140 | |
141 | /// Clangd's workspace root. Relevant for "workspace" operations not bound |
142 | /// to a particular file. |
143 | /// FIXME: If not set, should use the current working directory. |
144 | std::optional<std::string> WorkspaceRoot; |
145 | |
146 | /// The resource directory is used to find internal headers, overriding |
147 | /// defaults and -resource-dir compiler flag). |
148 | /// If std::nullopt, ClangdServer calls |
149 | /// CompilerInvocation::GetResourcePath() to obtain the standard resource |
150 | /// directory. |
151 | std::optional<std::string> ResourceDir; |
152 | |
153 | /// Time to wait after a new file version before computing diagnostics. |
154 | DebouncePolicy UpdateDebounce = DebouncePolicy{ |
155 | /*Min=*/.Min: std::chrono::milliseconds(50), |
156 | /*Max=*/.Max: std::chrono::milliseconds(500), |
157 | /*RebuildRatio=*/.RebuildRatio: 1, |
158 | }; |
159 | |
160 | /// Cancel certain requests if the file changes before they begin running. |
161 | /// This is useful for "transient" actions like enumerateTweaks that were |
162 | /// likely implicitly generated, and avoids redundant work if clients forget |
163 | /// to cancel. Clients that always cancel stale requests should clear this. |
164 | bool ImplicitCancellation = true; |
165 | |
166 | /// Clangd will execute compiler drivers matching one of these globs to |
167 | /// fetch system include path. |
168 | std::vector<std::string> QueryDriverGlobs; |
169 | |
170 | // Whether the client supports folding only complete lines. |
171 | bool LineFoldingOnly = false; |
172 | |
173 | FeatureModuleSet *FeatureModules = nullptr; |
174 | /// If true, use the dirty buffer contents when building Preambles. |
175 | bool = false; |
176 | |
177 | // If true, parse emplace-like functions in the preamble. |
178 | bool PreambleParseForwardingFunctions = false; |
179 | |
180 | /// Whether include fixer insertions for Objective-C code should use #import |
181 | /// instead of #include. |
182 | bool ImportInsertions = false; |
183 | |
184 | /// Whether to collect and publish information about inactive preprocessor |
185 | /// regions in the document. |
186 | bool PublishInactiveRegions = false; |
187 | |
188 | explicit operator TUScheduler::Options() const; |
189 | }; |
190 | // Sensible default options for use in tests. |
191 | // Features like indexing must be enabled if desired. |
192 | static Options optsForTest(); |
193 | |
194 | /// Creates a new ClangdServer instance. |
195 | /// |
196 | /// ClangdServer uses \p CDB to obtain compilation arguments for parsing. Note |
197 | /// that ClangdServer only obtains compilation arguments once for each newly |
198 | /// added file (i.e., when processing a first call to addDocument) and reuses |
199 | /// those arguments for subsequent reparses. However, ClangdServer will check |
200 | /// if compilation arguments changed on calls to forceReparse(). |
201 | ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, |
202 | const Options &Opts, Callbacks *Callbacks = nullptr); |
203 | ~ClangdServer(); |
204 | |
205 | /// Gets the installed feature module of a given type, if any. |
206 | /// This exposes access the public interface of feature modules that have one. |
207 | template <typename Mod> Mod *featureModule() { |
208 | return FeatureModules ? FeatureModules->get<Mod>() : nullptr; |
209 | } |
210 | template <typename Mod> const Mod *featureModule() const { |
211 | return FeatureModules ? FeatureModules->get<Mod>() : nullptr; |
212 | } |
213 | |
214 | /// Add a \p File to the list of tracked C++ files or update the contents if |
215 | /// \p File is already tracked. Also schedules parsing of the AST for it on a |
216 | /// separate thread. When the parsing is complete, DiagConsumer passed in |
217 | /// constructor will receive onDiagnosticsReady callback. |
218 | /// Version identifies this snapshot and is propagated to ASTs, preambles, |
219 | /// diagnostics etc built from it. If empty, a version number is generated. |
220 | void addDocument(PathRef File, StringRef Contents, |
221 | llvm::StringRef Version = "null" , |
222 | WantDiagnostics WD = WantDiagnostics::Auto, |
223 | bool ForceRebuild = false); |
224 | |
225 | /// Remove \p File from list of tracked files, schedule a request to free |
226 | /// resources associated with it. Pending diagnostics for closed files may not |
227 | /// be delivered, even if requested with WantDiags::Auto or WantDiags::Yes. |
228 | /// An empty set of diagnostics will be delivered, with Version = "". |
229 | void removeDocument(PathRef File); |
230 | |
231 | /// Requests a reparse of currently opened files using their latest source. |
232 | /// This will typically only rebuild if something other than the source has |
233 | /// changed (e.g. the CDB yields different flags, or files included in the |
234 | /// preamble have been modified). |
235 | void reparseOpenFilesIfNeeded( |
236 | llvm::function_ref<bool(llvm::StringRef File)> Filter); |
237 | |
238 | /// Run code completion for \p File at \p Pos. |
239 | /// |
240 | /// This method should only be called for currently tracked files. |
241 | void codeComplete(PathRef File, Position Pos, |
242 | const clangd::CodeCompleteOptions &Opts, |
243 | Callback<CodeCompleteResult> CB); |
244 | |
245 | /// Provide signature help for \p File at \p Pos. This method should only be |
246 | /// called for tracked files. |
247 | void signatureHelp(PathRef File, Position Pos, MarkupKind DocumentationFormat, |
248 | Callback<SignatureHelp> CB); |
249 | |
250 | /// Find declaration/definition locations of symbol at a specified position. |
251 | void locateSymbolAt(PathRef File, Position Pos, |
252 | Callback<std::vector<LocatedSymbol>> CB); |
253 | |
254 | /// Switch to a corresponding source file when given a header file, and vice |
255 | /// versa. |
256 | void (PathRef Path, |
257 | Callback<std::optional<clangd::Path>> CB); |
258 | |
259 | /// Get document highlights for a given position. |
260 | void findDocumentHighlights(PathRef File, Position Pos, |
261 | Callback<std::vector<DocumentHighlight>> CB); |
262 | |
263 | /// Get code hover for a given position. |
264 | void findHover(PathRef File, Position Pos, |
265 | Callback<std::optional<HoverInfo>> CB); |
266 | |
267 | /// Get information about type hierarchy for a given position. |
268 | void typeHierarchy(PathRef File, Position Pos, int Resolve, |
269 | TypeHierarchyDirection Direction, |
270 | Callback<std::vector<TypeHierarchyItem>> CB); |
271 | /// Get direct parents of a type hierarchy item. |
272 | void superTypes(const TypeHierarchyItem &Item, |
273 | Callback<std::optional<std::vector<TypeHierarchyItem>>> CB); |
274 | /// Get direct children of a type hierarchy item. |
275 | void subTypes(const TypeHierarchyItem &Item, |
276 | Callback<std::vector<TypeHierarchyItem>> CB); |
277 | |
278 | /// Resolve type hierarchy item in the given direction. |
279 | void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, |
280 | TypeHierarchyDirection Direction, |
281 | Callback<std::optional<TypeHierarchyItem>> CB); |
282 | |
283 | /// Get information about call hierarchy for a given position. |
284 | void prepareCallHierarchy(PathRef File, Position Pos, |
285 | Callback<std::vector<CallHierarchyItem>> CB); |
286 | |
287 | /// Resolve incoming calls for a given call hierarchy item. |
288 | void incomingCalls(const CallHierarchyItem &Item, |
289 | Callback<std::vector<CallHierarchyIncomingCall>>); |
290 | |
291 | /// Resolve inlay hints for a given document. |
292 | void inlayHints(PathRef File, std::optional<Range> RestrictRange, |
293 | Callback<std::vector<InlayHint>>); |
294 | |
295 | /// Retrieve the top symbols from the workspace matching a query. |
296 | void workspaceSymbols(StringRef Query, int Limit, |
297 | Callback<std::vector<SymbolInformation>> CB); |
298 | |
299 | /// Retrieve the symbols within the specified file. |
300 | void documentSymbols(StringRef File, |
301 | Callback<std::vector<DocumentSymbol>> CB); |
302 | |
303 | /// Retrieve ranges that can be used to fold code within the specified file. |
304 | void foldingRanges(StringRef File, Callback<std::vector<FoldingRange>> CB); |
305 | |
306 | /// Retrieve implementations for virtual method. |
307 | void findImplementations(PathRef File, Position Pos, |
308 | Callback<std::vector<LocatedSymbol>> CB); |
309 | |
310 | /// Retrieve symbols for types referenced at \p Pos. |
311 | void findType(PathRef File, Position Pos, |
312 | Callback<std::vector<LocatedSymbol>> CB); |
313 | |
314 | /// Retrieve locations for symbol references. |
315 | void findReferences(PathRef File, Position Pos, uint32_t Limit, |
316 | bool AddContainer, Callback<ReferencesResult> CB); |
317 | |
318 | /// Run formatting for the \p File with content \p Code. |
319 | /// If \p Rng is non-null, formats only that region. |
320 | void formatFile(PathRef File, std::optional<Range> Rng, |
321 | Callback<tooling::Replacements> CB); |
322 | |
323 | /// Run formatting after \p TriggerText was typed at \p Pos in \p File with |
324 | /// content \p Code. |
325 | void formatOnType(PathRef File, Position Pos, StringRef TriggerText, |
326 | Callback<std::vector<TextEdit>> CB); |
327 | |
328 | /// Test the validity of a rename operation. |
329 | /// |
330 | /// If NewName is provided, it performs a name validation. |
331 | void prepareRename(PathRef File, Position Pos, |
332 | std::optional<std::string> NewName, |
333 | const RenameOptions &RenameOpts, |
334 | Callback<RenameResult> CB); |
335 | |
336 | /// Rename all occurrences of the symbol at the \p Pos in \p File to |
337 | /// \p NewName. |
338 | /// If WantFormat is false, the final TextEdit will be not formatted, |
339 | /// embedders could use this method to get all occurrences of the symbol (e.g. |
340 | /// highlighting them in prepare stage). |
341 | void rename(PathRef File, Position Pos, llvm::StringRef NewName, |
342 | const RenameOptions &Opts, Callback<RenameResult> CB); |
343 | |
344 | struct TweakRef { |
345 | std::string ID; /// ID to pass for applyTweak. |
346 | std::string Title; /// A single-line message to show in the UI. |
347 | llvm::StringLiteral Kind; |
348 | }; |
349 | |
350 | // Ref to the clangd::Diag. |
351 | struct DiagRef { |
352 | clangd::Range Range; |
353 | std::string Message; |
354 | bool operator==(const DiagRef &Other) const { |
355 | return std::tie(args: Range, args: Message) == std::tie(args: Other.Range, args: Other.Message); |
356 | } |
357 | bool operator<(const DiagRef &Other) const { |
358 | return std::tie(args: Range, args: Message) < std::tie(args: Other.Range, args: Other.Message); |
359 | } |
360 | }; |
361 | |
362 | struct CodeActionInputs { |
363 | std::string File; |
364 | Range Selection; |
365 | |
366 | /// Requested kind of actions to return. |
367 | std::vector<std::string> RequestedActionKinds; |
368 | |
369 | /// Diagnostics attached to the code action request. |
370 | std::vector<DiagRef> Diagnostics; |
371 | |
372 | /// Tweaks where Filter returns false will not be checked or included. |
373 | std::function<bool(const Tweak &)> TweakFilter; |
374 | }; |
375 | struct CodeActionResult { |
376 | std::string Version; |
377 | struct QuickFix { |
378 | DiagRef Diag; |
379 | Fix F; |
380 | }; |
381 | std::vector<QuickFix> QuickFixes; |
382 | std::vector<TweakRef> TweakRefs; |
383 | struct Rename { |
384 | DiagRef Diag; |
385 | std::string FixMessage; |
386 | std::string NewName; |
387 | }; |
388 | std::vector<Rename> Renames; |
389 | }; |
390 | /// Surface code actions (quick-fixes for diagnostics, or available code |
391 | /// tweaks) for a given range in a file. |
392 | void codeAction(const CodeActionInputs &Inputs, |
393 | Callback<CodeActionResult> CB); |
394 | |
395 | /// Apply the code tweak with a specified \p ID. |
396 | void applyTweak(PathRef File, Range Sel, StringRef ID, |
397 | Callback<Tweak::Effect> CB); |
398 | |
399 | /// Called when an event occurs for a watched file in the workspace. |
400 | void onFileEvent(const DidChangeWatchedFilesParams &Params); |
401 | |
402 | /// Get symbol info for given position. |
403 | /// Clangd extension - not part of official LSP. |
404 | void symbolInfo(PathRef File, Position Pos, |
405 | Callback<std::vector<SymbolDetails>> CB); |
406 | |
407 | /// Get semantic ranges around a specified position in a file. |
408 | void semanticRanges(PathRef File, const std::vector<Position> &Pos, |
409 | Callback<std::vector<SelectionRange>> CB); |
410 | |
411 | /// Get all document links in a file. |
412 | void documentLinks(PathRef File, Callback<std::vector<DocumentLink>> CB); |
413 | |
414 | void semanticHighlights(PathRef File, |
415 | Callback<std::vector<HighlightingToken>>); |
416 | |
417 | /// Describe the AST subtree for a piece of code. |
418 | void getAST(PathRef File, std::optional<Range> R, |
419 | Callback<std::optional<ASTNode>> CB); |
420 | |
421 | /// Runs an arbitrary action that has access to the AST of the specified file. |
422 | /// The action will execute on one of ClangdServer's internal threads. |
423 | /// The AST is only valid for the duration of the callback. |
424 | /// As with other actions, the file must have been opened. |
425 | void customAction(PathRef File, llvm::StringRef Name, |
426 | Callback<InputsAndAST> Action); |
427 | |
428 | /// Fetches diagnostics for current version of the \p File. This might fail if |
429 | /// server is busy (building a preamble) and would require a long time to |
430 | /// prepare diagnostics. If it fails, clients should wait for |
431 | /// onSemanticsMaybeChanged and then retry. |
432 | /// These 'pulled' diagnostics do not interfere with the diagnostics 'pushed' |
433 | /// to Callbacks::onDiagnosticsReady, and clients may use either or both. |
434 | void diagnostics(PathRef File, Callback<std::vector<Diag>> CB); |
435 | |
436 | /// Returns estimated memory usage and other statistics for each of the |
437 | /// currently open files. |
438 | /// Overall memory usage of clangd may be significantly more than reported |
439 | /// here, as this metric does not account (at least) for: |
440 | /// - memory occupied by static and dynamic index, |
441 | /// - memory required for in-flight requests, |
442 | /// FIXME: those metrics might be useful too, we should add them. |
443 | llvm::StringMap<TUScheduler::FileStats> fileStats() const; |
444 | |
445 | /// Gets the contents of a currently tracked file. Returns nullptr if the file |
446 | /// isn't being tracked. |
447 | std::shared_ptr<const std::string> getDraft(PathRef File) const; |
448 | |
449 | // Blocks the main thread until the server is idle. Only for use in tests. |
450 | // Returns false if the timeout expires. |
451 | // FIXME: various subcomponents each get the full timeout, so it's more of |
452 | // an order of magnitude than a hard deadline. |
453 | [[nodiscard]] bool |
454 | blockUntilIdleForTest(std::optional<double> TimeoutSeconds = 10); |
455 | |
456 | /// Builds a nested representation of memory used by components. |
457 | void profile(MemoryTree &MT) const; |
458 | |
459 | private: |
460 | FeatureModuleSet *FeatureModules; |
461 | const GlobalCompilationDatabase &CDB; |
462 | const ThreadsafeFS &() const { |
463 | return UseDirtyHeaders ? *DirtyFS : TFS; |
464 | } |
465 | const ThreadsafeFS &TFS; |
466 | |
467 | Path ResourceDir; |
468 | // The index used to look up symbols. This could be: |
469 | // - null (all index functionality is optional) |
470 | // - the dynamic index owned by ClangdServer (DynamicIdx) |
471 | // - the static index passed to the constructor |
472 | // - a merged view of a static and dynamic index (MergedIndex) |
473 | const SymbolIndex *Index = nullptr; |
474 | // If present, an index of symbols in open files. Read via *Index. |
475 | std::unique_ptr<FileIndex> DynamicIdx; |
476 | // If present, the new "auto-index" maintained in background threads. |
477 | std::unique_ptr<BackgroundIndex> BackgroundIdx; |
478 | // Storage for merged views of the various indexes. |
479 | std::vector<std::unique_ptr<SymbolIndex>> MergedIdx; |
480 | |
481 | // When set, provides clang-tidy options for a specific file. |
482 | TidyProviderRef ClangTidyProvider; |
483 | |
484 | bool = false; |
485 | |
486 | // Whether the client supports folding only complete lines. |
487 | bool LineFoldingOnly = false; |
488 | |
489 | bool PreambleParseForwardingFunctions = false; |
490 | |
491 | bool ImportInsertions = false; |
492 | |
493 | bool PublishInactiveRegions = false; |
494 | |
495 | // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex) |
496 | llvm::StringMap<std::optional<FuzzyFindRequest>> |
497 | CachedCompletionFuzzyFindRequestByFile; |
498 | mutable std::mutex CachedCompletionFuzzyFindRequestMutex; |
499 | |
500 | std::optional<std::string> WorkspaceRoot; |
501 | std::optional<AsyncTaskRunner> IndexTasks; // for stdlib indexing. |
502 | std::optional<TUScheduler> WorkScheduler; |
503 | // Invalidation policy used for actions that we assume are "transient". |
504 | TUScheduler::ASTActionInvalidation Transient; |
505 | |
506 | // Store of the current versions of the open documents. |
507 | // Only written from the main thread (despite being threadsafe). |
508 | DraftStore DraftMgr; |
509 | |
510 | std::unique_ptr<ThreadsafeFS> DirtyFS; |
511 | }; |
512 | |
513 | } // namespace clangd |
514 | } // namespace clang |
515 | |
516 | #endif |
517 | |