From c58ac89083122824632a3ef9fc3c53531dc3b3ae Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Fri, 29 Aug 2025 23:26:29 -0700 Subject: [PATCH 1/2] [llvm][mustache] Align standalone partial indentation with spec The current implementation did not correctly handle indentation for standalone partial tags. It was only applied to lines following a newline, instead of the first line of a partial's content. This was fixed by updating the AddIndentation implementation to prepend the indentation to the first line of the partial. --- llvm/lib/Support/Mustache.cpp | 18 +++++++++++------- llvm/unittests/Support/MustacheTest.cpp | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp index be9cbfd46982f..a924c023256b1 100644 --- a/llvm/lib/Support/Mustache.cpp +++ b/llvm/lib/Support/Mustache.cpp @@ -282,18 +282,15 @@ void stripTokenAhead(SmallVectorImpl &Tokens, size_t Idx) { // For example: // The template string // " \t{{#section}}A{{/section}}" -// would be considered as having no text ahead and would be render as +// would be considered as having no text ahead and would be render as: // "A" -// The exception for this is partial tag which requires us to -// keep track of the indentation once it's rendered. void stripTokenBefore(SmallVectorImpl &Tokens, size_t Idx, Token &CurrentToken, Token::Type CurrentType) { Token &PrevToken = Tokens[Idx - 1]; StringRef PrevTokenBody = PrevToken.TokenBody; StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v"); size_t Indentation = PrevTokenBody.size() - Unindented.size(); - if (CurrentType != Token::Type::Partial) - PrevToken.TokenBody = Unindented.str(); + PrevToken.TokenBody = Unindented.str(); CurrentToken.setIndentation(Indentation); } @@ -425,7 +422,8 @@ class AddIndentationStringStream : public raw_ostream { public: explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream, size_t Indentation) - : Indentation(Indentation), WrappedStream(WrappedStream) { + : Indentation(Indentation), WrappedStream(WrappedStream), + NeedsIndent(true) { SetUnbuffered(); } @@ -434,10 +432,15 @@ class AddIndentationStringStream : public raw_ostream { llvm::StringRef Data(Ptr, Size); SmallString<0> Indent; Indent.resize(Indentation, ' '); + for (char C : Data) { + if (NeedsIndent && C != '\n') { + WrappedStream << Indent; + NeedsIndent = false; + } WrappedStream << C; if (C == '\n') - WrappedStream << Indent; + NeedsIndent = true; } } @@ -446,6 +449,7 @@ class AddIndentationStringStream : public raw_ostream { private: size_t Indentation; llvm::raw_ostream &WrappedStream; + bool NeedsIndent; }; class Parser { diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp index 02eaed4244cc7..3635463cd7570 100644 --- a/llvm/unittests/Support/MustacheTest.cpp +++ b/llvm/unittests/Support/MustacheTest.cpp @@ -998,7 +998,7 @@ TEST(MustachePartials, StandaloneIndentation) { std::string Out; raw_string_ostream OS(Out); T.render(D, OS); - EXPECT_NE("\\\n |\n <\n ->\n |\n/\n", Out); + EXPECT_EQ("\\\n |\n <\n ->\n |\n/\n", Out); } TEST(MustacheLambdas, BasicInterpolation) { From dc36a30738a2dd5962e5c667cc13264957f75f5c Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Fri, 29 Aug 2025 23:55:25 -0700 Subject: [PATCH 2/2] [llvm][mustache] Precommit test for Set Delimiter Adds a new unit test for the Mustache Set Delimiter feature. This test is written with inverted logic (`EXPECT_NE`) so that it passes with the current implementation, which does not support the feature. Once the feature is implemented, this test will fail, signaling that the test logic should be flipped to `EXPECT_EQ`. --- llvm/unittests/Support/MustacheTest.cpp | 136 ++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp index 3635463cd7570..0ebbc58e023cc 100644 --- a/llvm/unittests/Support/MustacheTest.cpp +++ b/llvm/unittests/Support/MustacheTest.cpp @@ -1328,3 +1328,139 @@ TEST(MustacheTripleMustache, WithPadding) { T.render(D, OS); EXPECT_EQ("|---|", Out); } + +TEST(MustacheDelimiters, PairBehavior) { + Value D = Object{{"text", "Hey!"}}; + auto T = Template("{{=<% %>=}}(<%text%>)"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("(Hey!)", Out); +} + +TEST(MustacheDelimiters, SpecialCharacters) { + Value D = Object{{"text", "It worked!"}}; + auto T = Template("({{=[ ]=}}[text])"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("(It worked!)", Out); +} + +TEST(MustacheDelimiters, Sections) { + Value D = Object{{"section", true}, {"data", "I got interpolated."}}; + auto T = + Template("[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= " + "| | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("[\n I got interpolated.\n |data|\n\n {{data}}\n I got " + "interpolated.\n]\n", + Out); +} + +TEST(MustacheDelimiters, InvertedSections) { + Value D = Object{{"section", false}, {"data", "I got interpolated."}}; + auto T = + Template("[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= " + "| | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("[\n I got interpolated.\n |data|\n\n {{data}}\n I got " + "interpolated.\n]\n", + Out); +} + +TEST(MustacheDelimiters, PartialInheritence) { + Value D = Object{{"value", "yes"}}; + auto T = Template("[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n"); + T.registerPartial("include", ".{{value}}."); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("[ .yes. ]\n[ .yes. ]\n", Out); +} + +TEST(MustacheDelimiters, PostPartialBehavior) { + Value D = Object{{"value", "yes"}}; + auto T = Template("[ {{>include}} ]\n[ .{{value}}. .|value|. ]\n"); + T.registerPartial("include", ".{{value}}. {{= | | =}} .|value|."); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("[ .yes. .yes. ]\n[ .yes. .|value|. ]\n", Out); +} + +TEST(MustacheDelimiters, SurroundingWhitespace) { + Value D = Object{}; + auto T = Template("| {{=@ @=}} |"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_EQ("| |", Out); +} + +TEST(MustacheDelimiters, OutlyingWhitespaceInline) { + Value D = Object{}; + auto T = Template(" | {{=@ @=}}\n"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_EQ(" | \n", Out); +} + +TEST(MustacheDelimiters, StandaloneTag) { + Value D = Object{}; + auto T = Template("Begin.\n{{=@ @=}}\nEnd.\n"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("Begin.\nEnd.\n", Out); +} + +TEST(MustacheDelimiters, IndentedStandaloneTag) { + Value D = Object{}; + auto T = Template("Begin.\n {{=@ @=}}\nEnd.\n"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("Begin.\nEnd.\n", Out); +} + +TEST(MustacheDelimiters, StandaloneLineEndings) { + Value D = Object{}; + auto T = Template("|\r\n{{= @ @ =}}\r\n|"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("|\r\n|", Out); +} + +TEST(MustacheDelimiters, StandaloneWithoutPreviousLine) { + Value D = Object{}; + auto T = Template(" {{=@ @=}}\n="); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("=", Out); +} + +TEST(MustacheDelimiters, StandaloneWithoutNewline) { + Value D = Object{}; + auto T = Template("=\n {{=@ @=}}"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_NE("=\n", Out); +} + +TEST(MustacheDelimiters, PairwithPadding) { + Value D = Object{}; + auto T = Template("|{{= @ @ =}}|"); + std::string Out; + raw_string_ostream OS(Out); + T.render(D, OS); + EXPECT_EQ("||", Out); +}