Skip to content

Commit

Permalink
Fix Qute namespace extensions with no parameters
Browse files Browse the repository at this point in the history
- resolves #15160
  • Loading branch information
mkouba committed Feb 23, 2021
1 parent 0c3d785 commit fba9536
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 21 deletions.
5 changes: 1 addition & 4 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1249,12 +1249,9 @@ The method name is used to match the property name by default.
However, it is possible to specify the matching name with `TemplateExtension#matchName()`.
A special constant - `TemplateExtension#ANY` - may be used to specify that the extension method matches any name.
It is also possible to match the name against a regular expression specified in `TemplateExtension#matchRegex()`.
In both cases, a string method parameter is used to pass the property name.
In both cases, an additional string method parameter must be used to pass the property name.
If both `matchName()` and `matchRegex()` are set the regular expression is used for matching.

If a namespace is specified the method must declare at least one parameter and the first parameter must be a string.
If no namespace is specified the method must declare at least two parameters and the second parameter must be a string.

.Extension Method Example
[source,java]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ void collectTemplateExtensionMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
continue;
}
if ((namespace == null || namespace.isEmpty()) && method.parameters().isEmpty()) {
// Filter methods with no params for non-namespace extensions
// Filter out methods with no params for non-namespace extensions
continue;
}
if (methods.containsKey(method)) {
Expand Down Expand Up @@ -790,7 +790,7 @@ private void produceExtensionMethod(IndexView index, BuildProducer<TemplateExten
matchRegex = matchRegexValue.asString();
}
extensionMethods.produce(new TemplateExtensionMethodBuildItem(method, matchName, matchRegex,
method.parameters().get(0), priority, namespace));
namespace.isEmpty() ? method.parameters().get(0) : null, priority, namespace));
}

private void validateInjectExpression(TemplateAnalysis templateAnalysis, Expression expression, IndexView index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public void testTemplateExtensions() {
engine.parse("{MyEnum:ONE}={MyEnum:one}").render());
assertEquals("IN_PROGRESS=0",
engine.parse("{txPhase:IN_PROGRESS}={txPhase:IN_PROGRESS.ordinal}").render());
assertEquals("Quark!",
engine.parse("{str:quark}").render());
assertEquals("QUARKUS!",
engine.parse("{str:quarkus}").render());
}

@TemplateExtension(namespace = "str")
Expand All @@ -52,11 +56,20 @@ static String reverse(String val) {
return new StringBuilder(val).reverse().toString();
}

@TemplateExtension(namespace = "str", matchRegex = "foo.*")
@TemplateExtension(namespace = "str", matchRegex = "foo.*", priority = 5)
static String foo(String name, String val) {
return name + ":" + new StringBuilder(val).reverse().toString();
}

static String quark() {
return "Quark!";
}

@TemplateExtension(namespace = "str", matchName = ANY, priority = 1)
static String quarkAny(String key) {
return key.toUpperCase() + "!";
}

}

public enum MyEnum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,11 @@
* <p>
* By default, the method name is used to match the property name. However, it is possible to specify the matching name with
* {@link #matchName()}. A special constant - {@link #ANY} - may be used to specify that the extension method matches any name.
* It is also possible to match the name against a regular expression specified in {@link #matchRegex()}. In both cases, a
* string method parameter is used to pass the property name.
*
* It is also possible to match the name against a regular expression specified in {@link #matchRegex()}. In both cases, an
* additional string method parameter must be used to pass the property name.
* <p>
* If both {@link #matchName()} and {@link #matchRegex()} are set the regular expression is used for matching.
*
* <p>
* If a namespace is specified the method must declare at least one parameter and the first parameter must be a string. If no
* namespace is specified the method must declare at least two parameters and the second parameter must be a string.
*
* <pre>
* {@literal @}TemplateExtension
* static BigDecimal discountedPrice(Item item) {
Expand Down Expand Up @@ -88,8 +83,7 @@
* <p>
* Template extension methods that share the same namespace and are declared on the same class are grouped in one resolver
* and ordered by {@link #priority()}. The first matching extension method is used to resolve an expression. Template
* extension
* methods declared on different classes cannot share the same namespace.
* extension methods declared on different classes cannot share the same namespace.
*
* @return the namespace
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,20 @@ public Set<String> getGeneratedTypes() {

public static void validate(MethodInfo method, List<Type> parameters, String namespace) {
if (!Modifier.isStatic(method.flags())) {
throw new IllegalStateException("Template extension method must be static: " + method);
throw new IllegalStateException(
"Template extension method declared on " + method.declaringClass().name() + " must be static: " + method);
}
if (method.returnType().kind() == Kind.VOID) {
throw new IllegalStateException("Template extension method must not return void: " + method);
throw new IllegalStateException("Template extension method declared on " + method.declaringClass().name()
+ " must not return void: " + method);
}
if (Modifier.isPrivate(method.flags())) {
throw new IllegalStateException("Template extension method declared on " + method.declaringClass().name()
+ " must not be private: " + method);
}
if ((namespace == null || namespace.isEmpty()) && parameters.isEmpty()) {
throw new IllegalStateException("Template extension method must declare at least one parameter: " + method);
throw new IllegalStateException("Template extension method declared on " + method.declaringClass().name()
+ " must declare at least one parameter: " + method);
}
}

Expand All @@ -97,6 +104,7 @@ public void generate(MethodInfo method, String matchName, String matchRegex, Int
List<Type> parameters = method.parameters();

// Validate the method first
// NOTE: this method is never used for namespace extension methods
validate(method, parameters, null);
ClassInfo declaringClass = method.declaringClass();

Expand Down Expand Up @@ -130,8 +138,8 @@ public void generate(MethodInfo method, String matchName, String matchRegex, Int
}

if (matchRegex != null || matchName.equals(TemplateExtension.ANY)) {
// The second parameter must be a string
if (parameters.size() < 2 || !parameters.get(1).name().equals(io.quarkus.qute.generator.DotNames.STRING)) {
// A string parameter is needed to match the name
if (parameters.size() < 2 || !parameters.get(1).name().equals(DotNames.STRING)) {
throw new TemplateException(
"A template extension method matching multiple names or a regular expression must declare at least two parameters and the second parameter must be string: "
+ method);
Expand Down Expand Up @@ -461,6 +469,11 @@ public void addMethod(MethodInfo method, String matchName, String matchRegex) {
}

boolean matchAnyOrRegex = patternField != null || matchName.equals(TemplateExtension.ANY);

if (matchAnyOrRegex && paramSize == 0) {
throw new IllegalStateException("Template extension method must declare at least one parameter: " + method);
}

// The real number of evaluated params, i.e. skip the name if matchAny==true
int realParamSize = paramSize - (matchAnyOrRegex ? 1 : 0);

Expand Down

0 comments on commit fba9536

Please sign in to comment.