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

pmd/7.0.x-kotlin/hasImport #2

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
87a4c59
Merge remote-tracking branch 'upstream/pmd/7.0.x' into pmd/7.0.x
stokpop Jan 20, 2022
b12469b
Merge branch 'pmd:pmd/7.0.x' into pmd/7.0.x
stokpop Jan 20, 2022
581ce2a
Adding PMD support for a new ANTLR grammar based language - step 1-4
jborgers Sep 7, 2021
27cf44f
Add official Kotlin ANTLR grammar files, copy and rename some more fr…
jborgers Sep 8, 2021
609c466
Combine KotlinParser, KotlinLexer and UnicodeClasses and use Swift.g4…
jborgers Sep 10, 2021
0f410a2
Handled all steps of "Adding PMD support for a new ANTLR grammar base…
jborgers Sep 13, 2021
2b5c59e
Separate Kotlin.g4 for parser grammar and KotlinLexer.g4 for lexer gr…
jborgers Sep 15, 2021
6bdd6ee
Fix compilation issues
oowekyala Sep 15, 2021
cd9c991
Fix pmd warning
oowekyala Sep 15, 2021
1ae9d17
checkstyle issues
oowekyala Sep 15, 2021
0c25b16
upped antlr plugin version, fix AST errors by using separate UnicodeC…
stokpop Oct 13, 2021
c1de14b
added FunctionNameTooShort test in bestpractices category for Kotlin,…
stokpop Oct 13, 2021
07fa95a
added kotlin xpath function 'pmd-kotlin:hasChildren(3)' as test case …
stokpop Oct 15, 2021
340fa40
added kotlin Simple parser test
stokpop Oct 15, 2021
f040487
fix checkstyle issues
stokpop Oct 15, 2021
186fb84
moved libDirectory setting for kotlin to pmd-kotlin pom.xml
stokpop Oct 15, 2021
9f9307e
upped pmd-designer.version to 7.0.0-SNAPSHOT
stokpop Nov 25, 2021
67a1a9b
add resources to the pmd-kotlin jar so rule definitions files can be …
stokpop Nov 25, 2021
dc1b86d
set version to 7.0.0-kotlin-SNAPSHOT to avoid mixups in 7.0.0-SNAPSHO…
stokpop Jan 20, 2022
a20676d
Merge remote-tracking branch 'origin/pmd/7.0.x' into pmd/7.0.x
stokpop Jan 31, 2022
3bcf2e6
Initial version hasImport - Request for Kotlin XPath function as simp…
jborgers Mar 11, 2024
cbe5772
removed redundant hasImport def
jborgers Mar 15, 2024
d1dac39
fixes various review remarks
jborgers Mar 15, 2024
1f4146e
addresses a few more review remarks
jborgers Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/_data/xpath_funs.yml
Expand Up @@ -186,4 +186,42 @@ langs:
description: "The number of children."
examples:
- code: '//Identifier[pmd-kotlin:hasChildren(3)]'
outcome: "true or false"

- name: hasImport
jborgers marked this conversation as resolved.
Show resolved Hide resolved
returnType: "xs:boolean"
shortDescription: "Test for an import header"
description: "Returns true if the document has the specified package in the import header"
parameters:
- name: "package"
type: "xs:string"
description: "The fully qualified package name"
examples:
- code: '/KotlinFile[pmd-kotlin:hasImport('javax.xml.xpath')]'
outcome: "true or false"

- name: hasImport
returnType: "xs:boolean"
shortDescription: "Test for an import header"
description: "Returns true if the document has the specified package in the import header"
parameters:
- name: "package"
type: "xs:string"
description: "The fully qualified package name"
examples:
- code: '/KotlinFile[pmd-kotlin:hasImport('javax.xml.xpath')]'
outcome: "true or false"

- name: hasImport
returnType: "xs:boolean"
shortDescription: "Test for an import header"
description: "Returns true if the document has the specified package in the import header"
parameters:
- name: "package"
type: "xs:string"
description: "The fully qualified package name"
examples:
- code: '/KotlinFile[pmd-kotlin:hasImport('javax.xml.xpath')]'
outcome: "true or false"
- code: '//FunctionBody//T-Identifier[@Text='newXPath' and pmd-kotlin:hasImport('javax.xml.xpath')]'
outcome: "true or false"
Expand Up @@ -17,7 +17,8 @@ public class KotlinHandler extends AbstractPmdLanguageVersionHandler {

private static final XPathHandler XPATH_HANDLER =
XPathHandler.getHandlerForFunctionDefs(
BaseContextNodeTestFun.HAS_CHILDREN
BaseContextNodeTestFun.HAS_CHILDREN,
BaseContextNodeTestFun.HAS_IMPORT
);

public KotlinHandler(String release) {
Expand Down
Expand Up @@ -6,6 +6,7 @@

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.kotlin.ast.KotlinNode;
import net.sourceforge.pmd.lang.kotlin.ast.KotlinParser;
import net.sourceforge.pmd.lang.rule.xpath.internal.AstElementNode;

import net.sf.saxon.expr.XPathContext;
Expand All @@ -14,6 +15,11 @@
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.BooleanValue;
import net.sf.saxon.value.SequenceType;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;

/**
* XPath function {@code pmd-kotlin:hasChildren(count as xs:decimal) as xs:boolean}
Expand All @@ -26,12 +32,15 @@ public class BaseContextNodeTestFun<T extends KotlinNode> extends BaseKotlinXPat

static final SequenceType[] NO_ARGUMENTS = { SequenceType.SINGLE_INTEGER };
private final Class<T> klass;
private final BiPredicate<String, T> checker;

public static final BaseKotlinXPathFunction HAS_CHILDREN = new BaseContextNodeTestFun<>(KotlinNode.class, "hasChildren");
public static final BaseKotlinXPathFunction HAS_CHILDREN = new BaseContextNodeTestFun<>(KotlinNode.class, "hasChildren", TestUtil::hasChildren);
public static final BaseKotlinXPathFunction HAS_IMPORT = new BaseContextNodeTestFun<>(KotlinNode.class, "hasImport", TestUtil::hasImport);

protected BaseContextNodeTestFun(Class<T> klass, String localName) {
protected BaseContextNodeTestFun(Class<T> klass, String localName, BiPredicate<String, T> checker) {
super(localName);
this.klass = klass;
this.checker = checker;
}

@Override
Expand All @@ -55,10 +64,87 @@ public ExtensionFunctionCall makeCallExpression() {
@Override
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
Node contextNode = ((AstElementNode) context.getContextItem()).getUnderlyingNode();
boolean hasChildren = contextNode.getNumChildren() == Integer.parseInt(arguments[0].head().getStringValue());
return BooleanValue.get(klass.isInstance(contextNode) && hasChildren);
return BooleanValue.get(klass.isInstance(contextNode) && checker.test(arguments[0].head().getStringValue(), (T) contextNode));
}
};
}

}

class TestUtil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put TestUtil in the Base class? And do we need an intermediate KotlinXPath class?

See: java functions in pmd master
and: children example Kotlin

Better create one sub class per Function?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Base in the name is misleading, it is not a (abstract) base class, it inherits from BaseKotlinXPathFunction, the base class where specific functions should inherit from. hasImport is combined with hasChildren, at least for now. 'Test' may be misleading, it is not like a unit test, but rather for testing nodes for properties. Renamed TestUtil to TestFunUtil.

private TestUtil() {
// utility class
}

public static boolean hasImport(final @NonNull String reqPackage, final @NonNull Node node) {
// assuming root is /KotlinFile, if not, add getFirstChild()
boolean aHeaderHasImport = false;
Node root = node.getRoot();
int numChildren = root.getNumChildren();
for (int i = 0; i < numChildren; i++) {
if (root.getChild(i) instanceof KotlinParser.KtImportList) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use import static for readability? Can use KtImportList instead of KotlinParser.KtImportList?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

aHeaderHasImport = headerHasImport(reqPackage, (KotlinParser.KtImportList)node);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to pass root.getChild(i) instead? put in variable

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well spotted, fixed

}
}
return aHeaderHasImport;
}

private static boolean headerHasImport(final @NonNull String reqPackage, final KotlinParser.KtImportList node) {
int numChildren = node.getNumChildren();
for (int i = 0; i < numChildren; i++) {
if (node.getChild(i) instanceof KotlinParser.KtImportHeader) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put node.getChild(i) in variable: is used twice

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

List<KotlinParser.KtSimpleIdentifier> simpleIdentifiers = ((KotlinParser.KtImportHeader) node.getChild(i)).identifier().simpleIdentifier(); // multiple
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any change on NPE?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on node? Made argument @nonnull

List<String> importParts = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

candidate for function extractIndentifierTexts(node)?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe. Extracted method joinIdentifiersText.

for (KotlinParser.KtSimpleIdentifier smplIdentifier: simpleIdentifiers) {
importParts.add(smplIdentifier.Identifier().getText()); // In designer called T-Identifier
}
String importJoined = String.join(".", importParts);
if (importJoined.contains(reqPackage)) return true;
}
}
return false;
}

public static boolean hasChildren(final @NonNull String reqNumChildren, final @NonNull Node node) {
return node.getNumChildren() == Integer.parseInt(reqNumChildren);
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment can be removed?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* Checks whether the static type of the node is a subtype of the
* class identified by the given name. This ignores type arguments,
* if the type of the node is parameterized. Examples:
*
* <pre>{@code
* isA(List.class, <new ArrayList<String>()>) = true
* isA(ArrayList.class, <new ArrayList<String>()>) = true
* isA(int[].class, <new int[0]>) = true
* isA(Object[].class, <new String[0]>) = true
* isA(_, null) = false
* isA(null, _) = NullPointerException
* }</pre>
*
* <p>If either type is unresolved, the types are tested for equality,
* thus giving more useful results than {@link JTypeMirror#isSubtypeOf(JTypeMirror)}.
*
* <p>Note that primitives are NOT considered subtypes of one another
* by this method, even though {@link JTypeMirror#isSubtypeOf(JTypeMirror)} does.
*
* @param clazz a class (non-null)
* @param node the type node to check
*
* @return true if the type test matches
*
* @throws NullPointerException if the class parameter is null
* TODO
*/
/*public static boolean isA(final @NonNull Class<?> clazz, final @Nullable TypeNode node) {
AssertionUtil.requireParamNotNull("class", clazz);
if (node == null) {
return false;
}

return hasNoSubtypes(clazz) ? isExactlyA(clazz, node)
: isA(clazz, node.getTypeMirror());
}*/

}