From e29000e0b59069e2a6fc1240e03490c3647bdc47 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 18 Mar 2022 12:35:27 +0100 Subject: [PATCH] Qute message bundles - ignore loop metadate during validation - also ignore template globals - follows up on #23734 --- .../deployment/MessageBundleProcessor.java | 40 ++++++++++++------- ...MessageBundleExpressionValidationTest.java | 14 +++++-- .../io/quarkus/qute/LoopSectionHelper.java | 25 ++++++------ .../test/java/io/quarkus/qute/ParserTest.java | 7 +++- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 02e2614b34bd5..e9c95ca295adf 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -80,6 +80,7 @@ import io.quarkus.qute.Expression; import io.quarkus.qute.Expression.Part; import io.quarkus.qute.Expressions; +import io.quarkus.qute.LoopSectionHelper; import io.quarkus.qute.Namespaces; import io.quarkus.qute.Resolver; import io.quarkus.qute.deployment.QuteProcessor.LookupConfig; @@ -309,11 +310,14 @@ void initBundleContext(MessageBundleRecorder recorder, @BuildStep void validateMessageBundleMethods(TemplatesAnalysisBuildItem templatesAnalysis, List messageBundleMethods, + List templateGlobals, BuildProducer incorrectExpressions) { Map bundleMethods = messageBundleMethods.stream() .filter(MessageBundleMethodBuildItem::isValidatable) .collect(Collectors.toMap(MessageBundleMethodBuildItem::getTemplateId, Function.identity())); + Set globals = templateGlobals.stream().map(TemplateGlobalBuildItem::getName) + .collect(Collectors.toUnmodifiableSet()); for (TemplateAnalysis analysis : templatesAnalysis.getAnalysis()) { MessageBundleMethodBuildItem messageBundleMethod = bundleMethods.get(analysis.id); @@ -323,7 +327,8 @@ void validateMessageBundleMethods(TemplatesAnalysisBuildItem templatesAnalysis, Set paramNames = IntStream.range(0, messageBundleMethod.getMethod().parameters().size()) .mapToObj(idx -> getParameterName(messageBundleMethod.getMethod(), idx)).collect(Collectors.toSet()); for (Expression expression : analysis.expressions) { - validateExpression(incorrectExpressions, messageBundleMethod, expression, paramNames, usedParamNames); + validateExpression(incorrectExpressions, messageBundleMethod, expression, paramNames, usedParamNames, + globals); } // Log a warning if a parameter is not used in the template for (String paramName : paramNames) { @@ -339,31 +344,38 @@ void validateMessageBundleMethods(TemplatesAnalysisBuildItem templatesAnalysis, private void validateExpression(BuildProducer incorrectExpressions, MessageBundleMethodBuildItem messageBundleMethod, Expression expression, Set paramNames, - Set usedParamNames) { + Set usedParamNames, Set globals) { if (expression.isLiteral()) { return; } if (!expression.hasNamespace()) { Expression.Part firstPart = expression.getParts().get(0); String name = firstPart.getName(); - // Skip expressions that have type info derived from a parent section, e.g "it" and "foo" - if (firstPart.getTypeInfo() == null || (firstPart.getTypeInfo().startsWith("" + Expressions.TYPE_INFO_SEPARATOR) - && !paramNames.contains(name))) { - // Expression has no type info or type info that does not match a method parameter - incorrectExpressions.produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), - name + " is not a parameter of the message bundle method " - + messageBundleMethod.getMethod().declaringClass().name() + "#" - + messageBundleMethod.getMethod().name() + "()", - expression.getOrigin())); - } else { - usedParamNames.add(name); + String typeInfo = firstPart.getTypeInfo(); + boolean isGlobal = globals.contains(name); + boolean isLoopMetadata = typeInfo != null && typeInfo.endsWith(LoopSectionHelper.Factory.HINT_METADATA); + // Type info derived from a parent section, e.g "it" and "foo" + boolean hasDerivedTypeInfo = typeInfo != null && !typeInfo.startsWith("" + Expressions.TYPE_INFO_SEPARATOR); + + if (!isGlobal && !isLoopMetadata && !hasDerivedTypeInfo) { + if (typeInfo == null || !paramNames.contains(name)) { + // Expression has no type info or type info that does not match a method parameter + // expressions that have + incorrectExpressions.produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), + name + " is not a parameter of the message bundle method " + + messageBundleMethod.getMethod().declaringClass().name() + "#" + + messageBundleMethod.getMethod().name() + "()", + expression.getOrigin())); + } else { + usedParamNames.add(name); + } } } // Inspect method params too for (Part part : expression.getParts()) { if (part.isVirtualMethod()) { for (Expression param : part.asVirtualMethod().getParameters()) { - validateExpression(incorrectExpressions, messageBundleMethod, param, paramNames, usedParamNames); + validateExpression(incorrectExpressions, messageBundleMethod, param, paramNames, usedParamNames, globals); } } } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleExpressionValidationTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleExpressionValidationTest.java index 9cb0d8f90968a..e1ce864d619f1 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleExpressionValidationTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleExpressionValidationTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.qute.TemplateException; +import io.quarkus.qute.TemplateGlobal; import io.quarkus.qute.i18n.Message; import io.quarkus.qute.i18n.MessageBundle; import io.quarkus.test.QuarkusUnitTest; @@ -17,7 +18,7 @@ public class MessageBundleExpressionValidationTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(WrongBundle.class, Item.class) + .addClasses(WrongBundle.class, Item.class, MyGlobals.class) .addAsResource(new StringAsset( // foo is not a parameter of WrongBundle.hello() "hello=Hallo {foo}!"), @@ -51,10 +52,17 @@ public void testValidation() { @MessageBundle public interface WrongBundle { - // item has no "foo" property, "bar" and "baf" are not parameters - @Message("Hello {item.foo} {bar} {#each item.names}{it}{it.baz}{baf}{/each}") + // item has no "foo" property, "bar" and "baf" are not parameters, string has no "baz" property + @Message("Hello {item.foo} {bar} {#each item.names}{it}{it.baz}{it_hasNext}{baf}{/each}{level}") String hello(Item item); } + @TemplateGlobal + static class MyGlobals { + + static int level = 5; + + } + } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java index 853fb864f73e9..cba891e516544 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java @@ -140,6 +140,7 @@ public static class Factory implements SectionHelperFactory { public static final String ITERATION_METADATA_PREFIX_NONE = ""; public static final String HINT_ELEMENT = ""; + public static final String HINT_METADATA = ""; public static final String HINT_PREFIX = ""); // Put bindings for iteration metadata String prefix = prefixValue(alias, metadataPrefix); - newScopeBinding(newScope, prefix, "count", Integer.class.getName()); - newScopeBinding(newScope, prefix, "index", Integer.class.getName()); - newScopeBinding(newScope, prefix, "indexParity", String.class.getName()); - newScopeBinding(newScope, prefix, "hasNext", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "isLast", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "isFirst", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "odd", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "isOdd", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "even", Boolean.class.getName()); - newScopeBinding(newScope, prefix, "isEven", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "count", Integer.class.getName()); + putMetadataBinding(newScope, prefix, "index", Integer.class.getName()); + putMetadataBinding(newScope, prefix, "indexParity", String.class.getName()); + putMetadataBinding(newScope, prefix, "hasNext", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "isLast", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "isFirst", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "odd", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "isOdd", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "even", Boolean.class.getName()); + putMetadataBinding(newScope, prefix, "isEven", Boolean.class.getName()); return newScope; } else { // Make sure we do not try to validate against the parent context @@ -224,8 +225,8 @@ public Scope initializeBlock(Scope previousScope, BlockInfo block) { } } - private void newScopeBinding(Scope scope, String prefix, String name, String typeName) { - scope.putBinding(prefix != null ? prefix + name : name, Expressions.typeInfoFrom(typeName)); + private void putMetadataBinding(Scope scope, String prefix, String name, String typeName) { + scope.putBinding(prefix != null ? prefix + name : name, Expressions.typeInfoFrom(typeName) + HINT_METADATA); } static String prefixValue(String alias, String metadataPrefix) { diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java index 8b1b2c7b40692..849440e0ed6ab 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java @@ -76,6 +76,7 @@ public void testTypeInfos() { + "{/for}" + "{#each labels}" + "{it.name}" + + "{it_hasNext}" + "{/each}" + "{inject:bean.name}" + "{#each inject:bean.labels('foo')}" @@ -89,7 +90,8 @@ public void testTypeInfos() { + "{foo.baz}" + "{/for}" + "{foo.call(labels,bar)}" - + "{#when machine.status}{#is OK}..{#is NOK}{/when}"); + + "{#when machine.status}{#is OK}..{#is NOK}{/when}" + + "{not_typesafe}"); List expressions = template.getExpressions(); assertExpr(expressions, "foo.name", 2, "|org.acme.Foo|.name"); @@ -117,6 +119,9 @@ public void testTypeInfos() { Expression machineStatusExpr = find(expressions, "machine.status"); assertExpr(expressions, "OK", 1, "OK"); + + assertExpr(expressions, "it_hasNext", 1, "|java.lang.Boolean|"); + assertExpr(expressions, "not_typesafe", 1, null); } @Test