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

[apex] Added Inner Classes to Apex Class Naming Conventions Rule #5048

Merged
merged 15 commits into from
Jul 18, 2024

Conversation

sgnl-labs
Copy link
Contributor

@sgnl-labs sgnl-labs commented Jun 3, 2024

Describe the PR

Added inner classes to Apex ClassNamingConventionsRule, and added property to support name checking for inner classes.

  • This is a trivial implementation.
  • Alternatively, this could live deeper (ASTUserClass, as a modifier of sorts, but keeping it simple...could also live just above summit.)

Related issues

Ready?

  • Added unit tests for fixed bug/feature
  • Passing all unit tests
  • Complete build ./mvnw clean verify passes (checked automatically by github actions)
  • Added (in-code) documentation (if needed)

… to support name checking for inner classes.
@sgnl-labs sgnl-labs changed the title Added Inner Classes to Class Naming Conventions Rule Added Inner Classes to Apex Class Naming Conventions Rule Jun 3, 2024
@adangel adangel changed the title Added Inner Classes to Apex Class Naming Conventions Rule [apex] Added Inner Classes to Apex Class Naming Conventions Rule Jun 27, 2024
@adangel adangel added the an:enhancement An improvement on existing features / rules label Jun 27, 2024
Copy link
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

Could you please add additional test cases in https://github.com/pmd/pmd/blob/master/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml
See https://docs.pmd-code.org/latest/pmd_userdocs_extending_testing.html for more information about rule testing.

Also I would be interested in:

  • Is this common for Apex, that inner classes / interfaces are created?
  • Can you give an example of the patterns, you would use?

@adangel adangel added this to the 7.4.0 milestone Jun 27, 2024
@sgnl-labs
Copy link
Contributor Author

Thanks for the PR!

Could you please add additional test cases in https://github.com/pmd/pmd/blob/master/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml See https://docs.pmd-code.org/latest/pmd_userdocs_extending_testing.html for more information about rule testing.

Also I would be interested in:

  • Is this common for Apex, that inner classes / interfaces are created?

I wouldn't say that it's a common practice, but it is out there. There are some examples in the official API, but essentially it boils down to organization and consistency. It can also be used to isolate classes that have hidden functionality (although this can be done in other ways, inner classes have the advantage of readability.) One example is Schema.SObjectType. Schema is a class, but so is SObjectType. It just provides organization.

  • Can you give an example of the patterns, you would use?

Most recently, a platform cached provider of RecordType objects grouped by Salesforce SObject (meaning database table, essentially an ORM object.) The 'provider/repository' class was the outer class. The classes that applied directly to the object (but inherited from another, separate class) were inner classes.

Although there isn't really any functional benefit to inner classes, they provide organization on a platform that doesn't allow Java-like namespaces in its Apex source code.

...and thanks for allowing me to possibly contribute to this amazing project! I have so many questions for you guys, but I wanted to start with this small offering. The reason is simple: in a project I was working on, we used class names as 'namespace indicators'...a domain prefix if you will. They had a specific standard that was enforced by PMD. Unfortunately, inner classes were being held to the same standard as outer classes and that didn't align with the standard we wanted to follow.

@sgnl-labs
Copy link
Contributor Author

  • Can you give an example of the patterns, you would use?

A colleague of mine put it much more succinctly:

In MVC design terminology, it is useful to define an inner class to define the data structure being returned to the view & containing its scope to the controller which interacts with the model. It provides containment/encapsulation.

@adangel
Copy link
Member

adangel commented Jun 28, 2024

They had a specific standard that was enforced by PMD. Unfortunately, inner classes were being held to the same standard as outer classes and that didn't align with the standard we wanted to follow.

Now I understand the problem, thanks for clarifying. In that case, adding a separate property for inner class regex is probably the most flexible solution. Another way could be, that inner classes/interfaces are simply not considered by the rule - but then, you won't be able to enforce any naming conventions on them.

@sgnl-labs
Copy link
Contributor Author

sgnl-labs commented Jun 29, 2024

They had a specific standard that was enforced by PMD. Unfortunately, inner classes were being held to the same standard as outer classes and that didn't align with the standard we wanted to follow.

Now I understand the problem, thanks for clarifying. In that case, adding a separate property for inner class regex is probably the most flexible solution. Another way could be, that inner classes/interfaces are simply not considered by the rule - but then, you won't be able to enforce any naming conventions on them.

The property solution is definitely the most flexible path. I already implemented it like that because the rule I modified was doing that anyways. I did toy around with one other possibility:

Summit already knows how to make the distinction between inner and outer classes, so why not make inner classes part of the ASTUserClass or ASTUserClassOrInterface object(s)? I tossed those ideas because they seemed too intrusive and I believed that to be a risk to overall design hygiene.

@adangel I am curious about something too, because I frequently find myself buried 'under the hood' of PMD for custom rules. Is there a reason that ANTLR4 isn't a 'strongly suggested' standard for every language? I was looking at different language modules and it seems to be kind of the Wild West.

@sgnl-labs
Copy link
Contributor Author

sgnl-labs commented Jun 29, 2024

(FYI: sgnl-labs and justinstroudbah are one and the same person...for whatever reason my initial commit was filed under the account I use for my day job.)

@sgnl-labs sgnl-labs requested a review from adangel June 29, 2024 07:27
Copy link
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

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

Thanks for creating the test cases. Unfortunately it fails at the moment already before executing the tests with our PMD checks, see the comments.

Summit already knows how to make the distinction between inner and outer classes, so why not make inner classes part of the ASTUserClass or ASTUserClassOrInterface object(s)?

The inner classes are already part of the outer classes in the tree, that's why you can figure it out via getParent().

In fact, exposing inner classes, interfaces, and enums via the ASTUserClassOrInterface at the parent level would allow the inner/outer hierarchy to be leveraged by any rule.

That is possible, e.g. create a default method in ASTUserClassOrInterface named isNested() - then you can simply use this method in the rule. See

/**
* Returns true if this type declaration is nested inside an interface,
* class or annotation.
*/
default boolean isNested() {
return getParent() instanceof ASTTypeBody;
}
for the equivalent implementation for Java.

@adangel I am curious about something too, because I frequently find myself buried 'under the hood' of PMD for custom rules. Is there a reason that ANTLR4 isn't a 'strongly suggested' standard for every language? I was looking at different language modules and it seems to be kind of the Wild West.

PMD started out with only supporting JavaCC grammars, since PMD 7, we also support Antlr. In addition, we also support preexisting ASTs (independent of any grammar), that need to be mapped (like Apex Summit-AST, JavaScript).
See https://docs.pmd-code.org/latest/pmd_devdocs_major_adding_new_language_antlr.html and https://docs.pmd-code.org/latest/pmd_devdocs_major_adding_new_language_javacc.html - these pages describe what it means to support a language in PMD...

(FYI: sgnl-labs and justinstroudbah are one and the same person...for whatever reason my initial commit was filed under the account I use for my day job.)

This is about the user name/email configuration in git, see https://git-scm.com/docs/git-config#Documentation/git-config.txt-username
GitHub maps the email to a user account then for attribution.

Another point: do we need a separate pattern for inner (nested) enums? And which pattern should be applied for a inner, abstract class? I actually don't want to add too many patterns. So, maybe we can ignore these cases for now until they are really needed?

sgnl-labs and others added 4 commits July 13, 2024 05:36
…style/ClassNamingConventionsRule.java

Co-authored-by: Andreas Dangel <[email protected]>
…/codestyle/xml/ClassNamingConventions.xml

Co-authored-by: Andreas Dangel <[email protected]>
…/codestyle/xml/ClassNamingConventions.xml

Co-authored-by: Andreas Dangel <[email protected]>
…style/ClassNamingConventionsRule.java

Co-authored-by: Andreas Dangel <[email protected]>
Copy link
Contributor Author

@sgnl-labs sgnl-labs left a comment

Choose a reason for hiding this comment

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

Committed suggested changes, thank you for the input!

@sgnl-labs sgnl-labs requested a review from adangel July 13, 2024 12:39
@sgnl-labs
Copy link
Contributor Author

sgnl-labs commented Jul 13, 2024

Another point: do we need a separate pattern for inner (nested) enums? And which pattern should be applied for a inner, abstract class? I actually don't want to add too many patterns. So, maybe we can ignore these cases for now until they are really needed?

@adangel Inner enums are actually more common than inner classes. I have an actual use case. We want the outer class to have a specific naming pattern (as traditional namespaces aren't supported, it's kind of a cheat.) However, while we may want that to apply to standalone enums, we do NOT want the 'namespace' to be part of the nested enum name. Does that make sense?

EDIT:
Also, where the enum lives is context-driven. If it applies globally, it should be in a separate file. If it applies to a singular class or whatever, it's an inner enum.

// Class file
public with sharing class Namespace_ThisClass{
  
  // Stuff happens

 enum Days{
   MONDAY,
   TUESDAY,
   // etc.
 }
}

// Standalone enum file
public enum MyNamespace_SomeEnumeratedValues{
 ONE,
 TWO
}

Copy link
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

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

Thanks!

I've added also "innerInterfacePatterns" to the test cases and introduced isNested().

I'll merge it soon (once the build is successful).

@adangel
Copy link
Member

adangel commented Jul 18, 2024

Inner enums are actually more common than inner classes.

So, currently the rule can't distinguish between an outer enum and an inner enum. If this is wanted, a new property is needed. I'd suggest to do this then in a new PR, if needed.

@pmd-test
Copy link

1 Message
📖 Compared to master:
This changeset changes 0 violations,
introduces 0 new violations, 0 new errors and 0 new configuration errors,
removes 0 violations, 0 errors and 0 configuration errors.
Download full report as build artifact

Generated by 🚫 Danger

@adangel adangel merged commit bd4d897 into pmd:master Jul 18, 2024
3 checks passed
adangel added a commit to adangel/pmd that referenced this pull request Jul 18, 2024
adangel added a commit to adangel/pmd that referenced this pull request Jul 18, 2024
…#5048)

Merge pull request pmd#5048 from sgnl-labs:discreet-inner-class-name-check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
an:enhancement An improvement on existing features / rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[apex] ClassNamingConvention: Support naming convention for *inner* classes
4 participants