Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -2140,6 +2140,39 @@ namespace Extra { }}} +**ConstPlacement** (``ConstPlacementStyle``) + Different ways to arrange const. + + *This style is ONLY supported in clang-format++* + + Possible values: + + * ``CS_Leave`` (in configuration: ``Leave``) + Don't change const to either East/Right const or West/Left const. + + .. code-block:: c++ + + int const a; + const int *a; + + * ``CS_West`` (in configuration: ``West``) + Change type decorations to be West/Left const. + + .. code-block:: c++ + + const int a; + const int *a; + + * ``CS_East`` (in configuration: ``East``) + Change type decorations to be East/Right const. + + .. code-block:: c++ + + int const a; + int const *a; + + + **ConstructorInitializerAllOnOneLineOrOnePerLine** (``bool``) If the constructor initializers don't fit on a line, put each initializer on its own line. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -280,6 +280,17 @@ - Support for formatting JSON file (\*.json) has been added to clang-format. +clang-format++ +-------------- + +A new clang-format++ tool has been added, this is an experimental binary for clang-format functionality which enables capability which can mutate source code above and beyond the original goals of clang-format. The tool is there as a means of bringing these changes into clang-format without destabilizing the clang-format functionality. + +The goal of clang-format++ is to allow such changes which would normally not be considered + +- Option ``ConstPlacement`` has been added in order to auto-arrange the positioning of `const` + in variable and parameter declarations to be either ``Right/East`` const or ``Left/West`` + const . + libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -1832,6 +1832,31 @@ /// \endcode std::string CommentPragmas; + /// Different const alignment styles. + enum ConstPlacementStyle { + /// Don't change const to either East/Right const or West/Left const. + /// \code + /// int const a; + /// const int *a; + /// \endcode + CS_Leave, + /// Change type decorations to be West/Left const. + /// \code + /// const int a; + /// const int *a; + /// \endcode + CS_West, + /// Change type decorations to be East/Right const. + /// \code + /// int const a; + /// int const *a; + /// \endcode + CS_East + }; + + /// Different ways to arrange const. + ConstPlacementStyle ConstPlacement; + /// Different ways to break inheritance list. enum BreakInheritanceListStyle : unsigned char { /// Break inheritance list before the colon and after the commas. @@ -3423,6 +3448,7 @@ BreakAfterJavaFieldAnnotations == R.BreakAfterJavaFieldAnnotations && BreakStringLiterals == R.BreakStringLiterals && ColumnLimit == R.ColumnLimit && CommentPragmas == R.CommentPragmas && + ConstPlacement == R.ConstPlacement && BreakInheritanceList == R.BreakInheritanceList && ConstructorInitializerAllOnOneLineOrOnePerLine == R.ConstructorInitializerAllOnOneLineOrOnePerLine && @@ -3694,7 +3720,8 @@ tooling::Replacements reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, StringRef FileName = "", - FormattingAttemptStatus *Status = nullptr); + FormattingAttemptStatus *Status = nullptr, + bool MutatingPasses = false); /// Same as above, except if ``IncompleteFormat`` is non-null, its value /// will be set to true if any of the affected ranges were not formatted due to Index: clang/lib/Format/CMakeLists.txt =================================================================== --- clang/lib/Format/CMakeLists.txt +++ clang/lib/Format/CMakeLists.txt @@ -15,6 +15,7 @@ UnwrappedLineFormatter.cpp UnwrappedLineParser.cpp UsingDeclarationsSorter.cpp + EastWestConstFixer.cpp WhitespaceManager.cpp LINK_LIBS Index: clang/lib/Format/ContinuationIndenter.cpp =================================================================== --- clang/lib/Format/ContinuationIndenter.cpp +++ clang/lib/Format/ContinuationIndenter.cpp @@ -1728,7 +1728,7 @@ std::pair Fixes = internal::reformat( RawStringStyle, RawText, {tooling::Range(0, RawText.size())}, FirstStartColumn, NextStartColumn, LastStartColumn, "", - /*Status=*/nullptr); + /*Status=*/nullptr, /*MutatingPasses=*/false); auto NewCode = applyAllReplacements(RawText, Fixes.first); tooling::Replacements NoFixes; Index: clang/lib/Format/EastWestConstFixer.h =================================================================== --- /dev/null +++ clang/lib/Format/EastWestConstFixer.h @@ -0,0 +1,36 @@ +//===--- EastWestConstFixer.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares EastWestConstFixer, a TokenAnalyzer that +/// enforces either east or west const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_FORMAT_EASTWESTCONSTFIXER_H +#define LLVM_CLANG_LIB_FORMAT_EASTWESTCONSTFIXER_H + +#include "TokenAnalyzer.h" + +namespace clang { +namespace format { + +class EastWestConstFixer : public TokenAnalyzer { +public: + EastWestConstFixer(const Environment &Env, const FormatStyle &Style); + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; +}; + +} // end namespace format +} // end namespace clang + +#endif Index: clang/lib/Format/EastWestConstFixer.cpp =================================================================== --- /dev/null +++ clang/lib/Format/EastWestConstFixer.cpp @@ -0,0 +1,322 @@ +//===--- EastWestConstFixer.cpp ---------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements EastWestConstFixer, a TokenAnalyzer that +/// enforces either east or west const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#include "EastWestConstFixer.h" +#include "FormatToken.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Regex.h" + +#include + +namespace clang { +namespace format { + +static void replaceToken(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const CharSourceRange &Range, std::string NewText) { + auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); + auto Err = Fixes.add(Replacement); + + if (Err) { + llvm::errs() << "Error while rearranging const : " + << llvm::toString(std::move(Err)) << "\n"; + } +} + +static void removeToken(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First) { + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + First->Tok.getEndLoc()); + replaceToken(SourceMgr, Fixes, Range, ""); +} + +static void insertConstAfter(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First) { + FormatToken *Next = First->Next; + if (!Next) { + return; + } + auto Range = CharSourceRange::getCharRange( + Next->getStartOfNonWhitespace(), Next->Next->getStartOfNonWhitespace()); + + std::string NewText = " const "; + NewText += Next->TokenText; + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static void insertConstBefore(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First) { + auto Range = CharSourceRange::getCharRange( + First->getStartOfNonWhitespace(), First->Next->getStartOfNonWhitespace()); + + std::string NewText = " const "; + NewText += First->TokenText; + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static void rotateTokens(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, const FormatToken *First, + const FormatToken *Last, bool Left) { + auto *End = Last; + auto *Begin = First; + if (!Left) { + End = Last->Next; + Begin = First->Next; + } + + std::string NewText; + // If we are rotating to the left we move the Last token to the front. + if (Left) { + NewText += Last->TokenText; + NewText += " "; + } + + // Then move through the other tokens. + auto *Tok = Begin; + while (Tok != End) { + if (!NewText.empty()) + NewText += " "; + + NewText += Tok->TokenText; + Tok = Tok->Next; + } + + // If we are rotating to the right we move the first token to the back. + if (!Left) { + NewText += " "; + NewText += First->TokenText; + } + + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + Last->Tok.getEndLoc()); + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static bool isCVQualifierOrType(const FormatToken *Tok) { + return Tok && (Tok->isSimpleTypeSpecifier() || + Tok->isOneOf(tok::kw_volatile, tok::kw_auto)); +} + +// If a token is an identifier and it's upper case, it could +// be a macro and hence we need to be able to ignore it. +static bool isPossibleMacro(const FormatToken *Tok) { + if (!Tok) + return false; + + if (!Tok->is(tok::identifier)) + return false; + + if (Tok->TokenText.upper() == Tok->TokenText.str()) + return true; + + return false; +} + +static FormatToken *analyzeEast(const SourceManager &SourceMgr, + const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, + FormatToken *Tok) { + // We only need to think about streams that begin with const. + if (!Tok->is(tok::kw_const)) { + return Tok; + } + // Don't concern yourself if nothing follows const. + if (!Tok->Next) { + return Tok; + } + if (isPossibleMacro(Tok->Next)) { + return Tok; + } + FormatToken *Const = Tok; + + FormatToken *Qualifier = Tok->Next; + FormatToken *LastQualifier = Qualifier; + while (Qualifier && isCVQualifierOrType(Qualifier)) { + LastQualifier = Qualifier; + Qualifier = Qualifier->Next; + } + if (LastQualifier && Qualifier != LastQualifier) { + rotateTokens(SourceMgr, Fixes, Const, LastQualifier, /*Left=*/false); + Tok = LastQualifier; + } else if (Tok->startsSequence(tok::kw_const, tok::identifier, + TT_TemplateOpener)) { + // Read from the TemplateOpener to + // TemplateCloser as in const ArrayRef a; const ArrayRef &a; + FormatToken *EndTemplate = Tok->Next->Next->MatchingParen; + if (EndTemplate) { + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (EndTemplate->startsSequence(TT_TemplateCloser, tok::coloncolon, + tok::identifier)) { + EndTemplate = EndTemplate->Next->Next; + } + } + if (EndTemplate && EndTemplate->Next && + !EndTemplate->Next->isOneOf(tok::equal, tok::l_paren)) { + // Remove the const. + insertConstAfter(SourceMgr, Fixes, EndTemplate); + removeToken(SourceMgr, Fixes, Tok); + return Tok; + } + } else if (Tok->startsSequence(tok::kw_const, tok::identifier)) { + FormatToken *Next = Tok->Next; + // The case `const Foo` -> `Foo const` + // The case `const Foo *` -> `Foo const *` + // The case `const Foo &` -> `Foo const &` + // The case `const Foo &&` -> `Foo const &&` + // The case `const std::Foo &&` -> `std::Foo const &&` + // The case `const std::Foo &&` -> `std::Foo const &&` + while (Next && Next->isOneOf(tok::identifier, tok::coloncolon)) { + Next = Next->Next; + } + if (Next && Next->is(TT_TemplateOpener)) { + Next = Next->MatchingParen; + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (Next && Next->startsSequence(TT_TemplateCloser, tok::coloncolon, + tok::identifier)) { + Next = Next->Next->Next; + return Tok; + } + Next = Next->Next; + } + if (Next && Next->isOneOf(tok::star, tok::amp, tok::ampamp) && + !Tok->Next->isOneOf(Keywords.kw_override, Keywords.kw_final)) { + if (Next->Previous && !Next->Previous->is(tok::kw_const)) { + insertConstAfter(SourceMgr, Fixes, Next->Previous); + removeToken(SourceMgr, Fixes, Const); + } + return Next; + } + } + + return Tok; +} + +static FormatToken *analyzeWest(const SourceManager &SourceMgr, + const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, + FormatToken *Tok) { + // if Tok is an identifier and possibly a macro then don't convert + if (isPossibleMacro(Tok)) { + return Tok; + } + + FormatToken *Qualifier = Tok; + FormatToken *LastQualifier = Qualifier; + while (Qualifier && isCVQualifierOrType(Qualifier)) { + LastQualifier = Qualifier; + Qualifier = Qualifier->Next; + if (Qualifier && Qualifier->is(tok::kw_const)) { + break; + } + } + if (LastQualifier && Qualifier != LastQualifier && + Qualifier->is(tok::kw_const)) { + rotateTokens(SourceMgr, Fixes, Tok, Qualifier, /*Left=*/true); + Tok = Qualifier->Next; + } else if (Tok->startsSequence(tok::identifier, tok::kw_const)) { + if (Tok->Next->Next && Tok->Next->Next->isOneOf(tok::identifier, tok::star, + tok::amp, tok::ampamp)) { + // Don't swap `::iterator const` to `::const iterator`. + if (!Tok->Previous || + (Tok->Previous && !Tok->Previous->is(tok::coloncolon))) { + rotateTokens(SourceMgr, Fixes, Tok, Tok->Next, /*Left=*/true); + } + } + } + if (Tok->is(TT_TemplateOpener) && Tok->Next && + (Tok->Next->is(tok::identifier) || Tok->Next->isSimpleTypeSpecifier()) && + Tok->Next->Next && Tok->Next->Next->is(tok::kw_const)) { + rotateTokens(SourceMgr, Fixes, Tok->Next, Tok->Next->Next, /*Left=*/true); + } + if (Tok->startsSequence(tok::identifier) && Tok->Next) { + if (Tok->Previous && + Tok->Previous->isOneOf(tok::star, tok::ampamp, tok::amp)) { + return Tok; + } + FormatToken *Next = Tok->Next; + // The case `std::Foo const` -> `const std::Foo &&` + while (Next && Next->isOneOf(tok::identifier, tok::coloncolon)) { + Next = Next->Next; + } + if (Next && Next->Previous && + Next->Previous->startsSequence(tok::identifier, TT_TemplateOpener)) { + // Read from to the end of the TemplateOpener to + // TemplateCloser const ArrayRef a; const ArrayRef &a; + Next = Next->MatchingParen->Next; + + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (Next && Next->startsSequence(tok::coloncolon, tok::identifier)) { + Next = Next->Next->Next; + } + if (Next && Next->is(tok::kw_const)) { + // Remove the const. + removeToken(SourceMgr, Fixes, Next); + insertConstBefore(SourceMgr, Fixes, Tok); + return Next; + } + } + if (Next && Next->Next && + Next->Next->isOneOf(tok::amp, tok::ampamp, tok::star)) { + if (Next->is(tok::kw_const)) { + // Remove the const. + removeToken(SourceMgr, Fixes, Next); + insertConstBefore(SourceMgr, Fixes, Tok); + return Next; + } + } + } + return Tok; +} + +EastWestConstFixer::EastWestConstFixer(const Environment &Env, + const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} + +std::pair +EastWestConstFixer::analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) { + tooling::Replacements Fixes; + const AdditionalKeywords &Keywords = Tokens.getKeywords(); + const SourceManager &SourceMgr = Env.getSourceManager(); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + + for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { + FormatToken *First = AnnotatedLines[I]->First; + const auto *Last = AnnotatedLines[I]->Last; + + for (auto *Tok = First; Tok && Tok != Last && Tok->Next; Tok = Tok->Next) { + if (Tok->is(tok::comment)) { + continue; + } + if (Style.ConstPlacement == FormatStyle::CS_East) { + Tok = analyzeEast(SourceMgr, Keywords, Fixes, Tok); + } else if (Style.ConstPlacement == FormatStyle::CS_West) { + Tok = analyzeWest(SourceMgr, Keywords, Fixes, Tok); + } + } + } + return {Fixes, 0}; +} +} // namespace format +} // namespace clang Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -16,6 +16,7 @@ #include "AffectedRangeManager.h" #include "BreakableToken.h" #include "ContinuationIndenter.h" +#include "EastWestConstFixer.h" #include "FormatInternal.h" #include "FormatTokenLexer.h" #include "NamespaceEndCommentsFixer.h" @@ -126,6 +127,17 @@ } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::ConstPlacementStyle &Value) { + IO.enumCase(Value, "Leave", FormatStyle::CS_Leave); + IO.enumCase(Value, "West", FormatStyle::CS_West); + IO.enumCase(Value, "East", FormatStyle::CS_East); + + IO.enumCase(Value, "Left", FormatStyle::CS_West); + IO.enumCase(Value, "Right", FormatStyle::CS_East); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::ShortFunctionStyle &Value) { IO.enumCase(Value, "None", FormatStyle::SFS_None); @@ -632,6 +644,7 @@ IO.mapOptional("BreakStringLiterals", Style.BreakStringLiterals); IO.mapOptional("ColumnLimit", Style.ColumnLimit); IO.mapOptional("CommentPragmas", Style.CommentPragmas); + IO.mapOptional("ConstPlacement", Style.ConstPlacement); IO.mapOptional("CompactNamespaces", Style.CompactNamespaces); IO.mapOptional("ConstructorInitializerAllOnOneLineOrOnePerLine", Style.ConstructorInitializerAllOnOneLineOrOnePerLine); @@ -1033,6 +1046,7 @@ LLVMStyle.BreakStringLiterals = true; LLVMStyle.ColumnLimit = 80; LLVMStyle.CommentPragmas = "^ IWYU pragma:"; + LLVMStyle.ConstPlacement = FormatStyle::CS_Leave; LLVMStyle.CompactNamespaces = false; LLVMStyle.ConstructorInitializerAllOnOneLineOrOnePerLine = false; LLVMStyle.ConstructorInitializerIndentWidth = 4; @@ -1243,6 +1257,7 @@ // taze:, triple slash directives (`/// <...`), tslint:, and @see, which is // commonly followed by overlong URLs. GoogleStyle.CommentPragmas = "(taze:|^/[ \t]*<|tslint:|@see)"; + GoogleStyle.ConstPlacement = FormatStyle::CS_Leave; // TODO: enable once decided, in particular re disabling bin packing. // https://google.github.io/styleguide/jsguide.html#features-arrays-trailing-comma // GoogleStyle.InsertTrailingCommas = FormatStyle::TCS_Wrapped; @@ -1303,6 +1318,7 @@ // _prepend that with a comment_ to prevent it" before changing behavior. ChromiumStyle.IncludeStyle.IncludeBlocks = tooling::IncludeStyle::IBS_Preserve; + ChromiumStyle.ConstPlacement = FormatStyle::CS_Leave; if (Language == FormatStyle::LK_Java) { ChromiumStyle.AllowShortIfStatementsOnASingleLine = @@ -1364,6 +1380,7 @@ MozillaStyle.PenaltyReturnTypeOnItsOwnLine = 200; MozillaStyle.PointerAlignment = FormatStyle::PAS_Left; MozillaStyle.SpaceAfterTemplateKeyword = false; + MozillaStyle.ConstPlacement = FormatStyle::CS_Leave; return MozillaStyle; } @@ -1387,6 +1404,7 @@ Style.PointerAlignment = FormatStyle::PAS_Left; Style.SpaceBeforeCpp11BracedList = true; Style.SpaceInEmptyBlock = true; + Style.ConstPlacement = FormatStyle::CS_Leave; return Style; } @@ -1402,6 +1420,7 @@ Style.FixNamespaceComments = false; Style.SpaceBeforeParens = FormatStyle::SBPO_Always; Style.Standard = FormatStyle::LS_Cpp03; + Style.ConstPlacement = FormatStyle::CS_Leave; return Style; } @@ -1432,6 +1451,7 @@ Style.AllowShortLoopsOnASingleLine = false; Style.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None; + Style.ConstPlacement = FormatStyle::CS_Leave; return Style; } @@ -1440,6 +1460,7 @@ NoStyle.DisableFormat = true; NoStyle.SortIncludes = FormatStyle::SI_Never; NoStyle.SortUsingDeclarations = false; + NoStyle.ConstPlacement = FormatStyle::CS_Leave; return NoStyle; } @@ -2820,7 +2841,7 @@ reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, unsigned FirstStartColumn, unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName, - FormattingAttemptStatus *Status) { + FormattingAttemptStatus *Status, bool mutatingPasses) { FormatStyle Expanded = expandPresets(Style); if (Expanded.DisableFormat) return {tooling::Replacements(), 0}; @@ -2865,6 +2886,13 @@ }); } + if (mutatingPasses) { + if (Style.isCpp() && Style.ConstPlacement != FormatStyle::CS_Leave) + Passes.emplace_back([&](const Environment &Env) { + return EastWestConstFixer(Env, Expanded).process(); + }); + } + if (Style.Language == FormatStyle::LK_JavaScript && Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) Passes.emplace_back([&](const Environment &Env) { @@ -2911,11 +2939,13 @@ tooling::Replacements reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, StringRef FileName, - FormattingAttemptStatus *Status) { + FormattingAttemptStatus *Status, + bool MutatingPasses) { return internal::reformat(Style, Code, Ranges, /*FirstStartColumn=*/0, /*NextStartColumn=*/0, - /*LastStartColumn=*/0, FileName, Status) + /*LastStartColumn=*/0, FileName, Status, + MutatingPasses) .first; } Index: clang/lib/Format/FormatInternal.h =================================================================== --- clang/lib/Format/FormatInternal.h +++ clang/lib/Format/FormatInternal.h @@ -72,7 +72,7 @@ reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, unsigned FirstStartColumn, unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName, - FormattingAttemptStatus *Status); + FormattingAttemptStatus *Status, bool MutatingPasses); } // namespace internal } // namespace format Index: clang/tools/CMakeLists.txt =================================================================== --- clang/tools/CMakeLists.txt +++ clang/tools/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_subdirectory(apinotes-test) add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) +add_clang_subdirectory(clang-format++) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) add_clang_subdirectory(clang-import-test) Index: clang/tools/clang-format++/CMakeLists.txt =================================================================== --- /dev/null +++ clang/tools/clang-format++/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_tool(clang-format++ + ClangFormatPlusPlus.cpp + ) + +set(CLANG_FORMAT_PLUSPLUS_LIB_DEPS + clangBasic + clangFormat + clangRewrite + clangToolingCore + ) + +clang_target_link_libraries(clang-format++ + PRIVATE + ${CLANG_FORMAT_PLUSPLUS_LIB_DEPS} + ) Index: clang/tools/clang-format++/ClangFormatPlusPlus.cpp =================================================================== --- /dev/null +++ clang/tools/clang-format++/ClangFormatPlusPlus.cpp @@ -0,0 +1,574 @@ +//===-- clang-format/ClangFormatPlusPlus.cpp - Clang FormatPlusPlus tool +//------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements a variant of the clang-format tool but one +/// that can actually modify the code +/// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Format/Format.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Process.h" + +using namespace llvm; +using clang::tooling::Replacements; + +static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); + +// Mark all our options with this category, everything else (except for -version +// and -help) will be hidden. +static cl::OptionCategory ClangFormatPlusPlusCategory("Clang-modify options"); + +static cl::list + Offsets("offset", + cl::desc("Format a range starting at this byte offset.\n" + "Multiple ranges can be formatted by specifying\n" + "several -offset and -length pairs.\n" + "Can only be used with one input file."), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::list + Lengths("length", + cl::desc("Format a range of this length (in bytes).\n" + "Multiple ranges can be formatted by specifying\n" + "several -offset and -length pairs.\n" + "When only a single -offset is specified without\n" + "-length, clang-format will format up to the end\n" + "of the file.\n" + "Can only be used with one input file."), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::list + LineRanges("lines", + cl::desc(": - format a range of\n" + "lines (both 1-based).\n" + "Multiple ranges can be formatted by specifying\n" + "several -lines arguments.\n" + "Can't be used with -offset and -length.\n" + "Can only be used with one input file."), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::opt + Style("style", cl::desc(clang::format::StyleOptionHelpDescription), + cl::init(clang::format::DefaultFormatStyle), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::opt + FallbackStyle("fallback-style", + cl::desc("The name of the predefined style used as a\n" + "fallback in case clang-format is invoked with\n" + "-style=file, but can not find the .clang-format\n" + "file to use.\n" + "Use -fallback-style=none to skip formatting."), + cl::init(clang::format::DefaultFallbackStyle), + cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt AssumeFileName( + "assume-filename", + cl::desc("Override filename used to determine the language.\n" + "When reading from stdin, clang-format assumes this\n" + "filename to determine the language."), + cl::init(""), cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt Inplace("i", + cl::desc("Inplace edit s, if specified."), + cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt OutputXML("output-replacements-xml", + cl::desc("Output replacements as XML."), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::opt + DumpConfig("dump-config", + cl::desc("Dump configuration options to stdout and exit.\n" + "Can be used with -style option."), + cl::cat(ClangFormatPlusPlusCategory)); +static cl::opt + Cursor("cursor", + cl::desc("The position of the cursor when invoking\n" + "clang-format from an editor integration"), + cl::init(0), cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt SortIncludes( + "sort-includes", + cl::desc("If set, overrides the include sorting behavior determined by the " + "SortIncludes style flag"), + cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt ConstPlacement( + "const-placement", + cl::desc("If set, overrides the const style behavior determined by the " + "ConstPlacement style flag"), + cl::init(""), cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt + Verbose("verbose", cl::desc("If set, shows the list of processed files"), + cl::cat(ClangFormatPlusPlusCategory)); + +// Use --dry-run to match other LLVM tools when you mean do it but don't +// actually do it +static cl::opt + DryRun("dry-run", + cl::desc("If set, do not actually make the formatting changes"), + cl::cat(ClangFormatPlusPlusCategory)); + +// Use -n as a common command as an alias for --dry-run. (git and make use -n) +static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"), + cl::cat(ClangFormatPlusPlusCategory), + cl::aliasopt(DryRun), cl::NotHidden); + +// Emulate being able to turn on/off the warning. +static cl::opt + WarnFormat("Wclang-format-violations", + cl::desc("Warnings about individual formatting changes needed. " + "Used only with --dry-run or -n"), + cl::init(true), cl::cat(ClangFormatPlusPlusCategory), + cl::Hidden); + +static cl::opt + NoWarnFormat("Wno-clang-format-violations", + cl::desc("Do not warn about individual formatting changes " + "needed. Used only with --dry-run or -n"), + cl::init(false), cl::cat(ClangFormatPlusPlusCategory), + cl::Hidden); + +static cl::opt ErrorLimit( + "ferror-limit", + cl::desc("Set the maximum number of clang-format errors to emit before " + "stopping (0 = no limit). Used only with --dry-run or -n"), + cl::init(0), cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt + WarningsAsErrors("Werror", + cl::desc("If set, changes formatting warnings to errors"), + cl::cat(ClangFormatPlusPlusCategory)); + +namespace { +enum class WNoError { Unknown }; +} + +static cl::bits WNoErrorList( + "Wno-error", + cl::desc("If set don't error out on the specified warning type."), + cl::values( + clEnumValN(WNoError::Unknown, "unknown", + "If set, unknown format options are only warned about.\n" + "This can be used to enable formatting, even if the\n" + "configuration contains unknown (newer) options.\n" + "Use with caution, as this might lead to dramatically\n" + "differing format depending on an option being\n" + "supported or not.")), + cl::cat(ClangFormatPlusPlusCategory)); + +static cl::opt + ShowColors("fcolor-diagnostics", + cl::desc("If set, and on a color-capable terminal controls " + "whether or not to print diagnostics in color"), + cl::init(true), cl::cat(ClangFormatPlusPlusCategory), + cl::Hidden); + +static cl::opt + NoShowColors("fno-color-diagnostics", + cl::desc("If set, and on a color-capable terminal controls " + "whether or not to print diagnostics in color"), + cl::init(false), cl::cat(ClangFormatPlusPlusCategory), + cl::Hidden); + +static cl::list FileNames(cl::Positional, cl::desc("[ ...]"), + cl::cat(ClangFormatPlusPlusCategory)); + +namespace clang { +namespace format { + +static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source, + SourceManager &Sources, FileManager &Files, + llvm::vfs::InMemoryFileSystem *MemFS) { + MemFS->addFileNoOwn(FileName, 0, Source); + auto File = Files.getOptionalFileRef(FileName); + assert(File && "File not added to MemFS?"); + return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User); +} + +// Parses : input to a pair of line numbers. +// Returns true on error. +static bool parseLineRange(StringRef Input, unsigned &FromLine, + unsigned &ToLine) { + std::pair LineRange = Input.split(':'); + return LineRange.first.getAsInteger(0, FromLine) || + LineRange.second.getAsInteger(0, ToLine); +} + +static bool fillRanges(MemoryBuffer *Code, + std::vector &Ranges) { + IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager Sources(Diagnostics, Files); + FileID ID = createInMemoryFile("", *Code, Sources, Files, + InMemoryFileSystem.get()); + if (!LineRanges.empty()) { + if (!Offsets.empty() || !Lengths.empty()) { + errs() << "error: cannot use -lines with -offset/-length\n"; + return true; + } + + for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { + unsigned FromLine, ToLine; + if (parseLineRange(LineRanges[i], FromLine, ToLine)) { + errs() << "error: invalid : pair\n"; + return true; + } + if (FromLine > ToLine) { + errs() << "error: start line should be less than end line\n"; + return true; + } + SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); + SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); + if (Start.isInvalid() || End.isInvalid()) + return true; + unsigned Offset = Sources.getFileOffset(Start); + unsigned Length = Sources.getFileOffset(End) - Offset; + Ranges.push_back(tooling::Range(Offset, Length)); + } + return false; + } + + if (Offsets.empty()) + Offsets.push_back(0); + if (Offsets.size() != Lengths.size() && + !(Offsets.size() == 1 && Lengths.empty())) { + errs() << "error: number of -offset and -length arguments must match.\n"; + return true; + } + for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { + if (Offsets[i] >= Code->getBufferSize()) { + errs() << "error: offset " << Offsets[i] << " is outside the file\n"; + return true; + } + SourceLocation Start = + Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); + SourceLocation End; + if (i < Lengths.size()) { + if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { + errs() << "error: invalid length " << Lengths[i] + << ", offset + length (" << Offsets[i] + Lengths[i] + << ") is outside the file.\n"; + return true; + } + End = Start.getLocWithOffset(Lengths[i]); + } else { + End = Sources.getLocForEndOfFile(ID); + } + unsigned Offset = Sources.getFileOffset(Start); + unsigned Length = Sources.getFileOffset(End) - Offset; + Ranges.push_back(tooling::Range(Offset, Length)); + } + return false; +} + +static void outputReplacementXML(StringRef Text) { + // FIXME: When we sort includes, we need to make sure the stream is correct + // utf-8. + size_t From = 0; + size_t Index; + while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { + outs() << Text.substr(From, Index - From); + switch (Text[Index]) { + case '\n': + outs() << " "; + break; + case '\r': + outs() << " "; + break; + case '<': + outs() << "<"; + break; + case '&': + outs() << "&"; + break; + default: + llvm_unreachable("Unexpected character encountered!"); + } + From = Index + 1; + } + outs() << Text.substr(From); +} + +static void outputReplacementsXML(const Replacements &Replaces) { + for (const auto &R : Replaces) { + outs() << ""; + outputReplacementXML(R.getReplacementText()); + outs() << "\n"; + } +} + +static bool +emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, + const std::unique_ptr &Code) { + if (Replaces.empty()) + return false; + + unsigned Errors = 0; + if (WarnFormat && !NoWarnFormat) { + llvm::SourceMgr Mgr; + const char *StartBuf = Code->getBufferStart(); + + Mgr.AddNewSourceBuffer( + MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); + for (const auto &R : Replaces) { + SMDiagnostic Diag = Mgr.GetMessage( + SMLoc::getFromPointer(StartBuf + R.getOffset()), + WarningsAsErrors ? SourceMgr::DiagKind::DK_Error + : SourceMgr::DiagKind::DK_Warning, + "code should be clang-formatted [-Wclang-format-violations]"); + + Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); + if (ErrorLimit && ++Errors >= ErrorLimit) + break; + } + } + return WarningsAsErrors; +} + +static void outputXML(const Replacements &Replaces, + const Replacements &FormatChanges, + const FormattingAttemptStatus &Status, + const cl::opt &Cursor, + unsigned CursorPosition) { + outs() << "\n\n"; + if (Cursor.getNumOccurrences() != 0) + outs() << "" << FormatChanges.getShiftedCodePosition(CursorPosition) + << "\n"; + + outputReplacementsXML(Replaces); + outs() << "\n"; +} + +// Returns true on error. +static bool format(StringRef FileName) { + if (!OutputXML && Inplace && FileName == "-") { + errs() << "error: cannot use -i when reading from stdin.\n"; + return false; + } + // On Windows, overwriting a file with an open file mapping doesn't work, + // so read the whole file into memory when formatting in-place. + ErrorOr> CodeOrErr = + !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) + : MemoryBuffer::getFileOrSTDIN(FileName); + if (std::error_code EC = CodeOrErr.getError()) { + errs() << EC.message() << "\n"; + return true; + } + std::unique_ptr Code = std::move(CodeOrErr.get()); + if (Code->getBufferSize() == 0) + return false; // Empty files are formatted correctly. + + StringRef BufStr = Code->getBuffer(); + + const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); + + if (InvalidBOM) { + errs() << "error: encoding with unsupported byte order mark \"" + << InvalidBOM << "\" detected"; + if (FileName != "-") + errs() << " in file '" << FileName << "'"; + errs() << ".\n"; + return true; + } + + std::vector Ranges; + if (fillRanges(Code.get(), Ranges)) + return true; + StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; + if (AssumedFileName.empty()) { + llvm::errs() << "error: empty filenames are not allowed\n"; + return true; + } + + llvm::Expected FormatStyle = + getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), + nullptr, WNoErrorList.isSet(WNoError::Unknown)); + if (!FormatStyle) { + llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; + return true; + } + + StringRef ConstAlignment = ConstPlacement; + + FormatStyle->ConstPlacement = + StringSwitch(ConstAlignment.lower()) + .Cases("right", "east", FormatStyle::CS_East) + .Cases("left", "west", FormatStyle::CS_West) + .Default(FormatStyle->ConstPlacement); + + if (SortIncludes.getNumOccurrences() != 0) { + if (SortIncludes) + FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; + else + FormatStyle->SortIncludes = FormatStyle::SI_Never; + } + unsigned CursorPosition = Cursor; + Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, + AssumedFileName, &CursorPosition); + + // To format JSON insert a variable to trick the code into thinking its + // JavaScript. + if (FormatStyle->isJson()) { + auto Err = Replaces.add(tooling::Replacement( + tooling::Replacement(AssumedFileName, 0, 0, "x = "))); + if (Err) { + llvm::errs() << "Bad Json variable insertion\n"; + } + } + + auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return true; + } + // Get new affected ranges after sorting `#includes`. + Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); + FormattingAttemptStatus Status; + Replacements FormatChanges = + reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); + Replaces = Replaces.merge(FormatChanges); + if (OutputXML || DryRun) { + if (DryRun) { + return emitReplacementWarnings(Replaces, AssumedFileName, Code); + } else { + outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); + } + } else { + IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager Sources(Diagnostics, Files); + FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files, + InMemoryFileSystem.get()); + Rewriter Rewrite(Sources, LangOptions()); + tooling::applyAllReplacements(Replaces, Rewrite); + if (Inplace) { + if (Rewrite.overwriteChangedFiles()) + return true; + } else { + if (Cursor.getNumOccurrences() != 0) { + outs() << "{ \"Cursor\": " + << FormatChanges.getShiftedCodePosition(CursorPosition) + << ", \"IncompleteFormat\": " + << (Status.FormatComplete ? "false" : "true"); + if (!Status.FormatComplete) + outs() << ", \"Line\": " << Status.Line; + outs() << " }\n"; + } + Rewrite.getEditBuffer(ID).write(outs()); + } + } + return false; +} + +} // namespace format +} // namespace clang + +static void PrintVersion(raw_ostream &OS) { + OS << clang::getClangToolFullVersion("clang-format") << '\n'; +} + +// Dump the configuration. +static int dumpConfig() { + StringRef FileName; + std::unique_ptr Code; + if (FileNames.empty()) { + // We can't read the code to detect the language if there's no + // file name, so leave Code empty here. + FileName = AssumeFileName; + } else { + // Read in the code in case the filename alone isn't enough to + // detect the language. + ErrorOr> CodeOrErr = + MemoryBuffer::getFileOrSTDIN(FileNames[0]); + if (std::error_code EC = CodeOrErr.getError()) { + llvm::errs() << EC.message() << "\n"; + return 1; + } + FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0]; + Code = std::move(CodeOrErr.get()); + } + llvm::Expected FormatStyle = + clang::format::getStyle(Style, FileName, FallbackStyle, + Code ? Code->getBuffer() : ""); + if (!FormatStyle) { + llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; + return 1; + } + std::string Config = clang::format::configurationAsText(*FormatStyle); + outs() << Config << "\n"; + return 0; +} + +int main(int argc, const char **argv) { + llvm::InitLLVM X(argc, argv); + + cl::HideUnrelatedOptions(ClangFormatPlusPlusCategory); + + cl::SetVersionPrinter(PrintVersion); + cl::ParseCommandLineOptions( + argc, argv, + "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " + "code.\n\n" + "If no arguments are specified, it formats the code from standard input\n" + "and writes the result to the standard output.\n" + "If s are given, it reformats the files. If -i is specified\n" + "together with s, the files are edited in-place. Otherwise, the\n" + "result is written to the standard output.\n"); + + if (Help) { + cl::PrintHelpMessage(); + return 0; + } + + if (DumpConfig) { + return dumpConfig(); + } + + bool Error = false; + if (FileNames.empty()) { + Error = clang::format::format("-"); + return Error ? 1 : 0; + } + if (FileNames.size() != 1 && + (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { + errs() << "error: -offset, -length and -lines can only be used for " + "single file.\n"; + return 1; + } + for (const auto &FileName : FileNames) { + if (Verbose) + errs() << "Formatting " << FileName << "\n"; + Error |= clang::format::format(FileName); + } + return Error ? 1 : 0; +} Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -39,7 +39,7 @@ std::vector Ranges(1, tooling::Range(0, Code.size())); FormattingAttemptStatus Status; tooling::Replacements Replaces = - reformat(Style, Code, Ranges, "", &Status); + reformat(Style, Code, Ranges, "", &Status, true); if (CheckComplete != SC_DoNotCheck) { bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete; EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete) @@ -18202,6 +18202,13 @@ CHECK_PARSE("ContinuationIndentWidth: 11", ContinuationIndentWidth, 11u); CHECK_PARSE("CommentPragmas: '// abc$'", CommentPragmas, "// abc$"); + Style.ConstPlacement = FormatStyle::CS_West; + CHECK_PARSE("ConstPlacement: Leave", ConstPlacement, FormatStyle::CS_Leave); + CHECK_PARSE("ConstPlacement: East", ConstPlacement, FormatStyle::CS_East); + CHECK_PARSE("ConstPlacement: West", ConstPlacement, FormatStyle::CS_West); + CHECK_PARSE("ConstPlacement: Right", ConstPlacement, FormatStyle::CS_East); + CHECK_PARSE("ConstPlacement: Left", ConstPlacement, FormatStyle::CS_West); + Style.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; CHECK_PARSE("AlignConsecutiveAssignments: None", AlignConsecutiveAssignments, FormatStyle::ACS_None); @@ -22146,6 +22153,330 @@ "}"; EXPECT_EQ(Code, format(Code, Style)); } + +TEST_F(FormatTest, EastWestConst) { + FormatStyle Style = getLLVMStyle(); + + // keep the const style unaltered + verifyFormat("const int a;", Style); + verifyFormat("const int *a;", Style); + verifyFormat("const int &a;", Style); + verifyFormat("const int &&a;", Style); + verifyFormat("int const b;", Style); + verifyFormat("int const *b;", Style); + verifyFormat("int const &b;", Style); + verifyFormat("int const &&b;", Style); + verifyFormat("int const *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("const Foo a;", Style); + verifyFormat("const Foo *a;", Style); + verifyFormat("const Foo &a;", Style); + verifyFormat("const Foo &&a;", Style); + verifyFormat("Foo const b;", Style); + verifyFormat("Foo const *b;", Style); + verifyFormat("Foo const &b;", Style); + verifyFormat("Foo const &&b;", Style); + verifyFormat("Foo const *b const;", Style); + + verifyFormat("LLVM_NODISCARD const int &Foo();", Style); + verifyFormat("LLVM_NODISCARD int const &Foo();", Style); + + verifyFormat("volatile const int *restrict;", Style); + verifyFormat("const volatile int *restrict;", Style); + verifyFormat("const int volatile *restrict;", Style); +} + +TEST_F(FormatTest, EastConst) { + FormatStyle Style = getLLVMStyle(); + Style.ConstPlacement = FormatStyle::CS_East; + + verifyFormat("int const a;", Style); + verifyFormat("int const *a;", Style); + verifyFormat("int const &a;", Style); + verifyFormat("int const &&a;", Style); + verifyFormat("int const b;", Style); + verifyFormat("int const *b;", Style); + verifyFormat("int const &b;", Style); + verifyFormat("int const &&b;", Style); + verifyFormat("int const *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("Foo const a;", Style); + verifyFormat("Foo const *a;", Style); + verifyFormat("Foo const &a;", Style); + verifyFormat("Foo const &&a;", Style); + verifyFormat("Foo const b;", Style); + verifyFormat("Foo const *b;", Style); + verifyFormat("Foo const &b;", Style); + verifyFormat("Foo const &&b;", Style); + verifyFormat("Foo const *b const;", Style); + verifyFormat("Foo *const b;", Style); + verifyFormat("Foo const *const b;", Style); + verifyFormat("auto const v = get_value();", Style); + verifyFormat("long long const &a;", Style); + verifyFormat("unsigned char const *a;", Style); + verifyFormat("int main(int const argc, char const *const *const argv)", + Style); + + verifyFormat("LLVM_NODISCARD int const &Foo();", Style); + verifyFormat("SourceRange getSourceRange() const override LLVM_READONLY", + Style); + verifyFormat("void foo() const override;", Style); + verifyFormat("void foo() const override LLVM_READONLY;", Style); + verifyFormat("void foo() const final;", Style); + verifyFormat("void foo() const final LLVM_READONLY;", Style); + verifyFormat("void foo() const LLVM_READONLY;", Style); + + verifyFormat( + "template explicit Action(Action const &action);", + Style); + verifyFormat( + "template explicit Action(Action const &action);", + "template explicit Action(const Action& action);", + Style); + verifyFormat( + "template explicit Action(Action const &action);", + "template \nexplicit Action(const Action& action);", + Style); + + verifyFormat("int const a;", "const int a;", Style); + verifyFormat("int const *a;", "const int *a;", Style); + verifyFormat("int const &a;", "const int &a;", Style); + verifyFormat("foo(int const &a)", "foo(const int &a)", Style); + verifyFormat("unsigned char *a;", "unsigned char *a;", Style); + verifyFormat("unsigned char const *a;", "const unsigned char *a;", Style); + verifyFormat("vector args1", + "vector args1", Style); + verifyFormat("unsigned int const &get_nu() const", + "const unsigned int &get_nu() const", Style); + verifyFormat("Foo const &a", "const Foo &a", Style); + verifyFormat("Foo::iterator const &a", "const Foo::iterator &a", + Style); + + verifyFormat("Foo(int a, " + "unsigned b, // c-style args\n" + " Bar const &c);", + "Foo(int a, " + "unsigned b, // c-style args\n" + " const Bar &c);", + Style); + + verifyFormat("volatile int const;", "volatile const int;", Style); + verifyFormat("volatile int const;", "const volatile int;", Style); + verifyFormat("int volatile const;", "const int volatile;", Style); + verifyFormat("volatile int const *restrict;", "volatile const int *restrict;", + Style); + verifyFormat("volatile int const *restrict;", "const volatile int *restrict;", + Style); + verifyFormat("int volatile const *restrict;", "const int volatile *restrict;", + Style); + + verifyFormat("static int const bat;", "static const int bat;", Style); + verifyFormat("static int const bat;", "static int const bat;", Style); + + verifyFormat("int const Foo::bat = 0;", "const int Foo::bat = 0;", + Style); + verifyFormat("int const Foo::bat = 0;", "int const Foo::bat = 0;", + Style); + verifyFormat("void fn(Foo const &i);", "void fn(const Foo &i);", Style); + verifyFormat("int const Foo::fn() {", "int const Foo::fn() {", + Style); + verifyFormat("Foo> const *p;", "const Foo> *p;", Style); + verifyFormat( + "Foo> const *p = const_cast> const *>(&ffi);", + "const Foo> *p = const_cast> *>(&ffi);", + Style); + + verifyFormat("void fn(Foo const &i);", "void fn(const Foo &i);", Style); + verifyFormat("void fns(ns::S const &s);", "void fns(const ns::S &s);", Style); + verifyFormat("void fn(ns::Foo const &i);", "void fn(const ns::Foo &i);", + Style); + verifyFormat("void fns(ns::ns2::S const &s);", + "void fns(const ns::ns2::S &s);", Style); + verifyFormat("void fn(ns::Foo> const &i);", + "void fn(const ns::Foo> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo> const &i);", + "void fn(const ns::ns2::Foo> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo> const &i);", + "void fn(const ns::ns2::Foo> &i);", Style); + + verifyFormat("LocalScope const *Scope = nullptr;", + "const LocalScope* Scope = nullptr;", Style); + verifyFormat("struct DOTGraphTraits", + "struct DOTGraphTraits", Style); + + verifyFormat( + "bool tools::addXRayRuntime(ToolChain const &TC, ArgList const &Args) {", + "bool tools::addXRayRuntime(const ToolChain&TC, const ArgList &Args) {", + Style); + verifyFormat("Foo const> P;", "Foo> P;", Style); + verifyFormat("Foo const> P;\n", "Foo> P;\n", Style); + verifyFormat("Foo const> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("auto const i = 0;", "const auto i = 0;", Style); + verifyFormat("auto const &ir = i;", "const auto &ir = i;", Style); + verifyFormat("auto const *ip = &i;", "const auto *ip = &i;", Style); + + verifyFormat("Foo const> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Bar const> P;\n#if 0\n#else\n#endif", + "Bar const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Baz const> P;\n#if 0\n#else\n#endif", + "Baz> P;\n#if 0\n#else\n#endif", Style); + + // verifyFormat("#if 0\nBoo const> P;\n#else\n#endif", + // "#if 0\nBoo> P;\n#else\n#endif", Style); + + verifyFormat("int const P;\n#if 0\n#else\n#endif", + "const int P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("unsigned long const a;", "const unsigned long a;", Style); + verifyFormat("unsigned long long const a;", "const unsigned long long a;", + Style); + + // don't adjust macros + verifyFormat("const INTPTR a;", "const INTPTR a;", Style); +} + +TEST_F(FormatTest, WestConst) { + FormatStyle Style = getLLVMStyle(); + Style.ConstPlacement = FormatStyle::CS_West; + + verifyFormat("const int a;", Style); + verifyFormat("const int *a;", Style); + verifyFormat("const int &a;", Style); + verifyFormat("const int &&a;", Style); + verifyFormat("const int b;", Style); + verifyFormat("const int *b;", Style); + verifyFormat("const int &b;", Style); + verifyFormat("const int &&b;", Style); + verifyFormat("const int *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("const Foo a;", Style); + verifyFormat("const Foo *a;", Style); + verifyFormat("const Foo &a;", Style); + verifyFormat("const Foo &&a;", Style); + verifyFormat("const Foo b;", Style); + verifyFormat("const Foo *b;", Style); + verifyFormat("const Foo &b;", Style); + verifyFormat("const Foo &&b;", Style); + verifyFormat("const Foo *b const;", Style); + verifyFormat("Foo *const b;", Style); + verifyFormat("const Foo *const b;", Style); + + verifyFormat("LLVM_NODISCARD const int &Foo();", Style); + + verifyFormat("const char a[];", Style); + verifyFormat("const auto v = get_value();", Style); + verifyFormat("const long long &a;", Style); + verifyFormat("const unsigned char *a;", Style); + verifyFormat("const unsigned char *a;", "unsigned char const *a;", Style); + verifyFormat("const Foo &a", "Foo const &a", Style); + verifyFormat("const Foo::iterator &a", "Foo::iterator const &a", + Style); + + verifyFormat("const int a;", "int const a;", Style); + verifyFormat("const int *a;", "int const *a;", Style); + verifyFormat("const int &a;", "int const &a;", Style); + verifyFormat("foo(const int &a)", "foo(int const &a)", Style); + verifyFormat("unsigned char *a;", "unsigned char *a;", Style); + verifyFormat("const unsigned int &get_nu() const", + "unsigned int const &get_nu() const", Style); + + verifyFormat("const volatile int;", "volatile const int;", Style); + verifyFormat("const volatile int;", "const volatile int;", Style); + verifyFormat("const int volatile;", "const int volatile;", Style); + + verifyFormat("const volatile int *restrict;", "volatile const int *restrict;", + Style); + verifyFormat("const volatile int *restrict;", "const volatile int *restrict;", + Style); + verifyFormat("const int volatile *restrict;", "const int volatile *restrict;", + Style); + + verifyFormat("SourceRange getSourceRange() const override LLVM_READONLY;", + Style); + + verifyFormat("void foo() const override;", Style); + verifyFormat("void foo() const override LLVM_READONLY;", Style); + verifyFormat("void foo() const final;", Style); + verifyFormat("void foo() const final LLVM_READONLY;", Style); + verifyFormat("void foo() const LLVM_READONLY;", Style); + + verifyFormat( + "template explicit Action(const Action &action);", + Style); + verifyFormat( + "template explicit Action(const Action &action);", + "template explicit Action(Action const &action);", + Style); + + verifyFormat("static const int bat;", "static const int bat;", Style); + verifyFormat("static const int bat;", "static int const bat;", Style); + + verifyFormat("static const int Foo::bat = 0;", + "static const int Foo::bat = 0;", Style); + verifyFormat("static const int Foo::bat = 0;", + "static int const Foo::bat = 0;", Style); + + verifyFormat("void fn(const Foo &i);"); + + verifyFormat("const int Foo::bat = 0;", "const int Foo::bat = 0;", + Style); + verifyFormat("const int Foo::bat = 0;", "int const Foo::bat = 0;", + Style); + verifyFormat("void fn(const Foo &i);", "void fn( Foo const &i);", + Style); + verifyFormat("const int Foo::fn() {", "int const Foo::fn() {", + Style); + verifyFormat("const Foo> *p;", "Foo> const *p;", Style); + verifyFormat( + "const Foo> *p = const_cast> *>(&ffi);", + "const Foo> *p = const_cast> const *>(&ffi);", + Style); + + verifyFormat("void fn(const Foo &i);", "void fn(Foo const &i);", Style); + verifyFormat("void fns(const ns::S &s);", "void fns(ns::S const &s);", Style); + verifyFormat("void fn(const ns::Foo &i);", "void fn(ns::Foo const &i);", + Style); + verifyFormat("void fns(const ns::ns2::S &s);", + "void fns(ns::ns2::S const &s);", Style); + verifyFormat("void fn(const ns::Foo> &i);", + "void fn(ns::Foo> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo> &i);", + "void fn(ns::ns2::Foo> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo> &i);", + "void fn(ns::ns2::Foo> const &i);", Style); + + verifyFormat("const auto i = 0;", "auto const i = 0;", Style); + verifyFormat("const auto &ir = i;", "auto const &ir = i;", Style); + verifyFormat("const auto *ip = &i;", "auto const *ip = &i;", Style); + + verifyFormat("Foo> P;\n#if 0\n#else\n#endif", + "Foo const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Foo> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("const int P;\n#if 0\n#else\n#endif", + "int const P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("const unsigned long a;", "unsigned long const a;", Style); + verifyFormat("const unsigned long long a;", "unsigned long long const a;", + Style); + + verifyFormat("const long long unsigned a;", "long const long unsigned a;", + Style); + + // don't adjust macros + verifyFormat("INTPTR const a;", "INTPTR const a;", Style); +} + } // namespace } // namespace format } // namespace clang