Differentiate objects of different languages
Currently, JavaClass (which is a slight misnomer now) handles classes of all languages (e.g. Java + Scala). There is #372 to explicitly support Scala, however this request aims more to improve the support to control which languages' objects to look at in rules.
For example, I would like to be able to apply rules only on Java classes, so something like isJava / isScala (or getLanguage with an Enum) would be great. The current workaround is to manually implement this, look at the Source and check the filename. I don't know if on byte code level there's a better way, especially since both Source and the source's filename are optional.
Thanks for raising this! :slightly_smiling_face: Yes, I know JavaClass might be somewhat misleading, maybe something like JvmClass would be more fitting, since it's actually looking at bytecode :wink:
Unfortunately I also don't know any way at the moment other than looking at the JavaClass.source :thinking: It seems like the only way to somewhat derive this... Take for example ArchUnit's own HasDescription
Classfile .../archunit/archunit/build/classes/java/main/com/tngtech/archunit/base/HasDescription.class
Last modified Oct 24, 2021; size 442 bytes
MD5 checksum 12407c91e13f16c5fd0a8ea9e93882a7
Compiled from "HasDescription.java"
public interface com.tngtech.archunit.base.HasDescription
minor version: 0
major version: 51
flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
this_class: #1 // com/tngtech/archunit/base/HasDescription
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
Constant pool:
#1 = Class #2 // com/tngtech/archunit/base/HasDescription
#2 = Utf8 com/tngtech/archunit/base/HasDescription
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 getDescription
#6 = Utf8 ()Ljava/lang/String;
#7 = Utf8 RuntimeInvisibleAnnotations
#8 = Utf8 Lcom/tngtech/archunit/PublicAPI;
#9 = Utf8 usage
#10 = Utf8 Lcom/tngtech/archunit/PublicAPI$Usage;
#11 = Utf8 ACCESS
#12 = Utf8 SourceFile
#13 = Utf8 HasDescription.java
#14 = Utf8 InnerClasses
#15 = Class #16 // com/tngtech/archunit/PublicAPI$Usage
#16 = Utf8 com/tngtech/archunit/PublicAPI$Usage
#17 = Class #18 // com/tngtech/archunit/PublicAPI
#18 = Utf8 com/tngtech/archunit/PublicAPI
#19 = Utf8 Usage
{
public abstract java.lang.String getDescription();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
RuntimeInvisibleAnnotations:
0: #8(#9=e#10.#11)
com.tngtech.archunit.PublicAPI(
usage=Lcom/tngtech/archunit/PublicAPI$Usage;.ACCESS
)
}
SourceFile: "HasDescription.java"
InnerClasses:
public static final #19= #15 of #17; // Usage=class com/tngtech/archunit/PublicAPI$Usage of class com/tngtech/archunit/PublicAPI
Besides the SourceFile and Compiled from there is hardly any info where this bytecode comes from. There is also no hint which compiler was used, etc. So it seems like this could only be a convenience feature based on JavaClass.source that might fail though, if no source is available :thinking:
That's a real shame, but I won't ask ArchUnit to do something that it can't, so feel free to close this issue if there's nothing useful to be done here. Thanks for the reply!
I'll leave it open for a little, in hopes that some bytecode guru might magically jump in and give us the right hint :wink: But yeah, if we can't come up with any good idea within the next weeks I'll likely close it, and apologize to you :disappointed:
One ugly hack might also be dependencies.any { it.targetClass.name.startsWith("kotlin.") } :see_no_evil: Because usually I would guess any non-Kotlin class doesn't depend on the Kotlin SDK and any Kotlin class will. But of course also just a hack :disappointed:
There's some ways a little less hacky than this, but each is language dependent.
For example, my project has a legacy package of Groovy tests, which we want to stop from growing any larger. All classes emitted from the Groovy compiler implement groovy.lang.GroovyObject, which I use as follows
@ArchTest
val `no ATs may be written in Groovy` = freeze(
noMethods()
.that().areMetaAnnotatedWith(Testable::class.java)
.should().beDeclaredInClassesThat().implement("groovy.lang.GroovyObject")
.`as`("No further tests may be written in Groovy")
)
(test is in Kotlin).
In a similar way, all classes emitted by the Kotlin compiler have a @Metadata annotation. https://www.mobileit.cz/Blog/Pages/arch-unit-4.aspx shows a way to implement a DescribedPredicate that tests for a kotlin class using the kotlinx-metadata library.
Given the number of languages targetting the JVM, I don't think an enum would be suitable - but having an extension suite of predicates etc for each language would be very useful.