From 8026c73a6499e6e05734d8544bdfb7f9f012c012 Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Tue, 13 May 2025 22:51:56 +0300 Subject: [PATCH 1/6] Add PrimaryConstructorLastRule to enforce constructor ordering - Implement rule checking primary constructor is placed last - Primary constructor is the one with most parameters - Improve code organization and readability - Fix issue #1326 This follows the convention that constructors should be ordered from least to most parameters, with the primary constructor last. --- .../pmd/rules/PrimaryConstructorLastRule.java | 106 ++++++++++++++++++ .../main/resources/com/qulice/pmd/ruleset.xml | 7 ++ 2 files changed, 113 insertions(+) create mode 100644 qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java diff --git a/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java new file mode 100644 index 000000000..50311cee1 --- /dev/null +++ b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java @@ -0,0 +1,106 @@ +/** + * Check that primary constructor is placed at the end of constructors list. + * + * This rule checks that the constructor with the most parameters (primary constructor) + * is placed after all other constructors in the class. + */ +package com.qulice.pmd.rules; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Comparator; + +/** + * Rule that checks primary constructor is placed at the end. + * Primary constructor is the one with the most parameters. + * + * @since 0.18 + */ +public final class PrimaryConstructorLastRule extends AbstractJavaRule { + + /** + * Error message for the rule violation. + */ + private static final String MESSAGE = + "Primary constructor (with most parameters) should be placed at the end of constructors list"; + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + // Only check classes, not interfaces + if (!node.isInterface()) { + checkConstructorOrder(node, data); + } + return super.visit(node, data); + } + + /** + * Check the order of constructors in the class. + * + * @param classNode Class declaration node + * @param data Rule context data + */ + private void checkConstructorOrder(ASTClassOrInterfaceDeclaration classNode, Object data) { + List constructors = + classNode.findDescendantsOfType(ASTConstructorDeclaration.class); + + // Filter only direct children constructors (not nested class constructors) + List directConstructors = new ArrayList<>(); + for (ASTConstructorDeclaration constructor : constructors) { + if (constructor.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class) == classNode) { + directConstructors.add(constructor); + } + } + + if (directConstructors.size() <= 1) { + // No need to check order if there's only one or no constructors + return; + } + + // Find the primary constructor (with most parameters) + ASTConstructorDeclaration primaryConstructor = findPrimaryConstructor(directConstructors); + if (primaryConstructor == null) { + return; + } + + // Check if primary constructor is the last one + ASTConstructorDeclaration lastConstructor = directConstructors.get(directConstructors.size() - 1); + if (primaryConstructor != lastConstructor) { + addViolation(data, primaryConstructor, MESSAGE); + } + } + + /** + * Find primary constructor (the one with most parameters). + * If there are multiple constructors with the same max parameter count, + * consider the first one as primary. + * + * @param constructors List of constructors + * @return Primary constructor or null if no constructors + */ + private ASTConstructorDeclaration findPrimaryConstructor( + List constructors) { + if (constructors.isEmpty()) { + return null; + } + + return constructors.stream() + .max(Comparator.comparingInt(this::getParameterCount)) + .orElse(null); + } + + /** + * Get parameter count for a constructor. + * + * @param constructor Constructor declaration + * @return Number of parameters + */ + private int getParameterCount(ASTConstructorDeclaration constructor) { + ASTFormalParameters params = constructor.getFirstDescendantOfType(ASTFormalParameters.class); + return params != null ? params.getParameterCount() : 0; + } +} \ No newline at end of file diff --git a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml index 66e0b8eef..3b7c4c011 100644 --- a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml +++ b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml @@ -61,6 +61,13 @@ + + + This rule checks that the constructor with the most parameters (primary constructor) + is placed after all other constructors in the class. This convention improves + code readability by placing the most complete constructor at the end. + + Instead of using plain JUnit assertions like org.junit.Assert.assert* From 2cb4efba4152b42cd595bca157a428925e76c0d4 Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Thu, 15 May 2025 06:56:22 +0300 Subject: [PATCH 2/6] fix copyrights --- .../java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java index 50311cee1..97378b9b6 100644 --- a/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java +++ b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java @@ -1,4 +1,6 @@ /** + * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko + * SPDX-License-Identifier: MIT * Check that primary constructor is placed at the end of constructors list. * * This rule checks that the constructor with the most parameters (primary constructor) From 30e2875913fabbc65fe08ec288cfae97d9eb71b5 Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Thu, 15 May 2025 07:04:17 +0300 Subject: [PATCH 3/6] fix markdownlint --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb5f6f5fa..fa0846aac 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -logo +# Qulice +![logo](https://www.qulice.com/logo.svg) [![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org) [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/qulice)](https://www.rultor.com/p/yegor256/qulice) [![We recommend IntelliJ IDEA](https://www.elegantobjects.org/intellij-idea.svg)](https://www.jetbrains.com/idea/) @@ -72,7 +73,7 @@ they don't violate our quality standards. To avoid frustration, before sending us your pull request please run full Maven build: ```bash -$ mvn clean install -Pqulice +mvn clean install -Pqulice ``` Keep in mind that JDK 11+ and Maven 3.8+ are the lowest versions you may use. From a28c3e9b512c8f6078bb111378e40e4476144c66 Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Thu, 15 May 2025 07:08:18 +0300 Subject: [PATCH 4/6] fix depreceted methods --- .../java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java index 97378b9b6..4f1b19e4a 100644 --- a/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java +++ b/qulice-pmd/src/main/java/com/qulice/pmd/rules/PrimaryConstructorLastRule.java @@ -72,7 +72,7 @@ private void checkConstructorOrder(ASTClassOrInterfaceDeclaration classNode, Obj // Check if primary constructor is the last one ASTConstructorDeclaration lastConstructor = directConstructors.get(directConstructors.size() - 1); if (primaryConstructor != lastConstructor) { - addViolation(data, primaryConstructor, MESSAGE); + asCtx(data).addViolation(primaryConstructor, MESSAGE); } } @@ -103,6 +103,6 @@ private ASTConstructorDeclaration findPrimaryConstructor( */ private int getParameterCount(ASTConstructorDeclaration constructor) { ASTFormalParameters params = constructor.getFirstDescendantOfType(ASTFormalParameters.class); - return params != null ? params.getParameterCount() : 0; + return params != null ? params.size() : 0; } } \ No newline at end of file From 2f6fc45964675cf7954b8e604bca4396b6530d7b Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Thu, 15 May 2025 07:11:59 +0300 Subject: [PATCH 5/6] fix ruleset.xml --- qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml index 3b7c4c011..c4498f702 100644 --- a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml +++ b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml @@ -61,7 +61,7 @@ - + This rule checks that the constructor with the most parameters (primary constructor) is placed after all other constructors in the class. This convention improves From 0502ad278de61e42854116b02818cd959254d6f1 Mon Sep 17 00:00:00 2001 From: Dema-koder Date: Thu, 15 May 2025 07:23:40 +0300 Subject: [PATCH 6/6] fix ruleset.xml --- qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml index c4498f702..1cdad9abc 100644 --- a/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml +++ b/qulice-pmd/src/main/resources/com/qulice/pmd/ruleset.xml @@ -109,7 +109,14 @@ 1] - [count(ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[BlockStatement])>1] + [count( + ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[ + BlockStatement[not( + Statement/StatementExpression/PrimaryExpression/PrimaryPrefix[@ThisModifier="true"] + or Statement/StatementExpression/PrimaryExpression/PrimaryPrefix[@SuperModifier="true"] + )] + ] + )>1] ]]>