https://github.com/owenca created https://github.com/llvm/llvm-project/pull/121404
Closes #114969. >From 8b7802141853f77f3f074e6f41f416dfd25d90e4 Mon Sep 17 00:00:00 2001 From: Owen Pan <owenpi...@gmail.com> Date: Tue, 31 Dec 2024 08:13:53 -0800 Subject: [PATCH] [clang-format] Support globstar in .clang-format-ignore Closes #114969. --- clang/docs/ClangFormat.rst | 4 +++ clang/docs/ReleaseNotes.rst | 1 + clang/lib/Format/MatchFilePath.cpp | 35 ++++++++++++++------ clang/unittests/Format/MatchFilePathTest.cpp | 35 ++++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst index c8f1d7f5a77581..21cf68236b8917 100644 --- a/clang/docs/ClangFormat.rst +++ b/clang/docs/ClangFormat.rst @@ -150,6 +150,10 @@ names. It has the following format: * Patterns follow the rules specified in `POSIX 2.13.1, 2.13.2, and Rule 1 of 2.13.3 <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ V3_chap02.html#tag_18_13>`_. +* Bash globstar is supported, i.e. "two adjacent ``*``s used as a single pattern + will match all files and zero or more directories and subdirectories. If + followed by a ``/``, two adjacent ``*``s will match only directories and + subdirectories." * A pattern is negated if it starts with a bang (``!``). To match all files in a directory, use e.g. ``foo/bar/*``. To match all files in diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b7da12bcf65818..2d59d74ddf35fc 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -1124,6 +1124,7 @@ clang-format - Adds ``RemoveEmptyLinesInUnwrappedLines`` option. - Adds ``KeepFormFeed`` option and set it to ``true`` for ``GNU`` style. - Adds ``AllowShortNamespacesOnASingleLine`` option. +- Adds support for bash globstar in ``.clang-format-ignore``. libclang -------- diff --git a/clang/lib/Format/MatchFilePath.cpp b/clang/lib/Format/MatchFilePath.cpp index 062b334dcdd8fd..aca3433dc7fa78 100644 --- a/clang/lib/Format/MatchFilePath.cpp +++ b/clang/lib/Format/MatchFilePath.cpp @@ -49,25 +49,38 @@ bool matchFilePath(StringRef Pattern, StringRef FilePath) { return false; break; case '*': { - while (++I < EOP && Pattern[I] == '*') { // Skip consecutive stars. + const bool MaybeGlobstar = I == 0 || Pattern[I - 1] == Separator; + int StarCount = 1; + for (; ++I < EOP && Pattern[I] == '*'; ++StarCount) { + // Skip consecutive stars. } const auto K = FilePath.find(Separator, J); // Index of next `Separator`. const bool NoMoreSeparatorsInFilePath = K == StringRef::npos; + bool Globstar = MaybeGlobstar && StarCount == 2; if (I == EOP) // `Pattern` ends with a star. - return NoMoreSeparatorsInFilePath; - // `Pattern` ends with a lone backslash. - if (Pattern[I] == '\\' && ++I == EOP) - return false; + return Globstar || NoMoreSeparatorsInFilePath; + if (Pattern[I] != Separator) { + Globstar = false; + // `Pattern` ends with a lone backslash. + if (Pattern[I] == '\\' && ++I == EOP) + return false; + } // The star is followed by a (possibly escaped) `Separator`. if (Pattern[I] == Separator) { - if (NoMoreSeparatorsInFilePath) - return false; - J = K; // Skip to next `Separator` in `FilePath`. - break; + if (!Globstar) { + if (NoMoreSeparatorsInFilePath) + return false; + J = K; // Skip to next `Separator` in `FilePath`. + break; + } + if (I + 1 < EOP && + matchFilePath(Pattern.substr(I + 1), FilePath.substr(J))) { + return true; + } } // Recurse. - for (auto Pat = Pattern.substr(I); J < End && FilePath[J] != Separator; - ++J) { + for (auto Pat = Pattern.substr(I); + J < End && (Globstar || FilePath[J] != Separator); ++J) { if (matchFilePath(Pat, FilePath.substr(J))) return true; } diff --git a/clang/unittests/Format/MatchFilePathTest.cpp b/clang/unittests/Format/MatchFilePathTest.cpp index 28f665635718e5..9876a4583c226a 100644 --- a/clang/unittests/Format/MatchFilePathTest.cpp +++ b/clang/unittests/Format/MatchFilePathTest.cpp @@ -164,6 +164,41 @@ TEST_F(MatchFilePathTest, Path) { EXPECT_FALSE(match("foo\\", R"(foo*\)")); } +TEST_F(MatchFilePathTest, Globstar) { + EXPECT_TRUE(match("/", "**")); + EXPECT_TRUE(match("foo", "**")); + EXPECT_TRUE(match("/foo", "**")); + EXPECT_TRUE(match("foo/", "**")); + EXPECT_TRUE(match("foo/bar", "**")); + + EXPECT_TRUE(match("/", "**/")); + EXPECT_TRUE(match("foo/", "**/")); + EXPECT_TRUE(match("/foo/", "**/")); + EXPECT_TRUE(match("foo/bar/", "**/")); + + EXPECT_TRUE(match("/", "/**")); + EXPECT_TRUE(match("/foo", "/**")); + EXPECT_TRUE(match("/foo/", "/**")); + EXPECT_TRUE(match("/foo/bar", "/**")); + + EXPECT_TRUE(match("foo", "**/foo")); + EXPECT_TRUE(match("/foo", "**/foo")); + EXPECT_TRUE(match("foo/bar", "**/bar")); + EXPECT_TRUE(match("/foo/bar", "**/foo/bar")); + EXPECT_TRUE(match("foo/bar/baz", "**/bar/baz")); + + EXPECT_TRUE(match("abc/foo", "abc/**")); + EXPECT_TRUE(match("abc/foo/", "abc/**")); + EXPECT_TRUE(match("abc/foo/bar", "abc/**")); + + EXPECT_TRUE(match("a/b", "a/**/b")); + EXPECT_TRUE(match("a/x/b", "a/**/b")); + EXPECT_TRUE(match("a/x/y/b", "a/**/b")); + + EXPECT_FALSE(match("a/x/b", "a**/b")); + EXPECT_FALSE(match("a/x/y/b", "a/**b")); +} + } // namespace } // namespace format } // namespace clang _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits