Skip to content

Commit

Permalink
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 b1bc7eb
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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,17 @@ 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 +223,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 PsiRecordComponent ? 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 b1bc7eb

Please sign in to comment.