clang-tools 10.0.0

DefineOutline.cpp
Go to the documentation of this file.
1 //===--- DefineOutline.cpp ---------------------------------------*- 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 #include "AST.h"
10 #include "FindTarget.h"
11 #include "HeaderSourceSwitch.h"
12 #include "Logger.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Selection.h"
16 #include "SourceCode.h"
17 #include "refactor/Tweak.h"
18 #include "clang/AST/ASTTypeTraits.h"
19 #include "clang/AST/Attr.h"
20 #include "clang/AST/Decl.h"
21 #include "clang/AST/DeclBase.h"
22 #include "clang/AST/DeclCXX.h"
23 #include "clang/AST/DeclTemplate.h"
24 #include "clang/AST/Stmt.h"
25 #include "clang/Basic/SourceLocation.h"
26 #include "clang/Basic/SourceManager.h"
27 #include "clang/Basic/TokenKinds.h"
28 #include "clang/Driver/Types.h"
29 #include "clang/Format/Format.h"
30 #include "clang/Lex/Lexer.h"
31 #include "clang/Tooling/Core/Replacement.h"
32 #include "clang/Tooling/Syntax/Tokens.h"
33 #include "llvm/ADT/None.h"
34 #include "llvm/ADT/Optional.h"
35 #include "llvm/ADT/STLExtras.h"
36 #include "llvm/ADT/StringRef.h"
37 #include "llvm/Support/Casting.h"
38 #include "llvm/Support/Error.h"
39 #include <cstddef>
40 #include <string>
41
42 namespace clang {
43 namespace clangd {
44 namespace {
45
46 // Deduces the FunctionDecl from a selection. Requires either the function body
47 // or the function decl to be selected. Returns null if none of the above
48 // criteria is met.
49 // FIXME: This is shared with define inline, move them to a common header once
50 // we have a place for such.
51 const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {
52 if (!SelNode)
53 return nullptr;
54 const ast_type_traits::DynTypedNode &AstNode = SelNode->ASTNode;
55 if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())
56 return FD;
57 if (AstNode.get<CompoundStmt>() &&
58 SelNode->Selected == SelectionTree::Complete) {
59 if (const SelectionTree::Node *P = SelNode->Parent)
60 return P->ASTNode.get<FunctionDecl>();
61 }
62 return nullptr;
63 }
64
65 llvm::Optional<Path> getSourceFile(llvm::StringRef FileName,
66 const Tweak::Selection &Sel) {
67 if (auto Source = getCorrespondingHeaderOrSource(
68 FileName,
69 &Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem()))
70 return *Source;
71 return getCorrespondingHeaderOrSource(FileName, *Sel.AST, Sel.Index);
72 }
73
74 // Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty
75 // for global namespace, and endwith "::" otherwise.
76 // Returns None if TargetNS is not a prefix of CurContext.
77 llvm::Optional<const DeclContext *>
78 findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
79 assert(TargetNS.empty() || TargetNS.endswith("::"));
80 // Skip any non-namespace contexts, e.g. TagDecls, functions/methods.
81 CurContext = CurContext->getEnclosingNamespaceContext();
82 // If TargetNS is empty, it means global ns, which is translation unit.
83 if (TargetNS.empty()) {
84 while (!CurContext->isTranslationUnit())
85 CurContext = CurContext->getParent();
86 return CurContext;
87 }
88 // Otherwise we need to drop any trailing namespaces from CurContext until
89 // we reach TargetNS.
90 std::string TargetContextNS =
91 CurContext->isNamespace()
92 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
93 : "";
94 TargetContextNS.append("::");
95
96 llvm::StringRef CurrentContextNS(TargetContextNS);
97 // If TargetNS is not a prefix of CurrentContext, there's no way to reach
98 // it.
99 if (!CurrentContextNS.startswith(TargetNS))
100 return llvm::None;
101
102 while (CurrentContextNS != TargetNS) {
103 CurContext = CurContext->getParent();
104 // These colons always exists since TargetNS is a prefix of
105 // CurrentContextNS, it ends with "::" and they are not equal.
106 CurrentContextNS = CurrentContextNS.take_front(
107 CurrentContextNS.drop_back(2).rfind("::") + 2);
108 }
109 return CurContext;
110 }
111
112 // Returns source code for FD after applying Replacements.
113 // FIXME: Make the function take a parameter to return only the function body,
114 // afterwards it can be shared with define-inline code action.
115 llvm::Expected<std::string>
116 getFunctionSourceAfterReplacements(const FunctionDecl *FD,
117 const tooling::Replacements &Replacements) {
118 const auto &SM = FD->getASTContext().getSourceManager();
119 auto OrigFuncRange = toHalfOpenFileRange(
120 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
121 if (!OrigFuncRange)
122 return llvm::createStringError(llvm::inconvertibleErrorCode(),
123 "Couldn't get range for function.");
124 // Include template parameter list.
125 if (auto *FTD = FD->getDescribedFunctionTemplate())
126 OrigFuncRange->setBegin(FTD->getBeginLoc());
127
128 // Get new begin and end positions for the qualified function definition.
129 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
130 unsigned FuncEnd = Replacements.getShiftedCodePosition(
131 SM.getFileOffset(OrigFuncRange->getEnd()));
132
133 // Trim the result to function definition.
134 auto QualifiedFunc = tooling::applyAllReplacements(
135 SM.getBufferData(SM.getMainFileID()), Replacements);
136 if (!QualifiedFunc)
137 return QualifiedFunc.takeError();
138 return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
139 }
140
141 // Creates a modified version of function definition that can be inserted at a
142 // different location, qualifies return value and function name to achieve that.
143 // Contains function signature, except defaulted parameter arguments, body and
144 // template parameters if applicable. No need to qualify parameters, as they are
145 // looked up in the context containing the function/method.
146 // FIXME: Drop attributes in function signature.
147 llvm::Expected<std::string>
148 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
149 const syntax::TokenBuffer &TokBuf) {
150 auto &AST = FD->getASTContext();
151 auto &SM = AST.getSourceManager();
152 auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
153 if (!TargetContext)
154 return llvm::createStringError(
155 llvm::inconvertibleErrorCode(),
156 "define outline: couldn't find a context for target");
157
158 llvm::Error Errors = llvm::Error::success();
159 tooling::Replacements DeclarationCleanups;
160
161 // Finds the first unqualified name in function return type and name, then
162 // qualifies those to be valid in TargetContext.
163 findExplicitReferences(FD, [&](ReferenceLoc Ref) {
164 // It is enough to qualify the first qualifier, so skip references with a
165 // qualifier. Also we can't do much if there are no targets or name is
166 // inside a macro body.
167 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
168 return;
169 // Only qualify return type and function name.
170 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
171 Ref.NameLoc != FD->getLocation())
172 return;
173
174 for (const NamedDecl *ND : Ref.Targets) {
175 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
176 elog("Targets from multiple contexts: {0}, {1}",
177 printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND));
178 return;
179 }
180 }
181 const NamedDecl *ND = Ref.Targets.front();
182 const std::string Qualifier = getQualification(
183 AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
184 if (auto Err = DeclarationCleanups.add(
185 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
186 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
187 });
188
189 // Get rid of default arguments, since they should not be specified in
190 // out-of-line definition.
191 for (const auto *PVD : FD->parameters()) {
192 if (PVD->hasDefaultArg()) {
193 // Deletion range initially spans the initializer, excluding the `=`.
194 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
195 // Get all tokens before the default argument.
196 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
197 .take_while([&SM, &DelRange](const syntax::Token &Tok) {
198 return SM.isBeforeInTranslationUnit(
199 Tok.location(), DelRange.getBegin());
200 });
201 // Find the last `=` before the default arg.
202 auto Tok =
203 llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
204 return Tok.kind() == tok::equal;
205 });
206 assert(Tok != Tokens.rend());
207 DelRange.setBegin(Tok->location());
208 if (auto Err =
209 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
210 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
211 }
212 }
213
214 auto DelAttr = [&](const Attr *A) {
215 if (!A)
216 return;
217 auto AttrTokens =
218 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
219 assert(A->getLocation().isValid());
220 if (!AttrTokens || AttrTokens->empty()) {
221 Errors = llvm::joinErrors(
222 std::move(Errors),
223 llvm::createStringError(
224 llvm::inconvertibleErrorCode(),
225 llvm::StringRef("define outline: Can't move out of line as "
226 "function has a macro `") +
227 A->getSpelling() + "` specifier."));
228 return;
229 }
230 CharSourceRange DelRange =
231 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
232 .toCharRange(SM);
233 if (auto Err =
234 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
235 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
236 };
237
238 DelAttr(FD->getAttr<OverrideAttr>());
239 DelAttr(FD->getAttr<FinalAttr>());
240
241 if (FD->isVirtualAsWritten()) {
242 SourceRange SpecRange{FD->getBeginLoc(), FD->getLocation()};
243 bool HasErrors = true;
244
245 // Clang allows duplicating virtual specifiers so check for multiple
246 // occurances.
247 for (const auto &Tok : TokBuf.expandedTokens(SpecRange)) {
248 if (Tok.kind() != tok::kw_virtual)
249 continue;
250 auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
251 if (!Spelling) {
252 HasErrors = true;
253 break;
254 }
255 HasErrors = false;
256 CharSourceRange DelRange =
257 syntax::Token::range(SM, Spelling->front(), Spelling->back())
258 .toCharRange(SM);
259 if (auto Err =
260 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
261 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
262 }
263 if (HasErrors) {
264 Errors = llvm::joinErrors(
265 std::move(Errors),
266 llvm::createStringError(llvm::inconvertibleErrorCode(),
267 "define outline: Can't move out of line as "
268 "function has a macro `virtual` specifier."));
269 }
270 }
271
272 if (Errors)
273 return std::move(Errors);
274 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
275 }
276
277 struct InsertionPoint {
278 std::string EnclosingNamespace;
279 size_t Offset;
280 };
281 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
282 // This currently cares about only the namespace proximity, but in feature it
283 // should also try to follow ordering of declarations. For example, if decls
284 // come in order `foo, bar, baz` then this function should return some point
285 // between foo and baz for inserting bar.
286 llvm::Expected<InsertionPoint>
287 getInsertionPoint(llvm::StringRef Contents, llvm::StringRef QualifiedName,
288 const format::FormatStyle &Style) {
289 auto Region = getEligiblePoints(Contents, QualifiedName, Style);
290
291 assert(!Region.EligiblePoints.empty());
292 // FIXME: This selection can be made smarter by looking at the definition
293 // locations for adjacent decls to Source. Unfortunately psudeo parsing in
294 // getEligibleRegions only knows about namespace begin/end events so we
295 // can't match function start/end positions yet.
296 auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
297 if (!Offset)
298 return Offset.takeError();
299 return InsertionPoint{Region.EnclosingNamespace, *Offset};
300 }
301
302 // Returns the range that should be deleted from declaration, which always
303 // contains function body. In addition to that it might contain constructor
304 // initializers.
305 SourceRange getDeletionRange(const FunctionDecl *FD,
306 const syntax::TokenBuffer &TokBuf) {
307 auto DeletionRange = FD->getBody()->getSourceRange();
308 if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
309 const auto &SM = TokBuf.sourceManager();
310 // AST doesn't contain the location for ":" in ctor initializers. Therefore
311 // we find it by finding the first ":" before the first ctor initializer.
312 SourceLocation InitStart;
313 // Find the first initializer.
314 for (const auto *CInit : CD->inits()) {
315 // We don't care about in-class initializers.
316 if (CInit->isInClassMemberInitializer())
317 continue;
318 if (InitStart.isInvalid() ||
319 SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart))
320 InitStart = CInit->getSourceLocation();
321 }
322 if (InitStart.isValid()) {
323 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
324 // Drop any tokens after the initializer.
325 Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
326 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
327 InitStart);
328 });
329 // Look for the first colon.
330 auto Tok =
331 llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
332 return Tok.kind() == tok::colon;
333 });
334 assert(Tok != Toks.rend());
335 DeletionRange.setBegin(Tok->location());
336 }
337 }
338 return DeletionRange;
339 }
340
341 /// Moves definition of a function/method to an appropriate implementation file.
342 ///
343 /// Before:
344 /// a.h
345 /// void foo() { return; }
346 /// a.cc
347 /// #include "a.h"
348 ///
349 /// ----------------
350 ///
351 /// After:
352 /// a.h
353 /// void foo();
354 /// a.cc
355 /// #include "a.h"
356 /// void foo() { return; }
357 class DefineOutline : public Tweak {
358 public:
359 const char *id() const override;
360
361 bool hidden() const override { return true; }
362 Intent intent() const override { return Intent::Refactor; }
363 std::string title() const override {
364 return "Move function body to out-of-line.";
365 }
366
367 bool prepare(const Selection &Sel) override {
368 // Bail out if we are not in a header file.
369 // FIXME: We might want to consider moving method definitions below class
370 // definition even if we are inside a source file.
371 if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
372 Sel.AST->getLangOpts()))
373 return false;
374
375 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
376 // Bail out if the selection is not a in-line function definition.
377 if (!Source || !Source->doesThisDeclarationHaveABody() ||
378 Source->isOutOfLine())
379 return false;
380
381 // Bail out in templated classes, as it is hard to spell the class name, i.e
382 // if the template parameter is unnamed.
383 if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
384 if (MD->getParent()->isTemplated())
385 return false;
386 }
387
388 // Note that we don't check whether an implementation file exists or not in
389 // the prepare, since performing disk IO on each prepare request might be
390 // expensive.
391 return true;
392 }
393
394 Expected<Effect> apply(const Selection &Sel) override {
395 const SourceManager &SM = Sel.AST->getSourceManager();
396 auto MainFileName =
397 getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
398 if (!MainFileName)
399 return llvm::createStringError(
400 llvm::inconvertibleErrorCode(),
401 "Couldn't get absolute path for mainfile.");
402
403 auto CCFile = getSourceFile(*MainFileName, Sel);
404 if (!CCFile)
405 return llvm::createStringError(
406 llvm::inconvertibleErrorCode(),
407 "Couldn't find a suitable implementation file.");
408
409 auto &FS =
410 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
411 auto Buffer = FS.getBufferForFile(*CCFile);
412 // FIXME: Maybe we should consider creating the implementation file if it
413 // doesn't exist?
414 if (!Buffer)
415 return llvm::createStringError(Buffer.getError(),
416 Buffer.getError().message());
417 auto Contents = Buffer->get()->getBuffer();
418 auto InsertionPoint =
419 getInsertionPoint(Contents, Source->getQualifiedNameAsString(),
420 getFormatStyleForFile(*CCFile, Contents, &FS));
421 if (!InsertionPoint)
422 return InsertionPoint.takeError();
423
424 auto FuncDef = getFunctionSourceCode(
425 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
426 if (!FuncDef)
427 return FuncDef.takeError();
428
429 SourceManagerForFile SMFF(*CCFile, Contents);
430 const tooling::Replacement InsertFunctionDef(
431 *CCFile, InsertionPoint->Offset, 0, *FuncDef);
432 auto Effect = Effect::mainFileEdit(
433 SMFF.get(), tooling::Replacements(InsertFunctionDef));
434 if (!Effect)
435 return Effect.takeError();
436
437 // FIXME: We should also get rid of inline qualifier.
438 const tooling::Replacement DeleteFuncBody(
439 Sel.AST->getSourceManager(),
440 CharSourceRange::getTokenRange(*toHalfOpenFileRange(
441 SM, Sel.AST->getLangOpts(),
442 getDeletionRange(Source, Sel.AST->getTokens()))),
443 ";");
444 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
445 tooling::Replacements(DeleteFuncBody));
446 if (!HeaderFE)
447 return HeaderFE.takeError();
448
449 Effect->ApplyEdits.try_emplace(HeaderFE->first,
450 std::move(HeaderFE->second));
451 return std::move(*Effect);
452 }
453
454 private:
455 const FunctionDecl *Source = nullptr;
456 };
457
458 REGISTER_TWEAK(DefineOutline)
459
460 } // namespace
461 } // namespace clangd
462 } // namespace clang
AST.h
clang::clangd::printQualifiedName
std::string printQualifiedName(const NamedDecl &ND)
Returns the qualified name of ND.
Definition: AST.cpp:169
Contents
llvm::StringRef Contents
Definition: CodeComplete.cpp:1021
clang::clangd::MessageType::Error
An error message.
SourceCode.h
FS
MockFSProvider FS
Definition: HeadersTests.cpp:119
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
clang::clangd::TextDocumentSyncKind::None
Documents should not be synced at all.
Offset
size_t Offset
Definition: DefineOutline.cpp:279
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:56
clang::clangd::ParsedAST::getASTContext
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
Definition: ParsedAST.cpp:424
EnclosingNamespace
std::string EnclosingNamespace
Definition: DefineOutline.cpp:278
Tweak.h
clang::clangd::positionToOffset
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:155
HeaderSourceSwitch.h
clang::clangd::getQualification
std::string getQualification(ASTContext &Context, const DeclContext *DestContext, SourceLocation InsertionPoint, const NamedDecl *ND)
Gets the nested name specifier necessary for spelling ND in DestContext, at InsertionPoint.
Definition: AST.cpp:437
clang::clangd::getEligiblePoints
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const format::FormatStyle &Style)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code...
Definition: SourceCode.cpp:1065
InsertionPoint
SourceLocation InsertionPoint
Definition: ExtractFunction.cpp:305
FileName
PathRef FileName
Definition: CodeComplete.cpp:1018
clang::clangd::getTokenRange
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
clang::clangd::getFormatStyleForFile
format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, llvm::vfs::FileSystem *FS)
Choose the clang-format style we should apply to a certain file.
Definition: SourceCode.cpp:693
clang::clangd::toHalfOpenFileRange
llvm::Optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
Definition: SourceCode.cpp:538
clang::clangd::ParsedAST::getSourceManager
SourceManager & getSourceManager()
Definition: ParsedAST.h:73
Logger.h
clang::clangd::SelectionTree::Complete
Definition: Selection.h:91
clang::clangd::getCorrespondingHeaderOrSource
llvm::Optional< Path > getCorrespondingHeaderOrSource(const Path &OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
Definition: HeaderSourceSwitch.cpp:19
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
ParsedAST.h
clang::clangd::findExplicitReferences
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out)
Recursively traverse S and report all references explicitly written in the code.
Definition: FindTarget.cpp:867
FindTarget.h
clang::doc::MD
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
Path.h
clang::clangd::getCanonicalPath
llvm::Optional< std::string > getCanonicalPath(const FileEntry *F, const SourceManager &SourceMgr)
Get the canonical path of F.
Definition: SourceCode.cpp:622
clang::clangd::isHeaderFile
bool isHeaderFile(llvm::StringRef FileName, llvm::Optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
Definition: SourceCode.cpp:1118
Selection.h
clang::tidy::readability::@556::Qualifier
Qualifier
Definition: QualifiedAutoCheck.cpp:29
FormatStyle
static cl::opt< std::string > FormatStyle("format-style", cl::desc(R"( Style for formatting code around applied fixes: - 'none' (default) turns off formatting - 'file' (literally 'file', not a placeholder) uses .clang-format file in the closest parent directory - '{ <json> }' specifies options inline, e.g. -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' - 'llvm', 'google', 'webkit', 'mozilla' See clang-format documentation for the up-to-date information about formatting styles and options. This option overrides the 'FormatStyle` option in .clang-tidy file, if any. )"), cl::init("none"), cl::cat(ClangTidyCategory))