diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index f3a7ad9ce2c612..f304793d91197a 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -672,6 +672,7 @@ public void beforeParsing(ParserHelper parserHelper) { @BuildStep void validateCheckedFragments(List validations, List expressionMatches, + List templateGlobals, BeanArchiveIndexBuildItem beanArchiveIndex, BuildProducer validationErrors) { @@ -682,6 +683,8 @@ void validateCheckedFragments(List validatio Map assignableCache = new HashMap<>(); String[] hintPrefixes = { LoopSectionHelper.Factory.HINT_PREFIX, WhenSectionHelper.Factory.HINT_PREFIX, SetSectionHelper.Factory.HINT_PREFIX }; + Set globals = templateGlobals.stream().map(TemplateGlobalBuildItem::getName) + .collect(Collectors.toUnmodifiableSet()); for (CheckedFragmentValidationBuildItem validation : validations) { Map paramNamesToTypes = new HashMap<>(); @@ -697,33 +700,40 @@ void validateCheckedFragments(List validatio } for (Expression expression : validation.fragmentExpressions) { - // Note that we ignore literals and expressions with no type info and expressions with a hint referencing an expression from inside the fragment - if (expression.isLiteral()) { + // Note that we ignore: + // - literals, + // - globals, + // - expressions with no type info, + // - loop metadata; e.g. |java.lang.Integer| + // - expressions with a hint referencing an expression from inside the fragment + if (expression.isLiteral() || globals.contains(expression.getParts().get(0).getName())) { continue; } - if (expression.hasTypeInfo()) { - Info info = TypeInfos.create(expression, index, null).get(0); - if (info.isTypeInfo()) { - // |org.acme.Foo|.name - paramNamesToTypes.put(expression.getParts().get(0).getName(), info.asTypeInfo().resolvedType); - } else if (info.hasHints()) { - // foo.name - hintLoop: for (String helperHint : info.asHintInfo().hints) { - for (String prefix : hintPrefixes) { - if (helperHint.startsWith(prefix)) { - int generatedId = parseHintId(helperHint, prefix); - Expression localExpression = findExpression(generatedId, validation.fragmentExpressions); - if (localExpression == null) { - Match match = matchResults.getMatch(generatedId); - if (match == null) { - throw new IllegalStateException( - "Match result not found for expression [" + expression.toOriginalString() - + "] in: " - + validation.templateId); - } - paramNamesToTypes.put(expression.getParts().get(0).getName(), match.type); - break hintLoop; + String typeInfo = expression.getParts().get(0).getTypeInfo(); + if (typeInfo == null || (typeInfo != null && typeInfo.endsWith(LoopSectionHelper.Factory.HINT_METADATA))) { + continue; + } + Info info = TypeInfos.create(expression, index, null).get(0); + if (info.isTypeInfo()) { + // |org.acme.Foo|.name + paramNamesToTypes.put(expression.getParts().get(0).getName(), info.asTypeInfo().resolvedType); + } else if (info.hasHints()) { + // foo.name + hintLoop: for (String helperHint : info.asHintInfo().hints) { + for (String prefix : hintPrefixes) { + if (helperHint.startsWith(prefix)) { + int generatedId = parseHintId(helperHint, prefix); + Expression localExpression = findExpression(generatedId, validation.fragmentExpressions); + if (localExpression == null) { + Match match = matchResults.getMatch(generatedId); + if (match == null) { + throw new IllegalStateException( + "Match result not found for expression [" + expression.toOriginalString() + + "] in: " + + validation.templateId); } + paramNamesToTypes.put(expression.getParts().get(0).getName(), match.type); + break hintLoop; } } } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java index 6f7f7ba7af2cb5..c5a7cb3502be36 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateGlobal; import io.quarkus.qute.TemplateInstance; import io.quarkus.test.QuarkusUnitTest; @@ -20,21 +21,30 @@ public class CheckedTemplateFragmentTest { .addClasses(Templates.class, Item.class) .addAsResource(new StringAsset( "{#each items}{#fragment id='item'}{it.name}{#if it.name.length > 5} is a long name{/if}{/fragment}{/each}"), - "templates/CheckedTemplateFragmentTest/items.html")); + "templates/CheckedTemplateFragmentTest/items.html") + .addAsResource(new StringAsset( + "{#fragment id=foo}{#for i in bar}{i_count}. <{i}>{#if i_hasNext}, {/if}{/for}{/fragment}"), + "templates/CheckedTemplateFragmentTest/foos.html")); @Test public void testFragment() { assertEquals("Foo", Templates.items(null).getFragment("item").data("it", new Item("Foo")).render()); assertEquals("Foo", Templates.items$item(new Item("Foo")).render()); assertEquals("FooAndBar is a long name", Templates.items$item(new Item("FooAndBar")).render()); + assertEquals("1. <1>, 2. <2>, 3. <3>, 4. <4>, 5. <5>", Templates.foos$foo().render()); } @CheckedTemplate public static class Templates { + @TemplateGlobal + static int bar = 5; + static native TemplateInstance items(List items); static native TemplateInstance items$item(Item it); + + static native TemplateInstance foos$foo(); } }