Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Qute - incorrect behavior for nested template records #1378

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -207,12 +207,16 @@ public Location getJavaDefinition(QuteJavaDefinitionParams params, IPsiUtils uti
monitor);
} else {
// Search field of the record
for (var recordField : type.getRecordComponents()) {
if (parameterName.equals(recordField.getName())) {
// returns the record field location
return utils.toLocation(recordField);
if (parameterName != null) {
for (var recordField : type.getRecordComponents()) {
if (parameterName.equals(recordField.getName())) {
// returns the record field location
return utils.toLocation(recordField);
}
}
}
// returns the record location
return utils.toLocation(type);
}
} else {
// The source type is a class
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 @@ -78,8 +79,7 @@ protected String[] getAnnotationNames() {
@Override
protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTemplateAnnotation, String annotationName,
SearchContext context, ProgressIndicator monitor) {
if (javaElement instanceof PsiClass) {
PsiClass type = (PsiClass) javaElement;
if (javaElement instanceof PsiClass type && !type.isRecord()) {
boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation);
String basePath = getBasePath(checkedTemplateAnnotation);
TemplateNameStrategy templateNameStrategy = getDefaultName(checkedTemplateAnnotation);
Expand All @@ -88,6 +88,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 +109,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 +139,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 +171,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 +227,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 +268,14 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type,
}
}

public static @Nullable String getParentClassName(PsiClass type) {
if (type.getContainingClass() != null) {
// Inner class
return PsiTypeUtils.getSimpleClassName(type.getContainingFile().getName());
}
return null;
}

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
Loading