Skip to content

Developed preference xml inspections #578

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

Merged
7 changes: 7 additions & 0 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@
enabledByDefault="true" level="WARNING"
implementationClass="com.magento.idea.magento2plugin.inspections.xml.InvalidDependencyInjectionTypeInspection"/>

<localInspection language="XML" groupPath="XML"
shortName="PreferenceXmlInspections"
bundle="magento2.inspection" key="inspection.displayName.PreferenceXmlInspections"
groupBundle="magento2.inspection" groupKey="inspection.group.name"
enabledByDefault="true" level="WARNING"
implementationClass="com.magento.idea.magento2plugin.inspections.xml.PreferenceDeclarationInspection"/>

<internalFileTemplate name="Magento Composer JSON"/>
<internalFileTemplate name="Magento Registration PHP"/>
<internalFileTemplate name="Magento Module XML"/>
Expand Down
18 changes: 18 additions & 0 deletions resources/inspectionDescriptions/PreferenceXmlInspections.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<html>
<body>
<p>
Validation of attributes inside the "preference" tag
</p>
<p>This inspection checks the "for" and "type" attribute of the &ltpreference/&gt; tag for:</p>
<ul>
<li>the correct path to the class or interface</li>
<li>existence of attribute values</li>
</ul>
</body>
</html>
1 change: 1 addition & 0 deletions resources/magento2/inspection.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ inspection.displayName.ModuleDeclarationInModuleXmlInspection=Inspection for the
inspection.displayName.AclResourceXmlInspection=Inspection for the Title XML required attribute in the `etc/acl.xml` file
inspection.displayName.WebApiServiceInspection=Inspection for the Web API XML service declaration
inspection.displayName.InvalidDiTypeInspection=Invalid type configuration in the `etc/di.xml` file
inspection.displayName.PreferenceXmlInspections=Inspection for the Preference declaration
inspection.plugin.duplicateInSameFile=The plugin name already used in this file. For more details see Inspection Description.
inspection.plugin.duplicateInOtherPlaces=The plugin name "{0}" for targeted "{1}" class is already used in the module "{2}" ({3} scope). For more details see Inspection Description.
inspection.plugin.disabledPluginDoesNotExist=This plugin does not exist to be disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public void visitXmlTag(final @NotNull XmlTag xmlTag) {

if (nameAttribute == null
|| nameAttribute.getValue() == null
|| nameAttribute.getValueElement() == null) {
|| nameAttribute.getValueElement() == null
|| nameAttribute.getValueElement().getText().isEmpty()) {
return;
}

Expand Down Expand Up @@ -100,8 +101,9 @@ private void checkObjectArgumentsRecursively(final @NotNull XmlTag tag) {
final XmlAttribute xsiTypeAttr = tag.getAttribute(ModuleDiXml.XSI_TYPE_ATTR);

if (xsiTypeAttr == null
|| xsiTypeAttr.getValue() == null
|| xsiTypeAttr.getValueElement() == null
|| xsiTypeAttr.getValue() == null) {
|| xsiTypeAttr.getValueElement().getText().isEmpty()) {
return;
}
final String xsiTypeValue = xsiTypeAttr.getValue();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

package com.magento.idea.magento2plugin.inspections.xml;

import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
import com.magento.idea.magento2plugin.inspections.validator.InspectionValidator;
import com.magento.idea.magento2plugin.inspections.validator.NotEmptyValidator;
import com.magento.idea.magento2plugin.inspections.validator.PhpClassExistenceValidator;
import com.magento.idea.magento2plugin.magento.files.ModuleDiXml;
import org.jetbrains.annotations.NotNull;

@SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.NPathComplexity"})
public class PreferenceDeclarationInspection extends XmlSuppressableInspectionTool {

@Override
public @NotNull PsiElementVisitor buildVisitor(
final @NotNull ProblemsHolder problemsHolder,
final boolean isOnTheFly
) {
return new XmlElementVisitor() {

private final InspectionBundle inspectionBundle = new InspectionBundle();
private final InspectionValidator phpClassExistenceValidator =
new PhpClassExistenceValidator(problemsHolder.getProject());
private final InspectionValidator notEmptyValidator = new NotEmptyValidator();

@Override
public void visitXmlTag(final XmlTag xmlTag) {
final PsiFile file = xmlTag.getContainingFile();

if (!file.getName().equals(ModuleDiXml.FILE_NAME)
|| !xmlTag.getName().equals(ModuleDiXml.PREFERENCE_TAG_NAME)) {
return;
}

final XmlAttribute preferenceForAttribute =
xmlTag.getAttribute(ModuleDiXml.PREFERENCE_ATTR_FOR);

if (preferenceForAttribute == null
|| preferenceForAttribute.getValue() == null
|| preferenceForAttribute.getValueElement() == null
|| preferenceForAttribute.getValueElement().getText().isEmpty()) {
return;
}

if (!notEmptyValidator.validate(preferenceForAttribute.getValue())) {
reportCouldNotBeEmpty(
preferenceForAttribute.getValueElement(),
preferenceForAttribute.getName()
);
}

final XmlAttribute preferenceTypeAttribute =
xmlTag.getAttribute(ModuleDiXml.TYPE_ATTR);

if (preferenceTypeAttribute == null
|| preferenceTypeAttribute.getValue() == null
|| preferenceTypeAttribute.getValueElement() == null
|| preferenceTypeAttribute.getValueElement().getText().isEmpty()) {
return;
}

if (!notEmptyValidator.validate(preferenceTypeAttribute.getValue())) {
reportCouldNotBeEmpty(
preferenceTypeAttribute.getValueElement(),
preferenceTypeAttribute.getName()
);
}

if (!phpClassExistenceValidator.validate(preferenceForAttribute.getValue())) {
reportClassDoesNotExists(
preferenceForAttribute.getValueElement(),
preferenceForAttribute.getValue()
);
}

if (!phpClassExistenceValidator.validate(preferenceTypeAttribute.getValue())) {
reportClassDoesNotExists(
preferenceTypeAttribute.getValueElement(),
preferenceTypeAttribute.getValue()
);
}
}

/**
* Report Attribute Value could not be empty.
*
* @param psiElement PsiElement
* @param messageParams Object...
*/
private void reportCouldNotBeEmpty(
final @NotNull PsiElement psiElement,
final Object... messageParams
) {
problemsHolder.registerProblem(
psiElement,
inspectionBundle.message(
"inspection.error.idAttributeCanNotBeEmpty",
messageParams
),
ProblemHighlightType.ERROR
);
}

/**
* Report class does not exists.
*
* @param psiElement PsiElement
* @param messageParams Object...
*/
private void reportClassDoesNotExists(
final @NotNull PsiElement psiElement,
final Object... messageParams
) {
problemsHolder.registerProblem(
psiElement,
inspectionBundle.message(
"inspection.warning.class.does.not.exist",
messageParams
),
ProblemHighlightType.WARNING
);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="" type="Foo\Bar\Model\Logger"/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Foo\Bar\Model\Logger" type=""/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Foo\Bar\Model\Logger" type=""/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="" type="Foo\Bar\Model\Logger"/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Not\Existent\Class" type=""/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Api\ProductRepositoryInterface" type="Foo\Bar\Model\Logger"/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="" type="Not\Existent\Class"/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Api\ProductRepositoryInterface" type="Foo\Bar\Model\Logger"/>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

package com.magento.idea.magento2plugin.inspections.xml;

import com.magento.idea.magento2plugin.magento.files.ModuleDiXml;

public class PreferenceDeclarationInspectionTest extends InspectionXmlFixtureTestCase {

private static final String ARGUMENT_VALUE_IS_EMPTY =
"inspection.error.idAttributeCanNotBeEmpty";
private static final String CLASS_DOES_NOT_EXIST =
"inspection.warning.class.does.not.exist";
private static final String EXISTENT_CLASS_ONE =
"Magento\\Catalog\\Api\\ProductRepositoryInterface";
private static final String EXISTENT_CLASS_TWO =
"Foo\\Bar\\Model\\Logger";
private static final String NOT_EXISTENT_CLASS =
"Not\\Existent\\Class";

@Override
public void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(PreferenceDeclarationInspection.class);
}

/**
* Test for an error for the "for" attribute because it is empty.
* <preference for="" type="Foo\Bar\Model\Logger"/>
*/
public void testAttrArgForValueIsEmpty() {
configureFixture();

final String forAttrIsEmptyMessage = inspectionBundle.message(
ARGUMENT_VALUE_IS_EMPTY,
ModuleDiXml.PREFERENCE_ATTR_FOR
);

assertHasHighlighting(forAttrIsEmptyMessage);
}

/**
* Test for an error for the "type" attribute because it is empty.
* <preference for="Foo\Bar\Model\Logger" type="Foo\Bar\Model\Logger"/>
*/
public void testAttrArgTypeValueIsEmpty() {
configureFixture();

final String forAttrIsEmptyMessage = inspectionBundle.message(
ARGUMENT_VALUE_IS_EMPTY,
ModuleDiXml.TYPE_ATTR
);

assertHasHighlighting(forAttrIsEmptyMessage);
}

/**
* Test for an no error for the "for" attribute because this class exists.
* <preference for="Foo\Bar\Model\Logger" type=""/>
*/
public void testAttrForClassExists() {
configureFixture();

final String typeAttrIsEmptyMessage = inspectionBundle.message(
ARGUMENT_VALUE_IS_EMPTY,
ModuleDiXml.PREFERENCE_ATTR_FOR
);

assertHasNoHighlighting(typeAttrIsEmptyMessage);
}

/**
* Test for an no error for the "type" attribute because this class exists.
* <preference for="" type="Foo\Bar\Model\Logger"/>
*/
public void testAttrTypeClassExists() {
configureFixture();

final String typeAttrIsEmptyMessage = inspectionBundle.message(
ARGUMENT_VALUE_IS_EMPTY,
ModuleDiXml.TYPE_ATTR
);

assertHasNoHighlighting(typeAttrIsEmptyMessage);
}

/**
* Test for throwing an error for a class that does not exist for the "for" attribute.
*/
public void testClassAttrForDoesNotExists() {
configureFixture();

final String forClassDoesNotExists = inspectionBundle.message(
CLASS_DOES_NOT_EXIST,
NOT_EXISTENT_CLASS
);

assertHasHighlighting(forClassDoesNotExists);
}

/**
* Test for the absence of an error in the presence of
* classes or interfaces specified for preferences.
*/
public void testClassAttrForIsExist() {
configureFixture();

final String classOneExists = inspectionBundle.message(
CLASS_DOES_NOT_EXIST,
EXISTENT_CLASS_ONE
);

assertHasNoHighlighting(classOneExists);
}

/**
* Test for throwing an error for a class that does not exist for the "type" attribute.
*/
public void testClassAttrTypeDoesNotExists() {
configureFixture();

final String forClassDoesNotExists = inspectionBundle.message(
CLASS_DOES_NOT_EXIST,
NOT_EXISTENT_CLASS
);

assertHasHighlighting(forClassDoesNotExists);
}

/**
* Test for the absence of an error in the presence of
* classes or interfaces specified for preferences.
*/
public void testClassAttrTypeIsExist() {
configureFixture();

final String classOneExists = inspectionBundle.message(
CLASS_DOES_NOT_EXIST,
EXISTENT_CLASS_TWO
);

assertHasNoHighlighting(classOneExists);
}

private void configureFixture() {
myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME));
}
}