Adding code inspections
The IntelliJ Platform provides tools designed for static code analysis called code inspections, which help you maintain and clean up code without actually executing it. Read more in the official documentation.
you can find plugin inspections in Settings | Preferences... | Editor | Inspections
.
Use a filter to find only the plugin inspections: Magento 2
.
See Inspections topic in the IntelliJ Platform UI Guidelines for naming, writing descriptions, and messages for inspections to avoid basic naming convention issues before code review.
To add a new inspection
Adding a new code inspection includes:
- Declaring an inspection in the plugin configuration file
- Implementing a local inspection class to inspect code in the IntelliJ Platform-based IDE editor
- Creating a visitor to traverse the PSI tree of the file being edited and inspecting for problematic syntax
- Writing an HTML description of the inspection for display in the inspection preferences panel
- Create a unit test for the inspection
- Implementing a quick fix class to correct syntax problems by altering the PSI tree as needed
- Implementing an inspection preferences panel to display information about the inspection
Declaring an inspection in the plugin configuration file
You must declare all plugin inspections in the <extensions defaultExtensionNs="com.intellij">
XML node of the plugin configuration file.
For example, we will implement a local inspection tool for checking preference XML tag attributes (di.xml
file)
if they are valid classes FQNs.
1
2
3
4
5
6
<localInspection language="XML" groupPath="XML"
shortName="InvalidDiTypeInspection"
bundle="magento2.inspection" key="inspection.displayName.InvalidDiTypeInspection"
groupBundle="magento2.inspection" groupKey="inspection.group.name"
enabledByDefault="true" level="WARNING"
implementationClass="com.magento.idea.magento2plugin.inspections.xml.InvalidDependencyInjectionTypeInspection"/>
The <localInspection/>
tag attributes:
language
- desired file language (ex.: XML, PHP, CSS, JavaScript, etc.)groupPath
- the first level group in the inspection preferences panelshortName
- local inspection identitybundle
- which bundle to use to display name in the inspection preferences panel, constant value:magento2.inspection
key
- which bundle message to use to display name in the inspection preferences panelgroupBundle
- the second level group bundle, constant value:magento2.inspection
groupKey
- group bundle message, constant value:inspection.group.name
enabledByDefault
- is it should be enabled by defaultlevel
- inspection severity levelImplementationClass
- inspection implementation class
Add a new bundle message with key inspection.displayName.InvalidDiTypeInspection
to the InspectionBundleMessagesFile:
1
inspection.displayName.InvalidDiTypeInspection=Invalid type configuration in the `etc/di.xml` file
Implementing a local inspection class to inspect code in the IntelliJ Platform-based IDE editor
The base class for all local inspections is com.intellij.codeInspection.LocalInspectionTool
.
Plugin extension points:
Language | Example Implementation | Extension Point Class |
---|---|---|
PHP |
PluginInspection | com.jetbrains.php.lang.inspections.PhpInspection |
XML |
ModuleDeclarationInModuleXmlInspection | com.intellij.codeInspection.XmlSuppressableInspectionTool |
Implement InvalidDependencyInjectionTypeInspection
inspection that extends XmlSuppressableInspectionTool
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
package com.magento.idea.magento2plugin.inspections.xml;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
import com.intellij.psi.PsiElementVisitor;
import org.jetbrains.annotations.NotNull;
public class InvalidDependencyInjectionTypeInspection extends XmlSuppressableInspectionTool {
@Override
public @NotNull PsiElementVisitor buildVisitor(
final @NotNull ProblemsHolder holder,
final boolean isOnTheFly
) {
// TODO: Implement visitor for InvalidDependencyInjectionTypeInspection type.
return super.buildVisitor(holder, isOnTheFly);
}
}
Creating a visitor to traverse the PSI tree of the file being edited and inspecting for problematic syntax
All visitors should extend com.intellij.psi.PsiElementVisitor
.
Visitors implementations for different languages:
Language | Visitor Implementation Class |
---|---|
PHP |
com.jetbrains.php.lang.psi.visitors.PhpElementVisitor |
XML |
com.intellij.psi.XmlElementVisitor |
JSON |
com.intellij.json.psi.JsonElementVisitor |
JavaScript |
com.intellij.psi.PsiElementVisitor |
Let’s implement new XmlElementVisitor
that overrides visitXmlTag
method. That visitor will check all XML tags
in all XML files. We should dedicate it to check only expected XML files (di.xml
) and expected XML tags (<type/>
).
Use the following code to check only expected XML files:
1
2
3
4
5
6
final PsiFile file = xmlTag.getContainingFile();
if (!file.getName().equals(ModuleDiXml.FILE_NAME)
|| !xmlTag.getName().equals(ModuleDiXml.TYPE_TAG)) {
return;
}
Now we can access the <type/>
tag’s name
attribute to check its value:
1
final XmlAttribute nameAttribute = xmlTag.getAttribute(ModuleDiXml.NAME_ATTR);
The XmlTag.getAttribute()
method returns @Nullable
value. We should check if our attribute is accessible for working with it.
Also, later we will need to use the XmlTag.getValueElement()
method to register problems using ProblemsHolder
if they occur.
The XmlTag.getValueElement()
method also returns @Nullable
value. Let’s use this code to skip checking incorrect XML attributes:
1
2
3
if (nameAttribute == null || nameAttribute.getValue() == null || nameAttribute.getValueElement() == null) {
return;
}
Now we can access attribute values. We can check if an attribute value is an existing class by using the PhpClassExistenceValidator type instance.
To report problem with the name
attribute value, you can use the following code:
1
2
3
4
5
6
7
8
problemsHolder.registerProblem(
nameAttribute.getValueElement(),
inspectionBundle.message(
"inspection.error.idAttributeCanNotBeEmpty",
nameAttribute.getName()
),
ProblemHighlightType.ERROR
);
You cannot access the tag value of the PsiElement
type.
You should report problems with the tag element similar to Intellij Idea inspections. For example: com.intellij.xml.util.CheckEmptyTagInspection
.
So, there is a full example: InvalidDependencyInjectionTypeInspection
Writing HTML descriptions
This section shows you how to display an HTML description of the inspection in the inspection preferences panel.
You must describe all inspections in the description file. To do this, you can add a new HTML file to the following path:
./resources/inspectionDescriptions/{shortName}.html
, where {shortName}
is a shortName
attribute value in the
local inspection declaration. Or you can just use Intellij Idea automation to do this (preferred).
All inspection implementation classes have highlighted class names if they don’t have description files. Here’s a quick fix to create it:
Use Inspections topic to write better descriptions for inspections using naming conventions.
Create a unit test for the inspection
You must deliver each inspection with the unit test for it. The root folder for all inspections unit tests is ./tests/com/magento/idea/magento2plugin/inspections
. As base classes for your tests you should use predefined implementations based on languages.
Base classes implementations for different languages:
Language | Abstract Class |
---|---|
php |
com.magento.idea.magento2plugin.inspections.php.InspectionPhpFixtureTestCase |
xml |
com.magento.idea.magento2plugin.inspections.xml.InspectionXmlFixtureTestCase |
graphql |
com.magento.idea.magento2plugin.inspections.graphqls.InspectionGraphqlsFixtureTestCase |
If you cover new language area, please extend the com.magento.idea.magento2plugin.inspections.BaseInspectionsTestCase
class and add a new abstract class for that area.
All test class names should have the suffix “test” and all testing methods should have the prefix “test” and detailed description in the annotation.
Also, you should enable testing inspection in the setUp()
method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TypeConfigurationTagTypesInspectionTest extends InspectionXmlFixtureTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(InvalidDependencyInjectionTypeInspection.class);
}
/**
* Test type doesn't exists highlighting: <type name="TestingType"/>.
*/
public void testNameAttributeValueTypeDoesNotExists() {
// TODO: write a test.
}
}
The root folder for your test data is: ./testData/inspections
. The internal folder structure is the same as for your test cases, accordingly to the testing file language.
To add data for your new test case use the next rule: create directory inside tested language folder that has the same name as your testing class without suffix Test (for the example above it is folder with the name: TypeConfigurationTagTypesInspection
).
For each testing method create folder in it that has a name the same as method name without test prefix in the camel case format (for the example above it is folder with the name: nameAttributeValueTypeDoesNotExists
inside the TypeConfigurationTagTypesInspection
folder).
Magento 2 SandBox that used for the tests is in the ./testData/project/magento2
folder.