More helpful error messages if someone uses non-camel case names.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/51e4bfe3 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/51e4bfe3 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/51e4bfe3 Branch: refs/heads/3 Commit: 51e4bfe3c9cd2a2063860978c7f7e9b48c64871c Parents: a04ab89 Author: ddekany <ddek...@apache.org> Authored: Sun Jul 16 17:00:52 2017 +0200 Committer: ddekany <ddek...@apache.org> Committed: Sun Jul 16 17:00:52 2017 +0200 ---------------------------------------------------------------------- .../core/FM2ASTToFM3SourceConverter.java | 10 +-- .../freemarker/converter/ConverterUtils.java | 28 ------- .../freemarker/converter/ConverterUtilTest.java | 18 ----- .../core/ParsingErrorMessagesTest.java | 41 +++++++++- .../freemarker/core/SpecialVariableTest.java | 2 +- .../freemarker/core/util/StringUtilTest.java | 15 +++- .../apache/freemarker/core/ASTDirSetting.java | 43 +++++++--- .../apache/freemarker/core/ASTExpBuiltIn.java | 67 +++++++++++----- .../freemarker/core/ASTExpBuiltInVariable.java | 55 +++++++------ .../org/apache/freemarker/core/MessageUtil.java | 3 + .../freemarker/core/util/_StringUtil.java | 48 ++++++----- freemarker-core/src/main/javacc/FTL.jj | 84 ++++++++++++++------ 12 files changed, 249 insertions(+), 165 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java index 03eb9c5..c60c31f 100644 --- a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java +++ b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java @@ -231,7 +231,7 @@ public class FM2ASTToFM3SourceConverter { } private String convertFtlHeaderParamName(String name) throws ConverterException { - String converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + String converted = name.indexOf('_') == -1 ? name : _StringUtil.snakeCaseToCamelCase(name); if (converted.equals("attributes")) { converted = "customSettings"; } @@ -605,7 +605,7 @@ public class FM2ASTToFM3SourceConverter { } private String convertSettingName(String name, TemplateObject node) throws ConverterException { - String converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + String converted = name.indexOf('_') == -1 ? name : _StringUtil.snakeCaseToCamelCase(name); if (converted.equals("classicCompatible")) { throw new UnconvertableLegacyFeatureException("There \"classicCompatible\" setting doesn't exist in " @@ -1513,7 +1513,7 @@ public class FM2ASTToFM3SourceConverter { } private String convertBuiltInVariableName(String name) throws ConverterException { - String converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + String converted = name.indexOf('_') == -1 ? name : _StringUtil.snakeCaseToCamelCase(name); // Will replace removed names here @@ -1546,7 +1546,7 @@ public class FM2ASTToFM3SourceConverter { static { Map<String, String> domKeyMapping = new HashMap<>(); for (String atAtKey : AtAtKeyAccessor.getAtAtKeys()) { - String atAtKeyCC = ConverterUtils.snakeCaseToCamelCase(atAtKey); + String atAtKeyCC = _StringUtil.snakeCaseToCamelCase(atAtKey); if (!atAtKeyCC.equals(atAtKey)) { domKeyMapping.put(atAtKey, atAtKeyCC); } @@ -1858,7 +1858,7 @@ public class FM2ASTToFM3SourceConverter { private String convertBuiltInName(String name) throws ConverterException { String converted = IRREGULAR_BUILT_IN_NAME_CONVERSIONS.get(name); if (converted == null) { - converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + converted = name.indexOf('_') == -1 ? name : _StringUtil.snakeCaseToCamelCase(name); } if (!fm3BuiltInNames.contains(converted)) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterUtils.java b/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterUtils.java index aa837b6..cbe3866 100644 --- a/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterUtils.java +++ b/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterUtils.java @@ -25,34 +25,6 @@ public final class ConverterUtils { // } - public static String snakeCaseToCamelCase(String s) { - if (s == null) { - return null; - } - - int wordEndIdx = s.indexOf('_'); - if (wordEndIdx == -1) { - return s.toLowerCase(); - } - - StringBuilder sb = new StringBuilder(s.length()); - int wordStartIdx = 0; - do { - if (wordStartIdx < wordEndIdx) { - char wordStartC = s.charAt(wordStartIdx); - sb.append(sb.length() != 0 ? Character.toUpperCase(wordStartC) : Character.toLowerCase(wordStartC)); - sb.append(s.substring(wordStartIdx + 1, wordEndIdx).toLowerCase()); - } - - wordStartIdx = wordEndIdx + 1; - wordEndIdx = s.indexOf('_', wordStartIdx); - if (wordEndIdx == -1) { - wordEndIdx = s.length(); - } - } while (wordStartIdx < s.length()); - return sb.toString(); - } - public static boolean isUpperCaseLetter(char c) { return Character.isUpperCase(c) && Character.isLetter(c); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-converter/src/test/java/org/freemarker/converter/ConverterUtilTest.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/ConverterUtilTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/ConverterUtilTest.java index 811ea18..3646834 100644 --- a/freemarker-converter/src/test/java/org/freemarker/converter/ConverterUtilTest.java +++ b/freemarker-converter/src/test/java/org/freemarker/converter/ConverterUtilTest.java @@ -19,24 +19,6 @@ package org.freemarker.converter; -import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertEquals; - -import org.apache.freemarker.converter.ConverterUtils; -import org.junit.Test; - public class ConverterUtilTest { - @Test - public void snakeCaseToCamelCase() { - assertNull(ConverterUtils.snakeCaseToCamelCase(null)); - assertEquals("", ConverterUtils.snakeCaseToCamelCase("")); - assertEquals("x", ConverterUtils.snakeCaseToCamelCase("x")); - assertEquals("xxx", ConverterUtils.snakeCaseToCamelCase("xXx")); - assertEquals("fooBar", ConverterUtils.snakeCaseToCamelCase("foo_bar")); - assertEquals("fooBar", ConverterUtils.snakeCaseToCamelCase("FOO_BAR")); - assertEquals("fooBar", ConverterUtils.snakeCaseToCamelCase("_foo__bar_")); - assertEquals("aBC", ConverterUtils.snakeCaseToCamelCase("a_b_c")); - } - } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/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 de746d5..3e77769 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 @@ -45,6 +45,10 @@ public class ParsingErrorMessagesTest { assertErrorContains("<#foo />", "nknown directive", "#foo"); assertErrorContains("<#set x = 1 />", "nknown directive", "#set", "#assign"); assertErrorContains("<#iterator></#iterator>", "nknown directive", "#iterator", "#list"); + assertErrorContains("<#if x><#elseif y></#if>", "nknown directive", "#elseIf"); + assertErrorContains("<#outputformat 'HTML'></#outputformat>", "nknown directive", "#outputFormat"); + assertErrorContains("<#autoesc></#autoesc>", "nknown directive", "#autoEsc"); + assertErrorContains("<#noautoesc></#noautoesc>", "nknown directive", "#noAutoEsc"); } @Test @@ -70,7 +74,42 @@ public class ParsingErrorMessagesTest { assertErrorContains("${(blah", "\"(\"", "unclosed"); assertErrorContains("${blah", "\"{\"", "unclosed"); } - + + @Test + public void testBuiltInWrongNames() { + assertErrorContains("${x?lower_case}", "camel case", "The correct name is: lowerCase"); + assertErrorContains("${x?iso_utc_nz}", "camel case", "The correct name is: isoUtcNZ"); + assertErrorContains("${x?no_such_name}", "camel case", "\\!The correct name is:", "alphabetical list"); + assertErrorContains("${x?nosuchname}", "\\!camel case", "\\!The correct name is:", "alphabetical list"); + } + + @Test + public void testSettingWrongNames() { + assertErrorContains("<#setting time_format='HHmm'>", "camel case", "The correct name is: timeFormat", + "\\!setting names are:"); + assertErrorContains("<#setting no_such_name=1>", "camel case", "\\!The correct name is:", + "setting names are:"); + assertErrorContains("<#setting nosuchname=1>", "\\!The correct name is:", "\\!camel case", + "setting names are:"); + } + + @Test + public void testSpecialVariableWrongNames() { + assertErrorContains("${.data_model}", "camel case", "The correct name is: dataModel", + "\\!variable names are:"); + assertErrorContains("${.no_such_name}", "camel case", "\\!The correct name is:", + "variable names are:"); + assertErrorContains("${.nosuchname}", "\\!camel case", "\\!The correct name is:", + "variable names are:"); + } + + @Test + public void testFtlParameterWrongNames() { + assertErrorContains("<#ftl strip_whitespace=false>", "camel case", "The correct name is: stripWhitespace"); + assertErrorContains("<#ftl no_such_name=1>", "camel case", "\\!The correct name is:"); + assertErrorContains("<#ftl nosuchname=1>", "\\!camel case", "\\!The correct name is:"); + } + @Test public void testInterpolatingClosingsErrors() { assertErrorContains("${x", "unclosed"); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java index 664de58..270701a 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java @@ -118,7 +118,7 @@ public class SpecialVariableTest extends TemplateTest { "false true false " + "true false true false true"); - assertErrorContains("${.autoEscaping}", "You may meant: \"autoEsc\""); + assertErrorContains("${.autoEscaping}", "The correct name is: autoEsc"); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java index 2a0ae9d..1d7043c 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java @@ -19,6 +19,7 @@ package org.apache.freemarker.core.util; +import static junit.framework.TestCase.assertNull; import static org.junit.Assert.*; import java.io.IOException; @@ -399,5 +400,17 @@ public class StringUtilTest { assertEquals("x\ny", _StringUtil.normalizeEOLs("x\ry")); assertEquals("\n\n\n\n\n\n", _StringUtil.normalizeEOLs("\n\r\r\r\n\r\n\r")); } - + + @Test + public void snakeCaseToCamelCase() { + assertNull(_StringUtil.snakeCaseToCamelCase(null)); + assertEquals("", _StringUtil.snakeCaseToCamelCase("")); + assertEquals("x", _StringUtil.snakeCaseToCamelCase("x")); + assertEquals("xxx", _StringUtil.snakeCaseToCamelCase("xXx")); + assertEquals("fooBar", _StringUtil.snakeCaseToCamelCase("foo_bar")); + assertEquals("fooBar", _StringUtil.snakeCaseToCamelCase("FOO_BAR")); + assertEquals("fooBar", _StringUtil.snakeCaseToCamelCase("_foo__bar_")); + assertEquals("aBC", _StringUtil.snakeCaseToCamelCase("a_b_c")); + } + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java index 0a8a160..134f6fe 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java @@ -19,12 +19,13 @@ package org.apache.freemarker.core; -import java.util.Arrays; +import java.util.Set; import org.apache.freemarker.core.model.TemplateBooleanModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.util._SortedArraySet; import org.apache.freemarker.core.util._StringUtil; /** @@ -35,7 +36,7 @@ final class ASTDirSetting extends ASTDirective { private final String key; private final ASTExpression value; - static final String[] SETTING_NAMES = new String[] { + static final Set<String> SETTING_NAMES = new _SortedArraySet<String>( // Must be sorted alphabetically! MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY, MutableProcessingConfiguration.DATE_FORMAT_KEY, @@ -46,13 +47,13 @@ final class ASTDirSetting extends ASTDirective { MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, MutableProcessingConfiguration.TIME_FORMAT_KEY, MutableProcessingConfiguration.TIME_ZONE_KEY, - MutableProcessingConfiguration.URL_ESCAPING_CHARSET_KEY, - }; + MutableProcessingConfiguration.URL_ESCAPING_CHARSET_KEY + ); ASTDirSetting(Token keyTk, FMParserTokenManager tokenManager, ASTExpression value, Configuration cfg) throws ParseException { String key = keyTk.image; - if (Arrays.binarySearch(SETTING_NAMES, key) < 0) { + if (!SETTING_NAMES.contains(key)) { StringBuilder sb = new StringBuilder(); if (Configuration.ExtendableBuilder.getSettingNames().contains(key)) { sb.append("The setting name is recognized, but changing this setting from inside a template isn't " @@ -60,17 +61,33 @@ final class ASTDirSetting extends ASTDirective { } else { sb.append("Unknown setting name: "); sb.append(_StringUtil.jQuote(key)).append("."); - sb.append(" The allowed setting names are: "); - boolean first = true; - for (String correctName : SETTING_NAMES) { - if (first) { - first = false; - } else { - sb.append(", "); + String correctedKey; + if (key.indexOf('_') != -1) { + sb.append(MessageUtil.FM3_SNAKE_CASE); + correctedKey = _StringUtil.snakeCaseToCamelCase(key); + if (!SETTING_NAMES.contains(correctedKey)) { + correctedKey = null; } + } else { + correctedKey = null; + } + + if (correctedKey != null) { + sb.append("\nThe correct name is: ").append(correctedKey); + } else { + sb.append("\nThe allowed setting names are: "); - sb.append(correctName); + boolean first = true; + for (String correctName : SETTING_NAMES) { + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(correctName); + } } } throw new ParseException(sb.toString(), null, keyTk); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java index 3f0454f..a6b2b77 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java @@ -304,34 +304,57 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { String key = keyTk.image; ASTExpBuiltIn bi = BUILT_INS_BY_NAME.get(key); if (bi == null) { - StringBuilder buf = new StringBuilder("Unknown built-in: ").append(_StringUtil.jQuote(key)).append(". "); - - buf.append( - "Help (latest version): http://freemarker.org/docs/ref_builtins.html; " - + "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n" - + "The alphabetical list of built-ins:"); - List<String> names = new ArrayList<>(BUILT_INS_BY_NAME.keySet().size()); - names.addAll(BUILT_INS_BY_NAME.keySet()); - Collections.sort(names); - char lastLetter = 0; + StringBuilder sb = new StringBuilder("Unknown built-in: ").append(_StringUtil.jQuote(key)).append("."); - boolean first = true; - for (String correctName : names) { - if (first) { - first = false; - } else { - buf.append(", "); + String correctedKey; + if (key.indexOf("_") != -1) { + sb.append(MessageUtil.FM3_SNAKE_CASE); + correctedKey = _StringUtil.snakeCaseToCamelCase(key); + if (!BUILT_INS_BY_NAME.containsKey(correctedKey)) { + if (correctedKey.length() > 1) { + correctedKey = correctedKey.substring(0, correctedKey.length() - 2) + + correctedKey.substring(correctedKey.length() - 2).toUpperCase(); + if (!BUILT_INS_BY_NAME.containsKey(correctedKey)) { + correctedKey = null; + } + } else { + correctedKey = null; + } } + } else { + correctedKey = null; + } + + if (correctedKey != null) { + sb.append("\nThe correct name is: ").append(correctedKey); + } else { + sb.append( + "\nHelp (latest version): http://freemarker.org/docs/ref_builtins.html; " + + "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n" + + "The alphabetical list of built-ins:"); + List<String> names = new ArrayList<>(BUILT_INS_BY_NAME.keySet().size()); + names.addAll(BUILT_INS_BY_NAME.keySet()); + Collections.sort(names); + char lastLetter = 0; + + boolean first = true; + for (String correctName : names) { + if (first) { + first = false; + } else { + sb.append(", "); + } - char firstChar = correctName.charAt(0); - if (firstChar != lastLetter) { - lastLetter = firstChar; - buf.append('\n'); + char firstChar = correctName.charAt(0); + if (firstChar != lastLetter) { + lastLetter = firstChar; + sb.append('\n'); + } + sb.append(correctName); } - buf.append(correctName); } - throw new ParseException(buf.toString(), null, keyTk); + throw new ParseException(sb.toString(), null, keyTk); } try { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java index c7afcda..f2b2537 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java @@ -20,8 +20,8 @@ package org.apache.freemarker.core; import java.nio.charset.Charset; -import java.util.Arrays; import java.util.Date; +import java.util.Set; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateHashModel; @@ -29,6 +29,7 @@ import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.impl.SimpleDate; import org.apache.freemarker.core.model.impl.SimpleScalar; +import org.apache.freemarker.core.util._SortedArraySet; import org.apache.freemarker.core.util._StringUtil; /** @@ -60,7 +61,8 @@ final class ASTExpBuiltInVariable extends ASTExpression { static final String URL_ESCAPING_CHARSET = "urlEscapingCharset"; static final String NOW = "now"; - static final String[] BUILT_IN_VARIABLE_NAMES = new String[] { + static final Set<String> BUILT_IN_VARIABLE_NAMES = new _SortedArraySet<>( + // Must be sorted alphabetically! AUTO_ESC, CURRENT_NODE, CURRENT_TEMPLATE_NAME, @@ -84,7 +86,7 @@ final class ASTExpBuiltInVariable extends ASTExpression { URL_ESCAPING_CHARSET, VARS, VERSION - }; + ); private final String name; private final TemplateModel parseTimeValue; @@ -93,35 +95,38 @@ final class ASTExpBuiltInVariable extends ASTExpression { throws ParseException { String name = nameTk.image; this.parseTimeValue = parseTimeValue; - if (Arrays.binarySearch(BUILT_IN_VARIABLE_NAMES, name) < 0) { + if (!BUILT_IN_VARIABLE_NAMES.contains(name)) { StringBuilder sb = new StringBuilder(); sb.append("Unknown special variable name: "); sb.append(_StringUtil.jQuote(name)).append("."); - { - String correctName; - if ( - name.equals("auto_escape") || name.equals("auto_escaping") || name.equals("autoEsc") || - name.equals("autoEscape") || name.equals("autoEscaping")) { - correctName = "autoEsc"; - } else { - correctName = null; - } - if (correctName != null) { - sb.append(" You may meant: "); - sb.append(_StringUtil.jQuote(correctName)).append("."); + String correctedName; + if (name.indexOf('_') != -1) { + sb.append(MessageUtil.FM3_SNAKE_CASE); + correctedName = _StringUtil.snakeCaseToCamelCase(name); + if (!BUILT_IN_VARIABLE_NAMES.contains(correctedName)) { + correctedName = null; } + } else if (name.equals("auto_escape") || name.equals("auto_escaping") || name.equals("autoEsc") + || name.equals("autoEscape") || name.equals("autoEscaping")) { + correctedName = "autoEsc"; + } else { + correctedName = null; } - - sb.append("\nThe allowed special variable names are: "); - boolean first = true; - for (final String correctName : BUILT_IN_VARIABLE_NAMES) { - if (first) { - first = false; - } else { - sb.append(", "); + + if (correctedName != null) { + sb.append("\nThe correct name is: ").append(correctedName); + } else { + sb.append("\nThe supported special variable names are: "); + boolean first = true; + for (final String supportedName : BUILT_IN_VARIABLE_NAMES) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(supportedName); } - sb.append(correctName); } throw new ParseException(sb.toString(), null, nameTk); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java index 6a2bc2f..8a90159 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java @@ -47,6 +47,9 @@ class MessageUtil { + "to specify which fields to display. " }; + static final String FM3_SNAKE_CASE = "\nThe name contains '_' character, but since FreeMarker 3 names defined " + + "by the template language use camel case (e.g. someExampleName)."; + static final String EMBEDDED_MESSAGE_BEGIN = "---begin-message---\n"; static final String EMBEDDED_MESSAGE_END = "\n---end-message---"; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtil.java index 08d7870..57ba42c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtil.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtil.java @@ -1573,33 +1573,31 @@ public class _StringUtil { } } - // [2.4] Won't be needed anymore - /** - * A deliberately very inflexible camel case to underscored converter; it must not convert improper camel case - * names to a proper underscored name. - */ - public static String camelCaseToUnderscored(String camelCaseName) { - int i = 0; - while (i < camelCaseName.length() && Character.isLowerCase(camelCaseName.charAt(i))) { - i++; + public static String snakeCaseToCamelCase(String s) { + if (s == null) { + return null; } - if (i == camelCaseName.length()) { - // No conversion needed - return camelCaseName; + + int wordEndIdx = s.indexOf('_'); + if (wordEndIdx == -1) { + return s.toLowerCase(); } - - StringBuilder sb = new StringBuilder(); - sb.append(camelCaseName.substring(0, i)); - while (i < camelCaseName.length()) { - final char c = camelCaseName.charAt(i); - if (_StringUtil.isUpperUSASCII(c)) { - sb.append('_'); - sb.append(Character.toLowerCase(c)); - } else { - sb.append(c); + + StringBuilder sb = new StringBuilder(s.length()); + int wordStartIdx = 0; + do { + if (wordStartIdx < wordEndIdx) { + char wordStartC = s.charAt(wordStartIdx); + sb.append(sb.length() != 0 ? Character.toUpperCase(wordStartC) : Character.toLowerCase(wordStartC)); + sb.append(s.substring(wordStartIdx + 1, wordEndIdx).toLowerCase()); } - i++; - } + + wordStartIdx = wordEndIdx + 1; + wordEndIdx = s.indexOf('_', wordStartIdx); + if (wordEndIdx == -1) { + wordEndIdx = s.length(); + } + } while (wordStartIdx < s.length()); return sb.toString(); } @@ -1623,5 +1621,5 @@ public class _StringUtil { } return NORMALIZE_EOLS_REGEXP.matcher(s).replaceAll("\n"); } - + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51e4bfe3/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 c289b74..37a66ae 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -841,8 +841,8 @@ TOKEN: if (dn.equals("set") || dn.equals("var")) { tip = "Use #assign or #local or #global, depending on the intented scope " + "(#assign is template-scope). " + PLANNED_DIRECTIVE_HINT; - } else if (dn.equals("else_if") || dn.equals("elif")) { - tip = "Use #elseIf."; + } else if (dn.equals("else_if") || dn.equals("elif") || dn.equals("elseif")) { + tip = "Use #elseIf instead."; } else if (dn.equals("no_escape")) { tip = "Use #noEscape instead."; } else if (dn.equals("method")) { @@ -859,6 +859,14 @@ TOKEN: tip = "You may meant #items."; } else if (dn.equals("separator") || dn.equals("separate") || dn.equals("separ")) { tip = "You may meant #sep."; + } else if (dn.equals("outputformat")) { + tip = "Use #outputFormat instead."; + } else if (dn.equals("noautoesc")) { + tip = "Use #noAutoEsc instead."; + } else if (dn.equals("autoesc")) { + tip = "Use #autoEsc instead."; + } else if (dn.equals("noparse")) { + tip = "Use #noParse instead."; } else { tip = "Help (latest version): http://freemarker.org/docs/ref_directive_alphaidx.html; " + "you're using FreeMarker " + Configuration.getVersion() + "."; @@ -3857,40 +3865,64 @@ void HeaderElement() : } catch (TemplateModelException tme) { } } else { + StringBuilder sb = new StringBuilder(); + sb.append("Unknown header parameter: ").append(ks); + + if (ks.indexOf('_') != -1) { + sb.append(MessageUtil.FM3_SNAKE_CASE); + } + String correctName; - String ksLC = ks.toLowerCase(); - if (ksLC.equals("charset") || ksLC.equals("source_encoding") - || ksLC.equals("sourcerncoding")) { + switch (ks.toLowerCase()) { + case "charset": + case "source_encoding": + case "sourcerncoding": correctName = "encoding"; - } else if (ksLC.equals("attributes") || ksLC.equals("customsettings") - || ksLC.equals("custom_settings") || ksLC.equals("settings")) { + break; + case "attributes": + case "customsettings": + case "custom_settings": + case "settings": correctName = "customSettings"; - } else if (ksLC.equalsIgnoreCase("strip_whitespace") - || ksLC.equalsIgnoreCase("stripwhitespace") - || ksLC.equalsIgnoreCase("remove_whitespace") - || ksLC.equalsIgnoreCase("removewhitespace")) { + break; + case "strip_whitespace": + case "stripwhitespace": + case "remove_whitespace": + case "removewhitespace": correctName = "stripWhitespace"; - } else if (ksLC.equalsIgnoreCase("strip_text") || ksLC.equalsIgnoreCase("striptext") - || ksLC.equalsIgnoreCase("remove_text") || ksLC.equalsIgnoreCase("removetext")) { + break; + case "strip_text": + case "striptext": + case "remove_text": + case "removetext": correctName = "stripText"; - } else if (ksLC.equals("xmlns") || ksLC.equalsIgnoreCase("ns_prefixes") - || ksLC.equalsIgnoreCase("nsprefixes")) { + break; + case "xmlns": + case "ns_prefixes": + case "nsprefixes": correctName = "nsPrefixes"; - } else if (ksLC.equalsIgnoreCase("output_format") - || ksLC.equalsIgnoreCase("outputformat")) { + break; + case "output_format": + case "outputformat": correctName = "outputFormat"; - } else if (ksLC.equals("autoEscape") || ksLC.equals("autoEscaping") - || ksLC.equals("autoesc") - || ksLC.equals("auto_escape") || ksLC.equals("auto_escaping") - || ksLC.equalsIgnoreCase("auto_esc")) { + break; + case "autoescape": + case "autoescaping": + case "autoesc": + case "auto_escape": + case "auto_escaping": + case "auto_esc": correctName = "autoEsc"; - } else { + break; + default: correctName = null; } - throw new ParseException( - "Unknown FTL header parameter: " + key.image - + (correctName == null ? "" : ". You may meant: " + correctName), - template, key); + + if (correctName != null) { + sb.append("\nThe correct name is: ").append(correctName); + } + + throw new ParseException(sb.toString(), template, key); } } }