diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 033bcdb..c90e14f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,9 +15,9 @@ body: attributes: label: "Checklist" options: - - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/template-placeholder/releases/latest)" + - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/pmd-java-rules/releases/latest)" required: true - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/pmd-java-rules/issues) or [closed](https://github.com/xdev-software/pmd-java-rules/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 764cae1..2dcdf4c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -13,7 +13,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/pmd-java-rules/issues) or [closed](https://github.com/xdev-software/pmd-java-rules/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 6ecd6ad..3960183 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -12,7 +12,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/pmd-java-rules/issues) or [closed](https://github.com/xdev-software/pmd-java-rules/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 0cd2d9b..281d3bf 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -19,9 +19,6 @@ on: - '.idea/**' - 'assets/**' -env: - DEMO_MAVEN_MODULE: ${{ github.event.repository.name }}-demo - jobs: build: runs-on: ubuntu-latest @@ -68,13 +65,6 @@ jobs: exit 1 fi - - name: Upload demo files - uses: actions/upload-artifact@v7 - with: - name: demo-files-java-${{ matrix.java }} - path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar - if-no-files-found: error - checkstyle: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6133d1a..628c7aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: branches: [ master ] env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} + PRIMARY_MAVEN_MODULE: rules permissions: contents: write diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 96e5d2a..41bc52b 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} + PRIMARY_MAVEN_MODULE: rules jobs: publish-maven: diff --git a/.run/Run Demo.run.xml b/.run/Run Demo.run.xml deleted file mode 100644 index 5fb2bc2..0000000 --- a/.run/Run Demo.run.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..60f840e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 +Initial release +* contains `AvoidVarForShortTypeRule` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee364fd..7d9908f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ You should have the following things installed: * Ensure that the JDK/Java-Version is correct -## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/template-placeholder/release.yml?branch=master)](https://github.com/xdev-software/template-placeholder/actions/workflows/release.yml) +## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/pmd-java-rules/release.yml?branch=master)](https://github.com/xdev-software/pmd-java-rules/actions/workflows/release.yml) Before releasing: -* Consider doing a [test-deployment](https://github.com/xdev-software/template-placeholder/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. +* Consider doing a [test-deployment](https://github.com/xdev-software/pmd-java-rules/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. * Check the [changelog](CHANGELOG.md) If the ``develop`` is ready for release, create a pull request to the ``master``-Branch and merge the changes diff --git a/README.md b/README.md index e97be21..569db2c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -[![Latest version](https://img.shields.io/maven-central/v/software.xdev/template-placeholder?logo=apache%20maven)](https://mvnrepository.com/artifact/software.xdev/template-placeholder) -[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/template-placeholder/check-build.yml?branch=develop)](https://github.com/xdev-software/template-placeholder/actions/workflows/check-build.yml?query=branch%3Adevelop) +[![Latest version](https://img.shields.io/maven-central/v/software.xdev.pmd/rules?logo=apache%20maven)](https://mvnrepository.com/artifact/software.xdev.pmd/rules) +[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/pmd-java-rules/check-build.yml?branch=develop)](https://github.com/xdev-software/pmd-java-rules/actions/workflows/check-build.yml?query=branch%3Adevelop) -# template-placeholder +# Custom PMD Java Rules +Advanced PMD rules that are not included out of the box - written in Java when XPath is not enough. ## Installation -[Installation guide for the latest release](https://github.com/xdev-software/template-placeholder/releases/latest#Installation) +[Installation guide for the latest release](https://github.com/xdev-software/pmd-java-rules/releases/latest#Installation) ## Support If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services-products/support). @@ -14,4 +15,4 @@ If you need support as soon as possible and you can't wait for any pull request, See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. ## Dependencies and Licenses -View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/template-placeholder/dependencies) +View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/pmd-java-rules/dependencies) diff --git a/SECURITY.md b/SECURITY.md index 34b9514..eadb2f7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/template-placeholder/security/advisories/new). +Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/pmd-java-rules/security/advisories/new). diff --git a/pom.xml b/pom.xml index 333c1c2..fad4c96 100644 --- a/pom.xml +++ b/pom.xml @@ -4,8 +4,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - software.xdev - template-placeholder-root + software.xdev.pmd + rules-root 1.0.0-SNAPSHOT pom @@ -15,8 +15,7 @@ - template-placeholder - template-placeholder-demo + rules diff --git a/renovate.json5 b/renovate.json5 index 2874d45..1226c95 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -4,7 +4,7 @@ "packageRules": [ { "description": "Ignore project internal dependencies", - "packagePattern": "^software.xdev:template-placeholder", + "packagePattern": "^software.xdev.pmd:rules", "datasources": [ "maven" ], diff --git a/template-placeholder/pom.xml b/rules/pom.xml similarity index 92% rename from template-placeholder/pom.xml rename to rules/pom.xml index 202e05b..015eda3 100644 --- a/template-placeholder/pom.xml +++ b/rules/pom.xml @@ -4,21 +4,21 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - software.xdev - template-placeholder + software.xdev.pmd + rules 1.0.0-SNAPSHOT jar - template-placeholder - template-placeholder - https://github.com/xdev-software/template-placeholder + rules + rules + https://github.com/xdev-software/pmd-java-rules - https://github.com/xdev-software/template-placeholder - scm:git:https://github.com/xdev-software/template-placeholder.git + https://github.com/xdev-software/pmd-java-rules + scm:git:https://github.com/xdev-software/pmd-java-rules.git - 2023 + 2026 XDEV Software @@ -49,6 +49,14 @@ UTF-8 + + + net.sourceforge.pmd + pmd-java + 7.26.0 + + + diff --git a/rules/src/main/java/software/xdev/pmd/rule/AvoidVarForShortTypeRule.java b/rules/src/main/java/software/xdev/pmd/rule/AvoidVarForShortTypeRule.java new file mode 100644 index 0000000..645c05e --- /dev/null +++ b/rules/src/main/java/software/xdev/pmd/rule/AvoidVarForShortTypeRule.java @@ -0,0 +1,206 @@ +/* + * Copyright © 2026 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package software.xdev.pmd.rule; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import net.sourceforge.pmd.lang.java.ast.ASTClassDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTVariableId; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; +import net.sourceforge.pmd.lang.java.types.JClassType; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyFactory; +import software.xdev.pmd.rule.prettyprint.PrettyPrintVisitor; +import software.xdev.pmd.rule.prettyprint.TypePrettyPrinter; + + +// Upstream code from https://github.com/pmd/pmd/pull/6904 +public class AvoidVarForShortTypeRule extends AbstractJavaRulechainRule +{ + private static final PropertyDescriptor LIMIT_LENGTH_DESCRIPTOR = + PropertyFactory.intProperty("lengthLimit") + .desc("The length of a type must be above this limit to allow using var") + .defaultValue(32) + .build(); + + public AvoidVarForShortTypeRule() + { + super(ASTLocalVariableDeclaration.class); + this.definePropertyDescriptor(LIMIT_LENGTH_DESCRIPTOR); + } + + @Override + public Object visit(final ASTLocalVariableDeclaration node, final Object data) + { + if(!node.isTypeInferred()) + { + // not var + return data; + } + + final List currentClassDeclarationCanonicalPrefixes = + this.computeCurrentClassDeclarationCanonicalPrefixes(node); + + final int limitLength = this.getProperty(LIMIT_LENGTH_DESCRIPTOR); + Optional.ofNullable(node.firstChild(ASTVariableDeclarator.class)) + .map(n -> n.firstChild(ASTVariableId.class)) + .map(ASTVariableId::getTypeMirror) + .map(variableTypeMirror -> { + final VisitState state = new VisitState(limitLength, currentClassDeclarationCanonicalPrefixes); + try + { + variableTypeMirror.acceptVisitor(Visitor.INSTANCE, state); + } + catch(final OverLengthLimitException ignored) + { + // Quick exit/abort to not compute full string length + return null; + } + return state.consumeResult(); + }) + .filter(s -> s.length() < limitLength) + .ifPresent(type -> this.asCtx(data).addViolation(node, type, limitLength)); + + return data; + } + + private List computeCurrentClassDeclarationCanonicalPrefixes(final ASTLocalVariableDeclaration node) + { + final List currentClassDeclarationCanonicalPrefixes = new ArrayList<>(); + final Set alreadyProcessed = new HashSet<>(); + + JavaNode parent = node.getParent(); + while(parent != null && !alreadyProcessed.contains(parent)) + { + if(parent instanceof final ASTClassDeclaration classDeclaration) + { + Optional.ofNullable(classDeclaration.getCanonicalName()) + .map(s -> s + ".") + .ifPresent(currentClassDeclarationCanonicalPrefixes::add); + } + alreadyProcessed.add(parent); + parent = parent.getParent(); + } + + return currentClassDeclarationCanonicalPrefixes; + } + + static class OverLengthLimitException extends RuntimeException + { + + } + + + static class VisitState extends TypePrettyPrinter + { + final int limitLength; + final List currentClassDeclarationCanonicalPrefixes; + + int currentLength; + + VisitState( + final int limitLength, + final List currentClassDeclarationCanonicalPrefixes) + { + this.limitLength = limitLength; + this.currentClassDeclarationCanonicalPrefixes = currentClassDeclarationCanonicalPrefixes; + + this.printMethodHeader(false); + this.printMethodResult(false); + this.printAnnotations(false); + } + + void throwIfCurrentLengthOverLimit() + { + if(this.currentLength >= this.limitLength) + { + throw new OverLengthLimitException(); + } + } + + @Override + public StringBuilder append(final char o) + { + this.currentLength++; + this.throwIfCurrentLengthOverLimit(); + return super.append(o); + } + + @Override + public StringBuilder append(final String o) + { + this.currentLength += o.length(); + this.throwIfCurrentLengthOverLimit(); + return super.append(o); + } + } + + + static class Visitor extends PrettyPrintVisitor + { + static final Visitor INSTANCE = new Visitor(); + + @Override + protected void appendClassName( + final JClassType t, + final VisitState s, + final JClassType enclosing, + final boolean isAnon) + { + final String canonicalName = t.getSymbol().getCanonicalName(); + if(canonicalName == null) + { + return; + } + + if(!s.currentClassDeclarationCanonicalPrefixes.isEmpty()) + { + // last -> top-most + final String topMostCurrentClassDeclarationBinaryName = + s.currentClassDeclarationCanonicalPrefixes.get( + s.currentClassDeclarationCanonicalPrefixes.size() - 1); + if(canonicalName.startsWith(topMostCurrentClassDeclarationBinaryName)) + { + final Optional optMostMatchedCanonicalClassPrefix = + s.currentClassDeclarationCanonicalPrefixes.stream() + .filter(canonicalName::startsWith) + .findFirst(); + if(optMostMatchedCanonicalClassPrefix.isPresent()) + { + s.append(canonicalName.substring(optMostMatchedCanonicalClassPrefix.orElseThrow().length())); + return; + } + } + } + + final String packageName = t.getSymbol().getPackageName(); + s.append(canonicalName.startsWith(packageName) && canonicalName.length() > packageName.length() + ? canonicalName.substring(packageName.length() + 1) + : canonicalName); + } + } +} diff --git a/rules/src/main/java/software/xdev/pmd/rule/prettyprint/PrettyPrintVisitor.java b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/PrettyPrintVisitor.java new file mode 100644 index 0000000..6e77132 --- /dev/null +++ b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/PrettyPrintVisitor.java @@ -0,0 +1,275 @@ +/* + * Copyright © 2026 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package software.xdev.pmd.rule.prettyprint; + +import static net.sourceforge.pmd.util.OptionalBool.NO; +import static net.sourceforge.pmd.util.OptionalBool.YES; + +import java.util.Arrays; +import java.util.List; + +import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol; +import net.sourceforge.pmd.lang.java.types.JArrayType; +import net.sourceforge.pmd.lang.java.types.JClassType; +import net.sourceforge.pmd.lang.java.types.JIntersectionType; +import net.sourceforge.pmd.lang.java.types.JMethodSig; +import net.sourceforge.pmd.lang.java.types.JPrimitiveType; +import net.sourceforge.pmd.lang.java.types.JTypeMirror; +import net.sourceforge.pmd.lang.java.types.JTypeVar; +import net.sourceforge.pmd.lang.java.types.JTypeVisitor; +import net.sourceforge.pmd.lang.java.types.JWildcardType; +import net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar; +import net.sourceforge.pmd.util.OptionalBool; + + +@SuppressWarnings("all") // Upstream code from https://github.com/pmd/pmd/pull/6904 +public class PrettyPrintVisitor

implements JTypeVisitor +{ + @Override + public Void visit(final JTypeMirror t, final P sb) + { + sb.printTypeAnnotations(t.getTypeAnnotations()); + sb.append(t.toString()); + return null; + } + + @Override + public Void visitClass(final JClassType t, final P sb) + { + final JClassType enclosing = t.getEnclosingType(); + final boolean isAnon = t.getSymbol().isAnonymousClass(); + + if(enclosing != null && !isAnon) + { + this.visitClass(enclosing, sb); + sb.append('#'); + } + else if(t.hasErasedSuperTypes() && !t.isRaw()) + { + sb.append("(erased) "); + } + + sb.printTypeAnnotations(t.getTypeAnnotations()); + + if(t.getSymbol().isUnresolved()) + { + sb.append('*'); // a small marker to spot them + } + + this.appendClassName(t, sb, enclosing, isAnon); + + final List targs = t.getTypeArgs(); + if(t.isRaw() || targs.isEmpty()) + { + return null; + } + + if(t.isGenericTypeDeclaration() && sb.printTypeVarBounds != NO) + { + sb.printTypeVarBounds = YES; + } + this.join(sb, targs, ", ", "<", ">"); + return null; + } + + protected void appendClassName(final JClassType t, final P sb, final JClassType enclosing, final boolean isAnon) + { + if(enclosing != null && !isAnon || !sb.qualifyNames) + { + sb.append(t.getSymbol().getSimpleName()); + } + else + { + sb.append(t.getSymbol().getBinaryName()); + } + } + + @Override + public Void visitWildcard(final JWildcardType t, final P sb) + { + sb.printTypeAnnotations(t.getTypeAnnotations()); + sb.append("?"); + if(t.isUnbounded()) + { + return null; + } + + sb.append(t.isUpperBound() ? " extends " : " super "); + + t.getBound().acceptVisitor(this, sb); + return null; + } + + @Override + public Void visitPrimitive(final JPrimitiveType t, final P sb) + { + sb.printTypeAnnotations(t.getTypeAnnotations()); + sb.append(t.getSimpleName()); + return null; + } + + @Override + public Void visitTypeVar(final JTypeVar t, final P sb) + { + if(!t.isCaptured() && sb.qualifyTvars) + { + final JTypeParameterSymbol sym = t.getSymbol(); + if(sym != null) + { + sb.append(sym.getDeclaringSymbol().getSimpleName()); + sb.append('#'); + } + } + + sb.printTypeAnnotations(t.getTypeAnnotations()); + sb.append(t.getName()); + + if(sb.printTypeVarBounds == YES) + { + sb.printTypeVarBounds = NO; + if(!t.getUpperBound().isTop()) + { + sb.append(" extends "); + t.getUpperBound().acceptVisitor(this, sb); + } + if(!t.getLowerBound().isBottom()) + { + sb.append(" super "); + t.getLowerBound().acceptVisitor(this, sb); + } + sb.printTypeVarBounds = YES; + } + return null; + } + + /** + * Formats {@link Arrays#asList(Object[])} as {@code asList(T...) -> List} + */ + @Override + public Void visitMethodType(final JMethodSig t, final P sb) + { + if(sb.printMethodHeader) + { + t.getDeclaringType().acceptVisitor(this, sb); + sb.append("."); + + if(t.isGeneric()) + { + final OptionalBool printBounds = sb.printTypeVarBounds; + if(printBounds != NO) + { + sb.printTypeVarBounds = YES; + } + this.join(sb, t.getTypeParameters(), ", ", "<", "> ", false); + sb.printTypeVarBounds = printBounds; + } + } + + sb.append(t.getName()); + + this.join(sb, t.getFormalParameters(), ", ", "(", ")", t.isVarargs()); + + if(sb.printMethodReturnType) + { + sb.append(" -> "); + t.getReturnType().acceptVisitor(this, sb); + } + return null; + } + + @Override + public Void visitIntersection(final JIntersectionType t, final P sb) + { + return this.join(sb, t.getComponents(), " & ", "", ""); + } + + @Override + public Void visitArray(final JArrayType t, final P sb) + { + final JTypeMirror component = t.getComponentType(); + if(component instanceof JIntersectionType) + { + sb.append("("); + } + + final boolean isVarargs = sb.isVarargs; + sb.isVarargs = false; + component.acceptVisitor(this, sb); + + if(component instanceof JIntersectionType) + { + sb.append(")"); + } + sb.printTypeAnnotations(t.getTypeAnnotations()); + sb.append(isVarargs ? "..." : "[]"); + return null; + } + + @Override + public Void visitNullType(final JTypeMirror t, final P sb) + { + sb.append("null"); + return null; + } + + @Override + public Void visitInferenceVar(final InferenceVar t, final P sb) + { + sb.append(t.getName()); + return null; + } + + protected Void join( + final P sb, + final List ts, + final String delim, + final String prefix, + final String suffix) + { + return this.join(sb, ts, delim, prefix, suffix, false); + } + + protected Void join( + final P sb, + final List types, + final String delim, + final String prefix, + final String suffix, + final boolean isVarargs) + { + sb.isVarargs = false; + sb.append(prefix); + if(!types.isEmpty()) + { + for(int i = 0; i < types.size() - 1; i++) + { + types.get(i).acceptVisitor(this, sb); + sb.append(delim); + } + if(isVarargs) + { + sb.isVarargs = true; + } + types.get(types.size() - 1).acceptVisitor(this, sb); + } + sb.append(suffix); + return null; + } +} diff --git a/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrint.java b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrint.java new file mode 100644 index 0000000..dac8f59 --- /dev/null +++ b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrint.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2026 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package software.xdev.pmd.rule.prettyprint; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.types.JTypeVisitable; + + +/** + * Pretty-printing methods to display types. The current API is only offered for debugging, not for displaying types to + * users. + */ +@SuppressWarnings("all") // Upstream code from https://github.com/pmd/pmd/pull/6904 +public final class TypePrettyPrint +{ + + private TypePrettyPrint() + { + } + + public static @NonNull String prettyPrint(@NonNull final JTypeVisitable t) + { + return prettyPrint(t, new TypePrettyPrinter()); + } + + public static @NonNull String prettyPrintWithSimpleNames(@NonNull final JTypeVisitable t) + { + return prettyPrint(t, new TypePrettyPrinter().qualifyNames(false)); + } + + public static String prettyPrint(@NonNull final JTypeVisitable t, final TypePrettyPrinter prettyPrinter) + { + t.acceptVisitor(DefaultVisitor.INSTANCE, prettyPrinter); + return prettyPrinter.consumeResult(); + } + + static final class DefaultVisitor extends PrettyPrintVisitor + { + static final DefaultVisitor INSTANCE = new DefaultVisitor(); + } +} diff --git a/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrinter.java b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrinter.java new file mode 100644 index 0000000..bf6dad2 --- /dev/null +++ b/rules/src/main/java/software/xdev/pmd/rule/prettyprint/TypePrettyPrinter.java @@ -0,0 +1,149 @@ +/* + * Copyright © 2026 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package software.xdev.pmd.rule.prettyprint; + +import static net.sourceforge.pmd.util.OptionalBool.UNKNOWN; + +import org.pcollections.PSet; + +import net.sourceforge.pmd.lang.java.symbols.SymbolicValue; +import net.sourceforge.pmd.util.OptionalBool; + + +/** + * Options to pretty print a type. Cannot be used concurrently. + */ +@SuppressWarnings("all") // Upstream code from https://github.com/pmd/pmd/pull/6904 +public class TypePrettyPrinter +{ + protected final StringBuilder sb = new StringBuilder(); + + protected boolean printMethodHeader = true; + protected boolean printMethodReturnType = true; + protected OptionalBool printTypeVarBounds = UNKNOWN; + protected boolean qualifyTvars = false; + protected boolean qualifyNames = true; + protected boolean isVarargs = false; + protected boolean printTypeAnnotations = true; + protected boolean qualifyAnnotations = false; + + /** + * Create a new pretty printer with the default configuration. + */ + public TypePrettyPrinter() + { + // default + } + + public StringBuilder append(final char o) + { + return this.sb.append(o); + } + + public StringBuilder append(final String o) + { + return this.sb.append(o); + } + + /** + * Print the declaring type of the method and its type parameters. Default: true. + */ + public TypePrettyPrinter printMethodHeader(final boolean printMethodHeader) + { + this.printMethodHeader = printMethodHeader; + return this; + } + + /** + * Print the return type of methods (as postfix). Default: true. + */ + public TypePrettyPrinter printMethodResult(final boolean printMethodResult) + { + this.printMethodReturnType = printMethodResult; + return this; + } + + /** + * Print the bounds of type variables. Default: false. + */ + public void printTypeVarBounds(final OptionalBool printTypeVarBounds) + { + this.printTypeVarBounds = printTypeVarBounds; + } + + /** + * Qualify type variables with the name of the declaring symbol. Eg {@code Foo#T} for {@code class Foo}}. + * Default: false. + */ + public TypePrettyPrinter qualifyTvars(final boolean qualifyTvars) + { + this.qualifyTvars = qualifyTvars; + return this; + } + + /** + * Whether to print the binary name of a type annotation or just the simple name if false. Default: false. + */ + public TypePrettyPrinter qualifyAnnotations(final boolean qualifyAnnotations) + { + this.qualifyAnnotations = qualifyAnnotations; + return this; + } + + /** + * Whether to print the type annotations. Default: true. + */ + public TypePrettyPrinter printAnnotations(final boolean printAnnotations) + { + this.printTypeAnnotations = printAnnotations; + return this; + } + + /** + * Use qualified names for class types instead of simple names. Default: true. + */ + public TypePrettyPrinter qualifyNames(final boolean qualifyNames) + { + this.qualifyNames = qualifyNames; + return this; + } + + public String consumeResult() + { + // The pretty printer might be reused by another call, + // delete the buffer. + final String result = this.sb.toString(); + this.sb.setLength(0); + return result; + } + + protected void printTypeAnnotations(final PSet annots) + { + if(this.printTypeAnnotations) + { + for(final SymbolicValue.SymAnnot annot : annots) + { + final String name = this.qualifyAnnotations ? annot.getBinaryName() + : annot.getSimpleName(); + this.append('@').append(name).append(' '); + } + } + } +} diff --git a/template-placeholder-demo/pom.xml b/template-placeholder-demo/pom.xml deleted file mode 100644 index 06fc318..0000000 --- a/template-placeholder-demo/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - 4.0.0 - - - software.xdev - template-placeholder-root - 1.0.0-SNAPSHOT - - - template-placeholder-demo - 1.0.0-SNAPSHOT - jar - - - XDEV Software - https://xdev.software - - - - 17 - ${javaVersion} - - UTF-8 - UTF-8 - - software.xdev.Application - - - - - software.xdev - template-placeholder - ${project.version} - - - - - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.15.0 - - ${maven.compiler.release} - - -proc:none - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.8.0 - - - - ${mainClass} - - - true - - - - jar-with-dependencies - - false - - - - make-assembly - package - - single - - - - - - -