blob: 16232c13f75aa80a81a8e5a9ccf399d6733ae33b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package freemarker.core;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.junit.Test;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.test.TemplateTest;
public class CamelCaseTest extends TemplateTest {
@Test
public void camelCaseSpecialVars() throws IOException, TemplateException {
getConfiguration().setOutputEncoding("utf-8");
getConfiguration().setURLEscapingCharset("iso-8859-1");
getConfiguration().setLocale(Locale.GERMANY);
assertOutput("${.dataModel?isHash?c}", "true");
assertOutput("${.data_model?is_hash?c}", "true");
assertOutput("${.localeObject.toString()}", "de_DE");
assertOutput("${.locale_object.toString()}", "de_DE");
assertOutput("${.templateName?length}", "0");
assertOutput("${.template_name?length}", "0");
assertOutput("${.outputEncoding}", "utf-8");
assertOutput("${.output_encoding}", "utf-8");
assertOutput("${.outputFormat}", UndefinedOutputFormat.INSTANCE.getName());
assertOutput("${.output_format}", UndefinedOutputFormat.INSTANCE.getName());
assertOutput("${.urlEscapingCharset}", "iso-8859-1");
assertOutput("${.url_escaping_charset}", "iso-8859-1");
assertOutput("${.currentNode!'-'}", "-");
assertOutput("${.current_node!'-'}", "-");
}
@Test
public void camelCaseSpecialVarsInErrorMessage() throws IOException, TemplateException {
assertErrorContains("${.fooBar}", "dataModel", "\\!data_model");
assertErrorContains("${.foo_bar}", "data_model", "\\!dataModel");
// [2.4] If camel case will be the recommended style, then this need to be inverted:
assertErrorContains("${.foo}", "data_model", "\\!dataModel");
assertErrorContains("<#if x><#elseIf y></#if>${.foo}", "dataModel", "\\!data_model");
assertErrorContains("<#if x><#elseif y></#if>${.foo}", "data_model", "\\!dataModel");
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertErrorContains("${.foo}", "dataModel", "\\!data_model");
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertErrorContains("${.foo}", "data_model", "\\!dataModel");
}
@Test
public void camelCaseSettingNames() throws IOException, TemplateException {
assertOutput("<#setting booleanFormat='Y,N'>${true} <#setting booleanFormat='+,-'>${true}", "Y +");
assertOutput("<#setting boolean_format='Y,N'>${true} <#setting boolean_format='+,-'>${true}", "Y +");
// Still works inside ?interpret
assertOutput("<@r\"<#setting booleanFormat='Y,N'>${true}\"?interpret />", "Y");
}
@Test
public void camelCaseFtlHeaderParameters() throws IOException, TemplateException {
getConfiguration().setOutputEncoding("utf-8");
assertOutput(
"<#ftl "
+ "stripWhitespace=false "
+ "stripText=true "
+ "strictSyntax=true "
+ "outputFormat='" + HTMLOutputFormat.INSTANCE.getName() + "' "
+ "autoEsc=true "
+ "nsPrefixes={} "
+ ">\nx\n<#if true>\n${.outputFormat}\n</#if>\n",
"\nHTML\n");
assertOutput(
"<#ftl "
+ "strip_whitespace=false "
+ "strip_text=true "
+ "strict_syntax=true "
+ "output_format='" + HTMLOutputFormat.INSTANCE.getName() + "' "
+ "auto_esc=true "
+ "ns_prefixes={} "
+ ">\nx\n<#if true>\n${.output_format}\n</#if>\n",
"\nHTML\n");
assertErrorContains("<#ftl strip_text=true xmlns={}>", "ns_prefixes", "\\!nsPrefixes");
assertErrorContains("<#ftl stripText=true xmlns={}>", "nsPrefixes");
assertErrorContains("<#ftl stripWhitespace=true strip_text=true>", "naming convention");
assertErrorContains("<#ftl strip_whitespace=true stripText=true>", "naming convention");
assertErrorContains("<#ftl stripWhitespace=true>${.foo_bar}", "naming convention");
assertErrorContains("<#ftl strip_whitespace=true>${.fooBar}", "naming convention");
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertErrorContains("<#ftl strip_whitespace=true>", "naming convention");
assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8");
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertErrorContains("<#ftl stripWhitespace=true>", "naming convention");
assertOutput("<#ftl strip_whitespace=true>${.output_encoding}", "utf-8");
getConfiguration().setNamingConvention(Configuration.AUTO_DETECT_NAMING_CONVENTION);
assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8");
assertOutput("<#ftl encoding='iso-8859-1' stripWhitespace=true>${.outputEncoding}", "utf-8");
assertOutput("<#ftl stripWhitespace=true encoding='iso-8859-1'>${.outputEncoding}", "utf-8");
assertOutput("<#ftl encoding='iso-8859-1' strip_whitespace=true>${.output_encoding}", "utf-8");
assertOutput("<#ftl strip_whitespace=true encoding='iso-8859-1'>${.output_encoding}", "utf-8");
}
@Test
public void camelCaseSettingNamesInErrorMessages() throws IOException, TemplateException {
assertErrorContains("<#setting fooBar=1>", "booleanFormat", "\\!boolean_format");
assertErrorContains("<#setting foo_bar=1>", "boolean_format", "\\!booleanFormat");
// [2.4] If camel case will be the recommended style, then this need to be inverted:
assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat");
assertErrorContains("<#if x><#elseIf y></#if><#setting foo=1>", "booleanFormat", "\\!boolean_format");
assertErrorContains("<#if x><#elseif y></#if><#setting foo=1>", "boolean_format", "\\!booleanFormat");
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertErrorContains("<#setting foo=1>", "booleanFormat", "\\!boolean_format");
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat");
}
@Test
public void camelCaseIncludeParameters() throws IOException, TemplateException {
assertOutput("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]", "[]");
assertOutput("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]", "[]");
assertErrorContains("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]",
"naming convention", "ignore_missing");
assertErrorContains("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]",
"naming convention", "ignoreMissing");
}
@Test
public void specialVarsHasBothNamingStyle() throws IOException, TemplateException {
assertContainsBothNamingStyles(
new HashSet(Arrays.asList(BuiltinVariable.SPEC_VAR_NAMES)),
new NamePairAssertion() { public void assertPair(String name1, String name2) { } });
}
@Test
public void camelCaseBuiltIns() throws IOException, TemplateException {
assertOutput("${'x'?upperCase}", "X");
assertOutput("${'x'?upper_case}", "X");
}
@Test
public void stringLiteralInterpolation() throws IOException, TemplateException {
assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
getConfiguration().setSharedVariable("x", "x");
assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
assertOutput("${x?upperCase} ${'-${x?upperCase}-'}", "X -X-");
assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X");
assertOutput("${x?upper_case} ${'-${x?upper_case}-'}", "X -X-");
assertErrorContains("${'-${x?upper_case}-'} ${x?upperCase}",
"naming convention", "legacy", "upperCase", "detection", "9");
assertErrorContains("${x?upper_case} ${'-${x?upperCase}-'}",
"naming convention", "legacy", "upperCase", "detection", "5");
assertErrorContains("${'-${x?upperCase}-'} ${x?upper_case}",
"naming convention", "camel", "upper_case");
assertErrorContains("${x?upperCase} ${'-${x?upper_case}-'}",
"naming convention", "camel", "upper_case");
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
assertErrorContains("${'-${x?upper_case}-'}",
"naming convention", "camel", "upper_case", "\\!detection");
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X");
assertErrorContains("${'-${x?upperCase}-'}",
"naming convention", "legacy", "upperCase", "\\!detection");
}
@Test
public void evalAndInterpret() throws IOException, TemplateException {
assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
// The naming convention detected doesn't affect the enclosing template's naming convention.
// - ?eval:
assertOutput("${\"'x'?upperCase\"?eval}${'x'?upper_case}", "XX");
assertOutput("${\"'x'?upper_case\"?eval}${'x'?upperCase}", "XX");
assertOutput("${'x'?upperCase}${\"'x'?upper_case\"?eval}", "XX");
assertErrorContains("${\"'x'\n?upperCase\n?is_string\"?eval}",
"naming convention", "camel", "upperCase", "is_string", "line 2", "line 3");
// - ?interpret:
assertOutput("<@r\"${'x'?upperCase}\"?interpret />${'x'?upper_case}", "XX");
assertOutput("<@r\"${'x'?upper_case}\"?interpret />${'x'?upperCase}", "XX");
assertOutput("${'x'?upper_case}<@r\"${'x'?upperCase}\"?interpret />", "XX");
assertErrorContains("<@r\"${'x'\n?upperCase\n?is_string}\"?interpret />",
"naming convention", "camel", "upperCase", "is_string", "line 2", "line 3");
// Will be inherited by ?eval-ed/?interpreted fragments:
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
// - ?eval:
assertErrorContains("${\"'x'?upper_case\"?eval}", "naming convention", "camel", "upper_case");
assertOutput("${\"'x'?upperCase\"?eval}", "X");
// - ?interpret:
assertErrorContains("<@r\"${'x'?upper_case}\"?interpret />", "naming convention", "camel", "upper_case");
assertOutput("<@r\"${'x'?upperCase}\"?interpret />", "X");
// Again, will be inherited by ?eval-ed/?interpreted fragments:
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
// - ?eval:
assertErrorContains("${\"'x'?upperCase\"?eval}", "naming convention", "legacy", "upperCase");
assertOutput("${\"'x'?upper_case\"?eval}", "X");
// - ?interpret:
assertErrorContains("<@r\"${'x'?upperCase}\"?interpret />", "naming convention", "legacy", "upperCase");
assertOutput("<@r\"${'x'?upper_case}\"?interpret />", "X");
}
@Test
public void camelCaseBuiltInErrorMessage() throws IOException, TemplateException {
assertErrorContains("${'x'?upperCasw}", "upperCase", "\\!upper_case");
assertErrorContains("${'x'?upper_casw}", "upper_case", "\\!upperCase");
// [2.4] If camel case will be the recommended style, then this need to be inverted:
assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase");
assertErrorContains("<#if x><#elseIf y></#if> ${'x'?foo}", "upperCase", "\\!upper_case");
assertErrorContains("<#if x><#elseif y></#if>${'x'?foo}", "upper_case", "\\!upperCase");
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertErrorContains("${'x'?foo}", "upperCase", "\\!upper_case");
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase");
}
@Test
public void builtInsHasBothNamingStyle() throws IOException, TemplateException {
assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() {
public void assertPair(String name1, String name2) {
BuiltIn bi1 = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name1);
BuiltIn bi2 = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name2);
assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't belong to the same BI object.",
bi1 == bi2);
}
});
}
private void assertContainsBothNamingStyles(Set<String> names, NamePairAssertion namePairAssertion) {
Set<String> underscoredNamesWithCamelCasePair = new HashSet<String>();
for (String name : names) {
if (_CoreStringUtils.getIdentifierNamingConvention(name) == Configuration.CAMEL_CASE_NAMING_CONVENTION) {
String underscoredName = correctIsoBIExceptions(_CoreStringUtils.camelCaseToUnderscored(name));
assertTrue(
"Missing underscored variation \"" + underscoredName + "\" for \"" + name + "\".",
names.contains(underscoredName));
assertTrue(underscoredNamesWithCamelCasePair.add(underscoredName));
namePairAssertion.assertPair(name, underscoredName);
}
}
for (String name : names) {
if (_CoreStringUtils.getIdentifierNamingConvention(name) == Configuration.LEGACY_NAMING_CONVENTION) {
assertTrue("Missing camel case variation for \"" + name + "\".",
underscoredNamesWithCamelCasePair.contains(name));
}
}
}
private String correctIsoBIExceptions(String underscoredName) {
return underscoredName.replace("_n_z", "_nz").replace("_f_z", "_fz");
}
@Test
public void camelCaseDirectivesNonStrict() throws IOException, TemplateException {
getConfiguration().setStrictSyntaxMode(false);
assertOutput(
"<list 1..4 as x><if x == 1>one <elseIf x == 2>two <elseif x == 3>three <else>other </if></list>",
"one <elseIf x == 2>two other three other ");
assertOutput(
"<escape x as x?upper_case>${'a'}<noEscape>${'b'}</noEscape></escape> "
+ "<escape x as x?upper_case>${'a'}<noescape>${'b'}</noescape></escape>",
"A<noEscape>B</noEscape> Ab");
assertOutput(
"<noParse>${1}</noParse> <noparse>${1}</noparse>",
"<noParse>1</noParse> ${1}");
assertOutput(
"<forEach x in 1..3>${x!'?'}</forEach> <foreach x in 1..3>${x}</foreach>",
"<forEach x in 1..3>?</forEach> 123");
assertOutput("<foreach x in 1..3>${x}</foreach> <#foreach x in 1..3>${x}</#foreach>",
"123 123");
assertErrorContains("<foreach x in 1..3>${x}</foreach> <#forEach x in 1..3>${x}</#forEach>",
"naming convention", "legacy", "#forEach");
assertErrorContains("<#forEach x in 1..3>${x}</#forEach> <foreach x in 1..3>${x}</foreach>",
"naming convention", "camel", "foreach");
camelCaseDirectives();
}
@Test
public void camelCaseDirectives() throws IOException, TemplateException {
camelCaseDirectives(false);
getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
camelCaseDirectives(true);
}
private void camelCaseDirectives(boolean squared) throws IOException, TemplateException {
assertOutput(
squared("<#list 1..4 as x><#if x == 1>one <#elseIf x == 2>two <#elseIf x == 3>three "
+ "<#else>more</#if></#list>", squared),
"one two three more");
assertOutput(
squared("<#list 1..4 as x><#if x == 1>one <#elseif x == 2>two <#elseif x == 3>three "
+ "<#else>more</#if></#list>", squared),
"one two three more");
assertOutput(
squared("<#escape x as x?upperCase>${'a'}<#noEscape>${'b'}</#noEscape></#escape>", squared),
"Ab");
assertOutput(
squared("<#escape x as x?upper_case>${'a'}<#noescape>${'b'}</#noescape></#escape>", squared),
"Ab");
assertOutput(
squared("<#noParse></#noparse></#noParse>", squared),
squared("</#noparse>", squared));
assertOutput(
squared("<#noparse></#noParse></#noparse>", squared),
squared("</#noParse>", squared));
assertOutput(
squared("<#forEach x in 1..3>${x}</#forEach>", squared),
"123");
assertOutput(
squared("<#foreach x in 1..3>${x}</#foreach>", squared),
"123");
}
private String squared(String ftl, boolean squared) {
return squared ? ftl.replace('<', '[').replace('>', ']') : ftl;
}
@Test
public void explicitNamingConvention() throws IOException, TemplateException {
explicitNamingConvention(false);
explicitNamingConvention(true);
}
private void explicitNamingConvention(boolean squared) throws IOException, TemplateException {
if (squared) {
getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
}
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertErrorContains(
squared("<#if true>t<#elseif false>f</#if>", squared),
"naming convention", "camel", "#elseif");
assertOutput(
squared("<#if true>t<#elseIf false>f</#if>", squared),
"t");
assertErrorContains(
squared("<#noparse>${x}</#noparse>", squared),
"naming convention", "camel", "#noparse");
assertOutput(
squared("<#noParse>${x}</#noParse>", squared),
"${x}");
assertErrorContains(
squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared),
"naming convention", "camel", "#noescape");
assertOutput(
squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared),
"1");
assertErrorContains(
squared("<#foreach x in 1..3>${x}</#foreach>", squared),
"naming convention", "camel", "#foreach");
assertOutput(
squared("<#forEach x in 1..3>${x}</#forEach>", squared),
"123");
// ---
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertErrorContains(
squared("<#if true>t<#elseIf false>f</#if>", squared),
"naming convention", "legacy", "#elseIf");
assertOutput(
squared("<#if true>t<#elseif false>f</#if>", squared),
"t");
assertErrorContains(
squared("<#noParse>${x}</#noParse>", squared),
"naming convention", "legacy", "#noParse");
assertOutput(
squared("<#noparse>${x}</#noparse>", squared),
"${x}");
assertErrorContains(
squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared),
"naming convention", "legacy", "#noEscape");
assertOutput(
squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared),
"1");
assertErrorContains(
squared("<#forEach x in 1..3>${x}</#forEach>", squared),
"naming convention", "legacy", "#forEach");
assertOutput(
squared("<#foreach x in 1..3>${x}</#foreach>", squared),
"123");
}
@Test
public void inconsistentAutoDetectedNamingConvention() {
assertErrorContains(
"<#if x><#elseIf y><#elseif z></#if>",
"naming convention", "camel");
assertErrorContains(
"<#if x><#elseif y><#elseIf z></#if>",
"naming convention", "legacy");
assertErrorContains(
"<#if x><#elseIf y></#if><#noparse></#noparse>",
"naming convention", "camel");
assertErrorContains(
"<#if x><#elseif y></#if><#noParse></#noParse>",
"naming convention", "legacy");
assertErrorContains(
"<#if x><#elseif y><#elseIf z></#if>",
"naming convention", "legacy");
assertErrorContains(
"<#escape x as x + 1><#noEscape></#noescape></#escape>",
"naming convention", "camel");
assertErrorContains(
"<#escape x as x + 1><#noEscape></#noEscape><#noescape></#noescape></#escape>",
"naming convention", "camel");
assertErrorContains(
"<#escape x as x + 1><#noescape></#noEscape></#escape>",
"naming convention", "legacy");
assertErrorContains(
"<#escape x as x + 1><#noescape></#noescape><#noEscape></#noEscape></#escape>",
"naming convention", "legacy");
assertErrorContains(
"<#forEach x in 1..3>${x}</#foreach>",
"naming convention", "camel");
assertErrorContains(
"<#forEach x in 1..3>${x}</#forEach><#foreach x in 1..3>${x}</#foreach>",
"naming convention", "camel");
assertErrorContains(
"<#foreach x in 1..3>${x}</#forEach>",
"naming convention", "legacy");
assertErrorContains(
"<#foreach x in 1..3>${x}</#foreach><#forEach x in 1..3>${x}</#forEach>",
"naming convention", "legacy");
assertErrorContains("${x?upperCase?is_string}",
"naming convention", "camel", "upperCase", "is_string");
assertErrorContains("${x?upper_case?isString}",
"naming convention", "legacy", "upper_case", "isString");
assertErrorContains("<#setting outputEncoding='utf-8'>${x?is_string}",
"naming convention", "camel", "outputEncoding", "is_string");
assertErrorContains("<#setting output_encoding='utf-8'>${x?isString}",
"naming convention", "legacy", "output_encoding", "isString");
assertErrorContains("${x?isString}<#setting output_encoding='utf-8'>",
"naming convention", "camel", "isString", "output_encoding");
assertErrorContains("${x?is_string}<#setting outputEncoding='utf-8'>",
"naming convention", "legacy", "is_string", "outputEncoding");
assertErrorContains("${.outputEncoding}${x?is_string}",
"naming convention", "camel", "outputEncoding", "is_string");
assertErrorContains("${.output_encoding}${x?isString}",
"naming convention", "legacy", "output_encoding", "isString");
assertErrorContains("${x?upperCase}<#noparse></#noparse>",
"naming convention", "camel", "upperCase", "noparse");
assertErrorContains("${x?upper_case}<#noParse></#noParse>",
"naming convention", "legacy", "upper_case", "noParse");
}
private interface NamePairAssertion {
void assertPair(String name1, String name2);
}
}