-
Notifications
You must be signed in to change notification settings - Fork 0
checking for use of implementation types #24
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
base: azure-sdk-plugin
Are you sure you want to change the base?
Changes from 3 commits
9aea6f3
7f10aab
48f0edd
bda40ff
b5f3b27
be2435d
d439397
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| package com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool; | ||
|
|
||
| import com.intellij.codeInspection.LocalInspectionTool; | ||
| import com.intellij.codeInspection.ProblemsHolder; | ||
| import com.intellij.psi.JavaElementVisitor; | ||
| import com.intellij.psi.PsiClass; | ||
| import com.intellij.psi.PsiClassType; | ||
| import com.intellij.psi.PsiType; | ||
| import com.intellij.psi.PsiVariable; | ||
| import org.jetbrains.annotations.NotNull; | ||
|
|
||
| /** | ||
| * This class is an inspection tool that checks if a variable type is an Azure implementation type. | ||
| * If the variable type is an Azure implementation type, or if the variable type extends or implements an Azure implementation type, | ||
| * the inspection tool will flag it as a problem. | ||
| */ | ||
| public class ImplementationTypeCheck extends LocalInspectionTool { | ||
|
|
||
| /** | ||
| * Build the visitor for the inspection. This visitor will be used to traverse the PSI tree. | ||
| * | ||
| * @param holder The holder for the problems found | ||
| *@param isOnTheFly Whether the inspection is being run on the fly - This is not used in this implementation, but is required by the interface | ||
| * @return The visitor for the inspection | ||
| */ | ||
| @NotNull | ||
| @Override | ||
| public JavaElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { | ||
| return new ImplementationTypeVisitor(holder); | ||
| } | ||
|
|
||
| /** | ||
| * This class extends the JavaElementVisitor to visit the elements in the code. | ||
| * It checks if the variable type is an Azure implementation type and flags it as a problem if it is. | ||
| */ | ||
| static class ImplementationTypeVisitor extends JavaElementVisitor { | ||
|
|
||
| private final ProblemsHolder holder; | ||
|
|
||
| /** | ||
| * Constructor for the visitor | ||
| * | ||
| * @param holder - the ProblemsHolder object to register the problem | ||
| */ | ||
| ImplementationTypeVisitor(ProblemsHolder holder) { | ||
| this.holder = holder; | ||
| } | ||
|
|
||
| // Define constants for string literals | ||
| private static final RuleConfig RULE_CONFIG; | ||
| private static final boolean SKIP_WHOLE_RULE; | ||
| private static final String RULE_NAME = "ImplementationTypeCheck"; | ||
|
|
||
| static { | ||
| RuleConfigLoader centralRuleConfigLoader = RuleConfigLoader.getInstance(); | ||
|
|
||
| // Get the RuleConfig object for the rule | ||
| RULE_CONFIG = centralRuleConfigLoader.getRuleConfig(RULE_NAME); | ||
| SKIP_WHOLE_RULE = RULE_CONFIG.skipRuleCheck() || RULE_CONFIG.getAntiPatternMessageMap().isEmpty() || RULE_CONFIG.getListedItemsToCheck().isEmpty(); | ||
| } | ||
|
|
||
| /** | ||
| * This method visits the variables in the code. | ||
| * It checks if the variable type is an Azure implementation type and flags it as a problem if it is. | ||
| */ | ||
| @Override | ||
| public void visitVariable(@NotNull PsiVariable variable) { | ||
| super.visitVariable(variable); | ||
|
|
||
| if (SKIP_WHOLE_RULE) { | ||
| return; | ||
| } | ||
|
|
||
| // Get the type of the variable - This is the type of the variable declaration | ||
| // eg. List<String> myList = new ArrayList<>(); -> type = List<String> | ||
| PsiType type = variable.getType(); | ||
|
|
||
| // Check if the type directly used is an implementation type | ||
| if (isImplementationType(type) && variable.getNameIdentifier() != null) { | ||
| holder.registerProblem(variable.getNameIdentifier(), RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage")); | ||
|
|
||
| // Check if the type extends or implements an implementation type | ||
| } else if (ExtendsOrImplementsImplementationType(type) && variable.getNameIdentifier() != null) { | ||
| holder.registerProblem(variable.getNameIdentifier(), RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage")); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * This method checks if the type is a class from the Azure package and if it is an implementation type. | ||
| * It returns true if the type is an implementation type and false otherwise. | ||
| */ | ||
| private boolean isImplementationType(PsiType type) { | ||
|
|
||
| if (!(type instanceof PsiClassType)) { | ||
| return false; | ||
| } | ||
| PsiClass psiClass = ((PsiClassType) type).resolve(); | ||
|
|
||
| if (psiClass == null) { | ||
| return false; | ||
| } | ||
| // Check if the class is in the Azure package and if it is an implementation type | ||
| return psiClass.getQualifiedName().startsWith(RuleConfig.AZURE_PACKAGE_NAME) && psiClass.getQualifiedName().contains(RULE_CONFIG.getListedItemsToCheck().get(0)); | ||
|
||
| } | ||
|
|
||
| /** | ||
| * This method checks if the type is a class that extends or implements an implementation type. | ||
| * It returns true if the type extends or implements an implementation type and false otherwise. | ||
| */ | ||
| private boolean ExtendsOrImplementsImplementationType(PsiType type) { | ||
Njeriiii marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (type instanceof PsiClassType) { | ||
| PsiClass psiClass = ((PsiClassType) type).resolve(); | ||
|
|
||
| if (psiClass != null) { | ||
|
|
||
| PsiClass[] interfaces = psiClass.getInterfaces(); | ||
| PsiClass superClass = psiClass.getSuperClass(); | ||
|
|
||
| // Case 1: Class has no implemented interfaces and does not extend any class | ||
| if (interfaces.length == 0 && superClass != null && superClass.getQualifiedName().equals("java.lang.Object")) { | ||
| return false; | ||
| } | ||
|
|
||
| // Case 2: Class has implemented interfaces or extends a class that is an implementation type | ||
| if (interfaces.length > 0 || (superClass != null && !superClass.getQualifiedName().startsWith("java."))) { | ||
|
||
|
|
||
| for (PsiClass iface : interfaces) { | ||
|
|
||
| // If the interface is from the Azure package and is an implementation type return true | ||
| String ifaceName = iface.getQualifiedName(); | ||
| return ifaceName != null && ifaceName.startsWith(RuleConfig.AZURE_PACKAGE_NAME) && ifaceName.contains(RULE_CONFIG.getListedItemsToCheck().get(0)); | ||
| } | ||
|
|
||
| // If the super class is from the Azure package and is an implementation type return true | ||
| String superClassName = superClass.getQualifiedName(); | ||
|
||
| return superClassName != null && superClassName.startsWith(RuleConfig.AZURE_PACKAGE_NAME) && superClassName.contains(RULE_CONFIG.getListedItemsToCheck().get(0)); | ||
|
||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,5 +117,9 @@ | |
| ], | ||
| "servicesToCheck": "KeyCredential", | ||
| "antiPatternMessage": "Endpoint should not be used with KeyCredential for non-Azure OpenAI clients" | ||
| }, | ||
| "ImplementationTypeCheck": { | ||
| "typesToCheck": "implementation", | ||
|
||
| "antiPatternMessage": "Detected usage of an implementation type. Implementation types are not intended for public use. Use the publicly available Azure classes instead." | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| package com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool; | ||
|
|
||
| import com.intellij.codeInspection.ProblemsHolder; | ||
| import com.intellij.psi.JavaElementVisitor; | ||
| import com.intellij.psi.PsiClass; | ||
| import com.intellij.psi.PsiClassType; | ||
| import com.intellij.psi.PsiIdentifier; | ||
| import com.intellij.psi.PsiVariable; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.mockito.Mock; | ||
|
|
||
| import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.ImplementationTypeCheck.ImplementationTypeVisitor; | ||
| import org.mockito.Mockito; | ||
|
|
||
| import static org.mockito.ArgumentMatchers.eq; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.times; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| /** | ||
| * This class tests the ImplementationTypeVisitor class. | ||
| */ | ||
| public class ImplementationTypeCheckTest { | ||
|
|
||
| // Declare as instance variables | ||
| @Mock | ||
| private ProblemsHolder mockHolder; | ||
|
|
||
| @Mock | ||
| private JavaElementVisitor mockVisitor; | ||
|
|
||
| @Mock | ||
| private PsiVariable mockVariable; | ||
|
|
||
| @BeforeEach | ||
| public void setup() { | ||
| mockHolder = mock(ProblemsHolder.class); | ||
| mockVisitor = createVisitor(); | ||
| mockVariable = mock(PsiVariable.class); | ||
| } | ||
|
|
||
| /** | ||
| * Test cases for the ImplementationTypeVisitor class. | ||
| * This case is for a class that is an implementation type. | ||
| * The class is in the implementation package. | ||
| * The registerProblem method should be called. | ||
| */ | ||
| @Test | ||
| public void testDirectUseOfImplementationType() { | ||
| String classQualifiedName = "com.azure.data.appconfiguration.implementation.models"; | ||
|
|
||
| // No interfaces implemented | ||
| PsiClass[] interfaces = new PsiClass[]{}; | ||
| String interfaceQualifiedName = null; | ||
|
|
||
| // Extends Object (implicitly) | ||
| PsiClass superClass = mock(PsiClass.class); | ||
| String superClassQualifiedName = "java.lang.Object"; // Extends java.lang.Object | ||
|
|
||
| int numOfInvocations = 1; | ||
| verifyRegisterProblem(classQualifiedName, interfaces, superClass, superClassQualifiedName, interfaceQualifiedName, numOfInvocations); | ||
| } | ||
|
|
||
| /** | ||
| * Test cases for the ImplementationTypeVisitor class. | ||
| * This case is for a class that is not an implementation type. | ||
| * The registerProblem method should not be called. | ||
| */ | ||
| @Test | ||
| public void testUseOfNonImplementationAzurePackage() { | ||
| String classQualifiedName = "com.azure.data.appconfiguration.models"; | ||
|
|
||
| // No interfaces implemented | ||
| PsiClass[] interfaces = new PsiClass[]{}; | ||
| String interfaceQualifiedName = null; | ||
|
|
||
| // Extends Object (implicitly) | ||
| PsiClass superClass = mock(PsiClass.class); | ||
| String superClassQualifiedName = "java.lang.Object"; // Extends java.lang.Object | ||
|
|
||
| int numOfInvocations = 0; | ||
| verifyRegisterProblem(classQualifiedName, interfaces, superClass, superClassQualifiedName, interfaceQualifiedName, numOfInvocations); | ||
| } | ||
|
|
||
| /** | ||
| * Test cases for the ImplementationTypeVisitor class. | ||
| * This case is for a class that implements an implementation type interface. | ||
| * The registerProblem method should be called. | ||
| */ | ||
| @Test | ||
| public void testUseOfImplementationTypeInterface() { | ||
| String classQualifiedName = "com.azure.data.appconfiguration.models"; | ||
|
|
||
| // Implements an implementation type interface | ||
| PsiClass interfaceClass = mock(PsiClass.class); | ||
| String interfaceQualifiedName = "com.azure.data.appconfiguration.implementation.models"; | ||
| PsiClass[] interfaces = new PsiClass[]{interfaceClass}; | ||
|
|
||
| // Extends Object (implicitly) | ||
| PsiClass superClass = mock(PsiClass.class); | ||
| String superClassQualifiedName = "java.lang.Object"; // Extends java.lang.Object | ||
|
|
||
| int numOfInvocations = 1; | ||
| verifyRegisterProblem(classQualifiedName, interfaces, superClass, superClassQualifiedName, interfaceQualifiedName, numOfInvocations); | ||
| } | ||
|
|
||
| /** | ||
| * Test cases for the ImplementationTypeVisitor class. | ||
| * This case is for a class that extends an implementation type abstract class. | ||
| * The registerProblem method should be called. | ||
| */ | ||
| @Test | ||
| public void testUseOfImplementationTypeAbstractClass() { | ||
| String classQualifiedName = "com.azure.data.appconfiguration.models"; | ||
|
|
||
| // Implements an implementation type interface | ||
| String interfaceQualifiedName = null; | ||
| PsiClass[] interfaces = new PsiClass[]{}; | ||
|
|
||
| // Extends Object (implicitly) | ||
| PsiClass superClass = mock(PsiClass.class); | ||
| String superClassQualifiedName = "com.azure.data.appconfiguration.implementation.models"; // Extends java.lang.Object | ||
|
|
||
| int numOfInvocations = 1; | ||
| verifyRegisterProblem(classQualifiedName, interfaces, superClass, superClassQualifiedName, interfaceQualifiedName, numOfInvocations); | ||
| } | ||
|
|
||
| /** | ||
| * Test cases for the ImplementationTypeVisitor class. | ||
| * This case is for a non-Azure class. | ||
| * The registerProblem method should not be called. | ||
| */ | ||
| @Test | ||
| public void testUseOfNonAzurePackage() { | ||
| String classQualifiedName = "com.nonazure.data.appconfiguration.implementation.models"; | ||
|
|
||
| // No interfaces implemented | ||
| PsiClass[] interfaces = new PsiClass[]{}; | ||
| String interfaceQualifiedName = null; | ||
|
|
||
| // Extends Object (implicitly) | ||
| PsiClass superClass = mock(PsiClass.class); | ||
| String superClassQualifiedName = "java.lang.Object"; // Extends java.lang.Object | ||
|
|
||
| int numOfInvocations = 0; | ||
| verifyRegisterProblem(classQualifiedName, interfaces, superClass, superClassQualifiedName, interfaceQualifiedName, numOfInvocations); | ||
| } | ||
|
|
||
| /** | ||
| * Helper method to create visitor. | ||
| * | ||
| * @return ImplementationTypeVisitor | ||
| */ | ||
| JavaElementVisitor createVisitor() { | ||
| return new ImplementationTypeVisitor(mockHolder); | ||
| } | ||
|
|
||
| /** | ||
| * Helper method to verify registerProblem method. | ||
| * | ||
| * @param classQualifiedName Qualified name of the class | ||
| * @param interfaces Array of interfaces implemented by the class | ||
| * @param superClass Super class of the class - This is the class that the class extends | ||
| * @param superClassQualifiedName Qualified name of the super class | ||
| * @param interfaceQualifiedName Qualified name of the interface - This is the interface that the class implements | ||
| * @param numOfInvocations Number of times registerProblem method is called | ||
| */ | ||
| private void verifyRegisterProblem(String classQualifiedName, PsiClass[] interfaces, PsiClass superClass, String superClassQualifiedName, String interfaceQualifiedName, int numOfInvocations) { | ||
|
|
||
| PsiClassType type = mock(PsiClassType.class); | ||
| PsiClass psiClass = mock(PsiClass.class); | ||
| PsiIdentifier mockIdentifier = mock(PsiIdentifier.class); | ||
|
|
||
| when(mockVariable.getType()).thenReturn(type); | ||
|
|
||
| // isImplementationType method | ||
| when(type.resolve()).thenReturn(psiClass); | ||
| when(psiClass.getQualifiedName()).thenReturn(classQualifiedName); | ||
|
|
||
| // ExtendsOrImplementsImplementationType method | ||
| when(psiClass.getInterfaces()).thenReturn(interfaces); | ||
| when(psiClass.getSuperClass()).thenReturn(superClass); | ||
|
|
||
| when(superClass.getQualifiedName()).thenReturn(superClassQualifiedName); | ||
|
|
||
| if (interfaces.length > 0) { | ||
| when(interfaces[0].getQualifiedName()).thenReturn(interfaceQualifiedName); | ||
| } | ||
|
|
||
| when(mockVariable.getNameIdentifier()).thenReturn(mockIdentifier); | ||
| mockVisitor.visitVariable(mockVariable); | ||
| verify(mockHolder, times(numOfInvocations)).registerProblem(eq(mockIdentifier), Mockito.contains("Detected usage of an implementation type. Implementation types are not intended for public use. Use the publicly available Azure classes instead.")); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.