Skip to content

Commit

Permalink
fix: Qute - incorrect behavior for nested template records
Browse files Browse the repository at this point in the history
Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Aug 29, 2024
1 parent 4d843a4 commit 7fed0c3
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.acme.sample;

import io.quarkus.qute.CheckedTemplate;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand All @@ -18,6 +19,9 @@ record Bonjour(String name) implements TemplateInstance {}

record Status() {}

@CheckedTemplate(basePath="Foo", defaultName=CheckedTemplate.HYPHENATED_ELEMENT_NAME)
record HelloWorld(String name) implements TemplateInstance {}

@GET
@Produces(MediaType.TEXT_PLAIN)
public TemplateInstance get(@QueryParam("name") String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,16 @@ public void visitClass(PsiClass node) {
*/
private void visitClassType(PsiClass node) {
levelTypeDecl++;
for (PsiAnnotation annotation : node.getAnnotations()) {
if (AnnotationUtils.isMatchAnnotation(annotation, CHECKED_TEMPLATE_ANNOTATION)
|| AnnotationUtils.isMatchAnnotation(annotation, OLD_CHECKED_TEMPLATE_ANNOTATION)) {
// @CheckedTemplate
// public static class Templates {
// public static native TemplateInstance book(Book book);
boolean ignoreFragments = isIgnoreFragments(annotation);
String basePath = getBasePath(annotation);
TemplateNameStrategy templateNameStrategy = getDefaultName(annotation);
for (PsiMethod method : node.getMethods()) {
collectTemplateLink(basePath, method, node, ignoreFragments, templateNameStrategy);
}
PsiAnnotation checkedAnnotation = getCheckedAnnotation(node);
if (checkedAnnotation != null) {
// @CheckedTemplate
// public static class Templates {
// public static native TemplateInstance book(Book book);
boolean ignoreFragments = isIgnoreFragments(checkedAnnotation);
String basePath = getBasePath(checkedAnnotation);
TemplateNameStrategy templateNameStrategy = getDefaultName(checkedAnnotation);
for (PsiMethod method : node.getMethods()) {
collectTemplateLinkForMethodOrRecord(basePath, method, method.getName(), node, ignoreFragments, templateNameStrategy);
}
}
super.visitClass(node);
Expand All @@ -187,8 +185,16 @@ private void visitClassType(PsiClass node) {
*/
private void visitRecordType(PsiClass node) {
if (isImplementTemplateInstance(node)) {

// public class HelloResource {
// record Hello(String name) implements TemplateInstance {}
String recordName = node.getName();
collectTemplateLink(null, node, null, node, null, recordName, false, TemplateNameStrategy.ELEMENT_NAME);
PsiAnnotation checkedAnnotation = getCheckedAnnotation(node);
boolean ignoreFragments = isIgnoreFragments(checkedAnnotation);
String basePath = getBasePath(checkedAnnotation);
TemplateNameStrategy templateNameStrategy = getDefaultName(checkedAnnotation);
collectTemplateLinkForMethodOrRecord(basePath, node, recordName, node, ignoreFragments,
templateNameStrategy);
}
}

Expand Down Expand Up @@ -216,14 +222,18 @@ private static PsiClass getTypeDeclaration(PsiElement node) {
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
}

private void collectTemplateLink(String basePath, PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments, TemplateNameStrategy templateNameStrategy) {
private void collectTemplateLinkForMethodOrRecord(String basePath,
PsiElement methodOrRecord,
String methodOrRecordName,
PsiClass type,
boolean ignoreFragments,
TemplateNameStrategy templateNameStrategy) {
String className = null;
boolean innerClass = levelTypeDecl > 1;
boolean innerClass = methodOrRecord instanceof PsiClass ? levelTypeDecl >= 1 : levelTypeDecl > 1;
if (innerClass) {
className = PsiTypeUtils.getSimpleClassName(typeRoot.getName());
}
String methodName = methodDeclaration.getName();
collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragments,templateNameStrategy);
collectTemplateLink(basePath, methodOrRecord, null, type, className, methodOrRecordName, ignoreFragments, templateNameStrategy);
}

private void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.redhat.qute.commons.datamodel.DataModelFragment;
import com.redhat.qute.commons.datamodel.DataModelParameter;
import com.redhat.qute.commons.datamodel.DataModelTemplate;
import org.jetbrains.annotations.Nullable;

/**
* CheckedTemplate support for template files:
Expand Down Expand Up @@ -88,6 +89,16 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
}
}

public static PsiAnnotation getCheckedAnnotation(PsiJvmModifiersOwner node) {
for (PsiAnnotation annotation : node.getAnnotations()) {
if (AnnotationUtils.isMatchAnnotation(annotation, CHECKED_TEMPLATE_ANNOTATION)
|| AnnotationUtils.isMatchAnnotation(annotation, OLD_CHECKED_TEMPLATE_ANNOTATION)) {
return annotation;
}
}
return null;
}

/**
* Returns true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
Expand All @@ -99,22 +110,24 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
* ignored and false otherwise.
* @CheckedTemplate(ignoreFragments=true) </code>
*/
public static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
public static boolean isIgnoreFragments(@Nullable PsiAnnotation checkedTemplateAnnotation) {
Boolean ignoreFragment = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS.equalsIgnoreCase(pair.getAttributeName())) {
ignoreFragment = AnnotationUtils.getValueAsBoolean(pair);
if (checkedTemplateAnnotation != null) {
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS.equalsIgnoreCase(pair.getAttributeName())) {
ignoreFragment = AnnotationUtils.getValueAsBoolean(pair);
}
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
}
Expand All @@ -127,22 +140,24 @@ public static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation)
* @return the <code>basePath</code> value declared in the @CheckedTemplate annotation
* @CheckedTemplate(basePath="somewhere") </code>
*/
public static String getBasePath(PsiAnnotation checkedTemplateAnnotation) {
public static String getBasePath(@Nullable PsiAnnotation checkedTemplateAnnotation) {
String basePath = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getAttributeName())) {
basePath = pair.getLiteralValue();
if (checkedTemplateAnnotation != null) {
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getAttributeName())) {
basePath = pair.getLiteralValue();
}
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return basePath;
}
Expand All @@ -157,28 +172,30 @@ public static String getBasePath(PsiAnnotation checkedTemplateAnnotation) {
*/
public static TemplateNameStrategy getDefaultName(PsiAnnotation checkedTemplateAnnotation) {
TemplateNameStrategy templateNameStrategy = TemplateNameStrategy.ELEMENT_NAME;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME.equalsIgnoreCase(pair.getAttributeName())) {
if (pair.getValue() != null
&& pair.getValue().getReference() != null
&& pair.getValue().getReference().resolve() != null &&
pair.getValue().getReference().resolve() instanceof PsiField field) {
Object value = field.computeConstantValue();
if (value != null) {
templateNameStrategy = getDefaultName(value.toString());
if (checkedTemplateAnnotation != null) {
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME.equalsIgnoreCase(pair.getAttributeName())) {
if (pair.getValue() != null
&& pair.getValue().getReference() != null
&& pair.getValue().getReference().resolve() != null &&
pair.getValue().getReference().resolve() instanceof PsiField field) {
Object value = field.computeConstantValue();
if (value != null) {
templateNameStrategy = getDefaultName(value.toString());
}
}
}
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return templateNameStrategy;
}
Expand Down Expand Up @@ -211,9 +228,7 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type,
ITypeResolver typeResolver,
List<DataModelTemplate<DataModelParameter>> templates,
ProgressIndicator monitor) {
boolean innerClass = type.getContainingClass() != null;
String className = !innerClass ? null
: PsiTypeUtils.getSimpleClassName(type.getContainingFile().getName());
String className = getParentClassName(type);

// Loop for each method (book, book) and create a template data model per
// method.
Expand Down Expand Up @@ -254,6 +269,13 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type,
}
}

public static @Nullable String getParentClassName(PsiClass type) {
boolean innerClass = type.getContainingClass() != null;
String className = !innerClass ? null
: PsiTypeUtils.getSimpleClassName(type.getContainingFile().getName());
return className;
}

private static DataModelTemplate<DataModelParameter> createTemplateDataModel(String templateUri, PsiMethod method, PsiClass type) {
String methodName = method.getName();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package com.redhat.devtools.intellij.qute.psi.internal.template.datamodel;

import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiRecordComponent;
import com.redhat.devtools.intellij.qute.psi.internal.template.TemplateDataSupport;
Expand All @@ -26,6 +27,7 @@
import java.util.List;

import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_INSTANCE_INTERFACE;
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.*;
import static com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils.getTemplatePath;

/**
Expand All @@ -36,6 +38,9 @@
* <p>
* record Hello(String name) implements TemplateInstance {}
* <p>
* <p>
* &#64;CheckedTemplate(basePath="Foo", defaultName=CheckedTemplate.HYPHENATED_ELEMENT_NAME)
* record HelloWorld(String name) implements TemplateInstance {}
* ...
* <p>
* <p>
Expand Down Expand Up @@ -76,13 +81,18 @@ private static void collectDataModelTemplateForTemplateRecord(PsiClass type,
templates.add(template);
}

private static DataModelTemplate<DataModelParameter> createTemplateDataModel(PsiClass type,
private static DataModelTemplate<DataModelParameter> createTemplateDataModel(PsiClass recordType,
String relativeTemplateBaseDir,
ProgressIndicator monitor) {

String recordName = type.getName();
PsiAnnotation checkedTemplateAnnotation = getCheckedAnnotation(recordType);
boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation);
String basePath = getBasePath(checkedTemplateAnnotation);
TemplateNameStrategy templateNameStrategy = getDefaultName(checkedTemplateAnnotation);
String className = getParentClassName(recordType);
String recordName = recordType.getName();
// src/main/resources/templates/${recordName}.qute.html
String templateUri = getTemplatePath(relativeTemplateBaseDir, null, null, recordName, true, TemplateNameStrategy.ELEMENT_NAME).getTemplateUri();
String templateUri = getTemplatePath(relativeTemplateBaseDir, basePath, className, recordName, ignoreFragments, templateNameStrategy).getTemplateUri();

// Create template data model with:
// - template uri : Qute template file which must be bind with data model.
Expand All @@ -91,10 +101,10 @@ private static DataModelTemplate<DataModelParameter> createTemplateDataModel(Psi
DataModelTemplate<DataModelParameter> template = new DataModelTemplate<DataModelParameter>();
template.setParameters(new ArrayList<>());
template.setTemplateUri(templateUri);
template.setSourceType(type.getQualifiedName());
template.setSourceType(recordType.getQualifiedName());

// Collect data parameters from the record fields
for (PsiRecordComponent field : type.getRecordComponents()) {
for (PsiRecordComponent field : recordType.getRecordComponents()) {
DataModelParameter parameter = new DataModelParameter();
parameter.setKey(field.getName());
parameter.setSourceType(PsiTypeUtils.resolveSignature(field.getType(), field.isVarArgs()));
Expand All @@ -106,7 +116,7 @@ private static DataModelTemplate<DataModelParameter> createTemplateDataModel(Psi
}

// Collect data parameters for the given template
TemplateDataSupport.collectParametersFromDataMethodInvocation(type, template, monitor);
TemplateDataSupport.collectParametersFromDataMethodInvocation(recordType, template, monitor);
return template;
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
</vendor>

<change-notes><![CDATA[
<h3>2.0.2</h3>
<ul>
<li>Fixed incorrect behavior for nested Qute template records</li>
</ul>
Learn more in the <a href="https://github.com/redhat-developer/intellij-quarkus/milestone/47?closed=1">changelog</a>.
<h3>2.0.1</h3>
<ul>
<li>Fixed gradle-powered project failing to 'Run Dev'</li>
Expand Down
Loading

0 comments on commit 7fed0c3

Please sign in to comment.