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: respect basePath in @CheckedTemplate to find templates #1173

Merged
merged 1 commit into from
Sep 21, 2023
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
@@ -0,0 +1,39 @@
package org.acme.qute;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.qute.TemplateInstance;

@Path("items3")
public class ItemResourceWithCustomBasePath {

@CheckedTemplate(basePath="ItemResourceWithFragment")
static class Templates {
static native TemplateInstance items(List<Item> items);
static native TemplateInstance items$id1(List<Item> items);
static native TemplateInstance items3$id2(List<Item> items);
static native TemplateInstance items3$(List<Item> items);
}

@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance get() {
List<Item> items = new ArrayList<>();
items.add(new Item(new BigDecimal(10), "Apple"));
items.add(new Item(new BigDecimal(16), "Pear"));
items.add(new Item(new BigDecimal(30), "Orange"));
return Templates.items(items);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.acme.qute;

import java.util.List;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

@CheckedTemplate(basePath="ItemResourceWithFragment")
public class ItemTemplatesCustomBasePath {

static native TemplateInstance items(List<Item> items);
static native TemplateInstance items$id1(List<Item> items);
static native TemplateInstance items3$id2(List<Item> items);
static native TemplateInstance items3$(List<Item> items);

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class QuteJavaConstants {
public static final String OLD_CHECKED_TEMPLATE_ANNOTATION = "io.quarkus.qute.api.CheckedTemplate";

public static final String CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS = "ignoreFragments";
public static final String CHECKED_TEMPLATE_ANNOTATION_BASE_PATH = "basePath";

// @TemplateExtension

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION;
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS;
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_CLASS;
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.getBasePath;
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.isIgnoreFragments;

/**
* Abstract class which collects {@link PsiMethod} or
Expand Down Expand Up @@ -108,7 +110,7 @@ public void visitField(PsiField node) {
.getLocationExpressionFromConstructorParameter(node.getName());
}
String fieldName = node.getName();
collectTemplateLink(node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
}
super.visitField(node);
}
Expand Down Expand Up @@ -137,67 +139,39 @@ public void visitClass(PsiClass node) {
// public static class Templates {
// public static native TemplateInstance book(Book book);
boolean ignoreFragments = isIgnoreFragments(annotation);
String basePath = getBasePath(annotation);
for(PsiMethod method : node.getMethods()) {
collectTemplateLink(method, node, ignoreFragments );
collectTemplateLink(basePath, method, node, ignoreFragments );
}
}
}
super.visitClass(node);
levelTypeDecl--;
}

/**
* Returns true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
*
* <code>
* @CheckedTemplate(ignoreFragments=true)
* </code>
*
* @param checkedTemplateAnnotation the CheckedTemplate annotation.
*
* @return true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
*/
private static boolean isIgnoreFragments(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);
}
}
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
}

private static PsiClass getTypeDeclaration(PsiElement node) {
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
}


private void collectTemplateLink(PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
private void collectTemplateLink(String basePath, PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
String className = null;
boolean innerClass = levelTypeDecl > 1;
if (innerClass) {
className = PsiTypeUtils.getSimpleClassName(typeRoot.getName());
}
String methodName = methodDeclaration.getName();
collectTemplateLink(methodDeclaration, null, type, className, methodName, ignoreFragments );
collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragments );
}

private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
private void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, boolean ignoreFragment ) {
try {
String location = locationAnnotation != null && locationAnnotation.getValue() instanceof String ? (String) locationAnnotation.getValue() : null;
Module project = utils.getModule();
TemplatePathInfo templatePathInfo = location != null
? PsiQuteProjectUtils.getTemplatePath(null, location, ignoreFragment)
: PsiQuteProjectUtils.getTemplatePath(className, fieldOrMethodName, ignoreFragment);
? PsiQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment)
: PsiQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment);

VirtualFile templateFile = null;
if (location == null) {
Expand All @@ -208,7 +182,7 @@ private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locat
} else {
templateFile = getVirtualFile(project, templatePathInfo.getTemplateUri(), "");
}
collectTemplateLink(fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
collectTemplateLink(basePath, fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
templateFile, templatePathInfo);
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
Expand Down Expand Up @@ -275,7 +249,7 @@ protected Range createRange(PsiElement fieldOrMethod) {
return utils.toRange(typeRoot, tr.getStartOffset(), tr.getLength());
}

protected abstract void collectTemplateLink(PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
protected abstract void collectTemplateLink(String basePath, PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
String className, String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo);

private static boolean isTemplateType(PsiType type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public QuteJavaCodeLensCollector(PsiFile typeRoot, List<CodeLens> lenses, IPsiUt
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
if (!templatePathInfo.isValid()) {
// It is an empty fragment which is not valid, don't generate a codelens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

import java.util.List;

import static com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils.appendAndSlash;
import static io.quarkus.qute.CheckedTemplate.DEFAULTED;

/**
* Report diagnostics error for non existing Qute template for:
*
Expand All @@ -49,13 +52,13 @@ public QuteJavaDiagnosticsCollector(PsiFile typeRoot, List<Diagnostic> diagnosti
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
QuteErrorCode error = getQuteErrorCode(templatePathInfo, templateFile);
if (error == null) {
return;
}
String path = createPath(className, fieldOrMethodName, location);
String path = createPath(basePath, className, fieldOrMethodName, location);
String fragmentId = templatePathInfo.getFragmentId();
if (templatePathInfo.hasFragment() && path.endsWith(fragmentId)) {
// Adjust path by removing fragment information
Expand Down Expand Up @@ -92,14 +95,17 @@ private static QuteErrorCode getQuteErrorCode(TemplatePathInfo templatePathInfo,
return null;
}

private static String createPath(String className, String fieldOrMethodName, String location) {
private static String createPath(String basePath, String className, String fieldOrMethodName, String location) {
if (location != null) {
return location;
}
if (className == null) {
return fieldOrMethodName;
StringBuilder path = new StringBuilder();
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(path, basePath);
} else if (className != null){
appendAndSlash(path, className);
}
return className + '/' + fieldOrMethodName;
return path.append(fieldOrMethodName).toString();
}

private static Diagnostic createDiagnostic(Range range, DiagnosticSeverity severity, IQuteErrorCode errorCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public QuteJavaDocumentLinkCollector(PsiFile typeRoot, List<DocumentLink> links,
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
if (!templatePathInfo.isValid()) {
// It is an empty fragment which is not valid, don't generate a document link.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
if (javaElement instanceof PsiClass) {
PsiClass type = (PsiClass) javaElement;
boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation);
collectDataModelTemplateForCheckedTemplate(type, ignoreFragments, context.getTypeResolver(type),
String basePath = getBasePath(checkedTemplateAnnotation);
collectDataModelTemplateForCheckedTemplate(type, basePath, ignoreFragments, context.getTypeResolver(type),
context.getDataModelProject().getTemplates(), monitor);
}
}
Expand All @@ -96,7 +97,7 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
* ignored and false otherwise.
* @CheckedTemplate(ignoreFragments=true) </code>
*/
private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
public static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
Boolean ignoreFragment = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
Expand All @@ -112,15 +113,41 @@ private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
}

/**
* Returns the <code>basePath</code> value declared in the @CheckedTemplate annotation, relative to the templates root, to search the templates from.
*<code>
* @CheckedTemplate(basePath="somewhere")
*</code>
*
* @param checkedTemplateAnnotation the CheckedTemplate annotation.
* @return the <code>basePath</code> value declared in the @CheckedTemplate annotation
*/
public static String getBasePath(PsiAnnotation checkedTemplateAnnotation) {
fbricon marked this conversation as resolved.
Show resolved Hide resolved
String basePath = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getAttributeName())) {
basePath = pair.getLiteralValue();
}
}
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return basePath;
}

/**
* Collect data model template from @CheckedTemplate.
*
* @param type the Java type.
* @param basePath the base path relative to the templates root
* @param ignoreFragments true if fragments must be ignored and false otherwise.
* @param templates the data model templates to update with collect of template.
* @param monitor the progress monitor.
*/
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, boolean ignoreFragments, ITypeResolver typeResolver,
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, String basePath, boolean ignoreFragments, ITypeResolver typeResolver,
List<DataModelTemplate<DataModelParameter>> templates, ProgressIndicator monitor) {
boolean innerClass = type.getContainingClass() != null;
String className = !innerClass ? null
Expand All @@ -131,7 +158,7 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, bo
PsiMethod[] methods = type.getMethods();
for (PsiMethod method : methods) {
// src/main/resources/templates/${className}/${methodName}.qute.html
TemplatePathInfo templatePathInfo = getTemplatePath(className, method.getName(), ignoreFragments);
TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getName(), ignoreFragments);

// Get or create template
String templateUri = templatePathInfo.getTemplateUri();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private static DataModelTemplate<DataModelParameter> createTemplateDataModel(Psi
String location = locationFromConstructorParameter != null ? locationFromConstructorParameter : getLocation(field);
String fieldName = field.getName();
// src/main/resources/templates/${methodName}.qute.html
String templateUri = getTemplatePath(null, location != null ? location : fieldName, true).getTemplateUri();
String templateUri = getTemplatePath(null,null, location != null ? location : fieldName, true).getTemplateUri();

// Create template data model with:
// - template uri : Qute template file which must be bind with data model.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
import com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants;
import com.redhat.qute.commons.ProjectInfo;
import org.jetbrains.annotations.NotNull;

/**
* JDT Qute utilities.
Expand All @@ -28,6 +29,11 @@ public class PsiQuteProjectUtils {

private static final String TEMPLATES_BASE_DIR = "src/main/resources/templates/";

/**
* Value for Qute annotations indicating behaviour should be using the default
*/
private static final String DEFAULTED = "<<defaulted>>";

private PsiQuteProjectUtils() {
}

Expand Down Expand Up @@ -61,21 +67,23 @@ public static boolean hasQuteSupport(Module javaProject) {
return PsiTypeUtils.findType(javaProject, QuteJavaConstants.ENGINE_BUILDER_CLASS) != null;
}

public static String getTemplatePath(String className, String methodOrFieldName) {
public static String getTemplatePath(String basePath, String className, String methodOrFieldName) {
StringBuilder path = new StringBuilder(TEMPLATES_BASE_DIR);
if (className != null) {
path.append(className);
path.append('/');
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(path, basePath);
} else if (className != null) {
appendAndSlash(path, className);
}
return path.append(methodOrFieldName).toString();
}

public static TemplatePathInfo getTemplatePath(String className, String methodOrFieldName, boolean ignoreFragments) {
public static TemplatePathInfo getTemplatePath(String basePath, String className, String methodOrFieldName, boolean ignoreFragments) {
String fragmentId = null;
StringBuilder templateUri = new StringBuilder(TEMPLATES_BASE_DIR);
if (className != null) {
templateUri.append(className);
templateUri.append('/');
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(templateUri, basePath);
} else if (className != null) {
appendAndSlash(templateUri, className);
}
if (!ignoreFragments) {
int fragmentIndex = methodOrFieldName != null ? methodOrFieldName.lastIndexOf('$') : -1;
Expand All @@ -87,4 +95,16 @@ public static TemplatePathInfo getTemplatePath(String className, String methodOr
templateUri.append(methodOrFieldName);
return new TemplatePathInfo(templateUri.toString(), fragmentId);
}

/**
* Appends a segment to a path, add trailing "/" if necessary
* @param path the path to append to
* @param segment the segment to append to the path
*/
public static void appendAndSlash(@NotNull StringBuilder path, @NotNull String segment) {
path.append(segment);
if (!segment.endsWith("/")) {
path.append('/');
}
}
}
Loading
Loading