Forward ported from 2.3-gae: FTL.jj cleanup. Can't close with "]" when using angle bracket tag syntax anymore.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/0d21cbb7 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/0d21cbb7 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/0d21cbb7 Branch: refs/heads/3 Commit: 0d21cbb77004014654b6e7160ad7791939f0faad Parents: 348e73d Author: ddekany <ddek...@apache.org> Authored: Mon Mar 19 23:42:35 2018 +0100 Committer: ddekany <ddek...@apache.org> Committed: Mon Mar 19 23:42:35 2018 +0100 ---------------------------------------------------------------------- .../core/InterpolationSyntaxTest.java | 27 +++++-- .../core/ParsingErrorMessagesTest.java | 67 ++++++--------- .../freemarker/core/ASTExpStringLiteral.java | 3 +- freemarker-core/src/main/javacc/FTL.jj | 85 ++++++++++++++------ 4 files changed, 109 insertions(+), 73 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0d21cbb7/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java index 4d7d0cc..7dc0dd0 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java @@ -48,7 +48,10 @@ public class InterpolationSyntaxTest extends TemplateTest { assertOutput("${'a${1}#{2}b[=3]'}", "a1#{2}b[=3]"); assertOutput("<@r'${1} #{1} [=1]'?interpret />", "1 #{1} [=1]"); - assertOutput("${'\"${1} #{1} [=1]\"'?eval}", "1 #{1} [=1]"); + assertOutput("${'\"${1} #{1} [=1]\"'?eval}", "1 #{1} [=1]"); + + assertOutput("<#setting booleanFormat='y,n'>${2>1}", "y"); // Not an error since 2.3.28 + assertOutput("[#ftl][#setting booleanFormat='y,n']${2>1}", "y"); // Not an error since 2.3.28 } @Test @@ -94,7 +97,10 @@ public class InterpolationSyntaxTest extends TemplateTest { assertOutput("[='[\\=1]']", "[=1]"); assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch... - assertOutput("[=r'[=1]']", "[=1]"); + assertOutput("[=r'[=1]']", "[=1]"); + + assertOutput("<#setting booleanFormat='y,n'>[=2>1]", "y"); + assertOutput("[#ftl][#setting booleanFormat='y,n'][=2>1]", "y"); StringWriter sw = new StringWriter(); new Template(null, "[= 1 + '[= 2 ]' ]", getConfiguration()).dump(sw); @@ -116,19 +122,28 @@ public class InterpolationSyntaxTest extends TemplateTest { } @Test - public void legacyTagSyntaxGlitchStillWorksTest() throws Exception { - String ftl = "<#if [true][0]]t<#else]f</#if]"; + public void legacyTagSyntaxGlitchFixedTest() throws Exception { + String badFtl1 = "<#if [true][0]]OK</#if>"; + String badFtl2 = "<#if true>OK</#if]"; + String badFtl3 = "<#assign x = 'OK'/]${x}"; + String badFtl4 = " <#t/]OK\n"; setConfiguration(new TestConfigurationBuilder() .interpolationSyntax(InterpolationSyntax.DOLLAR) .build()); - assertOutput(ftl, "t"); + assertErrorContains(badFtl1, "\"]\""); + assertErrorContains(badFtl2, "\"]\""); + assertErrorContains(badFtl3, "\"]\""); + assertErrorContains(badFtl4, "\"]\""); // Glitch is not emulated with this: setConfiguration(new TestConfigurationBuilder() .interpolationSyntax(InterpolationSyntax.SQUARE_BRACKET) .build()); - assertErrorContains(ftl, "\"]\""); + assertErrorContains(badFtl1, "\"]\""); + assertErrorContains(badFtl2, "\"]\""); + assertErrorContains(badFtl3, "\"]\""); + assertErrorContains(badFtl4, "\"]\""); } @Test http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0d21cbb7/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java index bf729ea..962e7a9 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java @@ -19,19 +19,20 @@ package org.apache.freemarker.core; -import static org.junit.Assert.*; - import java.io.IOException; -import org.apache.freemarker.core.util._StringUtils; +import org.apache.freemarker.test.TemplateTest; import org.apache.freemarker.test.TestConfigurationBuilder; import org.junit.Test; -public class ParsingErrorMessagesTest { +public class ParsingErrorMessagesTest extends TemplateTest { - private Configuration cfg = new TestConfigurationBuilder() - .tagSyntax(TagSyntax.AUTO_DETECT) - .build(); + @Override + protected Configuration createDefaultConfiguration() throws Exception { + return new TestConfigurationBuilder() + .tagSyntax(TagSyntax.AUTO_DETECT) + .build(); + } @Test public void testNeedlessInterpolation() { @@ -122,10 +123,14 @@ public class ParsingErrorMessagesTest { } @Test - public void testInterpolatingClosingsErrors() { - assertErrorContains("${x", "unclosed"); + public void testInterpolatingClosingsErrors() throws Exception { + assertErrorContains("<#ftl>${x", "unclosed"); assertErrorContains("<#assign x = x}>", "\"}\"", "open"); - // TODO assertErrorContains("<#assign x = '${x'>", "unclosed"); + assertErrorContains("<#ftl>${'x']", "\"]\"", "open"); + super.assertErrorContains("<#ftl>${'x'>", "end of file"); + super.assertErrorContains("[#ftl]${'x'>", "end of file"); + + assertOutput("<#assign x = '${x'>", ""); // TODO [FM3] Legacy glitch... should fail in theory. } @Test @@ -167,43 +172,19 @@ public class ParsingErrorMessagesTest { assertErrorContains("<#function f(a=0, b)></#function>", "with default", "without a default"); assertErrorContains("<#function f(a,)></#function>", "Comma without"); assertErrorContains("<#macro m a{positional}, b{positional},></#macro>", "Comma without"); - assertErrorContains(false, "<#function f(a, b></#function>"); - assertErrorContains(false, "<#function f(></#function>"); - assertErrorContains(false, "[#ftl][#function f(a, b][/#function]", "Missing closing \")\""); - assertErrorContains(false, "[#ftl][#function f(][/#function]", "Missing closing \")\""); + super.assertErrorContains("<#function f(a, b></#function>"); + super.assertErrorContains("<#function f(></#function>"); + super.assertErrorContains("[#ftl][#function f(a, b][/#function]", "Missing closing \")\""); + super.assertErrorContains("[#ftl][#function f(][/#function]", "Missing closing \")\""); assertErrorContains("<#macro m a b)></#macro>", "\")\" without", "opening"); assertErrorContains("<#macro m a b a></#macro>", "\"a\"", "multiple"); } - private void assertErrorContains(String ftl, String... expectedSubstrings) { - assertErrorContains(false, ftl, expectedSubstrings); - assertErrorContains(true, ftl, expectedSubstrings); - } - - private void assertErrorContains(boolean convertToSquare, String ftl, String... expectedSubstrings) { - try { - if (convertToSquare) { - ftl = ftl.replace('<', '[').replace('>', ']'); - } - new Template("adhoc", ftl, cfg); - fail("The template had to fail"); - } catch (ParseException e) { - String msg = e.getMessage(); - for (String needle: expectedSubstrings) { - if (needle.startsWith("\\!")) { - String netNeedle = needle.substring(2); - if (msg.contains(netNeedle)) { - fail("The message shouldn't contain substring " + _StringUtils.jQuote(netNeedle) + ":\n" + msg); - } - } else if (!msg.contains(needle)) { - fail("The message didn't contain substring " + _StringUtils.jQuote(needle) + ":\n" + msg); - } - } - showError(e); - } catch (IOException e) { - // Won't happen - throw new RuntimeException(e); - } + @Override + protected Throwable assertErrorContains(String ftl, String... expectedSubstrings) { + super.assertErrorContains(ftl, expectedSubstrings); + ftl = ftl.replace('<', '[').replace('>', ']'); + return super.assertErrorContains(ftl, expectedSubstrings); } private void showError(Throwable e) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0d21cbb7/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java index a86c4d5..ebd8ff6 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java @@ -49,13 +49,14 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateStringM * inherit tokenizer-level auto-detected settings. */ void parseValue(FMParserTokenManager parentTkMan, OutputFormat outputFormat) throws ParseException { - // TODO [FM3] (Find related: [interpolation prefixes]) + // TODO [FM3] // The way this works is incorrect (the literal should be parsed without un-escaping), // but we can't fix this backward compatibly. Template parentTemplate = getTemplate(); ParsingConfiguration pCfg = parentTemplate.getParsingConfiguration(); InterpolationSyntax intSyn = pCfg.getInterpolationSyntax(); if (value.length() > 3 && ( + // Find related: [interpolation prefixes] intSyn == InterpolationSyntax.DOLLAR && value.indexOf("${") != -1 || intSyn == InterpolationSyntax.SQUARE_BRACKET && value.indexOf("[=") != -1)) { try { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0d21cbb7/freemarker-core/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj index 76b32f5..fa29105 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -533,7 +533,7 @@ TOKEN_MGR_DECLS: private boolean inFTLHeader; boolean squBracTagSyntax, autodetectTagSyntax, - directiveSyntaxEstablished, + tagSyntaxEstablished, inNamedParameterExpression; InterpolationSyntax interpolationSyntax; int incompatibleImprovements; @@ -550,7 +550,7 @@ TOKEN_MGR_DECLS: final String image = tok.image; char firstChar = image.charAt(0); - if (autodetectTagSyntax && !directiveSyntaxEstablished) { + if (autodetectTagSyntax && !tagSyntaxEstablished) { squBracTagSyntax = (firstChar == '['); } if ((firstChar == '[' && !squBracTagSyntax) || (firstChar == '<' && squBracTagSyntax)) { @@ -558,7 +558,22 @@ TOKEN_MGR_DECLS: return; } - directiveSyntaxEstablished = true; + tagSyntaxEstablished = true; + + // For END_xxx tags, as they can't contain expressions, the whole tag is a single token. So this is the only + // chance to check if we got something inconsistent like `</#if]`. (We can't do this at the #CLOSE_TAG1 or + // such, as at that point it's possible that the tag syntax is not yet established.) + char lastChar = image.charAt(image.length() - 1); + // Is it an end tag? + if (lastChar == ']' || lastChar == '>') { + if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') { + throw new TokenMgrError( + "The tag shouldn't end with \""+ lastChar + "\".", + TokenMgrError.LEXICAL_ERROR, + tok.beginLine, tok.beginColumn, + tok.endLine, tok.endColumn); + } + } // if end-tag SwitchTo(newLexState); } @@ -581,7 +596,7 @@ TOKEN_MGR_DECLS: private void dynamicTopLevelCall(Token tok) { char firstChar = tok.image.charAt(0); - if (autodetectTagSyntax && !directiveSyntaxEstablished) { + if (autodetectTagSyntax && !tagSyntaxEstablished) { squBracTagSyntax = (firstChar == '['); } if (squBracTagSyntax && firstChar == '<') { @@ -592,7 +607,7 @@ TOKEN_MGR_DECLS: tok.kind = STATIC_TEXT_NON_WS; return; } - directiveSyntaxEstablished = true; + tagSyntaxEstablished = true; SwitchTo(NO_SPACE_EXPRESSION); } @@ -633,9 +648,6 @@ TOKEN_MGR_DECLS: } private void endInterpolation(Token closingTk) { - if (postInterpolationLexState == -1) { - throw newUnexpectedClosingTokenException(closingTk); - } SwitchTo(postInterpolationLexState); postInterpolationLexState = -1; } @@ -674,9 +686,9 @@ TOKEN_MGR_DECLS: } private void ftlHeader(Token matchedToken) { - if (!directiveSyntaxEstablished) { + if (!tagSyntaxEstablished) { squBracTagSyntax = matchedToken.image.charAt(0) == '['; - directiveSyntaxEstablished = true; + tagSyntaxEstablished = true; autodetectTagSyntax = false; } String img = matchedToken.image; @@ -869,9 +881,9 @@ TOKEN: { char firstChar = matchedToken.image.charAt(0); - if (!directiveSyntaxEstablished && autodetectTagSyntax) { + if (!tagSyntaxEstablished && autodetectTagSyntax) { squBracTagSyntax = (firstChar == '['); - directiveSyntaxEstablished = true; + tagSyntaxEstablished = true; } if (firstChar == '<' && squBracTagSyntax) { @@ -1090,18 +1102,18 @@ TOKEN: { if (bracketNesting > 0) { --bracketNesting; - } else if (interpolationSyntax == InterpolationSyntax.SQUARE_BRACKET - && (postInterpolationLexState != -1 || !squBracTagSyntax)) { + } else if (interpolationSyntax == InterpolationSyntax.SQUARE_BRACKET && postInterpolationLexState != -1) { endInterpolation(matchedToken); - } else { - // TODO [FM3] Don't mimic this glitch anymore. - // Glitch where you can close any tag with `]`, like `<#if x]`. We keep it for backward compatibility. + } else if (squBracTagSyntax + && postInterpolationLexState == -1 /* We're in an interpolation => We aren't in a tag */) { matchedToken.kind = DIRECTIVE_END; if (inFTLHeader) { eatNewline(); inFTLHeader = false; } SwitchTo(DEFAULT); + } else { + throw newUnexpectedClosingTokenException(matchedToken); } } | @@ -1128,7 +1140,7 @@ TOKEN: { if (curlyBracketNesting > 0) { --curlyBracketNesting; - } else if (interpolationSyntax != InterpolationSyntax.SQUARE_BRACKET) { + } else if (interpolationSyntax != InterpolationSyntax.SQUARE_BRACKET && postInterpolationLexState != -1) { endInterpolation(matchedToken); } else { throw newUnexpectedClosingTokenException(matchedToken); @@ -1321,19 +1333,46 @@ TOKEN: { <DIRECTIVE_END : ">"> { - if (inFTLHeader) eatNewline(); - inFTLHeader = false; - if (squBracTagSyntax) { + if (inFTLHeader) { + eatNewline(); + inFTLHeader = false; + } + if (squBracTagSyntax || postInterpolationLexState != -1 /* We are in an interpolation */) { matchedToken.kind = NATURAL_GT; } else { + if (tagSyntaxEstablished) { + if (squBracTagSyntax) { + throw new TokenMgrError( + "The tag shouldn't end with \"" + image.charAt(image.length() - 1) + "\".", + TokenMgrError.LEXICAL_ERROR, + matchedToken.beginLine, matchedToken.beginColumn, + matchedToken.endLine, matchedToken.endColumn); + } + } + SwitchTo(DEFAULT); } } | <EMPTY_DIRECTIVE_END : "/>" | "/]"> { - if (inFTLHeader) eatNewline(); - inFTLHeader = false; + if (inFTLHeader) { + eatNewline(); + inFTLHeader = false; + } + + if (tagSyntaxEstablished) { + String image = matchedToken.image; + char lastChar = image.charAt(image.length() - 1); + if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') { + throw new TokenMgrError( + "The tag shouldn't end with \""+ lastChar + "\".", + TokenMgrError.LEXICAL_ERROR, + matchedToken.beginLine, matchedToken.beginColumn, + matchedToken.endLine, matchedToken.endColumn); + } + } + SwitchTo(DEFAULT); } }