MyDeveloperDay updated this revision to Diff 372292. MyDeveloperDay added a comment.
QualifierAlignmentFixer need to process all the Left/Right passes internally before return the fixes on the original code. So now QualifierAlignmentFixer has its own inner set of passes which will be processed, and then "no op" fixes (replacements which make no change to the original code are removed (this was a problem before, as multiple passes causes changes that changed one way then changed it back "static const int a;" -> "const static int a;" -> "static const a;" Move QualifierAlignmentFixer tests out to its own unit test file so Ultimately I can add more "Unit tests" on the functions Still WIP but getting much closer. CHANGES SINCE LAST ACTION https://reviews.llvm.org/D69764/new/ https://reviews.llvm.org/D69764 Files: clang/docs/ClangFormatStyleOptions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Format/Format.h clang/lib/Format/CMakeLists.txt clang/lib/Format/Format.cpp clang/lib/Format/QualifierAlignmentFixer.cpp clang/lib/Format/QualifierAlignmentFixer.h clang/tools/clang-format/ClangFormat.cpp clang/unittests/Format/CMakeLists.txt clang/unittests/Format/FormatTest.cpp clang/unittests/Format/QualifierFixerTest.cpp
Index: clang/unittests/Format/QualifierFixerTest.cpp =================================================================== --- /dev/null +++ clang/unittests/Format/QualifierFixerTest.cpp @@ -0,0 +1,643 @@ +//===- unittest/Format/QualifierFixerTest.cpp - Formatting unit tests -----===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Format/Format.h" + +#include "FormatTestUtils.h" +#include "TestLexer.h" +#include "gtest/gtest.h" + +#include "../../lib/Format/QualifierAlignmentFixer.h" + +#define DEBUG_TYPE "format-qualifier-fixer-test" + +using testing::ScopedTrace; + +namespace clang { +namespace format { +namespace { + +#define CHECK_PARSE(TEXT, FIELD, VALUE) \ + EXPECT_NE(VALUE, Style.FIELD) << "Initial value already the same!"; \ + EXPECT_EQ(0, parseConfiguration(TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" + +#define FAIL_PARSE(TEXT, FIELD, VALUE) \ + EXPECT_NE(0, parseConfiguration(TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" + +class QualifierFixerTest : public ::testing::Test { +protected: + enum StatusCheck { SC_ExpectComplete, SC_ExpectIncomplete, SC_DoNotCheck }; + + std::string format(llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle(), + StatusCheck CheckComplete = SC_ExpectComplete) { + LLVM_DEBUG(llvm::errs() << "---\n"); + LLVM_DEBUG(llvm::errs() << Code << "\n\n"); + std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); + FormattingAttemptStatus Status; + tooling::Replacements Replaces = + reformat(Style, Code, Ranges, "<stdin>", &Status); + if (CheckComplete != SC_DoNotCheck) { + bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete; + EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete) + << Code << "\n\n"; + } + ReplacementCount = Replaces.size(); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast<bool>(Result)); + LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); + return *Result; + } + + FormatStyle getStyleWithColumns(FormatStyle Style, unsigned ColumnLimit) { + Style.ColumnLimit = ColumnLimit; + return Style; + } + + FormatStyle getLLVMStyleWithColumns(unsigned ColumnLimit) { + return getStyleWithColumns(getLLVMStyle(), ColumnLimit); + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Expected, + llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + ScopedTrace t(File, Line, ::testing::Message() << Code.str()); + EXPECT_EQ(Expected.str(), format(Expected, Style)) + << "Expected code is not stable"; + EXPECT_EQ(Expected.str(), format(Code, Style)); + if (Style.Language == FormatStyle::LK_Cpp) { + // Objective-C++ is a superset of C++, so everything checked for C++ + // needs to be checked for Objective-C++ as well. + FormatStyle ObjCStyle = Style; + ObjCStyle.Language = FormatStyle::LK_ObjC; + EXPECT_EQ(Expected.str(), format(test::messUp(Code), ObjCStyle)); + } + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + _verifyFormat(File, Line, Code, test::messUp(Code), Style); + } + + void _verifyIncompleteFormat(const char *File, int Line, llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + ScopedTrace t(File, Line, ::testing::Message() << Code.str()); + EXPECT_EQ(Code.str(), + format(test::messUp(Code), Style, SC_ExpectIncomplete)); + } + + void _verifyIndependentOfContext(const char *File, int Line, + llvm::StringRef Text, + const FormatStyle &Style = getLLVMStyle()) { + _verifyFormat(File, Line, Text, Style); + _verifyFormat(File, Line, llvm::Twine("void f() { " + Text + " }").str(), + Style); + } + + /// \brief Verify that clang-format does not crash on the given input. + void verifyNoCrash(llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + format(Code, Style, SC_DoNotCheck); + } + + int ReplacementCount; +}; + +#define verifyFormat(...) _verifyFormat(__FILE__, __LINE__, __VA_ARGS__) + +}; // namespace + +TEST_F(QualifierFixerTest, RotateTokens) { + // TODO add test + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("const"), + tok::kw_const); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("volatile"), + tok::kw_volatile); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("inline"), + tok::kw_inline); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("static"), + tok::kw_static); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("restrict"), + tok::kw_restrict); +} + +TEST_F(QualifierFixerTest, FailQualifierInvalidConfiguration) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile, apples, type]", + QualifierOrder, + std::vector<std::string>({"const", "volatile", "apples", "type"})); +} + +TEST_F(QualifierFixerTest, FailQualifierDuplicateConfiguration) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile, const, type]", + QualifierOrder, + std::vector<std::string>({"const", "volatile", "const", "type"})); +} + +TEST_F(QualifierFixerTest, FailQualifierMissingType) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile ]", + QualifierOrder, + std::vector<std::string>({ + "const", + "volatile", + })); +} + +TEST_F(QualifierFixerTest, FailQualifierEmptyOrder) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\nQualifierOrder: []", QualifierOrder, + std::vector<std::string>({})); +} + +TEST_F(QualifierFixerTest, FailQualifierMissingOrder) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom", QualifierOrder, + std::vector<std::string>()); +} + +TEST_F(QualifierFixerTest, QualifierLeft) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + CHECK_PARSE("QualifierAlignment: Left", QualifierOrder, + std::vector<std::string>({"const", "volatile", "type"})); +} + +TEST_F(QualifierFixerTest, QualifierRight) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + CHECK_PARSE("QualifierAlignment: Right", QualifierOrder, + std::vector<std::string>({"type", "const", "volatile"})); +} + +TEST_F(QualifierFixerTest, QualifiersCustomOrder) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("inline"); + Style.QualifierOrder.push_back("constexpr"); + Style.QualifierOrder.push_back("static"); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("volatile"); + Style.QualifierOrder.push_back("type"); + + verifyFormat("const volatile int a;", "const volatile int a;", Style); + verifyFormat("const volatile int a;", "volatile const int a;", Style); + verifyFormat("const volatile int a;", "int const volatile a;", Style); + verifyFormat("const volatile int a;", "int volatile const a;", Style); + verifyFormat("const volatile int a;", "const int volatile a;", Style); + + verifyFormat("static const volatile int a;", "const static int volatile a;", + Style); + verifyFormat("inline static const volatile int a;", + "const static inline int volatile a;", Style); + + verifyFormat("constexpr static int a;", "static constexpr int a;", Style); + verifyFormat("constexpr static int A;", "static constexpr int A;", Style); + verifyFormat("constexpr static int Bar;", "static constexpr int Bar;", Style); + verifyFormat("constexpr static LPINT Bar;", "static constexpr LPINT Bar;", + Style); + verifyFormat("const const int a;", "const int const a;", Style); +} + +TEST_F(QualifierFixerTest, LeftRightQualifier) { + 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(QualifierFixerTest, RightQualifier) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("type"); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("volatile"); + + 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 <typename Func> explicit Action(Action<Func> const &action);", + Style); + verifyFormat( + "template <typename Func> explicit Action(Action<Func> const &action);", + "template <typename Func> explicit Action(const Action<Func>& action);", + Style); + verifyFormat( + "template <typename Func> explicit Action(Action<Func> const &action);", + "template <typename Func>\nexplicit Action(const Action<Func>& 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<int, int const, int &, int const &> args1", + "vector<int, const int, int &, const int &> args1", Style); + verifyFormat("unsigned int const &get_nu() const", + "const unsigned int &get_nu() const", Style); + verifyFormat("Foo<int> const &a", "const Foo<int> &a", Style); + verifyFormat("Foo<int>::iterator const &a", "const Foo<int>::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("int const volatile;", "volatile const int;", Style); + verifyFormat("int const volatile;", "const volatile int;", Style); + verifyFormat("int const volatile;", "const int volatile;", Style); + verifyFormat("int const volatile *restrict;", "volatile const int *restrict;", + Style); + verifyFormat("int const volatile *restrict;", "const volatile int *restrict;", + Style); + verifyFormat("int const volatile *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<int>::bat = 0;", "const int Foo<int>::bat = 0;", + Style); + verifyFormat("int const Foo<int>::bat = 0;", "int const Foo<int>::bat = 0;", + Style); + verifyFormat("void fn(Foo<T> const &i);", "void fn(const Foo<T> &i);", Style); + verifyFormat("int const Foo<int>::fn() {", "int const Foo<int>::fn() {", + Style); + verifyFormat("Foo<Foo<int>> const *p;", "const Foo<Foo<int>> *p;", Style); + verifyFormat( + "Foo<Foo<int>> const *p = const_cast<Foo<Foo<int>> const *>(&ffi);", + "const Foo<Foo<int>> *p = const_cast<const Foo<Foo<int>> *>(&ffi);", + Style); + + verifyFormat("void fn(Foo<T> const &i);", "void fn(const Foo<T> &i);", Style); + verifyFormat("void fns(ns::S const &s);", "void fns(const ns::S &s);", Style); + verifyFormat("void fn(ns::Foo<T> const &i);", "void fn(const ns::Foo<T> &i);", + Style); + verifyFormat("void fns(ns::ns2::S const &s);", + "void fns(const ns::ns2::S &s);", Style); + verifyFormat("void fn(ns::Foo<Bar<T>> const &i);", + "void fn(const ns::Foo<Bar<T>> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo<Bar<T>> const &i);", + "void fn(const ns::ns2::Foo<Bar<T>> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo<Bar<T, U>> const &i);", + "void fn(const ns::ns2::Foo<Bar<T, U>> &i);", Style); + + verifyFormat("LocalScope const *Scope = nullptr;", + "const LocalScope* Scope = nullptr;", Style); + verifyFormat("struct DOTGraphTraits<Stmt const *>", + "struct DOTGraphTraits<const Stmt *>", Style); + + verifyFormat( + "bool tools::addXRayRuntime(ToolChain const &TC, ArgList const &Args) {", + "bool tools::addXRayRuntime(const ToolChain&TC, const ArgList &Args) {", + Style); + verifyFormat("Foo<Foo<int> const> P;", "Foo<const Foo<int>> P;", Style); + verifyFormat("Foo<Foo<int> const> P;\n", "Foo<const Foo<int>> P;\n", Style); + verifyFormat("Foo<Foo<int> const> P;\n#if 0\n#else\n#endif", + "Foo<const Foo<int>> 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<Foo<int> const> P;\n#if 0\n#else\n#endif", + "Foo<const Foo<int>> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Bar<Bar<int const> const> P;\n#if 0\n#else\n#endif", + "Bar<Bar<const int> const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Baz<Baz<int const> const> P;\n#if 0\n#else\n#endif", + "Baz<const Baz<const int>> P;\n#if 0\n#else\n#endif", Style); + + // verifyFormat("#if 0\nBoo<Boo<int const> const> P;\n#else\n#endif", + // "#if 0\nBoo<const Boo<const int>> 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(QualifierFixerTest, LeftQualifier) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + + 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<int> &a", "Foo<int> const &a", Style); + verifyFormat("const Foo<int>::iterator &a", "Foo<int>::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 volatile int;", "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 volatile int *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 <typename Func> explicit Action(const Action<Func> &action);", + Style); + verifyFormat( + "template <typename Func> explicit Action(const Action<Func> &action);", + "template <typename Func> explicit Action(Action<Func> 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<int>::bat = 0;", + "static const int Foo<int>::bat = 0;", Style); + verifyFormat("static const int Foo<int>::bat = 0;", + "static int const Foo<int>::bat = 0;", Style); + + verifyFormat("void fn(const Foo<T> &i);"); + + verifyFormat("const int Foo<int>::bat = 0;", "const int Foo<int>::bat = 0;", + Style); + verifyFormat("const int Foo<int>::bat = 0;", "int const Foo<int>::bat = 0;", + Style); + verifyFormat("void fn(const Foo<T> &i);", "void fn( Foo<T> const &i);", + Style); + verifyFormat("const int Foo<int>::fn() {", "int const Foo<int>::fn() {", + Style); + verifyFormat("const Foo<Foo<int>> *p;", "Foo<Foo<int>> const *p;", Style); + verifyFormat( + "const Foo<Foo<int>> *p = const_cast<const Foo<Foo<int>> *>(&ffi);", + "const Foo<Foo<int>> *p = const_cast<Foo<Foo<int>> const *>(&ffi);", + Style); + + verifyFormat("void fn(const Foo<T> &i);", "void fn(Foo<T> const &i);", Style); + verifyFormat("void fns(const ns::S &s);", "void fns(ns::S const &s);", Style); + verifyFormat("void fn(const ns::Foo<T> &i);", "void fn(ns::Foo<T> 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<Bar<T>> &i);", + "void fn(ns::Foo<Bar<T>> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo<Bar<T>> &i);", + "void fn(ns::ns2::Foo<Bar<T>> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo<Bar<T, U>> &i);", + "void fn(ns::ns2::Foo<Bar<T, U>> 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<const Foo<int>> P;\n#if 0\n#else\n#endif", + "Foo<Foo<int> const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Foo<Foo<const int>> P;\n#if 0\n#else\n#endif", + "Foo<Foo<int const>> 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); + + verifyFormat("const std::Foo", "const std::Foo", Style); + verifyFormat("const std::Foo<>", "const std::Foo<>", Style); + verifyFormat("const std::Foo < int", "const std::Foo<int", Style); + verifyFormat("const std::Foo<int>", "const std::Foo<int>", Style); + + // don't adjust macros + verifyFormat("INTPTR const a;", "INTPTR const a;", Style); +} + +TEST_F(QualifierFixerTest, ConstVolatileQualifiersOrder) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + + // the Default + EXPECT_EQ(Style.QualifierOrder.size(), 5); + EXPECT_EQ(Style.QualifierOrder[0], "inline"); + EXPECT_EQ(Style.QualifierOrder[1], "static"); + EXPECT_EQ(Style.QualifierOrder[2], "const"); + EXPECT_EQ(Style.QualifierOrder[3], "volatile"); + EXPECT_EQ(Style.QualifierOrder[4], "type"); + + verifyFormat("const volatile int a;", "const volatile int a;", Style); + verifyFormat("const volatile int a;", "volatile const int a;", Style); + verifyFormat("const volatile int a;", "int const volatile a;", Style); + verifyFormat("const volatile int a;", "int volatile const a;", Style); + verifyFormat("const volatile int a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("type"); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("volatile"); + + verifyFormat("int const volatile a;", "const volatile int a;", Style); + verifyFormat("int const volatile a;", "volatile const int a;", Style); + verifyFormat("int const volatile a;", "int const volatile a;", Style); + verifyFormat("int const volatile a;", "int volatile const a;", Style); + verifyFormat("int const volatile a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("volatile"); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("type"); + + verifyFormat("volatile const int a;", "const volatile int a;", Style); + verifyFormat("volatile const int a;", "volatile const int a;", Style); + verifyFormat("volatile const int a;", "int const volatile a;", Style); + verifyFormat("volatile const int a;", "int volatile const a;", Style); + verifyFormat("volatile const int a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("type"); + Style.QualifierOrder.push_back("volatile"); + Style.QualifierOrder.push_back("const"); + + verifyFormat("int volatile const a;", "const volatile int a;", Style); + verifyFormat("int volatile const a;", "volatile const int a;", Style); + verifyFormat("int volatile const a;", "int const volatile a;", Style); + verifyFormat("int volatile const a;", "int volatile const a;", Style); + verifyFormat("int volatile const a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("type"); + Style.QualifierOrder.push_back("volatile"); + Style.QualifierOrder.push_back("const"); + + verifyFormat("int volatile const a;", "const volatile int a;", Style); + verifyFormat("int volatile const a;", "volatile const int a;", Style); + verifyFormat("int volatile const a;", "int const volatile a;", Style); + verifyFormat("int volatile const a;", "int volatile const a;", Style); + verifyFormat("int volatile const a;", "const int volatile a;", Style); +} + +TEST_F(QualifierFixerTest, InlineStatics) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + + // the Default + EXPECT_EQ(Style.QualifierOrder.size(), 5); + EXPECT_EQ(Style.QualifierOrder[0], "inline"); + EXPECT_EQ(Style.QualifierOrder[1], "static"); + EXPECT_EQ(Style.QualifierOrder[2], "const"); + EXPECT_EQ(Style.QualifierOrder[3], "volatile"); + EXPECT_EQ(Style.QualifierOrder[4], "type"); + + verifyFormat("inline static const volatile int a;", + "const inline static volatile int a;", Style); + verifyFormat("inline static const volatile int a;", + "volatile inline static const int a;", Style); + verifyFormat("inline static const volatile int a;", + "int const inline static volatile a;", Style); + verifyFormat("inline static const volatile int a;", + "int volatile inline static const a;", Style); + verifyFormat("inline static const volatile int a;", + "const int inline static volatile a;", Style); +} + +} // namespace format +} // namespace clang Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -18230,6 +18230,10 @@ EXPECT_EQ(0, parseConfiguration(TEXT, &Style).value()); \ EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" +#define FAIL_PARSE(TEXT, FIELD, VALUE) \ + EXPECT_NE(0, parseConfiguration(TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" + TEST_F(FormatTest, ParsesConfigurationBools) { FormatStyle Style = {}; Style.Language = FormatStyle::LK_Cpp; @@ -18325,6 +18329,26 @@ CHECK_PARSE("ContinuationIndentWidth: 11", ContinuationIndentWidth, 11u); CHECK_PARSE("CommentPragmas: '// abc$'", CommentPragmas, "// abc$"); + Style.QualifierAlignment = FormatStyle::QAS_Right; + CHECK_PARSE("QualifierAlignment: Leave", QualifierAlignment, + FormatStyle::QAS_Leave); + CHECK_PARSE("QualifierAlignment: Right", QualifierAlignment, + FormatStyle::QAS_Right); + CHECK_PARSE("QualifierAlignment: Left", QualifierAlignment, + FormatStyle::QAS_Left); + CHECK_PARSE("QualifierAlignment: Custom", QualifierAlignment, + FormatStyle::QAS_Custom); + + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [ const, volatile, type ]", QualifierOrder, + std::vector<std::string>({"const", "volatile", "type"})); + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [const, type]", QualifierOrder, + std::vector<std::string>({"const", "type"})); + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [volatile, type]", QualifierOrder, + std::vector<std::string>({"volatile", "type"})); + Style.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; CHECK_PARSE("AlignConsecutiveAssignments: None", AlignConsecutiveAssignments, FormatStyle::ACS_None); @@ -18992,8 +19016,6 @@ EXPECT_EQ(FormatStyle::LK_Cpp, Style.Language); } -#undef CHECK_PARSE - TEST_F(FormatTest, UsesLanguageForBasedOnStyle) { FormatStyle Style = {}; Style.Language = FormatStyle::LK_JavaScript; @@ -22294,6 +22316,7 @@ "}"; EXPECT_EQ(Code, format(Code, Style)); } + } // namespace } // namespace format } // namespace clang Index: clang/unittests/Format/CMakeLists.txt =================================================================== --- clang/unittests/Format/CMakeLists.txt +++ clang/unittests/Format/CMakeLists.txt @@ -18,6 +18,7 @@ FormatTestTextProto.cpp MacroExpanderTest.cpp NamespaceEndCommentsFixerTest.cpp + QualifierFixerTest.cpp SortImportsTestJS.cpp SortImportsTestJava.cpp SortIncludesTest.cpp Index: clang/tools/clang-format/ClangFormat.cpp =================================================================== --- clang/tools/clang-format/ClangFormat.cpp +++ clang/tools/clang-format/ClangFormat.cpp @@ -19,6 +19,7 @@ #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" @@ -104,6 +105,13 @@ "SortIncludes style flag"), cl::cat(ClangFormatCategory)); +static cl::opt<std::string> QualifierAlignment( + "qualifier-aligment", + cl::desc( + "If set, overrides the cvqualifier style behavior determined by the " + "QualifierAlignment style flag"), + cl::init(""), cl::cat(ClangFormatCategory)); + static cl::opt<bool> Verbose("verbose", cl::desc("If set, shows the list of processed files"), cl::cat(ClangFormatCategory)); @@ -402,6 +410,14 @@ return true; } + StringRef ConstAlignment = QualifierAlignment; + + FormatStyle->QualifierAlignment = + StringSwitch<FormatStyle::QualifierAlignmentStyle>(ConstAlignment.lower()) + .Case("right", FormatStyle::QAS_Right) + .Case("left", FormatStyle::QAS_Left) + .Default(FormatStyle->QualifierAlignment); + if (SortIncludes.getNumOccurrences() != 0) { if (SortIncludes) FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; Index: clang/lib/Format/QualifierAlignmentFixer.h =================================================================== --- /dev/null +++ clang/lib/Format/QualifierAlignmentFixer.h @@ -0,0 +1,74 @@ +//===--- LeftRightQualifierAlignmentFixer.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 LeftRightQualifierAlignmentFixer, a TokenAnalyzer that +/// enforces either east or west const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_FORMAT_LeftRightQualifierAlignmentFixer_H +#define LLVM_CLANG_LIB_FORMAT_LeftRightQualifierAlignmentFixer_H + +#include "TokenAnalyzer.h" + +namespace clang { +namespace format { + +typedef std::function<std::pair<tooling::Replacements, unsigned>( + const Environment &)> + AnalyzerPass; + +class QualifierAlignmentFixer : public TokenAnalyzer { + // Left to Right ordering requires multiple passes + SmallVector<AnalyzerPass, 8> Passes; + StringRef &Code; + ArrayRef<tooling::Range> Ranges; + unsigned FirstStartColumn; + unsigned NextStartColumn; + unsigned LastStartColumn; + StringRef FileName; + SmallVector<tok::TokenKind, 8> QualifierTokens; + +public: + QualifierAlignmentFixer(const Environment &Env, const FormatStyle &Style, + StringRef &Code, ArrayRef<tooling::Range> Ranges, + unsigned FirstStartColumn, unsigned NextStartColumn, + unsigned LastStartColumn, StringRef FileName); + + std::pair<tooling::Replacements, unsigned> + analyze(TokenAnnotator &Annotator, + SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, + FormatTokenLexer &Tokens) override; +}; + +class LeftRightQualifierAlignmentFixer : public TokenAnalyzer { + std::string Qualifier; + FormatStyle::QualifierAlignmentStyle Alignment; + SmallVector<tok::TokenKind, 8> QualifierTokens; + +public: + LeftRightQualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, + const std::string &Qualifier, + const FormatStyle::QualifierAlignmentStyle &Alignment, + const SmallVector<tok::TokenKind, 8> &QualifierTokens); + + std::pair<tooling::Replacements, unsigned> + analyze(TokenAnnotator &Annotator, + SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, + FormatTokenLexer &Tokens) override; + + static tok::TokenKind getTokenFromQualifier(const std::string &Qualifier); +}; + +} // end namespace format +} // end namespace clang + +#endif Index: clang/lib/Format/QualifierAlignmentFixer.cpp =================================================================== --- /dev/null +++ clang/lib/Format/QualifierAlignmentFixer.cpp @@ -0,0 +1,495 @@ +//===--- LeftRightQualifierAlignmentFixer.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 LeftRightQualifierAlignmentFixer, a TokenAnalyzer that +/// enforces either east or west const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#include "QualifierAlignmentFixer.h" +#include "FormatToken.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Regex.h" + +#include <algorithm> + +#define DEBUG_TYPE "format-qualifier-alignment-fixer" + +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 qualifier : " + << 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 insertQualifierAfter(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First, + const std::string &Qualifier) { + FormatToken *Next = First->Next; + if (!Next) + return; + auto Range = CharSourceRange::getCharRange( + Next->getStartOfNonWhitespace(), Next->Next->getStartOfNonWhitespace()); + + std::string NewText = " " + Qualifier + " "; + NewText += Next->TokenText; + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static void insertQualifierBefore(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First, + const std::string &Qualifier) { + auto Range = CharSourceRange::getCharRange( + First->getStartOfNonWhitespace(), First->Next->getStartOfNonWhitespace()); + + std::string NewText = " " + Qualifier + " "; + NewText += First->TokenText; + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +// trim from start +static inline std::string <rim(std::string &s) { + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun<int, int>(std::isspace)))); + return s; +} + +// trim from end +static inline std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun<int, int>(std::isspace))) + .base(), + s.end()); + return s; +} + +// trim from both ends +static inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); } + +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.trim(); + NewText += " "; + } + + // Then move through the other tokens. + auto *Tok = Begin; + while (Tok != End) { + if (!NewText.empty()) { + NewText = trim(NewText); + NewText += " "; + } + + NewText += Tok->TokenText.trim(); + Tok = Tok->Next; + } + + // If we are rotating to the right we move the first token to the back. + if (!Left) { + NewText = trim(NewText); + NewText += " "; + NewText += First->TokenText.trim(); + } + + NewText = trim(NewText); + + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + Last->Tok.getEndLoc()); + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static bool +isQualifierOrType(const FormatToken *Tok, + const SmallVector<tok::TokenKind, 8> &QualifierTokens) { + return Tok && + (Tok->isSimpleTypeSpecifier() || + /* + std::find(QualifierTokens.begin(), + QualifierTokens.end(), Tok->getType()) != QualifierTokens.end()); + */ + + Tok->isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_auto, + tok::kw_static, tok::kw_inline, tok::kw_constexpr, + tok::kw_restrict)); +} + +// 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 * +analyzeRight(const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, tok::TokenKind QualifierType, + const SmallVector<tok::TokenKind, 8> &QualifierTokens) { + // We only need to think about streams that begin with const. + if (!Tok->is(QualifierType)) + 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 *Qual = Tok->Next; + FormatToken *LastQual = Qual; + while (Qual && isQualifierOrType(Qual, QualifierTokens)) { + LastQual = Qual; + Qual = Qual->Next; + } + if (LastQual && Qual != LastQual) { + rotateTokens(SourceMgr, Fixes, Const, LastQual, /*Left=*/false); + Tok = LastQual; + } else if (Tok->startsSequence(QualifierType, tok::identifier, + TT_TemplateOpener)) { + // Read from the TemplateOpener to + // TemplateCloser as in const ArrayRef<int> a; const ArrayRef<int> &a; + FormatToken *EndTemplate = Tok->Next->Next->MatchingParen; + if (EndTemplate) { + // Move to the end of any template class members e.g. + // `Foo<int>::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. + insertQualifierAfter(SourceMgr, Fixes, EndTemplate, Qualifier); + removeToken(SourceMgr, Fixes, Tok); + return Tok; + } + } else if (Tok->startsSequence(QualifierType, 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<T> &&` -> `std::Foo<T> 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<int>::iterator`. + if (Next && Next->startsSequence(TT_TemplateCloser, tok::coloncolon, + tok::identifier)) { + Next = Next->Next->Next; + return Tok; + } + assert(Next && "Missing template opener"); + 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(QualifierType)) { + insertQualifierAfter(SourceMgr, Fixes, Next->Previous, Qualifier); + removeToken(SourceMgr, Fixes, Const); + } + return Next; + } + } + + return Tok; +} + +static FormatToken * +analyzeLeft(const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, tok::TokenKind QualifierType, + const SmallVector<tok::TokenKind, 8> &QualifierTokens) { + // if Tok is an identifier and possibly a macro then don't convert + if (isPossibleMacro(Tok)) + return Tok; + + FormatToken *Qual = Tok; + FormatToken *LastQual = Qual; + while (Qual && isQualifierOrType(Qual, QualifierTokens)) { + LastQual = Qual; + Qual = Qual->Next; + if (Qual && Qual->is(QualifierType)) + break; + } + + if (!Qual) { + return Tok; + } + + if (LastQual && Qual != LastQual && Qual->is(QualifierType)) { + rotateTokens(SourceMgr, Fixes, Tok, Qual, /*Left=*/true); + Tok = Qual->Next; + } else if (Tok->startsSequence(tok::identifier, QualifierType)) { + 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(QualifierType)) { + 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<T> const` -> `const std::Foo<T> &&` + 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<int> a; const ArrayRef<int> &a; + assert(Next->MatchingParen && "Missing template closer"); + Next = Next->MatchingParen->Next; + + // Move to the end of any template class members e.g. + // `Foo<int>::iterator`. + if (Next && Next->startsSequence(tok::coloncolon, tok::identifier)) + Next = Next->Next->Next; + if (Next && Next->is(QualifierType)) { + // Remove the const. + removeToken(SourceMgr, Fixes, Next); + insertQualifierBefore(SourceMgr, Fixes, Tok, Qualifier); + return Next; + } + } + if (Next && Next->Next && + Next->Next->isOneOf(tok::amp, tok::ampamp, tok::star)) { + if (Next->is(QualifierType)) { + // Remove the const. + removeToken(SourceMgr, Fixes, Next); + insertQualifierBefore(SourceMgr, Fixes, Tok, Qualifier); + return Next; + } + } + } + return Tok; +} + +tok::TokenKind LeftRightQualifierAlignmentFixer::getTokenFromQualifier( + const std::string &Qualifier) { + // don't let 'type' be an indentifier steal typeof token + return llvm::StringSwitch<tok::TokenKind>(Qualifier) + .Case("type", tok::kw_typeof) + .Case("const", tok::kw_const) + .Case("volatile", tok::kw_volatile) + .Case("static", tok::kw_static) + .Case("inline", tok::kw_inline) + .Case("constexpr", tok::kw_constexpr) + .Case("restrict", tok::kw_restrict) + .Default(tok::identifier); +} + +LeftRightQualifierAlignmentFixer::LeftRightQualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, + const std::string &Qualifier, + const FormatStyle::QualifierAlignmentStyle &Alignment, + const SmallVector<tok::TokenKind, 8> &QualifierTokens) + : TokenAnalyzer(Env, Style), Qualifier(Qualifier), Alignment(Alignment), + QualifierTokens(QualifierTokens) {} + +std::pair<tooling::Replacements, unsigned> +LeftRightQualifierAlignmentFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, + FormatTokenLexer &Tokens) { + tooling::Replacements Fixes; + const AdditionalKeywords &Keywords = Tokens.getKeywords(); + const SourceManager &SourceMgr = Env.getSourceManager(); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + + tok::TokenKind QualifierToken = getTokenFromQualifier(Qualifier); + assert(QualifierToken != tok::identifier && "Unrecognised Qualifier"); + + 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 (Alignment == FormatStyle::QAS_Right) + Tok = analyzeRight(SourceMgr, Keywords, Fixes, Tok, Qualifier, + QualifierToken, QualifierTokens); + else if (Alignment == FormatStyle::QAS_Left) + Tok = analyzeLeft(SourceMgr, Keywords, Fixes, Tok, Qualifier, + QualifierToken, QualifierTokens); + } + } + return {Fixes, 0}; +} + +/// LeftToRight + +namespace internal { +void PrepareLeftRightOrdering(const std::vector<std::string> &Order, + std::vector<std::string> &LeftOrder, + std::vector<std::string> &RightOrder) { + + // Depending on the position of type in the order you need + // To iterate forward or backward through the order list as qualifier + // can push through each other. + auto type = std::find(Order.begin(), Order.end(), "type"); + // The Order list must define the position of "type" to signify + assert(type != Order.end() && "QualifierOrder must contain type"); + // Split the Order list by type and reverse the left side + bool left = true; + for (const auto &s : Order) { + if (s == "type") { + left = false; + continue; + } + if (left) { + LeftOrder.push_back(s); + } else { + RightOrder.push_back(s); + } + } + // Reverse the order for left aligned items. + std::reverse(LeftOrder.begin(), LeftOrder.end()); +} +} // namespace internal + +QualifierAlignmentFixer::QualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, StringRef &Code, + ArrayRef<tooling::Range> Ranges, unsigned FirstStartColumn, + unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName) + : TokenAnalyzer(Env, Style), Code(Code), Ranges(Ranges), + FirstStartColumn(FirstStartColumn), NextStartColumn(NextStartColumn), + LastStartColumn(LastStartColumn), FileName(FileName) { + std::vector<std::string> LeftOrder; + std::vector<std::string> RightOrder; + internal::PrepareLeftRightOrdering(Style.QualifierOrder, LeftOrder, + RightOrder); + + for (auto Qualifier : Style.QualifierOrder) { + tok::TokenKind kind = + LeftRightQualifierAlignmentFixer::getTokenFromQualifier(Qualifier); + if (kind != tok::identifier) + QualifierTokens.push_back(kind); + } + + // Handle the left and right Alignment Seperately + for (const auto &Qualifier : LeftOrder) { + Passes.emplace_back([&, Qualifier](const Environment &Env) { + return LeftRightQualifierAlignmentFixer( + Env, Style, Qualifier, FormatStyle::QAS_Left, QualifierTokens) + .process(); + }); + } + for (const auto &Qualifier : RightOrder) { + Passes.emplace_back([&, Qualifier](const Environment &Env) { + return LeftRightQualifierAlignmentFixer( + Env, Style, Qualifier, FormatStyle::QAS_Right, QualifierTokens) + .process(); + }); + } +} + +std::pair<tooling::Replacements, unsigned> QualifierAlignmentFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, + FormatTokenLexer &Tokens) { + + auto Env = + std::make_unique<Environment>(Code, FileName, Ranges, FirstStartColumn, + NextStartColumn, LastStartColumn); + llvm::Optional<std::string> CurrentCode = None; + tooling::Replacements Fixes; + unsigned Penalty = 0; + for (size_t I = 0, E = Passes.size(); I < E; ++I) { + std::pair<tooling::Replacements, unsigned> PassFixes = Passes[I](*Env); + auto NewCode = applyAllReplacements( + CurrentCode ? StringRef(*CurrentCode) : Code, PassFixes.first); + if (NewCode) { + Fixes = Fixes.merge(PassFixes.first); + Penalty += PassFixes.second; + if (I + 1 < E) { + CurrentCode = std::move(*NewCode); + Env = std::make_unique<Environment>( + *CurrentCode, FileName, + tooling::calculateRangesAfterReplacements(Fixes, Ranges), + FirstStartColumn, NextStartColumn, LastStartColumn); + } + } + } + + // Don't make replacements that replace nothing + tooling::Replacements NonNoOpFixes; + + for (auto I = Fixes.begin(), E = Fixes.end(); I != E; ++I) { + + StringRef OriginalCode = Code.substr(I->getOffset(), I->getLength()); + + LLVM_DEBUG(llvm::dbgs() << "Replacement:[" << OriginalCode << "][" + << I->getReplacementText() << "]\n"); + + if (!OriginalCode.equals(I->getReplacementText())) { + auto Err = NonNoOpFixes.add(*I); + if (Err) + llvm::errs() << "Error removing no op replacements : " + << llvm::toString(std::move(Err)) << "\n"; + } else { + LLVM_DEBUG(llvm::dbgs() << "No Op Fix:" << OriginalCode); + } + } + + return {NonNoOpFixes, 0}; +} + +} // namespace format +} // namespace clang Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -19,6 +19,7 @@ #include "FormatInternal.h" #include "FormatTokenLexer.h" #include "NamespaceEndCommentsFixer.h" +#include "QualifierAlignmentFixer.h" #include "SortJavaScriptImports.h" #include "TokenAnalyzer.h" #include "TokenAnnotator.h" @@ -126,6 +127,16 @@ } }; +template <> +struct ScalarEnumerationTraits<FormatStyle::QualifierAlignmentStyle> { + static void enumeration(IO &IO, FormatStyle::QualifierAlignmentStyle &Value) { + IO.enumCase(Value, "Leave", FormatStyle::QAS_Leave); + IO.enumCase(Value, "Left", FormatStyle::QAS_Left); + IO.enumCase(Value, "Right", FormatStyle::QAS_Right); + IO.enumCase(Value, "Custom", FormatStyle::QAS_Custom); + } +}; + template <> struct ScalarEnumerationTraits<FormatStyle::ShortFunctionStyle> { static void enumeration(IO &IO, FormatStyle::ShortFunctionStyle &Value) { IO.enumCase(Value, "None", FormatStyle::SFS_None); @@ -641,6 +652,24 @@ IO.mapOptional("BreakStringLiterals", Style.BreakStringLiterals); IO.mapOptional("ColumnLimit", Style.ColumnLimit); IO.mapOptional("CommentPragmas", Style.CommentPragmas); + IO.mapOptional("QualifierAlignment", Style.QualifierAlignment); + + // Default Order for Left/Right based Qualifier alignment. + if (Style.QualifierAlignment == FormatStyle::QAS_Right) { + + Style.QualifierOrder.push_back("type"); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("volatile"); + } else if (Style.QualifierAlignment == FormatStyle::QAS_Left) { + Style.QualifierOrder.clear(); + Style.QualifierOrder.push_back("const"); + Style.QualifierOrder.push_back("volatile"); + Style.QualifierOrder.push_back("type"); + } else { + Style.QualifierOrder.clear(); + } + IO.mapOptional("QualifierOrder", Style.QualifierOrder); + IO.mapOptional("CompactNamespaces", Style.CompactNamespaces); IO.mapOptional("ConstructorInitializerIndentWidth", Style.ConstructorInitializerIndentWidth); @@ -905,6 +934,14 @@ return "Unsuitable"; case ParseError::BinPackTrailingCommaConflict: return "trailing comma insertion cannot be used with bin packing"; + case ParseError::InvalidQualifierSpecified: + return "Invalid qualifier specified in QualifierOrder"; + case ParseError::DuplicateQualifierSpecified: + return "Duplicate qualifier specified in QualfierOrder"; + case ParseError::MissingQualifierType: + return "Missing type in QualfierOrder"; + case ParseError::MissingQualifierOrder: + return "Missing QualfierOrder"; } llvm_unreachable("unexpected parse error"); } @@ -1078,6 +1115,16 @@ LLVMStyle.ConstructorInitializerIndentWidth = 4; LLVMStyle.ContinuationIndentWidth = 4; LLVMStyle.Cpp11BracedListStyle = true; + + // Off by default Qualifier ordering + LLVMStyle.QualifierAlignment = FormatStyle::QAS_Leave; + // Default 'Left' qualifier order but off by default. + LLVMStyle.QualifierOrder.push_back("inline"); + LLVMStyle.QualifierOrder.push_back("static"); + LLVMStyle.QualifierOrder.push_back("const"); + LLVMStyle.QualifierOrder.push_back("volatile"); + LLVMStyle.QualifierOrder.push_back("type"); + LLVMStyle.DeriveLineEnding = true; LLVMStyle.DerivePointerAlignment = false; LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never; @@ -1512,6 +1559,37 @@ return true; } +ParseError validateQualifierOrder(FormatStyle *Style) { + // If its empty then it means don't do anything. + if (Style->QualifierOrder.empty()) + return ParseError::MissingQualifierOrder; + + // Ensure the list contains only currently valid qualifiers. + for (const auto &Qualifier : Style->QualifierOrder) { + if (Qualifier == "type") + continue; + auto token = + LeftRightQualifierAlignmentFixer::getTokenFromQualifier(Qualifier); + if (token == tok::identifier) + return ParseError::InvalidQualifierSpecified; + } + // Ensure the list is unqiue (no duplicates). + std::set<std::string> UniqueQualifiers(Style->QualifierOrder.begin(), + Style->QualifierOrder.end()); + if (Style->QualifierOrder.size() != UniqueQualifiers.size()) { + LLVM_DEBUG(llvm::dbgs() + << "Duplicate Qualifiers " << Style->QualifierOrder.size() + << " vs " << UniqueQualifiers.size() << "\n"); + return ParseError::DuplicateQualifierSpecified; + } + + auto type = std::find(Style->QualifierOrder.begin(), + Style->QualifierOrder.end(), "type"); + if (type == Style->QualifierOrder.end()) + return ParseError::MissingQualifierType; + return ParseError::Success; +} + std::error_code parseConfiguration(llvm::MemoryBufferRef Config, FormatStyle *Style, bool AllowUnknownOptions, llvm::SourceMgr::DiagHandlerTy DiagHandler, @@ -1573,6 +1651,8 @@ // See comment on FormatStyle::TSC_Wrapped. return make_error_code(ParseError::BinPackTrailingCommaConflict); } + if (Style->QualifierAlignment != FormatStyle::QAS_Leave) + return make_error_code(validateQualifierOrder(Style)); return make_error_code(ParseError::Success); } @@ -2857,6 +2937,7 @@ } namespace internal { + std::pair<tooling::Replacements, unsigned> reformat(const FormatStyle &Style, StringRef Code, ArrayRef<tooling::Range> Ranges, unsigned FirstStartColumn, @@ -2894,6 +2975,15 @@ AnalyzerPass; SmallVector<AnalyzerPass, 4> Passes; + if (Style.isCpp() && Style.QualifierAlignment != FormatStyle::QAS_Leave) { + Passes.emplace_back([&](const Environment &Env) { + return QualifierAlignmentFixer(Env, Expanded, Code, Ranges, + FirstStartColumn, NextStartColumn, + LastStartColumn, FileName) + .process(); + }); + } + if (Style.Language == FormatStyle::LK_Cpp) { if (Style.FixNamespaceComments) Passes.emplace_back([&](const Environment &Env) { Index: clang/lib/Format/CMakeLists.txt =================================================================== --- clang/lib/Format/CMakeLists.txt +++ clang/lib/Format/CMakeLists.txt @@ -9,6 +9,7 @@ FormatTokenLexer.cpp MacroExpander.cpp NamespaceEndCommentsFixer.cpp + QualifierAlignmentFixer.cpp SortJavaScriptImports.cpp TokenAnalyzer.cpp TokenAnnotator.cpp Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -40,7 +40,11 @@ Success = 0, Error, Unsuitable, - BinPackTrailingCommaConflict + BinPackTrailingCommaConflict, + InvalidQualifierSpecified, + DuplicateQualifierSpecified, + MissingQualifierType, + MissingQualifierOrder }; class ParseErrorCategory final : public std::error_category { public: @@ -1819,6 +1823,63 @@ /// \endcode std::string CommentPragmas; + /// Different const/volatile qualifier alignment styles. + enum QualifierAlignmentStyle { + /// Don't change specifiers/qualifier to either Left or Right alignment + /// \code + /// int const a; + /// const int *a; + /// \endcode + QAS_Leave, + /// Change specifiers/qualifiers to be Left aligned. + /// \code + /// const int a; + /// const int *a; + /// \endcode + QAS_Left, + /// Change specifiers/qualifiers to be Right aligned. + /// \code + /// int const a; + /// int const *a; + /// \endcode + QAS_Right, + /// Change specifiers/qualifiers to be aligned based on QualfierOrder. + /// With: + /// \code{.yaml} + /// QualifierOrder: ['inline', 'static' , '<type>', 'const'] + /// \endcode + /// + /// \code + /// + /// int const a; + /// int const *a; + /// \endcode + QAS_Custom + }; + + /// Different ways to arrange const/volatile qualifiers. + QualifierAlignmentStyle QualifierAlignment; + + /// The Order in which the qualifiers appear. + /// Order is a an array can contain any of the following + /// + /// * const + /// * inline + /// * static + /// * constexpr + /// * volatile + /// * restrict + /// * type + /// + /// Note: it MUST contain 'type'. + /// Items to the left of type will be aligned in the order supplied. + /// Items to the right of type will be aligned in the order supplied. + /// + /// \code{.yaml} + /// QualifierOrder: ['inline', 'static', 'type', 'const', 'volatile' ] + /// \endcode + std::vector<std::string> QualifierOrder; + /// Different ways to break inheritance list. enum BreakInheritanceListStyle : unsigned char { /// Break inheritance list before the colon and after the commas. @@ -3438,6 +3499,8 @@ BreakAfterJavaFieldAnnotations == R.BreakAfterJavaFieldAnnotations && BreakStringLiterals == R.BreakStringLiterals && ColumnLimit == R.ColumnLimit && CommentPragmas == R.CommentPragmas && + QualifierAlignment == R.QualifierAlignment && + QualifierOrder == R.QualifierOrder && BreakInheritanceList == R.BreakInheritanceList && ConstructorInitializerIndentWidth == R.ConstructorInitializerIndentWidth && Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -188,6 +188,16 @@ - Option ``AllowShortEnumsOnASingleLine: false`` has been improved, it now correctly places the opening brace according to ``BraceWrapping.AfterEnum``. +- Option ``QualifierAligment`` has been added in order to auto-arrange the + positioning of specifiers/qualifiers + `const` `volatile` `static` `inline` `constexpr` `restrict` + in variable and parameter declarations to be either ``Right`` aligned + or ``Left`` aligned or ``Custom`` using ``QualifierOrder``. + +- Option ``QualifierOrder`` has been added to allow the order + `const` `volatile` `static` `inline` `constexpr` `restrict` + to be controlled relative to the `<type>`. + libclang -------- Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -3225,6 +3225,73 @@ +**QualifierAlignment** (``QualifierAlignmentStyle``) + Different ways to arrange const/volatile qualifiers. + + Possible values: + + * ``QAS_Leave`` (in configuration: ``Leave``) + Don't change specifiers/qualifier to either Left or Right alignment + + .. code-block:: c++ + + int const a; + const int *a; + + * ``QAS_Left`` (in configuration: ``Left``) + Change specifiers/qualifiers to be Left aligned. + + .. code-block:: c++ + + const int a; + const int *a; + + * ``QAS_Right`` (in configuration: ``Right``) + Change specifiers/qualifiers to be Right aligned. + + .. code-block:: c++ + + int const a; + int const *a; + + * ``QAS_Custom`` (in configuration: ``Custom``) + Change specifiers/qualifiers to be aligned based on QualfierOrder. + With: + + .. code-block:: yaml + + QualifierOrder: ['inline', 'static' , '<type>', 'const'] + + + .. code-block:: c++ + + + int const a; + int const *a; + + + +**QualifierOrder** (``std::vector<std::string>``) + The Order in which the qualifiers appear. + Order is a an array can contain any of the following + + * const + * inline + * static + * constexpr + * volatile + * restrict + * type + + Note: it MUST contain 'type'. + Items to the left of type will be aligned in the order supplied. + Items to the right of type will be aligned in the order supplied. + + + .. code-block:: yaml + + QualifierOrder: ['inline', 'static', 'type', 'const', 'volatile' ] + **RawStringFormats** (``std::vector<RawStringFormat>``) Defines hints for detecting supported languages code blocks in raw strings.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits