diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index a1fbfdee1..b0cc5b59f 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -75,6 +75,7 @@ + @@ -126,6 +127,9 @@ + + + #parse("XML File Header.xml") - +#if (${IS_ADMIN} == "true") + #set ($isAdmin = true) +#else + #set ($isAdmin = false) +#end + diff --git a/resources/inspectionDescriptions/CallingDeprecatedMethod.html b/resources/inspectionDescriptions/CallingDeprecatedMethod.html index f4cb7d794..78867f210 100644 --- a/resources/inspectionDescriptions/CallingDeprecatedMethod.html +++ b/resources/inspectionDescriptions/CallingDeprecatedMethod.html @@ -1,6 +1,6 @@ -

[1439] Call Adobe Commerce @deprecated method: The deprecated method will be removed in upcoming versions. Consider relying on methods declared in API interfaces instead.

+

[1439] Call Magento 2 @deprecated method: The deprecated method will be removed in upcoming versions. Consider relying on methods declared in API interfaces instead.

diff --git a/resources/inspectionDescriptions/ExtendingDeprecatedClass.html b/resources/inspectionDescriptions/ExtendingDeprecatedClass.html index 735578a74..1b7f9a55e 100644 --- a/resources/inspectionDescriptions/ExtendingDeprecatedClass.html +++ b/resources/inspectionDescriptions/ExtendingDeprecatedClass.html @@ -1,6 +1,6 @@ -

[1131] Extending from Adobe Commerce @deprecated class: The extended class will be removed in upcoming versions. Inheritance is not recommended way of extending Adobe Commerce functionality. Update code to use a class marked as @api.

+

[1131] Extending from Magento 2 @deprecated class: The extended class will be removed in upcoming versions. Inheritance is not recommended way of extending Magento Open Source|Adobe Commerce functionality. Update code to use a class marked as @api.

diff --git a/resources/inspectionDescriptions/ImplementedDeprecatedInterface.html b/resources/inspectionDescriptions/ImplementedDeprecatedInterface.html index dcc132bbe..fda1b33ef 100644 --- a/resources/inspectionDescriptions/ImplementedDeprecatedInterface.html +++ b/resources/inspectionDescriptions/ImplementedDeprecatedInterface.html @@ -1,6 +1,6 @@ -

[1338] Implemented Adobe Commerce @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider removing the interface inheritance, using an interface marked as @api or an interface introduced within your implementation instead.

+

[1338] Implemented Magento 2 @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider removing the interface inheritance, using an interface marked as @api or an interface introduced within your implementation instead.

diff --git a/resources/inspectionDescriptions/ImportingDeprecatedClass.html b/resources/inspectionDescriptions/ImportingDeprecatedClass.html index 395fcca44..505ee62a1 100644 --- a/resources/inspectionDescriptions/ImportingDeprecatedClass.html +++ b/resources/inspectionDescriptions/ImportingDeprecatedClass.html @@ -1,6 +1,6 @@ -

[1132] Importing Adobe Commerce @deprecated class: The extended class will be removed in upcoming versions. Consider using Adobe Commerce class marked as @api instead.

+

[1132] Importing Magento 2 @deprecated class: The extended class will be removed in upcoming versions. Consider using Magento Open Source|Adobe Commerce class marked as @api instead.

diff --git a/resources/inspectionDescriptions/ImportingDeprecatedInterface.html b/resources/inspectionDescriptions/ImportingDeprecatedInterface.html index 3026e83a8..f7945974f 100644 --- a/resources/inspectionDescriptions/ImportingDeprecatedInterface.html +++ b/resources/inspectionDescriptions/ImportingDeprecatedInterface.html @@ -1,6 +1,6 @@ -

[1332] Imported Adobe Commerce @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider using an interface or class marked as @api instead.

+

[1332] Imported Magento 2 @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider using an interface or class marked as @api instead.

- \ No newline at end of file + diff --git a/resources/inspectionDescriptions/InheritedDeprecatedInterface.html b/resources/inspectionDescriptions/InheritedDeprecatedInterface.html index 682eadc89..954ee5a97 100644 --- a/resources/inspectionDescriptions/InheritedDeprecatedInterface.html +++ b/resources/inspectionDescriptions/InheritedDeprecatedInterface.html @@ -1,6 +1,6 @@ -

[1337] Inherited from Adobe Commerce @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider removing the interface inheritance, using an interface marked as @api or an interface introduced within your implementation instead.

+

[1337] Inherited from Magento 2 @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider removing the interface inheritance, using an interface marked as @api or an interface introduced within your implementation instead.

diff --git a/resources/inspectionDescriptions/OverridingDeprecatedConstant.html b/resources/inspectionDescriptions/OverridingDeprecatedConstant.html index d3b9dfb30..680aed0a7 100644 --- a/resources/inspectionDescriptions/OverridingDeprecatedConstant.html +++ b/resources/inspectionDescriptions/OverridingDeprecatedConstant.html @@ -1,6 +1,6 @@ -

[1235] Overriding Adobe Commerce @deprecated constant: The deprecated constant will be removed in upcoming versions. Consider using a constant marked as @api or a private constant within your implementation instead.

+

[1235] Overriding Magento 2 @deprecated constant: The deprecated constant will be removed in upcoming versions. Consider using a constant marked as @api or a private constant within your implementation instead.

diff --git a/resources/inspectionDescriptions/OverridingDeprecatedProperty.html b/resources/inspectionDescriptions/OverridingDeprecatedProperty.html index cc74490bb..57af0fab7 100644 --- a/resources/inspectionDescriptions/OverridingDeprecatedProperty.html +++ b/resources/inspectionDescriptions/OverridingDeprecatedProperty.html @@ -1,6 +1,6 @@ -

[1535] Overriding Adobe Commerce @deprecated property: The deprecated property will be removed in upcoming versions. Consider relying on methods declared in API interfaces or using a private property within your implementation instead.

+

[1535] Overriding Magento 2 @deprecated property: The deprecated property will be removed in upcoming versions. Consider relying on methods declared in API interfaces or using a private property within your implementation instead.

diff --git a/resources/inspectionDescriptions/UsingDeprecatedClass.html b/resources/inspectionDescriptions/UsingDeprecatedClass.html index fd5062e5e..02c294146 100644 --- a/resources/inspectionDescriptions/UsingDeprecatedClass.html +++ b/resources/inspectionDescriptions/UsingDeprecatedClass.html @@ -1,6 +1,6 @@ -

[1134] Using Adobe Commerce @deprecated class: The extended class will be removed in upcoming versions. Consider using Adobe Commerce class marked as @api instead.

+

[1134] Using Magento 2 @deprecated class: The extended class will be removed in upcoming versions. Consider using Magento Open Source|Adobe Commerce class marked as @api instead.

diff --git a/resources/inspectionDescriptions/UsingDeprecatedConstant.html b/resources/inspectionDescriptions/UsingDeprecatedConstant.html index 9b7133d4a..fcae69895 100644 --- a/resources/inspectionDescriptions/UsingDeprecatedConstant.html +++ b/resources/inspectionDescriptions/UsingDeprecatedConstant.html @@ -1,6 +1,6 @@ -

[1234] Using Adobe Commerce @deprecated constant: The deprecated constant will be removed in upcoming versions. Consider using a constant marked as @api or a private constant within your implementation instead.

+

[1234] Using Magento 2 @deprecated constant: The deprecated constant will be removed in upcoming versions. Consider using a constant marked as @api or a private constant within your implementation instead.

diff --git a/resources/inspectionDescriptions/UsingDeprecatedInterface.html b/resources/inspectionDescriptions/UsingDeprecatedInterface.html index 49a77b6be..a4e906cfe 100644 --- a/resources/inspectionDescriptions/UsingDeprecatedInterface.html +++ b/resources/inspectionDescriptions/UsingDeprecatedInterface.html @@ -1,6 +1,6 @@ -

[1334] Used Adobe Commerce @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider using an interface or class marked as @api instead.

+

[1334] Used Magento 2 @deprecated interface: The deprecated interface will be removed in upcoming versions. Consider using an interface or class marked as @api instead.

diff --git a/resources/inspectionDescriptions/UsingDeprecatedProperty.html b/resources/inspectionDescriptions/UsingDeprecatedProperty.html index 38a2d179c..27f849a61 100644 --- a/resources/inspectionDescriptions/UsingDeprecatedProperty.html +++ b/resources/inspectionDescriptions/UsingDeprecatedProperty.html @@ -1,6 +1,6 @@ -

[1534] Using Adobe Commerce @deprecated property: The deprecated method will be removed in upcoming versions. Consider relying on methods declared in API interfaces instead.

+

[1534] Using Magento 2 @deprecated property: The deprecated method will be removed in upcoming versions. Consider relying on methods declared in API interfaces instead.

diff --git a/resources/magento2/common.properties b/resources/magento2/common.properties index 7270fbea4..01ad830e0 100644 --- a/resources/magento2/common.properties +++ b/resources/magento2/common.properties @@ -72,4 +72,5 @@ common.template.type=Email Type common.diagnostic.reportButtonText=Report Me common.diagnostic.reportSubmittedTitle=The report is successfully submitted! common.diagnostic.reportSubmittedMessage=Thank you for submitting your report! We will check it as soon as possible. +common.layout.filename=Layout File Name common.targetMethod=Target Method diff --git a/resources/magento2/validation.properties b/resources/magento2/validation.properties index b868b58fb..e3e52c9c0 100644 --- a/resources/magento2/validation.properties +++ b/resources/magento2/validation.properties @@ -9,6 +9,7 @@ validator.alphaAndDashCharacters=The {0} field must contain alphabets and dashes validator.alreadyDeclared={0} is already declared in the {1} module validator.startWithNumberOrCapitalLetter=The {0} field must start with a number or a capital letter validator.onlyNumbers=The {0} field must contain numbers only +validator.onlyIntegerOrFloatNumbers=The {0} field must contain only integer or float numbers validator.mustNotBeNegative={0} must not be negative validator.identifier=The {0} field must contain letters, numbers, dashes, and underscores only validator.identifier.colon=The {0} field must contain letters, numbers, colons, dashes, and underscores only @@ -41,3 +42,8 @@ validator.lowerSnakeCase=The {0} field must be of the lower snake case format validator.menuIdentifierInvalid=The menu identifier is invalid validator.someFieldsHaveErrors=Please, check the dialog. Some fields have errors validator.dbSchema.invalidColumnType=Invalid ''{0}'' column type specified +validator.arrayValuesDialog.invalidValueForRowWithName=Invalid value ''{0}'' specified for the row with name ''{1}'' +validator.arrayValuesDialog.namesMustBeUnique=Duplicated items names +validator.arrayValuesDialog.nameMustNotBeEmpty=The array name cannot be empty +validator.layoutNameRuleInvalid=The layout name is invalid +validator.layoutNameUnderscoreQtyInvalid=Wrong layout name, please check diff --git a/resources/uct/bundle/inspection.properties b/resources/uct/bundle/inspection.properties index d51e8fdc2..14dd91065 100644 --- a/resources/uct/bundle/inspection.properties +++ b/resources/uct/bundle/inspection.properties @@ -14,28 +14,28 @@ inspection.displayName.InheritedDeprecatedInterface=Inherited from @deprecated i inspection.displayName.ImplementedDeprecatedInterface=Implemented @deprecated interface inspection.displayName.CallingDeprecatedMethod=Call @deprecated method inspection.displayName.UsingDeprecatedProperty=Using @deprecated property -inspection.displayName.ImportingNonExistentClass=Importing non-existent Adobe Commerce class -inspection.displayName.ImportingNonExistentInterface=Importing non-existent Adobe Commerce interface -inspection.displayName.InheritedNonExistentInterface=Inherited non-existent Adobe Commerce interface -inspection.displayName.ImplementedNonExistentInterface=Implemented non-existent Adobe Commerce interface -inspection.displayName.ExtendedNonExistentClass=Extended non-existent Adobe Commerce class -inspection.displayName.OverriddenNonExistentConstant=Overridden non-existent Adobe Commerce constant -inspection.displayName.OverriddenNonExistentProperty=Overridden non-existent Adobe Commerce property -inspection.displayName.CalledNonExistentMethod=Call non-existent Adobe Commerce method -inspection.displayName.UsedNonExistentType=Used non-existent Adobe Commerce type -inspection.displayName.UsedNonExistentConstant=Used non-existent Adobe Commerce constant -inspection.displayName.UsedNonExistentProperty=Used non-existent Adobe Commerce property -inspection.displayName.ImportedNonApiClass=Imported non Adobe Commerce API class -inspection.displayName.ImportedNonApiInterface=Imported non Adobe Commerce API interface -inspection.displayName.CalledNonApiMethod=Called non Adobe Commerce API method -inspection.displayName.OverriddenNonApiConstant=Overridden non Adobe Commerce API constant -inspection.displayName.OverriddenNonApiProperty=Overridden non Adobe Commerce API property -inspection.displayName.UsedNonApiConstant=Used non Adobe Commerce API constant -inspection.displayName.UsedNonApiProperty=Used non Adobe Commerce API property -inspection.displayName.UsedNonApiType=Used non Adobe Commerce API type -inspection.displayName.ImplementedNonApiInterface=Implemented non Adobe Commerce API interface -inspection.displayName.ExtendedNonApiClass=Extended non Adobe Commerce API class -inspection.displayName.InheritedNonApiInterface=Inherited non Adobe Commerce API interface +inspection.displayName.ImportingNonExistentClass=Importing non-existent Magento 2 class +inspection.displayName.ImportingNonExistentInterface=Importing non-existent Magento 2 interface +inspection.displayName.InheritedNonExistentInterface=Inherited non-existent Magento 2 interface +inspection.displayName.ImplementedNonExistentInterface=Implemented non-existent Magento 2 interface +inspection.displayName.ExtendedNonExistentClass=Extended non-existent Magento 2 class +inspection.displayName.OverriddenNonExistentConstant=Overridden non-existent Magento 2 constant +inspection.displayName.OverriddenNonExistentProperty=Overridden non-existent Magento 2 property +inspection.displayName.CalledNonExistentMethod=Call non-existent Magento 2 method +inspection.displayName.UsedNonExistentType=Used non-existent Magento 2 type +inspection.displayName.UsedNonExistentConstant=Used non-existent Magento 2 constant +inspection.displayName.UsedNonExistentProperty=Used non-existent Magento 2 property +inspection.displayName.ImportedNonApiClass=Imported non Magento 2 API class +inspection.displayName.ImportedNonApiInterface=Imported non Magento 2 API interface +inspection.displayName.CalledNonApiMethod=Called non Magento 2 API method +inspection.displayName.OverriddenNonApiConstant=Overridden non Magento 2 API constant +inspection.displayName.OverriddenNonApiProperty=Overridden non Magento 2 API property +inspection.displayName.UsedNonApiConstant=Used non Magento 2 API constant +inspection.displayName.UsedNonApiProperty=Used non Magento 2 API property +inspection.displayName.UsedNonApiType=Used non Magento 2 API type +inspection.displayName.ImplementedNonApiInterface=Implemented non Magento 2 API interface +inspection.displayName.ExtendedNonApiClass=Extended non Magento 2 API class +inspection.displayName.InheritedNonApiInterface=Inherited non Magento 2 API interface inspection.displayName.PossibleDependencyOnImplDetails=Possible dependency on implementation details inspection.displayName.CalledNonInterfaceMethod=Called non-interface method customCode.warnings.deprecated.1131=[1131] Extended class ''{0}'' that is @deprecated in the ''{1}'' diff --git a/src/com/magento/idea/magento2plugin/actions/CopyMagentoPath.java b/src/com/magento/idea/magento2plugin/actions/CopyMagentoPath.java index 1e1bc0be5..1d381b0fd 100644 --- a/src/com/magento/idea/magento2plugin/actions/CopyMagentoPath.java +++ b/src/com/magento/idea/magento2plugin/actions/CopyMagentoPath.java @@ -15,8 +15,10 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.magento.idea.magento2plugin.util.magento.GetModuleNameByDirectoryUtil; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javax.imageio.ImageIO; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,6 +29,8 @@ public class CopyMagentoPath extends CopyPathProvider { public static final String CSS_EXTENSION = "css"; private final List acceptedTypes = Arrays.asList(PHTML_EXTENSION, JS_EXTENSION, CSS_EXTENSION); + private static final List SUPPORTED_IMAGE_EXTENSIONS + = new ArrayList<>(Arrays.asList(ImageIO.getReaderFormatNames())); public static final String SEPARATOR = "::"; private int index; @@ -44,6 +48,15 @@ public class CopyMagentoPath extends CopyPathProvider { "web/" }; + /** + * Copy Magento Path actions for phtml, css, js, images extensions. + */ + public CopyMagentoPath() { + super(); + + SUPPORTED_IMAGE_EXTENSIONS.add("svg"); + } + @Override public void update(@NotNull final AnActionEvent event) { final VirtualFile virtualFile = event.getData(PlatformDataKeys.VIRTUAL_FILE); @@ -54,7 +67,8 @@ public void update(@NotNull final AnActionEvent event) { private boolean isNotValidFile(final VirtualFile virtualFile) { return virtualFile != null && virtualFile.isDirectory() - || virtualFile != null && !acceptedTypes.contains(virtualFile.getExtension()); + || virtualFile != null && !acceptedTypes.contains(virtualFile.getExtension()) + && !SUPPORTED_IMAGE_EXTENSIONS.contains(virtualFile.getExtension()); } @Override @@ -85,24 +99,23 @@ private boolean isNotValidFile(final VirtualFile virtualFile) { if (PHTML_EXTENSION.equals(virtualFile.getExtension())) { paths = templatePaths; } else if (JS_EXTENSION.equals(virtualFile.getExtension()) - || CSS_EXTENSION.equals(virtualFile.getExtension())) { + || CSS_EXTENSION.equals(virtualFile.getExtension()) + || SUPPORTED_IMAGE_EXTENSIONS.contains(virtualFile.getExtension())) { paths = webPaths; } else { return fullPath.toString(); } - int endIndex; try { - endIndex = getIndexOf(paths, fullPath, paths[++index]); - } catch (ArrayIndexOutOfBoundsException exception) { - // endIndex could not be found. - return ""; - } - final int offset = paths[index].length(); + final int endIndex = getIndexOf(paths, fullPath, paths[++index]); + final int offset = paths[index].length(); - fullPath.replace(0, endIndex + offset, ""); + fullPath.replace(0, endIndex + offset, ""); - return moduleName + SEPARATOR + fullPath; + return moduleName + SEPARATOR + fullPath; + } catch (ArrayIndexOutOfBoundsException exception) { + return fullPath.toString(); + } } /** diff --git a/src/com/magento/idea/magento2plugin/actions/context/xml/NewLayoutXmlAction.java b/src/com/magento/idea/magento2plugin/actions/context/xml/NewLayoutXmlAction.java new file mode 100644 index 000000000..04e3cc15b --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/context/xml/NewLayoutXmlAction.java @@ -0,0 +1,137 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.context.xml; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.MagentoIcons; +import com.magento.idea.magento2plugin.actions.generation.dialog.NewLayoutTemplateDialog; +import com.magento.idea.magento2plugin.magento.files.LayoutXml; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.ComponentType; +import com.magento.idea.magento2plugin.magento.packages.Package; +import com.magento.idea.magento2plugin.project.Settings; +import com.magento.idea.magento2plugin.util.magento.GetMagentoModuleUtil; +import java.util.Arrays; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class NewLayoutXmlAction extends AnAction { + + public static final String ACTION_NAME = "Magento 2 Layout File"; + public static final String ACTION_DESCRIPTION = "Create a new Magento 2 layout.xml file"; + private PsiDirectory targetDirectory; + + /** + * New layout.xml file generation action constructor. + */ + public NewLayoutXmlAction() { + super(ACTION_NAME, ACTION_DESCRIPTION, MagentoIcons.MODULE); + } + + @Override + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) + public void update(final @NotNull AnActionEvent event) { + setIsAvailableForEvent(event, false); + final Project project = event.getProject(); + + if (project == null || !Settings.isEnabled(project)) { + return; + } + final DataContext context = event.getDataContext(); + final PsiElement targetElement = LangDataKeys.PSI_ELEMENT.getData(context); + final PsiDirectory targetDirectoryCandidate = resolveTargetDirectory(targetElement); + + if (targetDirectoryCandidate == null) { + return; + } + final GetMagentoModuleUtil.MagentoModuleData moduleData = GetMagentoModuleUtil + .getByContext(targetDirectoryCandidate, project); + + if (moduleData == null) { + return; + } + final PsiDirectory viewDir = moduleData.getViewDir(); + + if (viewDir == null) { + return; + } + final List allowedDirectories = Arrays.asList( + Package.moduleViewDir, + Areas.adminhtml.toString(), + Areas.frontend.toString() + ); + if (!allowedDirectories.contains(targetDirectoryCandidate.getName()) + || !moduleData.getType().equals(ComponentType.module)) { + return; + } + final PsiDirectory parentDir = targetDirectoryCandidate.getParentDirectory(); + + if (parentDir == null + || !targetDirectoryCandidate.equals(viewDir) && !parentDir.equals(viewDir)) { + return; + } + targetDirectory = targetDirectoryCandidate; + setIsAvailableForEvent(event, true); + } + + @Override + public void actionPerformed(final @NotNull AnActionEvent event) { + if (event.getProject() == null || targetDirectory == null) { + return; + } + + NewLayoutTemplateDialog.open(event.getProject(), targetDirectory); + } + + /** + * Set is action available for event. + * + * @param event AnActionEvent + * @param isAvailable boolean + */ + private void setIsAvailableForEvent( + final @NotNull AnActionEvent event, + final boolean isAvailable + ) { + event.getPresentation().setVisible(isAvailable); + event.getPresentation().setEnabled(isAvailable); + } + + /** + * Resolve target directory. + * + * @param targetElement PsiElement + * + * @return PsiDirectory + */ + private @Nullable PsiDirectory resolveTargetDirectory(final PsiElement targetElement) { + PsiDirectory target = null; + + if (targetElement instanceof PsiDirectory) { + target = (PsiDirectory) targetElement; + } else if (targetElement instanceof PsiFile) { + target = ((PsiFile) targetElement).getContainingDirectory(); + } + + if (target == null) { + return null; + } + + if (LayoutXml.PARENT_DIR.equals(target.getName())) { + target = target.getParentDirectory(); + } + + return target; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/InjectConstructorArgumentAction.java b/src/com/magento/idea/magento2plugin/actions/generation/InjectConstructorArgumentAction.java new file mode 100644 index 000000000..8802b51b2 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/InjectConstructorArgumentAction.java @@ -0,0 +1,108 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.Parameter; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.magento.idea.magento2plugin.MagentoIcons; +import com.magento.idea.magento2plugin.actions.generation.dialog.NewArgumentInjectionDialog; +import com.magento.idea.magento2plugin.magento.packages.MagentoPhpClass; +import com.magento.idea.magento2plugin.project.Settings; +import com.magento.idea.magento2plugin.util.RegExUtil; +import com.magento.idea.magento2plugin.util.php.PhpPsiElementsUtil; +import org.jetbrains.annotations.NotNull; + +public class InjectConstructorArgumentAction extends AnAction { + + public static final String ACTION_NAME = "Inject argument"; + public static final String ACTION_DESCRIPTION = "Inject argument through the DI"; + public static final String GATHER_ARRAY_VALUES_ACTION_DESCRIPTION = "Specify array values"; + private PhpClass currentPhpClass; + private Parameter currentParameter; + + /** + * Inject constructor argument action constructor. + */ + public InjectConstructorArgumentAction() { + super(ACTION_NAME, ACTION_DESCRIPTION, MagentoIcons.MODULE); + } + + @Override + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) + public void update(final @NotNull AnActionEvent event) { + setIsAvailableForEvent(event, false); + final Project project = event.getProject(); + + if (project == null || !Settings.isEnabled(project)) { + return; + } + final PhpClass phpClass = PhpPsiElementsUtil.getPhpClass(event); + + if (phpClass == null) { + return; + } + // Excluding argument injection generators for Test/ and *Test.php files + // in order to not overload the context menu. + final String filename = phpClass.getContainingFile().getName(); + + if (filename.matches(RegExUtil.Magento.TEST_FILE_NAME) + || phpClass.getPresentableFQN().matches(RegExUtil.Magento.TEST_CLASS_FQN)) { + return; + } + final Parameter parameter = PhpPsiElementsUtil.getMethodArgument(event); + + if (parameter == null) { + return; + } + final Method method = parameter.getParent().getParent() instanceof Method + ? (Method) parameter.getParent().getParent() : null; + + if (method == null) { + return; + } + + if (!method.getAccess().isPublic() + || !MagentoPhpClass.CONSTRUCT_METHOD_NAME.equals(method.getName())) { + return; + } + currentPhpClass = phpClass; + currentParameter = parameter; + setIsAvailableForEvent(event, true); + } + + @Override + public void actionPerformed(final @NotNull AnActionEvent event) { + if (event.getProject() == null + || currentPhpClass == null + || currentParameter == null) { + return; + } + + NewArgumentInjectionDialog.open( + event.getProject(), + currentPhpClass, + currentParameter + ); + } + + /** + * Set is action available for event. + * + * @param event AnActionEvent + * @param isAvailable boolean + */ + private void setIsAvailableForEvent( + final @NotNull AnActionEvent event, + final boolean isAvailable + ) { + event.getPresentation().setVisible(isAvailable); + event.getPresentation().setEnabled(isAvailable); + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/LayoutXmlData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/LayoutXmlData.java index 3be8921b5..c9727dbb1 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/data/LayoutXmlData.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/data/LayoutXmlData.java @@ -39,6 +39,30 @@ public LayoutXmlData( this.uiComponentName = uiComponentName; } + /** + * Layout XML data. + * + * @param area String + * @param route String + * @param moduleName String + * @param controllerName String + * @param actionName String + */ + public LayoutXmlData( + final String area, + final String route, + final String moduleName, + final String controllerName, + final String actionName + ) { + this.area = area; + this.route = route; + this.moduleName = moduleName; + this.controllerName = controllerName; + this.actionName = actionName; + this.uiComponentName = ""; + } + public String getArea() { return area; } diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArgumentData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArgumentData.java new file mode 100644 index 000000000..04123da52 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArgumentData.java @@ -0,0 +1,100 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.data.xml; + +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import org.jetbrains.annotations.NotNull; + +public class DiArgumentData { + + private final String moduleName; + private final String clazz; + private final String parameter; + private final Areas area; + private final DiArgumentType valueType; + private final String value; + + /** + * DI argument DTO constructor. + * + * @param moduleName String + * @param clazz String + * @param parameter String + * @param area Areas + * @param valueType DiArgumentType + * @param value String + */ + public DiArgumentData( + final @NotNull String moduleName, + final @NotNull String clazz, + final @NotNull String parameter, + final @NotNull Areas area, + final @NotNull DiArgumentType valueType, + final @NotNull String value + ) { + this.moduleName = moduleName; + this.clazz = clazz; + this.parameter = parameter; + this.area = area; + this.valueType = valueType; + this.value = value; + } + + /** + * Get module name. + * + * @return String + */ + public String getModuleName() { + return moduleName; + } + + /** + * Get target class. + * + * @return String + */ + public String getClazz() { + return clazz; + } + + /** + * Get target parameter. + * + * @return String + */ + public String getParameter() { + return parameter; + } + + /** + * Get target area. + * + * @return Areas + */ + public Areas getArea() { + return area; + } + + /** + * Get argument value xsi:type. + * + * @return DiArgumentType + */ + public DiArgumentType getValueType() { + return valueType; + } + + /** + * Get argument value. + * + * @return String + */ + public String getValue() { + return value; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArrayValueData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArrayValueData.java new file mode 100644 index 000000000..4affc15cd --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/data/xml/DiArrayValueData.java @@ -0,0 +1,237 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.data.xml; + +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class DiArrayValueData { + + private static final String ITEM_TEMPLATE = + "%value%"; + private static final String NULL_VALUE_ITEM_TEMPLATE = + ""; + + private final List items = new ArrayList<>(); + + /** + * Set items data. + * + * @param items List[DiArrayItemData] + */ + public void setItems(final List items) { + this.items.clear(); + this.items.addAll(items); + } + + /** + * Get items data. + * + * @return List[DiArrayItemData] + */ + public List getItems() { + return items; + } + + /** + * Get internal item by its path. + * + * @param path String divided by `:`. + * + * @return DiArrayItemData + */ + public DiArrayItemData getItemByPath(final String path) { + DiArrayItemData target = null; + final String[] parts = path.split(":"); + final String[] left = Arrays.copyOfRange(parts, 1, parts.length); + final String key = parts[0]; + + for (final DiArrayItemData item : getItems()) { + if (item.getName().equals(key)) { + target = item; + + if (left.length > 0) { + final DiArrayValueData childrenHolder = item.getChildren(); + + if (childrenHolder == null) { // NOPMD + break; + } + target = childrenHolder.getItemByPath(String.join(":", left)); + } + break; + } + } + + return target; + } + + @Override + public String toString() { + return buildArrayTree(this, 0); + } + + /** + * Convert to XML. + * + * @param data DiArrayValueData + * + * @return String + */ + public String convertToXml(final DiArrayValueData data) { + final StringBuilder stringBuilder = new StringBuilder(); + + for (final DiArrayItemData item : data.getItems()) { + String value = item.getValue(); + String template = ITEM_TEMPLATE; + + if (item.getType().equals(DiArgumentType.ARRAY) && item.hasChildren()) { + value = "\n" + convertToXml(item.getChildren()) + "\n"; + } else if (item.getType().equals(DiArgumentType.NULL)) { + template = NULL_VALUE_ITEM_TEMPLATE; + } + final String name = item.getName(); + final String type = item.getType().getArgumentType(); + + stringBuilder.append( + template + .replace("%name%", name) + .replace("%type%", type) + .replace("%value%", value) + ); + } + + return stringBuilder.toString(); + } + + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CyclomaticComplexity"}) + private String buildArrayTree(final DiArrayValueData data, final int indentSize) { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append(indent(0)) + .append("[\n"); + int currentItem = 0; + final int itemsCount = data.getItems().size(); + + for (final DiArrayItemData item : data.getItems()) { + stringBuilder.append(indent(indentSize + 1)); + String value = item.getValue(); + + if (item.getType().equals(DiArgumentType.STRING)) { + value = "'" + value + "'";// NOPMD + } else if (item.getType().equals(DiArgumentType.ARRAY) && item.hasChildren()) { + value = buildArrayTree(item.getChildren(), indentSize + 1); + } else if (item.getType().equals(DiArgumentType.ARRAY) && !item.hasChildren()) { + value = "[]"; + } else if (item.getType().equals(DiArgumentType.BOOLEAN)) { + value = Arrays.asList("1", "true").contains(item.getValue()) ? "true" : "false"; + } else if (item.getType().equals(DiArgumentType.NULL)) { + value = "null"; + } + + stringBuilder + .append('\'') + .append(item.getName()) + .append("' => ") + .append(value); + + if (currentItem != itemsCount - 1) { + stringBuilder.append(','); + } + stringBuilder.append('\n'); + currentItem++; + } + stringBuilder + .append(indent(indentSize)) + .append(']'); + + return stringBuilder.toString(); + } + + private String indent(final int indentSize) { + return " ".repeat(Math.max(0, indentSize)); + } + + public static class DiArrayItemData { + + private DiArrayValueData children; + private final String name; + private final DiArgumentType type; + private final String value; + + /** + * DiArrayItemData DTO constructor. + * + * @param name String + * @param type DiArgumentType + * @param value DiArgumentType + */ + public DiArrayItemData( + final String name, + final DiArgumentType type, + final String value + ) { + this.name = name; + this.type = type; + this.value = value; + } + + /** + * Check if current item has children. + * + * @return boolean + */ + public boolean hasChildren() { + return children != null && !children.getItems().isEmpty(); + } + + /** + * Get children elements holder. + * + * @return DiArrayValueData + */ + public DiArrayValueData getChildren() { + return children; + } + + /** + * Set children elements holder. + * + * @param children DiArrayValueData + */ + public void setChildren(final DiArrayValueData children) { + this.children = children; + } + + /** + * Get item name. + * + * @return String + */ + public String getName() { + return name; + } + + /** + * Get item type. + * + * @return DiArgumentType + */ + public DiArgumentType getType() { + return type; + } + + /** + * Get item value. + * + * @return String + */ + public String getValue() { + return value; + } + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java index 395e2793f..1736a00a4 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java @@ -6,6 +6,7 @@ package com.magento.idea.magento2plugin.actions.generation.dialog; import com.intellij.openapi.util.Pair; +import com.magento.idea.magento2plugin.actions.generation.data.ui.ComboBoxItemData; import com.magento.idea.magento2plugin.actions.generation.dialog.prompt.PlaceholderInitializerUtil; import com.magento.idea.magento2plugin.actions.generation.dialog.reflection.ExtractComponentFromFieldUtil; import com.magento.idea.magento2plugin.actions.generation.dialog.util.DialogFieldErrorUtil; @@ -209,8 +210,17 @@ private String resolveFieldValueByComponentType(final Field field) { } else if (component instanceof JComboBox) { if (((JComboBox) component).getSelectedIndex() == -1) { return ""; + } + final Object selectedItem = ((JComboBox) component).getSelectedItem(); + + if (selectedItem == null) { + return ""; + } + + if (selectedItem instanceof ComboBoxItemData) { + return ((ComboBoxItemData) selectedItem).getKey(); } else { - return ((JComboBox) component).getSelectedItem().toString(); + return selectedItem.toString(); } } else if (component instanceof JTextArea) { return ((JTextArea) component).getText(); diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.form new file mode 100644 index 000000000..12fb4d29c --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.form @@ -0,0 +1,102 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.java new file mode 100644 index 000000000..b27a371be --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/GatherArrayValuesDialog.java @@ -0,0 +1,231 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.util.ui.UIUtil; +import com.magento.idea.magento2plugin.actions.generation.InjectConstructorArgumentAction; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArrayValueData; +import com.magento.idea.magento2plugin.bundles.ValidatorBundle; +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import com.magento.idea.magento2plugin.ui.table.TableGroupWrapper; +import java.awt.Color; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.KeyStroke; +import javax.swing.table.DefaultTableModel; +import org.jetbrains.annotations.NotNull; + +public class GatherArrayValuesDialog extends AbstractDialog { + + private static final String ITEM_NAME = "Name"; + private static final String ITEM_TYPE = "Type"; + private static final String ITEM_VALUE = "Value"; + + private final @NotNull Project project;// NOPMD + private final DiArrayValueData arrayValueData; + + private JPanel contentPane; + private JButton buttonCancel; + private JButton buttonOK; + private JPanel itemsPane;// NOPMD + private JScrollPane itemsScrollPane;// NOPMD + private JTable itemsTable; + private JButton buttonAdd; + private JLabel itemsTableErrorMessage; + + /** + * Array values gathering dialog constructor. + * + * @param project Project + * @param arrayValueData DiArrayValueData + */ + public GatherArrayValuesDialog( + final @NotNull Project project, + final DiArrayValueData arrayValueData + ) { + super(); + + this.project = project; + this.arrayValueData = arrayValueData; + + setContentPane(contentPane); + setModal(true); + setTitle(InjectConstructorArgumentAction.GATHER_ARRAY_VALUES_ACTION_DESCRIPTION); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener(event -> onOK()); + buttonCancel.addActionListener(event -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent event) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction( + event -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ); + + initTable(); + itemsTableErrorMessage.setVisible(false); + itemsTableErrorMessage.setText(""); + } + + /** + * Open a new array values gathering dialog. + * + * @param project Project + * @param arrayValueData DiArrayValueData + */ + public static void open( + final @NotNull Project project, + final DiArrayValueData arrayValueData + ) { + final GatherArrayValuesDialog dialog = new GatherArrayValuesDialog(project, arrayValueData); + dialog.pack(); + dialog.centerDialog(dialog); + dialog.setVisible(true); + } + + /** + * Fire process if all fields are valid. + */ + private void onOK() { + if (itemsTable.isEditing()) { + itemsTable.getCellEditor().stopCellEditing(); + } + final List items = extractItems(getTableModel()); + final Pair validationResult = validateItems(items); + + if (!validationResult.getFirst()) { + showErrorMessage(validationResult.getSecond()); + return; + } + arrayValueData.setItems(items); + exit(); + } + + @Override + protected void showErrorMessage(final @NotNull String errorMessage) { + itemsTableErrorMessage.setVisible(true); + itemsTableErrorMessage.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); + itemsTableErrorMessage.setForeground(new Color(252, 119, 83)); + itemsTableErrorMessage.setText(errorMessage); + } + + private void initTable() { + final List columns = new LinkedList<>(Arrays.asList( + ITEM_NAME, + ITEM_TYPE, + ITEM_VALUE + )); + final Map> sources = new HashMap<>(); + sources.put(ITEM_TYPE, DiArgumentType.getValueList()); + + final TableGroupWrapper tableGroupWrapper = new TableGroupWrapper( + itemsTable, + buttonAdd, + columns, + new HashMap<>(), + sources + ); + tableGroupWrapper.initTableGroup(); + } + + private Pair validateItems( + final List items + ) { + final ValidatorBundle validatorBundle = new ValidatorBundle(); + final List itemsNames = new ArrayList<>(); + + for (final DiArrayValueData.DiArrayItemData item : items) { + final String name = item.getName().trim(); + + if (name.isEmpty()) { + return new Pair<>( + Boolean.FALSE, + validatorBundle.message("validator.arrayValuesDialog.nameMustNotBeEmpty") + ); + } + itemsNames.add(name); + final DiArgumentType type = item.getType(); + final String value = item.getValue().trim(); + + final boolean isValid = type.isValid(value); + + if (!isValid) { + return new Pair<>( + Boolean.FALSE, + validatorBundle.message( + "validator.arrayValuesDialog.invalidValueForRowWithName", + value, + name + ) + ); + } + } + + if (itemsNames.stream().distinct().count() != itemsNames.size()) { + return new Pair<>( + Boolean.FALSE, + validatorBundle.message( + "validator.arrayValuesDialog.namesMustBeUnique" + ) + ); + } + + return new Pair<>(Boolean.TRUE, ""); + } + + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + private List extractItems( + final DefaultTableModel tableModel + ) { + final List items = new ArrayList<>(); + + for (int rowNumber = 0; rowNumber < tableModel.getRowCount(); rowNumber++) { + final DiArrayValueData.DiArrayItemData item = new DiArrayValueData.DiArrayItemData( + tableModel.getValueAt(rowNumber, 0).toString(), + DiArgumentType.getByValue(tableModel.getValueAt(rowNumber, 1).toString()), + tableModel.getValueAt(rowNumber, 2).toString().trim() + ); + + items.add(item); + } + + return new ArrayList<>(items); + } + + /** + * Get table model. + * + * @return DefaultTableModel + */ + private DefaultTableModel getTableModel() { + return (DefaultTableModel) itemsTable.getModel(); + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.form new file mode 100644 index 000000000..01efe99ed --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.form @@ -0,0 +1,460 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.java new file mode 100644 index 000000000..16dab90f7 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewArgumentInjectionDialog.java @@ -0,0 +1,695 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.psi.PsiFile; +import com.intellij.ui.EditorTextField; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.completion.PhpCompletionUtil; +import com.jetbrains.php.lang.PhpLangUtil; +import com.jetbrains.php.lang.psi.elements.Field; +import com.jetbrains.php.lang.psi.elements.Parameter; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.impl.ClassConstImpl; +import com.magento.idea.magento2plugin.actions.generation.InjectConstructorArgumentAction; +import com.magento.idea.magento2plugin.actions.generation.data.ui.ComboBoxItemData; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArgumentData; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArrayValueData; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.FieldValidation; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.RuleRegistry; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.ExtendedNumericRule; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule; +import com.magento.idea.magento2plugin.actions.generation.generator.code.ArgumentInjectionGenerator; +import com.magento.idea.magento2plugin.bundles.ValidatorBundle; +import com.magento.idea.magento2plugin.indexes.ModuleIndex; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import com.magento.idea.magento2plugin.ui.FilteredComboBox; +import com.magento.idea.magento2plugin.util.php.PhpTypeMetadataParserUtil; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings({ + "PMD.TooManyFields", + "PMD.UnusedPrivateField", + "PMD.ExcessiveImports", + "PMD.AvoidInstantiatingObjectsInLoops" +}) +public class NewArgumentInjectionDialog extends AbstractDialog { + + private static final String TARGET_AREA = "Target Area"; + private static final String TARGET_MODULE = "Target Module"; + private static final String ARGUMENT_TYPE = "Argument Type"; + private static final String ARGUMENT_VALUE = "Argument Value"; + private static final String TYPE_VALUE = "Class/Interface"; + private static final String CONST_VALUE = "Target Constant"; + + private final @NotNull Project project; + private final PhpClass targetClass; + private final Parameter targetParameter; + + private JPanel contentPane; + private JButton buttonCancel; + private JButton buttonOK; + private JTextField targetClassField; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, TARGET_MODULE}) + private FilteredComboBox targetModule; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, TARGET_AREA}) + private JComboBox targetArea; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, ARGUMENT_TYPE}) + private JComboBox argumentType; + + private JTextField targetArgument; + + private JPanel objectValuePane; + private JRadioButton noneRB; + private JRadioButton proxyRB; + private JRadioButton factoryRB; + private EditorTextField objectValue; + private String customObjectValue; + + private JPanel stringValuePane; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, + message = {NotEmptyRule.MESSAGE, ARGUMENT_VALUE}) + private JTextField stringValue; + + private JPanel booleanValuePane; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, + message = {NotEmptyRule.MESSAGE, ARGUMENT_VALUE}) + private JComboBox booleanValue; + + private JPanel numberValuePane; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, + message = {NotEmptyRule.MESSAGE, ARGUMENT_VALUE}) + @FieldValidation(rule = RuleRegistry.EXTENDED_NUMERIC, + message = {ExtendedNumericRule.MESSAGE, ARGUMENT_VALUE}) + private JTextField numberValue; + + private JPanel initParameterValuePane; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, TYPE_VALUE}) + private EditorTextField initParameterTypeValue; + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, CONST_VALUE}) + private JComboBox initParameterConstValue; + + private JPanel constantValuePane; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, TYPE_VALUE}) + private EditorTextField constantTypeValue; + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, CONST_VALUE}) + private JComboBox constantValue; + + private JPanel arrayValuePane; + private JComboBox subArrayKey; + private JButton addArrayValueBtn; + private JTextArea arrayView; + + private final DiArrayValueData arrayValues; + + // labels + private JLabel argumentTypeLabel;//NOPMD + private JLabel targetModuleLabel;//NOPMD + private JLabel targetAreaLabel;//NOPMD + private JLabel targetClassLabel;//NOPMD + private JLabel targetArgumentLabel;//NOPMD + private JLabel customObjectValueLabel;//NOPMD + private JLabel stringValueLabel;//NOPMD + private JLabel booleanValueLabel;//NOPMD + private JLabel numberValueLabel;//NOPMD + private JLabel initParameterTypeValueLabel;//NOPMD + private JLabel initParameterConstValueLabel;//NOPMD + private JLabel constantTypeValueLabel;//NOPMD + private JLabel constantValueLabel;//NOPMD + private JLabel subArrayKeyLabel;//NOPMD + + /** + * New argument injection dialog constructor. + * + * @param project Project + * @param targetClass PhpClass + * @param parameter Parameter + */ + @SuppressWarnings({ + "PMD.AccessorMethodGeneration", + "PMD.ExcessiveMethodLength", + "PMD.CognitiveComplexity", + "PMD.CyclomaticComplexity", + }) + public NewArgumentInjectionDialog( + final @NotNull Project project, + final @NotNull PhpClass targetClass, + final @NotNull Parameter parameter + ) { + super(); + + this.project = project; + this.targetClass = targetClass; + targetParameter = parameter; + arrayValues = new DiArrayValueData(); + + setContentPane(contentPane); + setModal(true); + setTitle(InjectConstructorArgumentAction.ACTION_DESCRIPTION); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener(event -> onOK()); + buttonCancel.addActionListener(event -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent event) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction( + event -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ); + + addComponentListener(new FocusOnAFieldListener(() -> targetModule.requestFocusInWindow())); + + targetClassField.setText(targetClass.getPresentableFQN()); + targetArgument.setText(parameter.getName()); + + // make all value panes invisible + objectValuePane.setVisible(false); + stringValuePane.setVisible(false); + booleanValuePane.setVisible(false); + numberValuePane.setVisible(false); + initParameterValuePane.setVisible(false); + constantValuePane.setVisible(false); + arrayValuePane.setVisible(false); + + subArrayKeyLabel.setVisible(false); + subArrayKey.setVisible(false); + + objectValue.setEnabled(false); + + argumentType.addItemListener(event -> { + if (!(event.getItem() instanceof ComboBoxItemData)) { + return; + } + final ComboBoxItemData selectedItem = (ComboBoxItemData) event.getItem(); + changeViewBySelectedArgumentType(selectedItem.getKey()); + }); + + final ActionListener objectTypeGroupActionListener = event -> { + if (!(argumentType.getSelectedItem() instanceof ComboBoxItemData)) { + return; + } + final ComboBoxItemData item = (ComboBoxItemData) argumentType.getSelectedItem(); + changeViewBySelectedArgumentType(item.getKey()); + }; + noneRB.addActionListener(objectTypeGroupActionListener); + proxyRB.addActionListener(objectTypeGroupActionListener); + factoryRB.addActionListener(objectTypeGroupActionListener); + + final Disposable disposable = new Disposable() { + @Override + public @NonNls String toString() { + return NewArgumentInjectionDialog.this.toString(); + } + + @Override + public void dispose() { + NewArgumentInjectionDialog.this.dispose(); + } + }; + + PhpCompletionUtil.installClassCompletion( + objectValue, + "", + disposable, + (clazz) -> !clazz.isFinal() + && !PhpCompletionUtil.hasNamespace(clazz, "\\___PHPSTORM_HELPERS") + ); + + objectValue.addDocumentListener(new DocumentListener() { + @Override + public void documentChanged(final @NotNull DocumentEvent event) { + if (noneRB.isSelected()) { + customObjectValue = objectValue.getText().trim(); + } + } + }); + + PhpCompletionUtil.installClassCompletion( + initParameterTypeValue, + "", + disposable, + (clazz) -> !clazz.isFinal() + && !PhpCompletionUtil.hasNamespace(clazz, "\\___PHPSTORM_HELPERS") + ); + + initParameterTypeValue.addDocumentListener( + new DocumentListener() { + @Override + public void documentChanged(final @NotNull DocumentEvent event) { + populateInitParameterTypeConstants(); + } + } + ); + + PhpCompletionUtil.installClassCompletion( + constantTypeValue, + "", + disposable, + (clazz) -> !clazz.isFinal() + && !PhpCompletionUtil.hasNamespace(clazz, "\\___PHPSTORM_HELPERS") + ); + + constantTypeValue.addDocumentListener( + new DocumentListener() { + @Override + public void documentChanged(final @NotNull DocumentEvent event) { + populateConstantTypeConstants(); + } + } + ); + + addArrayValueBtn.addActionListener(event -> { + final DiArrayValueData requestedArrayValues = new DiArrayValueData(); + GatherArrayValuesDialog.open(project, requestedArrayValues); + + if (!requestedArrayValues.getItems().isEmpty()) { + if (arrayValues.getItems().isEmpty()) { + arrayValues.setItems(requestedArrayValues.getItems()); + } else { + if (subArrayKey.getSelectedItem() == null) { + return; + } + final String subArrayName = ((ComboBoxItemData) subArrayKey + .getSelectedItem()).getKey(); + + if (subArrayName.isEmpty()) { + arrayValues.setItems(requestedArrayValues.getItems()); + } else { + final DiArrayValueData.DiArrayItemData parentItem = arrayValues + .getItemByPath( + subArrayName + ); + if (parentItem != null) { + parentItem.setChildren(requestedArrayValues); + } + } + } + populateSubArrayKeyCombobox(); + final String arrayRepresentation = arrayValues.toString(); + + if (!arrayRepresentation.isEmpty()) { + arrayView.setText(arrayRepresentation); + } + } + }); + guessTargetType(); + } + + /** + * Open a new argument injection dialog. + * + * @param project Project + * @param targetClass PhpClass + * @param parameter Parameter + */ + public static void open( + final @NotNull Project project, + final @NotNull PhpClass targetClass, + final @NotNull Parameter parameter + ) { + final NewArgumentInjectionDialog dialog = + new NewArgumentInjectionDialog(project, targetClass, parameter); + dialog.pack(); + dialog.centerDialog(dialog); + dialog.setVisible(true); + } + + /** + * Fire generation process if all fields are valid. + */ + private void onOK() { + if (validateFormFields()) { + final DiArgumentData data = getDialogDataObject(); + + if (data == null) { + return; + } + final ArgumentInjectionGenerator generator = new ArgumentInjectionGenerator( + data, + project + ); + + final PsiFile generatedFile = generator.generate( + InjectConstructorArgumentAction.ACTION_NAME, + true + ); + + if (generatedFile == null) { + if (generator.getGenerationErrorMessage() == null) { + showErrorMessage( + new ValidatorBundle().message( + "validator.file.cantBeCreated", + "DI XML file" + ) + ); + } else { + showErrorMessage( + new ValidatorBundle().message( + "validator.file.cantBeCreatedWithException", + "DI XML file", + generator.getGenerationErrorMessage() + ) + ); + } + } + exit(); + } + } + + /** + * Create custom components and fill their entries. + */ + @SuppressWarnings({"PMD.UnusedPrivateMethod"}) + private void createUIComponents() { + targetModule = new FilteredComboBox(new ModuleIndex(project).getEditableModuleNames()); + targetArea = new ComboBox<>(); + argumentType = new ComboBox<>(); + booleanValue = new ComboBox<>(); + initParameterConstValue = new ComboBox<>(); + constantValue = new ComboBox<>(); + subArrayKey = new ComboBox<>(); + + for (final Areas area : Areas.values()) { + targetArea.addItem(new ComboBoxItemData(area.toString(), area.toString())); + } + + argumentType.addItem( + new ComboBoxItemData("", " --- Select Type --- ") + ); + + for (final DiArgumentType type : DiArgumentType.values()) { + argumentType.addItem( + new ComboBoxItemData(type.getArgumentType(), type.getArgumentType()) + ); + } + objectValue = new EditorTextField("", project, FileTypes.PLAIN_TEXT); + + booleanValue.addItem(new ComboBoxItemData("", " --- Select Value --- ")); + booleanValue.addItem(new ComboBoxItemData("false", "False")); + booleanValue.addItem(new ComboBoxItemData("true", "True")); + final String selectConstantText = " --- Select Constant --- "; + + initParameterConstValue.addItem(new ComboBoxItemData("", selectConstantText)); + constantValue.addItem(new ComboBoxItemData("", selectConstantText)); + + initParameterTypeValue = new EditorTextField("", project, FileTypes.PLAIN_TEXT); + constantTypeValue = new EditorTextField("", project, FileTypes.PLAIN_TEXT); + } + + private void populateInitParameterTypeConstants() { + final String type = initParameterTypeValue.getText().trim(); + + if (type.isEmpty()) { + return; + } + final List constants = getConstantsNames(type); + + if (constants.isEmpty()) { + return; + } + initParameterConstValue.removeAllItems(); + initParameterConstValue.addItem(new ComboBoxItemData("", " --- Select Constant --- ")); + + for (final String constantName : constants) { + initParameterConstValue.addItem(new ComboBoxItemData(constantName, constantName)); + } + } + + private void populateConstantTypeConstants() { + final String type = constantTypeValue.getText().trim(); + + if (type.isEmpty()) { + return; + } + final List constants = getConstantsNames(type); + + if (constants.isEmpty()) { + return; + } + constantValue.removeAllItems(); + constantValue.addItem(new ComboBoxItemData("", " --- Select Constant --- ")); + + for (final String constantName : constants) { + constantValue.addItem(new ComboBoxItemData(constantName, constantName)); + } + } + + private List getConstantsNames(final @NotNull String fqn) { + final List result = new ArrayList<>(); + final PhpIndex phpIndex = PhpIndex.getInstance(project); + + final Collection interfaces = phpIndex.getInterfacesByFQN(fqn); + final Collection classes = phpIndex.getClassesByFQN(fqn); + PhpClass clazz = null; + + if (!interfaces.isEmpty()) { // NOPMD + clazz = interfaces.iterator().next(); + } else if (!classes.isEmpty()) { + clazz = classes.iterator().next(); + } + + if (clazz == null) { + return result; + } + + for (final Field field : clazz.getOwnFields()) { + if (!(field instanceof ClassConstImpl)) { + continue; + } + result.add(field.getName()); + } + + return result; + } + + private void populateSubArrayKeyCombobox() { + if (arrayValues.getItems().isEmpty()) { + subArrayKeyLabel.setVisible(false); + subArrayKey.setVisible(false); + return; + } + subArrayKey.removeAllItems(); + subArrayKey.addItem(new ComboBoxItemData("", " --- Top Array --- ")); + populateSubArrayKeyCombobox(arrayValues, ""); + + if (subArrayKey.getItemCount() > 1) { // NOPMD + subArrayKeyLabel.setVisible(true); + subArrayKey.setVisible(true); + } + } + + private void populateSubArrayKeyCombobox(final DiArrayValueData data, final String parentKey) { + for (final DiArrayValueData.DiArrayItemData item : data.getItems()) { + if (item.getType().equals(DiArgumentType.ARRAY)) { + final String key = parentKey.isEmpty() + ? item.getName() + : parentKey + ":" + item.getName(); + final String value = parentKey.isEmpty() + ? item.getName() + : parentKey.replaceAll(":", " -> ") + " -> " + item.getName(); + + subArrayKey.addItem(new ComboBoxItemData(key, value)); + + if (item.hasChildren()) { + populateSubArrayKeyCombobox(item.getChildren(), key); + } + } + } + } + + private void changeViewBySelectedArgumentType(final String itemValue) { + // make target value pane visible + objectValuePane.setVisible(itemValue.equals(DiArgumentType.OBJECT.getArgumentType())); + stringValuePane.setVisible(itemValue.equals(DiArgumentType.STRING.getArgumentType())); + booleanValuePane.setVisible(itemValue.equals(DiArgumentType.BOOLEAN.getArgumentType())); + numberValuePane.setVisible(itemValue.equals(DiArgumentType.NUMBER.getArgumentType())); + initParameterValuePane.setVisible( + itemValue.equals(DiArgumentType.INIT_PARAMETER.getArgumentType()) + ); + constantValuePane.setVisible(itemValue.equals(DiArgumentType.CONST.getArgumentType())); + arrayValuePane.setVisible(itemValue.equals(DiArgumentType.ARRAY.getArgumentType())); + + if (itemValue.equals(DiArgumentType.OBJECT.getArgumentType())) { + final String targetType = targetClass.getPresentableFQN(); + + if (noneRB.isSelected()) { + if (customObjectValue != null) { + objectValue.setText(customObjectValue); + } + objectValue.setEnabled(true); + } else if (proxyRB.isSelected()) { + objectValue.setEnabled(false); + objectValue.setText(targetType.concat("\\Proxy")); + } else if (factoryRB.isSelected()) { + objectValue.setEnabled(false); + objectValue.setText(targetType.concat("Factory")); + } + } + } + + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CyclomaticComplexity"}) + private @NotNull String getArgumentValue() { + final ComboBoxItemData item = (ComboBoxItemData) argumentType.getSelectedItem(); + + if (item == null) { + return ""; + } + + if (item.getKey().equals(DiArgumentType.OBJECT.getArgumentType())) { + return objectValue.getText().trim(); + } else if (item.getKey().equals(DiArgumentType.STRING.getArgumentType())) { + return stringValue.getText().trim(); + } else if (item.getKey().equals(DiArgumentType.BOOLEAN.getArgumentType())) { + final ComboBoxItemData booleanValueItem = + (ComboBoxItemData) booleanValue.getSelectedItem(); + + if (booleanValueItem == null) { + return ""; + } + return booleanValueItem.getKey(); + } else if (item.getKey().equals(DiArgumentType.NUMBER.getArgumentType())) { + return numberValue.getText().trim(); + } else if (item.getKey().equals(DiArgumentType.INIT_PARAMETER.getArgumentType())) { + final ComboBoxItemData initParamValueItem = + (ComboBoxItemData) initParameterConstValue.getSelectedItem(); + + if (initParamValueItem == null) { + return ""; + } + final String initParamType = initParameterTypeValue.getText().trim(); + + return initParamType.concat("::").concat(initParamValueItem.getKey()); + } else if (item.getKey().equals(DiArgumentType.CONST.getArgumentType())) { + final ComboBoxItemData constValueItem = + (ComboBoxItemData) constantValue.getSelectedItem(); + + if (constValueItem == null) { + return ""; + } + final String constType = constantTypeValue.getText().trim(); + + return constType.concat("::").concat(constValueItem.getKey()); + } else if (item.getKey().equals(DiArgumentType.NULL.getArgumentType())) { + return ""; + } else if (item.getKey().equals(DiArgumentType.ARRAY.getArgumentType())) { + return arrayValues.convertToXml(arrayValues); + } + + return ""; + } + + @SuppressWarnings("PMD.CyclomaticComplexity") + private void guessTargetType() { + final String mainType = PhpTypeMetadataParserUtil.getMainType(targetParameter); + + if (mainType == null) { + return; + } + String targetDiType = ""; + + if (Arrays.asList("int", "float").contains(mainType)) { + targetDiType = DiArgumentType.NUMBER.getArgumentType(); + } else if (DiArgumentType.STRING.getArgumentType().equals(mainType)) { + targetDiType = DiArgumentType.STRING.getArgumentType(); + } else if ("bool".equals(mainType)) { + targetDiType = DiArgumentType.BOOLEAN.getArgumentType(); + } else if (PhpLangUtil.isFqn(mainType)) { + targetDiType = DiArgumentType.OBJECT.getArgumentType(); + } else if ("array".equals(mainType)) { + targetDiType = DiArgumentType.ARRAY.getArgumentType(); + } + + if (targetDiType.isEmpty()) { + return; + } + + for (int i = 0; i < argumentType.getItemCount(); i++) { + if (targetDiType.equals(argumentType.getItemAt(i).getKey())) { + argumentType.setSelectedIndex(i); + break; + } + } + } + + private DiArgumentData getDialogDataObject() { + if (targetArea.getSelectedItem() == null) { + showErrorMessage(new ValidatorBundle().message(NotEmptyRule.MESSAGE, TARGET_AREA)); + return null; + } + + if (argumentType.getSelectedItem() == null) { + showErrorMessage(new ValidatorBundle().message(NotEmptyRule.MESSAGE, ARGUMENT_TYPE)); + return null; + } + final String argumentTypeValue = ((ComboBoxItemData) argumentType + .getSelectedItem()).getKey().trim(); + + if (argumentTypeValue.isEmpty()) { + showErrorMessage(new ValidatorBundle().message(NotEmptyRule.MESSAGE, ARGUMENT_TYPE)); + return null; + } + + final String argValue = getArgumentValue(); + + if (argValue.isEmpty() + && !argumentTypeValue.equals(DiArgumentType.NULL.getArgumentType())) { + showErrorMessage(new ValidatorBundle().message(NotEmptyRule.MESSAGE, ARGUMENT_VALUE)); + return null; + } + + return new DiArgumentData( + targetModule.getSelectedItem().toString().trim(), + targetClassField.getText().trim(), + targetArgument.getText().trim(), + Areas.getAreaByString(targetArea.getSelectedItem().toString().trim()), + DiArgumentType.getByValue(argumentTypeValue), + argValue + ); + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.form new file mode 100644 index 000000000..f5c0221d5 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.form @@ -0,0 +1,113 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.java new file mode 100644 index 000000000..9dc5507af --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewLayoutTemplateDialog.java @@ -0,0 +1,246 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.psi.PsiDirectory; +import com.magento.idea.magento2plugin.actions.context.xml.NewLayoutXmlAction; +import com.magento.idea.magento2plugin.actions.generation.data.LayoutXmlData; +import com.magento.idea.magento2plugin.actions.generation.data.ui.ComboBoxItemData; +import com.magento.idea.magento2plugin.actions.generation.dialog.util.DialogFieldErrorUtil; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.FieldValidation; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.RuleRegistry; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.IdentifierRule; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule; +import com.magento.idea.magento2plugin.actions.generation.generator.LayoutXmlTemplateGenerator; +import com.magento.idea.magento2plugin.bundles.ValidatorBundle; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.util.magento.GetModuleNameByDirectoryUtil; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.HashMap; +import java.util.Map; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import org.jetbrains.annotations.NotNull; + +public class NewLayoutTemplateDialog extends AbstractDialog { + + private static final String LAYOUT_NAME = "Layout Name"; + + private final @NotNull Project project; + private final String moduleName; + private final PsiDirectory directory; + + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, message = {NotEmptyRule.MESSAGE, LAYOUT_NAME}) + @FieldValidation(rule = RuleRegistry.LAYOUT_NAME, + message = {IdentifierRule.MESSAGE, LAYOUT_NAME}) + private JTextField layoutName; + + private JComboBox area; + + // labels + private JLabel layoutNameLabel; // NOPMD + private JLabel areaLabel; // NOPMD + private JLabel layoutNameErrorMessage; // NOPMD + + /** + * NewLayoutTemplateDialog constructor. + * + * @param project Project + * @param directory PsiDirectory + */ + public NewLayoutTemplateDialog( + final @NotNull Project project, + final @NotNull PsiDirectory directory + ) { + super(); + + this.project = project; + this.moduleName = GetModuleNameByDirectoryUtil.execute(directory, project); + this.directory = directory; + + setContentPane(contentPane); + setModal(true); + setTitle(NewLayoutXmlAction.ACTION_DESCRIPTION); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener(event -> onOK()); + buttonCancel.addActionListener(event -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent event) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction( + event -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ); + + addComponentListener(new FocusOnAFieldListener(() -> area.requestFocusInWindow())); + autoSelectCurrentArea(); + } + + /** + * Open a new layout template dialog. + * + * @param project Project + * @param directory Directory + */ + public static void open( + final @NotNull Project project, + final @NotNull PsiDirectory directory + ) { + final NewLayoutTemplateDialog dialog = new NewLayoutTemplateDialog(project, directory); + dialog.pack(); + dialog.centerDialog(dialog); + dialog.setVisible(true); + } + + /** + * Fire generation process if all fields are valid. + */ + private void onOK() { + if (validateFormFields() && isUnderscoreCorrect()) { + final String[] layoutNameParts = getLayoutNameParts(); + new LayoutXmlTemplateGenerator( + new LayoutXmlData( + getArea(), + layoutNameParts[0], + moduleName, + layoutNameParts[1], + layoutNameParts[2] + ), + project + ).generate(NewLayoutXmlAction.ACTION_NAME, true); + exit(); + } + } + + /** + * Create custom components and fill their entries. + */ + @SuppressWarnings({"PMD.UnusedPrivateMethod", "PMD.AvoidInstantiatingObjectsInLoops"}) + private void createUIComponents() { + area = new ComboBox<>(); + + for (final Areas areaEntry : Areas.values()) { + if (!areaEntry.equals(Areas.adminhtml) && !areaEntry.equals(Areas.frontend)) { + continue; + } + area.addItem(new ComboBoxItemData(areaEntry.toString(), areaEntry.toString())); + } + } + + private void autoSelectCurrentArea() { + final String selectedDirName = directory.getName(); + final Map areaIndexMap = new HashMap<>(); + + for (int i = 0; i < area.getItemCount(); i++) { + final ComboBoxItemData item = area.getItemAt(i); + areaIndexMap.put(item.getKey(), i); + } + + if (areaIndexMap.containsKey(selectedDirName)) { + area.setSelectedIndex(areaIndexMap.get(selectedDirName)); + } + } + + /** + * Get parts of inserted layout name. + * + * @return String[] + */ + private String[] getLayoutNameParts() { + + final String[] layoutNameParts = layoutName.getText().trim().split("_"); + String routeName = ""; + String controllerName = ""; + String actionName = ""; + + if (layoutNameParts.length >= 1) { // NOPMD + routeName = layoutNameParts[0]; + } + + if (layoutNameParts.length == 3) { // NOPMD + controllerName = layoutNameParts[1]; + actionName = layoutNameParts[2]; + } + + return new String[]{routeName, controllerName, actionName}; + } + + /** + * Check is count of underscore is correct in layout name. + * + * @return boolean + */ + private boolean isUnderscoreCorrect() { + final String name = layoutName.getText().trim(); + + if (name.contains("_")) { + final int count = countUnderscore(name); + + if (count != 0 && count != 2) { + DialogFieldErrorUtil.showErrorMessageForField( + layoutName, + layoutNameErrorMessage, + new ValidatorBundle() + .message("validator.layoutNameUnderscoreQtyInvalid") + ); + + return false; + } + } + + return true; + } + + /** + * Count underscore symbols in string. + * + * @param name String + * @return int + */ + private int countUnderscore(final String name) { + int count = 0; + + for (int i = 0; i < name.length(); i++) { + if (name.charAt(i) == '_') { //NOPMD + count++; + } + } + + return count; + } + + /** + * Get area. + * + * @return String + */ + private String getArea() { + return area.getSelectedItem().toString(); + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/util/DialogFieldErrorUtil.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/util/DialogFieldErrorUtil.java index 80c12d54a..385fc1566 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/dialog/util/DialogFieldErrorUtil.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/util/DialogFieldErrorUtil.java @@ -19,6 +19,7 @@ import javax.swing.JTextField; import javax.swing.UIManager; import javax.swing.border.Border; +import javax.swing.text.JTextComponent; import org.jetbrains.annotations.NotNull; public final class DialogFieldErrorUtil { @@ -165,6 +166,40 @@ public void focusLost(final FocusEvent event) { return true; } + /** + * Show error message for field for component. + * + * @param fieldComponent JTextComponent + * @param messageHolder JLabel + * @param message String + */ + public static void showErrorMessageForField( + final @NotNull JTextComponent fieldComponent, + final @NotNull JLabel messageHolder, + final @NotNull String message + ) { + highlightField(fieldComponent); + + messageHolder.setVisible(true); + messageHolder.setFont(UIUtil.getLabelFont(UIUtil.FontSize.MINI)); + messageHolder.setForeground(ERROR_COLOR); + messageHolder.setText(message); + + fieldComponent.addFocusListener(new FocusListener() { + @Override + public void focusGained(final FocusEvent event) { + messageHolder.setVisible(false); + messageHolder.setText(""); + } + + @Override + public void focusLost(final FocusEvent event) { + messageHolder.setVisible(false); + messageHolder.setText(""); + } + }); + } + /** * Get message holder component for field. * diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/annotation/RuleRegistry.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/annotation/RuleRegistry.java index a9a626fdc..14a5ee1de 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/annotation/RuleRegistry.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/annotation/RuleRegistry.java @@ -15,9 +15,11 @@ import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.ConfigPathRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.CronScheduleRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.DirectoryRule; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.ExtendedNumericRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.IdentifierRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.IdentifierWithColonRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.IdentifierWithForwardSlash; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.LayoutNameRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.Lowercase; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.MenuIdentifierRule; import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule; @@ -31,6 +33,7 @@ import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.TableNameLength; public enum RuleRegistry { + NOT_EMPTY(NotEmptyRule.class), BOX_NOT_EMPTY(BoxNotEmptyRule.class), PHP_CLASS(PhpClassRule.class), @@ -53,8 +56,10 @@ public enum RuleRegistry { CONFIG_PATH(ConfigPathRule.class), CLI_COMMAND(CliCommandRule.class), NUMERIC(NumericRule.class), + EXTENDED_NUMERIC(ExtendedNumericRule.class), TABLE_NAME_LENGTH(TableNameLength.class), - MENU_IDENTIFIER(MenuIdentifierRule.class); + MENU_IDENTIFIER(MenuIdentifierRule.class), + LAYOUT_NAME(LayoutNameRule.class); private Class rule; diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/ExtendedNumericRule.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/ExtendedNumericRule.java new file mode 100644 index 000000000..2668cd4a2 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/ExtendedNumericRule.java @@ -0,0 +1,24 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule; + +import com.magento.idea.magento2plugin.util.RegExUtil; +import org.jetbrains.annotations.NotNull; + +public class ExtendedNumericRule implements ValidationRule { + + public static final String MESSAGE = "validator.onlyIntegerOrFloatNumbers"; + private static final ValidationRule INSTANCE = new ExtendedNumericRule(); + + @Override + public boolean check(final @NotNull String value) { + return value.matches(RegExUtil.EXTENDED_NUMERIC); + } + + public static ValidationRule getInstance() { + return INSTANCE; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/LayoutNameRule.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/LayoutNameRule.java new file mode 100644 index 000000000..9af322458 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/validator/rule/LayoutNameRule.java @@ -0,0 +1,24 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule; + +import com.magento.idea.magento2plugin.util.RegExUtil; +import org.jetbrains.annotations.NotNull; + +public class LayoutNameRule implements ValidationRule { + + public static final String MESSAGE = "validator.layoutNameRuleInvalid"; + public static final ValidationRule INSTANCE = new LayoutNameRule(); + + @Override + public boolean check(final @NotNull String value) { + return value.matches(RegExUtil.LAYOUT_NAME); + } + + public static ValidationRule getInstance() { + return INSTANCE; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/LayoutXmlTemplateGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/LayoutXmlTemplateGenerator.java new file mode 100644 index 000000000..dc95aa570 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/LayoutXmlTemplateGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.magento.idea.magento2plugin.actions.generation.data.LayoutXmlData; +import com.magento.idea.magento2plugin.actions.generation.generator.util.FindOrCreateLayoutXml; +import java.util.Properties; +import org.jetbrains.annotations.NotNull; + +public class LayoutXmlTemplateGenerator extends FileGenerator { + + private final LayoutXmlData layoutXmlData; + private final Project project; + private final FindOrCreateLayoutXml findOrCreateLayoutXml; + + /** + * Constructor. + * + * @param layoutXmlData LayoutXmlData + * @param project Project + */ + public LayoutXmlTemplateGenerator( + final @NotNull LayoutXmlData layoutXmlData, + final Project project + ) { + super(project); + this.layoutXmlData = layoutXmlData; + this.project = project; + this.findOrCreateLayoutXml = new FindOrCreateLayoutXml(project); + } + + /** + * Creates a module layout file. + * + * @param actionName String + * + * @return PsiFile + */ + @Override + public PsiFile generate(final @NotNull String actionName) { + final XmlFile layoutXml = (XmlFile) findOrCreateLayoutXml.execute( + actionName, + layoutXmlData.getRoute(), + layoutXmlData.getControllerName(), + layoutXmlData.getActionName(), + layoutXmlData.getModuleName(), + layoutXmlData.getArea() + ); + + if (layoutXml == null) { + return null; + } + final PsiDocumentManager psiDocumentManager = + PsiDocumentManager.getInstance(project); + final Document document = psiDocumentManager.getDocument(layoutXml); + + if (document == null) { + return null; + } + WriteCommandAction.runWriteCommandAction(project, () -> { + final XmlTag rootTag = layoutXml.getRootTag(); + + if (rootTag == null) { + return; + } + psiDocumentManager.commitDocument(document); + }); + + return layoutXml; + } + + @Override + @SuppressWarnings("PMD.UncommentedEmptyMethodBody") + protected void fillAttributes(final Properties attributes) {} +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/code/ArgumentInjectionGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/code/ArgumentInjectionGenerator.java new file mode 100644 index 000000000..91e048614 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/code/ArgumentInjectionGenerator.java @@ -0,0 +1,211 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator.code; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArgumentData; +import com.magento.idea.magento2plugin.actions.generation.generator.FileGenerator; +import com.magento.idea.magento2plugin.actions.generation.generator.code.util.DiXmlTagManipulatorUtil; +import com.magento.idea.magento2plugin.actions.generation.generator.util.DirectoryGenerator; +import com.magento.idea.magento2plugin.actions.generation.generator.util.FileFromTemplateGenerator; +import com.magento.idea.magento2plugin.indexes.ModuleIndex; +import com.magento.idea.magento2plugin.magento.files.ModuleDiXml; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.Package; +import com.magento.idea.magento2plugin.util.magento.FileBasedIndexUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; + +public final class ArgumentInjectionGenerator extends FileGenerator { + + private final DiArgumentData data; + private final ModuleDiXml file; + private final DirectoryGenerator directoryGenerator; + private final FileFromTemplateGenerator fileFromTemplateGenerator; + private String generationErrorMessage; + + /** + * Argument injection generator constructor. + * + * @param data DiArgumentData + * @param project Project + */ + public ArgumentInjectionGenerator( + final @NotNull DiArgumentData data, + final @NotNull Project project + ) { + super(project); + this.data = data; + file = new ModuleDiXml(); + directoryGenerator = DirectoryGenerator.getInstance(); + fileFromTemplateGenerator = new FileFromTemplateGenerator(project); + } + + /** + * Generate Web API XML declaration. + * + * @param actionName String + * + * @return PsiFile + */ + @Override + public PsiFile generate(final @NotNull String actionName) { + final PsiDirectory moduleDirectory = new ModuleIndex(project) + .getModuleDirectoryByModuleName(data.getModuleName()); + + if (moduleDirectory == null) { + generationErrorMessage = "Could not find the target module directory"; + return null; + } + + final XmlFile diXmlFile = getDiXmlFile(actionName, moduleDirectory); + + if (diXmlFile == null) { + if (generationErrorMessage == null) { + generationErrorMessage = "Could not generate the target di.xml file"; + } + return null; + } + + try { + final XmlTag rootTag = diXmlFile.getRootTag(); + + if (rootTag == null) { + generationErrorMessage = "Could not read the target di.xml file"; + return null; + } + final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); + final Document document = psiDocumentManager.getDocument(diXmlFile); + + if (document == null) { + generationErrorMessage = "Could not get the document for the target di.xml file"; + return null; + } + XmlTag targetTag = getTargetTag(rootTag); + + if (targetTag == null) { + final List> attributes = new ArrayList<>(); + attributes.add(new Pair<>("name", data.getClazz())); + + targetTag = DiXmlTagManipulatorUtil.insertTag( + rootTag, + "type", + attributes + ); + } + + if (targetTag == null) { + return null; + } + DiXmlTagManipulatorUtil.insertArgumentInTypeTag( + targetTag, + data.getParameter(), + data.getValueType(), + data.getValue() + ); + } catch (Exception exception) { // NOPMD + generationErrorMessage = exception.getMessage(); + } + final PsiFile diXmlFileToReformat = diXmlFile; + + WriteCommandAction.runWriteCommandAction(project, () -> { + CodeStyleManager.getInstance(project).reformat(diXmlFileToReformat); + }); + + return diXmlFileToReformat; + } + + /** + * Get generation error message. + * + * @return String + */ + public String getGenerationErrorMessage() { + return generationErrorMessage; + } + + /** + * Fill argument injection values. + * + * @param attributes Properties + */ + @Override + protected void fillAttributes(final @NotNull Properties attributes) { + attributes.put("TYPE", data.getClazz()); + } + + private XmlTag getTargetTag(final @NotNull XmlTag rootTag) { + final Collection tags = PsiTreeUtil.findChildrenOfType(rootTag, XmlTag.class); + final List result = tags.stream() + .filter( + xmlTag -> xmlTag.getName().equals("type") + && xmlTag.getAttributeValue("name") != null + && xmlTag.getAttributeValue("name").equals(data.getClazz()) + ).collect(Collectors.toList()); + + return result.isEmpty() ? null : result.get(0); + } + + private XmlFile getDiXmlFile( + final @NotNull String actionName, + final @NotNull PsiDirectory moduleDirectory + ) { + PsiFile diXmlFile = FileBasedIndexUtil.findModuleConfigFile( + file.getFileName(), + data.getArea(), + data.getModuleName(), + project + ); + + if (diXmlFile == null) { + PsiDirectory diXmlFileDir; + + if (data.getArea().equals(Areas.base)) { + diXmlFileDir = directoryGenerator.findOrCreateSubdirectory( + moduleDirectory, + Package.moduleBaseAreaDir + ); + } else { + diXmlFileDir = directoryGenerator.findOrCreateSubdirectories( + moduleDirectory, + String.format( + "%s/%s", + Package.moduleBaseAreaDir, + this.data.getArea().toString() + ) + ); + } + + if (diXmlFileDir == null) { + generationErrorMessage = "Could not locate/generate the target scope directory"; + return null; + } + + diXmlFile = fileFromTemplateGenerator.generate( + file, + new Properties(), + diXmlFileDir, + actionName + ); + } + + return diXmlFile instanceof XmlFile ? (XmlFile) diXmlFile : null; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/code/util/DiXmlTagManipulatorUtil.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/code/util/DiXmlTagManipulatorUtil.java new file mode 100644 index 000000000..c0a83e044 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/code/util/DiXmlTagManipulatorUtil.java @@ -0,0 +1,179 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator.code.util; + +import com.intellij.openapi.util.Pair; +import com.intellij.psi.XmlElementFactory; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.magento.idea.magento2plugin.actions.generation.generator.util.CommitXmlFileUtil; +import com.magento.idea.magento2plugin.magento.files.ModuleDiXml; +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public final class DiXmlTagManipulatorUtil { + + private DiXmlTagManipulatorUtil() {} + + /** + * Insert new tag. + * + * @param parentTag XmlTag + * @param tagName String + * @param attributes List[Pair[String, String]] + * + * @return XmlTag + */ + public static XmlTag insertTag( + final @NotNull XmlTag parentTag, + final @NotNull String tagName, + final List> attributes + ) { + XmlTag tag = parentTag.createChildTag( + tagName, + null, + "", + false + ); + + for (final Pair attributeData : attributes) { + tag.setAttribute(attributeData.getFirst(), attributeData.getSecond()); + } + final Map childParentRelationMap = new HashMap<>(); + childParentRelationMap.put(tag, parentTag); + + CommitXmlFileUtil.execute( + (XmlFile) parentTag.getContainingFile(), + List.of(tag), + childParentRelationMap + ); + + for (final XmlTag childTag : PsiTreeUtil.findChildrenOfType(parentTag, XmlTag.class)) { + if (childTag.getText().equals(tag.getText())) { + tag = childTag; + } + } + + return tag; + } + + /** + * Wrap Type/VirtualType argument value into corresponding XML. + * + * @param typeTag XmlTag + * @param name String + * @param type DiArgumentType + * @param value String + */ + @SuppressWarnings("PMD.CognitiveComplexity") + public static void insertArgumentInTypeTag( + final @NotNull XmlTag typeTag, + final @NotNull String name, + final @NotNull DiArgumentType type, + final @NotNull String value + ) { + final List addSubTagsQueue = new ArrayList<>(); + final Map childParentRelationMap = new HashMap<>(); + final XmlTag argumentsTag = getOrCreateArgumentsTag( + typeTag, + addSubTagsQueue, + childParentRelationMap + ); + boolean isExists = false; + + for (final XmlTag argTag : PsiTreeUtil.findChildrenOfType(argumentsTag, XmlTag.class)) { + final String argName = argTag.getAttributeValue(ModuleDiXml.NAME_ATTR); + + if (name.equals(argName)) { + CommitXmlFileUtil.execute( + (XmlFile) typeTag.getContainingFile(), + () -> { + argTag.setAttribute(ModuleDiXml.XSI_TYPE_ATTR, type.getArgumentType()); + + if (type.equals(DiArgumentType.ARRAY)) { + argTag.getValue().setEscapedText(""); + String arrayValue = value; + boolean isNotChanged = false; + + while (!arrayValue.isEmpty() && !isNotChanged) { + final XmlTag xmlValueTag = XmlElementFactory.getInstance( + argTag.getProject() + ).createTagFromText(arrayValue); + argTag.addSubTag(xmlValueTag, false); + + final String newArrayValue = arrayValue.replace( + xmlValueTag.getText(), + "" + ); + + if (newArrayValue.equals(arrayValue)) { + isNotChanged = true; + } + arrayValue = newArrayValue; + } + } else { + argTag.getValue().setText(value); + } + + if (value.isEmpty()) { + argTag.collapseIfEmpty(); + } + } + ); + isExists = true; + break; + } + } + + if (!isExists) { + final XmlTag argTag = argumentsTag.createChildTag( + ModuleDiXml.ARGUMENT_TAG, + null, + value.isEmpty() ? null : value, + false + ); + addSubTagsQueue.add(argTag); + childParentRelationMap.put(argTag, argumentsTag); + argTag.setAttribute(ModuleDiXml.NAME_ATTR, name); + argTag.setAttribute(ModuleDiXml.XSI_TYPE_ATTR, type.getArgumentType()); + } + + if (!addSubTagsQueue.isEmpty()) { + CommitXmlFileUtil.execute( + (XmlFile) typeTag.getContainingFile(), + addSubTagsQueue, + childParentRelationMap + ); + } + } + + private static XmlTag getOrCreateArgumentsTag( + final @NotNull XmlTag typeTag, + final List addSubTagsQueue, + final Map childParentRelationMap + ) { + for (final XmlTag tag : PsiTreeUtil.findChildrenOfType(typeTag, XmlTag.class)) { + if (ModuleDiXml.ARGUMENTS_TAG.equals(tag.getName())) { + return tag; + } + } + final XmlTag argumentsTag = typeTag.createChildTag( + ModuleDiXml.ARGUMENTS_TAG, + null, + "", + false + ); + addSubTagsQueue.add(argumentsTag); + childParentRelationMap.put(argumentsTag, typeTag); + + return argumentsTag; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/CommitXmlFileUtil.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/CommitXmlFileUtil.java index e450cec31..1b1958532 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/CommitXmlFileUtil.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/CommitXmlFileUtil.java @@ -29,8 +29,7 @@ private CommitXmlFileUtil() {} public static XmlFile execute( final XmlFile xmlFile, final List subTags, - final Map childParentRelationMap + final Map childParentRelationMap ) { WriteCommandAction.runWriteCommandAction(xmlFile.getProject(), () -> { for (final XmlTag tag : Lists.reverse(subTags)) { @@ -50,4 +49,26 @@ public static XmlFile execute( } return xmlFile; } + + /** + * Make some XML editing operation in the safe env. + * + * @param xmlFile XmlFile + * @param runnable Runnable + */ + public static void execute( + final XmlFile xmlFile, + final Runnable runnable + ) { + WriteCommandAction.runWriteCommandAction(xmlFile.getProject(), runnable); + + final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance( + xmlFile.getProject() + ); + final Document document = psiDocumentManager.getDocument(xmlFile); + + if (document != null) { + psiDocumentManager.commitDocument(document); + } + } } diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateLayoutXml.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateLayoutXml.java index 177059235..d6b2e8055 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateLayoutXml.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateLayoutXml.java @@ -15,12 +15,16 @@ import com.magento.idea.magento2plugin.util.magento.FileBasedIndexUtil; import java.util.ArrayList; import java.util.Properties; +import org.jetbrains.annotations.NotNull; public final class FindOrCreateLayoutXml { + private final Project project; + private final Properties properties; public FindOrCreateLayoutXml(final Project project) { this.project = project; + properties = new Properties(); } /** @@ -60,7 +64,12 @@ public PsiFile execute( parentDirectory = directoryGenerator .findOrCreateSubdirectory(parentDirectory, fileDirectory); } - final LayoutXml layoutXml = new LayoutXml(routeId, controllerName, controllerActionName); + + LayoutXml layoutXml = new LayoutXml(routeId, controllerName, controllerActionName); + + if (controllerName.isEmpty()) { + layoutXml = new LayoutXml(routeId); + } PsiFile layoutXmlFile = FileBasedIndexUtil.findModuleViewFile( layoutXml.getFileName(), getArea(area), @@ -69,9 +78,10 @@ public PsiFile execute( LayoutXml.PARENT_DIR ); if (layoutXmlFile == null) { + fillDefaultAttributes(area, properties); layoutXmlFile = fileFromTemplateGenerator.generate( layoutXml, - new Properties(), + properties, parentDirectory, actionName ); @@ -82,4 +92,15 @@ public PsiFile execute( private Areas getArea(final String area) { return Areas.getAreaByString(area); } + + private void fillDefaultAttributes( + final @NotNull String area, + final @NotNull Properties properties + ) { + if (Areas.adminhtml.toString().equals(area)) { + properties.setProperty("IS_ADMIN", Boolean.TRUE.toString()); + } else { + properties.setProperty("IS_ADMIN", Boolean.FALSE.toString()); + } + } } diff --git a/src/com/magento/idea/magento2plugin/magento/files/LayoutXml.java b/src/com/magento/idea/magento2plugin/magento/files/LayoutXml.java index 37faf3f34..cfe0910a8 100644 --- a/src/com/magento/idea/magento2plugin/magento/files/LayoutXml.java +++ b/src/com/magento/idea/magento2plugin/magento/files/LayoutXml.java @@ -11,19 +11,19 @@ @SuppressWarnings({"PMD.FieldNamingConventions", "PMD.ClassNamingConventions"}) public class LayoutXml implements ModuleFileInterface { - public static String DEFAULT_FILENAME = "default.xml"; - public static String CACHEABLE_ATTRIBUTE_NAME = "cacheable"; - public static String CACHEABLE_ATTRIBUTE_VALUE_FALSE = "false"; - public static String BLOCK_ATTRIBUTE_TAG_NAME = "block"; - public static String REFERENCE_BLOCK_ATTRIBUTE_TAG_NAME = "referenceBlock"; - public static String ROOT_TAG_NAME = "body"; - public static String REFERENCE_CONTAINER_TAG_NAME = "referenceContainer"; - public static String UI_COMPONENT_TAG_NAME = "uiComponent"; - public static String XML_ATTRIBUTE_TEMPLATE = "template"; - public static String ARGUMENTS_TEMPLATE = "Magento Module Class Arguments In Xml"; - public static String PARENT_DIR = "layout"; - public static String NAME_ATTRIBUTE = "name"; - public static String CONTENT_CONTAINER_NAME = "content"; + public static final String DEFAULT_FILENAME = "default.xml"; + public static final String CACHEABLE_ATTRIBUTE_NAME = "cacheable"; + public static final String CACHEABLE_ATTRIBUTE_VALUE_FALSE = "false"; + public static final String BLOCK_ATTRIBUTE_TAG_NAME = "block"; + public static final String REFERENCE_BLOCK_ATTRIBUTE_TAG_NAME = "referenceBlock"; + public static final String ROOT_TAG_NAME = "body"; + public static final String REFERENCE_CONTAINER_TAG_NAME = "referenceContainer"; + public static final String UI_COMPONENT_TAG_NAME = "uiComponent"; + public static final String XML_ATTRIBUTE_TEMPLATE = "template"; + public static final String ARGUMENTS_TEMPLATE = "Magento Module Class Arguments In Xml"; + public static final String PARENT_DIR = "layout"; + public static final String NAME_ATTRIBUTE = "name"; + public static final String CONTENT_CONTAINER_NAME = "content"; public static String TEMPLATE = "Magento Layout XML"; private String fileName; @@ -46,6 +46,15 @@ public LayoutXml(final String routeId, final String controllerName, final String ); } + /** + * Layout XML file. + * + * @param routeId String + */ + public LayoutXml(final String routeId) { + this.setFileName(routeId + ".xml"); + } + /** * Get name of file. * diff --git a/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java b/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java index edcfa91bd..9d9d9faf7 100644 --- a/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java +++ b/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java @@ -10,7 +10,8 @@ @SuppressWarnings({"PMD.FieldNamingConventions"}) public enum ComponentType { module, - theme; + theme, + library; /** * Get component type by value. diff --git a/src/com/magento/idea/magento2plugin/magento/packages/DiArgumentType.java b/src/com/magento/idea/magento2plugin/magento/packages/DiArgumentType.java new file mode 100644 index 000000000..e0da3bcfc --- /dev/null +++ b/src/com/magento/idea/magento2plugin/magento/packages/DiArgumentType.java @@ -0,0 +1,133 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.magento.packages; + +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.ExtendedNumericRule; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.InputMismatchException; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public enum DiArgumentType { + + OBJECT("object"), + STRING("string"), + BOOLEAN("boolean"), + NUMBER("number"), + INIT_PARAMETER("init_parameter"), + CONST("const"), + NULL("null"), + ARRAY("array"); + + private final String argumentType; + + /** + * Dependency Injection argument types ENUM constructor. + * + * @param argumentType String + */ + DiArgumentType(final String argumentType) { + this.argumentType = argumentType; + } + + /** + * Get argument type. + * + * @return String + */ + public String getArgumentType() { + return argumentType; + } + + /** + * Check if provided value is valid for the current type. + * + * @param value String + * + * @return boolean + */ + public boolean isValid(final @NotNull String value) { + if (getArgumentType().equals(DiArgumentType.STRING.getArgumentType())) { + return validateString(value); + } else if (getArgumentType().equals(DiArgumentType.BOOLEAN.getArgumentType())) { + return validateBoolean(value); + } else if (getArgumentType().equals(DiArgumentType.NUMBER.getArgumentType())) { + return validateNumber(value); + } else if (getArgumentType().equals(DiArgumentType.NULL.getArgumentType())) { + return validateNull(value); + } else if (getArgumentType().equals(DiArgumentType.ARRAY.getArgumentType())) { + return validateArray(value); + } + + return true; + } + + /** + * Get ENUM by its string representation. + * + * @param value String + * + * @return PropertiesTypes + */ + public static DiArgumentType getByValue(final @NotNull String value) { + for (final DiArgumentType type : DiArgumentType.values()) { + if (type.getArgumentType().equals(value)) { + return type; + } + } + + throw new InputMismatchException( + "Invalid argument type value provided. Should be compatible with " + + DiArgumentType.class + ); + } + + /** + * Get argument value list. + * + * @return List[String] + */ + public static List getValueList() { + final List valueList = new ArrayList<>(); + final List simpleTypes = new ArrayList<>(); + + simpleTypes.add(DiArgumentType.STRING); + simpleTypes.add(DiArgumentType.BOOLEAN); + simpleTypes.add(DiArgumentType.NUMBER); + simpleTypes.add(DiArgumentType.NULL); + simpleTypes.add(DiArgumentType.ARRAY); + + for (final DiArgumentType type : DiArgumentType.values()) { + if (!simpleTypes.contains(type)) { + continue; + } + valueList.add(type.getArgumentType()); + } + + return valueList; + } + + private boolean validateString(final @NotNull String value) { + return true; + } + + private boolean validateBoolean(final @NotNull String value) { + return Arrays.asList("true", "false", "1", "0").contains(value); + } + + private boolean validateNumber(final @NotNull String value) { + return ExtendedNumericRule.getInstance().check(value); + } + + private boolean validateNull(final @NotNull String value) { + return true; + } + + private boolean validateArray(final @NotNull String value) { + return true; + } +} diff --git a/src/com/magento/idea/magento2plugin/util/RegExUtil.java b/src/com/magento/idea/magento2plugin/util/RegExUtil.java index 83ce45ed9..2603d8b4b 100644 --- a/src/com/magento/idea/magento2plugin/util/RegExUtil.java +++ b/src/com/magento/idea/magento2plugin/util/RegExUtil.java @@ -25,6 +25,9 @@ public class RegExUtil { public static final String NUMERIC = "[0-9]*"; + public static final String EXTENDED_NUMERIC + = "-?\\d+(\\.\\d+)?"; + public static final String IDENTIFIER = "[a-zA-Z0-9_\\-]*"; @@ -46,6 +49,9 @@ public class RegExUtil { public static final String MAGENTO_VERSION = "(\\d+)\\.(\\d+)\\.(\\d+)[a-zA-Z0-9_\\-]*"; + public static final String LAYOUT_NAME + = "^([a-zA-Z0-9]+){1,}(_[a-zA-Z0-9]+){0,2}"; + public static class Magento { public static final String PHP_CLASS diff --git a/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java b/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java index 207b7eaf4..503844a89 100644 --- a/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java +++ b/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java @@ -51,6 +51,9 @@ public static MagentoModuleData getByContext( final PsiDirectory configDir = registrationFile .getContainingDirectory() .findSubdirectory(Package.moduleBaseAreaDir); + final PsiDirectory viewDir = registrationFile + .getContainingDirectory() + .findSubdirectory(Package.moduleViewDir); final Collection methodReferences = PsiTreeUtil.findChildrenOfType( registrationFile, MethodReference.class @@ -76,7 +79,7 @@ public static MagentoModuleData getByContext( return null; } - return new MagentoModuleData(name, resolvedType, configDir); + return new MagentoModuleData(name, resolvedType, configDir, viewDir); } return null; @@ -130,6 +133,7 @@ public static class MagentoModuleData { private final String name; private final ComponentType type; private final PsiDirectory configDir; + private final PsiDirectory viewDir; /** * Default constructor. @@ -141,7 +145,7 @@ public MagentoModuleData( final @NotNull String name, final @NotNull ComponentType type ) { - this(name, type, null); + this(name, type, null, null); } /** @@ -150,15 +154,18 @@ public MagentoModuleData( * @param name String * @param type ComponentType * @param configDir PsiDirectory + * @param viewDir PsiDirectory */ public MagentoModuleData( final @NotNull String name, final @NotNull ComponentType type, - final @Nullable PsiDirectory configDir + final @Nullable PsiDirectory configDir, + final @Nullable PsiDirectory viewDir ) { this.name = name; this.type = type; this.configDir = configDir; + this.viewDir = viewDir; } public String getName() { @@ -172,5 +179,9 @@ public ComponentType getType() { public @Nullable PsiDirectory getConfigDir() { return configDir; } + + public @Nullable PsiDirectory getViewDir() { + return viewDir; + } } } diff --git a/src/com/magento/idea/magento2plugin/util/php/PhpPsiElementsUtil.java b/src/com/magento/idea/magento2plugin/util/php/PhpPsiElementsUtil.java index 49dd6b8ba..a2df66c80 100644 --- a/src/com/magento/idea/magento2plugin/util/php/PhpPsiElementsUtil.java +++ b/src/com/magento/idea/magento2plugin/util/php/PhpPsiElementsUtil.java @@ -13,6 +13,7 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.Parameter; import com.jetbrains.php.lang.psi.elements.PhpClass; import com.magento.idea.magento2plugin.util.GetFirstClassOfFile; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,23 @@ public static Method getPhpMethod(final @NotNull AnActionEvent event) { return element instanceof Method ? (Method) element : null; } + /** + * Get method argument from event. + * + * @param event AnActionEvent + * + * @return Method + */ + public static Parameter getMethodArgument(final @NotNull AnActionEvent event) { + PsiElement element = getElementUnderCursor(event); + + if (element instanceof LeafPsiElement && element.getParent() instanceof Parameter) { + element = element.getParent(); + } + + return element instanceof Parameter ? (Parameter) element : null; + } + /** * Get php file for event. * diff --git a/src/com/magento/idea/magento2plugin/util/php/PhpTypeMetadataParserUtil.java b/src/com/magento/idea/magento2plugin/util/php/PhpTypeMetadataParserUtil.java index 6f8cec22f..9ea2c4941 100644 --- a/src/com/magento/idea/magento2plugin/util/php/PhpTypeMetadataParserUtil.java +++ b/src/com/magento/idea/magento2plugin/util/php/PhpTypeMetadataParserUtil.java @@ -135,7 +135,12 @@ public static List getMethodsByNames( * * @return String */ - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ConfusingTernary"}) + @SuppressWarnings({ + "PMD.CyclomaticComplexity", + "PMD.CognitiveComplexity", + "PMD.NPathComplexity", + "PMD.ConfusingTernary" + }) public static String getMethodDefinitionForInterface( final @NotNull Method method, final String defaultMethodDescription @@ -269,6 +274,29 @@ public static List getMethodParametersTypes(final @NotNull Method method return types; } + /** + * Get main type for the specified parameter (?int -> main type is int). + * + * @param parameter Parameter + * + * @return String + */ + public static String getMainType(final @NotNull Parameter parameter) { + final List types = extractMultipleTypesFromString( + parameter.getDeclaredType().toString() + ); + + for (final String type : types) { + if (PhpLangUtil.isFqn(type)) { + return type; + } else if (PhpLangUtil.isParameterTypeHint(type, parameter.getProject())) { + return type; + } + } + + return null; + } + /** * Get method return type. * @@ -309,6 +337,7 @@ public static String getMethodReturnType(final @NotNull Method method) { * * @return List[String] */ + @SuppressWarnings("PMD.CognitiveComplexity") public static List getMethodExceptionsTypes(final @NotNull Method method) { final List types = new ArrayList<>(); diff --git a/src/com/magento/idea/magento2uct/execution/GenerateUctReportCommand.java b/src/com/magento/idea/magento2uct/execution/GenerateUctReportCommand.java index 2418a8ad9..d12aec9f5 100644 --- a/src/com/magento/idea/magento2uct/execution/GenerateUctReportCommand.java +++ b/src/com/magento/idea/magento2uct/execution/GenerateUctReportCommand.java @@ -13,6 +13,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; @@ -20,6 +21,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.jetbrains.php.lang.psi.PhpFile; +import com.magento.idea.magento2plugin.util.magento.MagentoVersionUtil; import com.magento.idea.magento2uct.execution.output.ReportBuilder; import com.magento.idea.magento2uct.execution.output.Summary; import com.magento.idea.magento2uct.execution.output.UctReportOutputUtil; @@ -35,6 +37,7 @@ import com.magento.idea.magento2uct.util.inspection.FilterDescriptorResultsUtil; import com.magento.idea.magento2uct.util.inspection.SortDescriptorResultsUtil; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,6 +45,8 @@ @SuppressWarnings({"PMD.NPathComplexity", "PMD.ExcessiveImports", "PMD.CognitiveComplexity"}) public class GenerateUctReportCommand { + private static final String DEFAULT_MAGENTO_EDITION_LABEL = "Magento Open Source"; + private final Project project; private final OutputWrapper output; private final ProcessHandler process; @@ -80,7 +85,7 @@ public void processTerminated(final @NotNull ProcessEvent event) { @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.AvoidInstantiatingObjectsInLoops"}) public void execute() { output.write("Upgrade compatibility tool\n"); - final PsiDirectory rootDirectory = getTargetPsiDirectory(); + final PsiDirectory rootDirectory = getTargetPsiDirectory(settingsService.getModulePath()); if (rootDirectory == null) { output.print( @@ -89,8 +94,21 @@ public void execute() { process.destroyProcess(); return; } + final List directoriesToScan = new ArrayList<>(); + directoriesToScan.add(rootDirectory); + + if (settingsService.getHasAdditionalPath()) { + final PsiDirectory additionalDirectory = getTargetPsiDirectory( + settingsService.getAdditionalPath() + ); + + if (additionalDirectory != null) { + directoriesToScan.add(additionalDirectory); + } + } + final ModuleScanner scanner = new ModuleScanner( - rootDirectory, + directoriesToScan, new ExcludeMagentoBundledFilter() ); final Summary summary = new Summary( @@ -99,6 +117,13 @@ public void execute() { ); final ReportBuilder reportBuilder = new ReportBuilder(project); final UctReportOutputUtil outputUtil = new UctReportOutputUtil(output); + final Pair version = MagentoVersionUtil.getVersionData( + project, + project.getBasePath() + ); + final String resolvedEdition = version.getSecond() == null + ? DEFAULT_MAGENTO_EDITION_LABEL + : version.getSecond(); ApplicationManager.getApplication().executeOnPooledThread(() -> { ApplicationManager.getApplication().runReadAction(() -> { @@ -125,7 +150,7 @@ public void execute() { if (fileProblemsHolder.hasResults()) { if (!isModuleHeaderPrinted) { - outputUtil.printModuleName(componentData.getName()); + outputUtil.printModuleName(componentData); isModuleHeaderPrinted = true; } outputUtil.printProblemFile(filename); @@ -154,9 +179,10 @@ public void execute() { } summary.trackProcessFinished(); summary.setProcessedModules(scanner.getModuleCount()); - outputUtil.printSummary(summary); + summary.setProcessedThemes(scanner.getThemeCount()); + outputUtil.printSummary(summary, resolvedEdition); - if (summary.getProcessedModules() == 0) { + if (summary.getProcessedModules() == 0 && summary.getProcessedThemes() == 0) { process.destroyProcess(); return; } @@ -185,11 +211,11 @@ public void execute() { /** * Get target psi directory. * + * @param targetDirPath String + * * @return PsiDirectory */ - private @Nullable PsiDirectory getTargetPsiDirectory() { - final String targetDirPath = settingsService.getModulePath(); - + private @Nullable PsiDirectory getTargetPsiDirectory(final String targetDirPath) { if (targetDirPath == null) { return null; } diff --git a/src/com/magento/idea/magento2uct/execution/output/ReportBuilder.java b/src/com/magento/idea/magento2uct/execution/output/ReportBuilder.java index b48a0775d..fd6a0734e 100644 --- a/src/com/magento/idea/magento2uct/execution/output/ReportBuilder.java +++ b/src/com/magento/idea/magento2uct/execution/output/ReportBuilder.java @@ -163,6 +163,7 @@ public JsonFile build() { + "installedVersion\": \"" + summary.getInstalledVersion() + "\"," + "\"AdobeCommerceVersion\": \"" + summary.getTargetVersion() + "\"," + "\"checkedModules\": " + summary.getProcessedModules() + "," + + "\"checkedThemes\": " + summary.getProcessedThemes() + "," + "\"runningTime\": \"" + summary.getProcessRunningTime() + "\"," + "\"totalWarnings\": " + summary.getPhpWarnings() + "," + "\"totalErrors\": " + summary.getPhpErrors() + "," diff --git a/src/com/magento/idea/magento2uct/execution/output/Summary.java b/src/com/magento/idea/magento2uct/execution/output/Summary.java index 1c7f5b954..470fddf69 100644 --- a/src/com/magento/idea/magento2uct/execution/output/Summary.java +++ b/src/com/magento/idea/magento2uct/execution/output/Summary.java @@ -18,6 +18,7 @@ public class Summary { private final SupportedVersion installedVersion; private final SupportedVersion targetVersion; private int processedModules; + private int processedThemes; private long processStartedTime; private long processEndedTime; private int phpWarnings; @@ -74,6 +75,24 @@ public void setProcessedModules(final int processedModules) { this.processedModules = processedModules; } + /** + * Get processed theme qty. + * + * @return int + */ + public int getProcessedThemes() { + return processedThemes; + } + + /** + * Set processed theme qty. + * + * @param processedThemes int + */ + public void setProcessedThemes(final int processedThemes) { + this.processedThemes = processedThemes; + } + /** * Track time before process is started. */ diff --git a/src/com/magento/idea/magento2uct/execution/output/UctReportOutputUtil.java b/src/com/magento/idea/magento2uct/execution/output/UctReportOutputUtil.java index cd370f81f..28a7a3664 100644 --- a/src/com/magento/idea/magento2uct/execution/output/UctReportOutputUtil.java +++ b/src/com/magento/idea/magento2uct/execution/output/UctReportOutputUtil.java @@ -8,8 +8,10 @@ import com.intellij.codeInspection.ProblemDescriptor; import com.magento.idea.magento2uct.bundles.UctInspectionBundle; import com.magento.idea.magento2uct.execution.process.OutputWrapper; +import com.magento.idea.magento2uct.execution.scanner.data.ComponentData; import com.magento.idea.magento2uct.packages.SupportedIssue; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -31,10 +33,19 @@ public UctReportOutputUtil(final @NotNull OutputWrapper output) { /** * Print module name header. * - * @param moduleName String + * @param componentData ComponentData */ - public void printModuleName(final @NotNull String moduleName) { - final String moduleNameLine = "Module Name: ".concat(moduleName); + public void printModuleName(final @NotNull ComponentData componentData) { + final String componentType = componentData.getType().toString(); + final String componentTypeFormatted = componentType + .substring(0, 1) + .toUpperCase(new Locale("en","EN")) + .concat(componentType.substring(1)); + + final String moduleNameLine = componentTypeFormatted + .concat(" Name: ") + .concat(componentData.getName()); + stdout.print("\n\n" + stdout.wrapInfo(moduleNameLine).concat("\n")); stdout.print(stdout.wrapInfo("-".repeat(moduleNameLine.length())).concat("\n")); } @@ -78,10 +89,11 @@ public void printIssue(final @NotNull ProblemDescriptor descriptor, final int co * Print summary information. * * @param summary Summary + * @param platformName String */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - public void printSummary(final Summary summary) { - if (summary.getProcessedModules() == 0) { + public void printSummary(final Summary summary, final String platformName) { + if (summary.getProcessedModules() == 0 && summary.getProcessedThemes() == 0) { stdout.print(stdout.wrapInfo("Couldn't find modules to analyse").concat("\n")); return; } @@ -92,9 +104,10 @@ public void printSummary(final Summary summary) { final Map summaryMap = new LinkedHashMap<>(); summaryMap.put("Installed version", summary.getInstalledVersion()); - summaryMap.put("Adobe Commerce version", summary.getTargetVersion()); + summaryMap.put(platformName + " version", summary.getTargetVersion()); summaryMap.put("Running time", summary.getProcessRunningTime()); summaryMap.put("Checked modules", String.valueOf(summary.getProcessedModules())); + summaryMap.put("Checked themes", String.valueOf(summary.getProcessedThemes())); summaryMap.put("Total warnings found", String.valueOf(summary.getPhpWarnings())); summaryMap.put("Total errors found", String.valueOf(summary.getPhpErrors())); summaryMap.put("Total critical errors found", diff --git a/src/com/magento/idea/magento2uct/execution/scanner/ModuleScanner.java b/src/com/magento/idea/magento2uct/execution/scanner/ModuleScanner.java index 8ced9c569..9665b017e 100644 --- a/src/com/magento/idea/magento2uct/execution/scanner/ModuleScanner.java +++ b/src/com/magento/idea/magento2uct/execution/scanner/ModuleScanner.java @@ -11,12 +11,17 @@ import com.intellij.json.psi.JsonValue; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.xml.XmlFile; -import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.psi.PhpFile; +import com.jetbrains.php.lang.psi.elements.ClassConstantReference; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import com.jetbrains.php.lang.psi.elements.impl.ClassConstImpl; import com.magento.idea.magento2plugin.magento.files.ComposerJson; -import com.magento.idea.magento2plugin.magento.files.ModuleXml; -import com.magento.idea.magento2plugin.magento.packages.Package; +import com.magento.idea.magento2plugin.magento.files.RegistrationPhp; +import com.magento.idea.magento2plugin.magento.packages.ComponentType; import com.magento.idea.magento2uct.execution.scanner.data.ComponentData; import com.magento.idea.magento2uct.execution.scanner.filter.ModuleScannerFilter; import java.util.ArrayList; @@ -27,11 +32,11 @@ public final class ModuleScanner implements Iterable { - private static final String FRAMEWORK_LIBRARY_NAME = "magento/framework"; - - private final PsiDirectory rootDirectory; + private final List rootDirectories; private final List componentDataList; private final List filters; + private int modulesQty; + private int themesQty; /** * Magento 2 module components scanner constructor. @@ -44,9 +49,25 @@ public ModuleScanner( final @NotNull ModuleScannerFilter... filters ) { - this.rootDirectory = rootDirectory; + this(List.of(rootDirectory), filters); + } + + /** + * Magento 2 module components scanner constructor. + * + * @param directories List[PsiDirectory] + * @param filters ModuleScannerFilter[] + */ + public ModuleScanner( + final List directories, + final @NotNull ModuleScannerFilter... filters + + ) { + this.rootDirectories = new ArrayList<>(directories); this.filters = Arrays.asList(filters); componentDataList = new ArrayList<>(); + modulesQty = 0; + themesQty = 0; } @Override @@ -60,7 +81,16 @@ public ModuleScanner( * @return int */ public int getModuleCount() { - return componentDataList.size(); + return modulesQty; + } + + /** + * Get found themes qty. + * + * @return int + */ + public int getThemeCount() { + return themesQty; } /** @@ -70,7 +100,10 @@ public int getModuleCount() { */ private List run() { componentDataList.clear(); - findModuleComponent(rootDirectory); + + for (final PsiDirectory rootDirectory : rootDirectories) { + findModuleComponent(rootDirectory); + } return componentDataList; } @@ -89,68 +122,51 @@ private List run() { private void findModuleComponent(final @NotNull PsiDirectory directory) { String name = null; String composerBasedName = null; - String composerType; - String type = "magento2-module"; - - for (final PsiDirectory subDirectory : directory.getSubdirectories()) { - if (Package.moduleBaseAreaDir.equals(subDirectory.getName())) { - for (final PsiFile file : subDirectory.getFiles()) { - if (file instanceof XmlFile && ModuleXml.FILE_NAME.equals(file.getName())) { - final XmlTag rootTag = ((XmlFile) file).getRootTag(); - - if (rootTag != null && rootTag.getSubTags().length > 0) { - final XmlTag moduleTag = rootTag.getSubTags()[0]; - name = moduleTag.getAttributeValue("name"); - } - break; - } - } - break; - } - } + ComponentType type = null; - for (final PsiFile file : directory.getFiles()) { - if (file instanceof JsonFile && ComposerJson.FILE_NAME.equals(file.getName())) { - final Pair meta = scanModuleComposerMeta((JsonFile) file); - composerBasedName = meta.getFirst(); - composerType = meta.getSecond(); + final PsiFile registration = directory.findFile(RegistrationPhp.FILE_NAME); - if (composerBasedName == null || composerType == null) { - return; - } - - if (name == null && FRAMEWORK_LIBRARY_NAME.equals(composerBasedName)) { - name = meta.getFirst(); - type = composerType; - } + if (registration instanceof PhpFile) { + final Pair registrationMeta = scanRegistrationMeta( + (PhpFile) registration + ); - if (!type.equals(composerType) && !"project".equals(composerType)) { - return; - } - break; + if (registrationMeta != null) { + name = registrationMeta.getFirst(); + type = registrationMeta.getSecond(); } - } - if (name != null) { - final ComponentData component = new ComponentData( - name, - composerBasedName, - type, - directory - ); - boolean isExcluded = false; + if (name != null) { + final PsiFile composerFile = directory.findFile(ComposerJson.FILE_NAME); - for (final ModuleScannerFilter filter : filters) { - if (filter.isExcluded(component)) { - isExcluded = true; - break; + if (composerFile instanceof JsonFile) { + composerBasedName = getComposerComponentName((JsonFile) composerFile); + } + final ComponentData component = new ComponentData( + name, + composerBasedName, + type, + directory + ); + boolean isExcluded = false; + + for (final ModuleScannerFilter filter : filters) { + if (filter.isExcluded(component)) { + isExcluded = true; + break; + } } - } - if (!isExcluded) { - componentDataList.add(component); + if (!isExcluded) { + if (component.getType().equals(ComponentType.theme)) { + themesQty++; + } else { + modulesQty++; + } + componentDataList.add(component); + } + return; } - return; } for (final PsiDirectory subDirectory : directory.getSubdirectories()) { @@ -159,44 +175,80 @@ private void findModuleComponent(final @NotNull PsiDirectory directory) { } /** - * Scan module metadata from the composer.json file. + * Scan module name from the composer.json file. * * @param composerFile JsonFile * - * @return Pair[String, String] + * @return String */ - @SuppressWarnings({ - "PMD.NPathComplexity", - "PMD.CyclomaticComplexity", - "PMD.CognitiveComplexity" - }) - private Pair scanModuleComposerMeta(final JsonFile composerFile) { + private String getComposerComponentName(final JsonFile composerFile) { final JsonValue topNode = composerFile.getTopLevelValue(); String name = null; - String type = null; if (topNode instanceof JsonObject) { for (final JsonProperty property : ((JsonObject) topNode).getPropertyList()) { - switch (property.getName()) { - case "name": - if (property.getValue() != null) { - name = property.getValue().getText().replace("\"", ""); - } - break; - case "type": - if (property.getValue() != null) { - type = property.getValue().getText().replace("\"", ""); - } - break; - default: - break; - } - if (name != null && type != null) { + if ("name".equals(property.getName())) { + if (property.getValue() != null) { + name = property.getValue().getText().replace("\"", ""); + } break; } } } - return new Pair<>(name, type); + return name; + } + + private Pair scanRegistrationMeta(final PhpFile registrationFile) { + for (final MethodReference reference + : PsiTreeUtil.findChildrenOfType(registrationFile, MethodReference.class)) { + + if (!RegistrationPhp.REGISTER_METHOD_NAME.equals(reference.getName())) { + continue; + } + final PsiElement typeHolder = reference.getParameter(0); + final PsiElement nameHolder = reference.getParameter(1); + + if (typeHolder == null || nameHolder == null) { + return null; + } + final String type = unwrapParameterValue(typeHolder); + final String name = unwrapParameterValue(nameHolder); + + if (name == null || type == null) { + return null; + } + final ComponentType resolvedType = ComponentType.getByValue(type); + + if (resolvedType == null) { + return null; + } + + return new Pair<>(name, resolvedType); + } + + return null; + } + + private String unwrapParameterValue(final @NotNull PsiElement parameterValue) { + if (parameterValue instanceof ClassConstantReference) { + final PsiElement resolvedValue = ((ClassConstantReference) parameterValue).resolve(); + + if (!(resolvedValue instanceof ClassConstImpl)) { + return null; + } + final ClassConstImpl resolvedConst = (ClassConstImpl) resolvedValue; + final PsiElement value = resolvedConst.getDefaultValue(); + + if (value == null) { + return null; + } + + return unwrapParameterValue(value); + } else if (parameterValue instanceof StringLiteralExpression) { + return ((StringLiteralExpression) parameterValue).getContents(); + } + + return null; } } diff --git a/src/com/magento/idea/magento2uct/execution/scanner/data/ComponentData.java b/src/com/magento/idea/magento2uct/execution/scanner/data/ComponentData.java index 1c1980b38..9d6d7ffee 100644 --- a/src/com/magento/idea/magento2uct/execution/scanner/data/ComponentData.java +++ b/src/com/magento/idea/magento2uct/execution/scanner/data/ComponentData.java @@ -6,13 +6,14 @@ package com.magento.idea.magento2uct.execution.scanner.data; import com.intellij.psi.PsiDirectory; +import com.magento.idea.magento2plugin.magento.packages.ComponentType; import org.jetbrains.annotations.NotNull; public class ComponentData { private final String name; private final String composerName; - private final String type; + private final ComponentType type; private final PsiDirectory directory; /** @@ -20,13 +21,13 @@ public class ComponentData { * * @param name String * @param composerName String - * @param type String + * @param type ComponentType * @param directory PsiDirectory */ public ComponentData( final @NotNull String name, final String composerName, - final @NotNull String type, + final @NotNull ComponentType type, final @NotNull PsiDirectory directory ) { this.name = name; @@ -56,9 +57,9 @@ public String getComposerName() { /** * Get component type. * - * @return String + * @return ComponentType */ - public String getType() { + public ComponentType getType() { return type; } diff --git a/src/com/magento/idea/magento2uct/inspections/UctInspectionManager.java b/src/com/magento/idea/magento2uct/inspections/UctInspectionManager.java index df1f69ee1..093530835 100644 --- a/src/com/magento/idea/magento2uct/inspections/UctInspectionManager.java +++ b/src/com/magento/idea/magento2uct/inspections/UctInspectionManager.java @@ -52,11 +52,6 @@ public UctInspectionManager(final @NotNull Project project) { if (!(psiFile instanceof PhpFile)) { return null; } - final PhpClass phpClass = GetFirstClassOfFile.getInstance().execute((PhpFile) psiFile); - - if (phpClass == null) { - return null; - } final UctProblemsHolder problemsHolder = new UctProblemsHolder( InspectionManager.getInstance(project), psiFile, @@ -64,7 +59,7 @@ public UctInspectionManager(final @NotNull Project project) { ); final List visitors = SupportedIssue.getVisitors(problemsHolder); - for (final PsiElement element : collectElements(phpClass)) { + for (final PsiElement element : collectElements(psiFile)) { for (final PsiElementVisitor visitor : visitors) { element.accept(visitor); } @@ -76,26 +71,31 @@ public UctInspectionManager(final @NotNull Project project) { /** * Collect elements for PHP based inspections. * - * @param phpClass PhpClass + * @param psiFile PsiFile * * @return List[PsiElement] */ - private List collectElements(final @NotNull PhpClass phpClass) { + private List collectElements(final @NotNull PsiFile psiFile) { final List elements = new LinkedList<>(); - elements.add(phpClass); - final PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator( - phpClass - ); - if (scopeForUseOperator != null) { - elements.addAll(PhpCodeInsightUtil.collectImports(scopeForUseOperator)); + final PhpClass phpClass = GetFirstClassOfFile.getInstance().execute((PhpFile) psiFile); + + if (phpClass != null) { + elements.add(phpClass); + final PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator( + phpClass + ); + + if (scopeForUseOperator != null) { + elements.addAll(PhpCodeInsightUtil.collectImports(scopeForUseOperator)); + } + elements.addAll(Arrays.asList(phpClass.getOwnFields())); } - elements.addAll(PsiTreeUtil.findChildrenOfType(phpClass, ClassConstantReference.class)); - elements.addAll(Arrays.asList(phpClass.getOwnFields())); - elements.addAll(PsiTreeUtil.findChildrenOfType(phpClass, MethodReference.class)); - elements.addAll(PsiTreeUtil.findChildrenOfType(phpClass, AssignmentExpression.class)); - elements.addAll(PsiTreeUtil.findChildrenOfType(phpClass, ClassReference.class)); - elements.addAll(PsiTreeUtil.findChildrenOfType(phpClass, FieldReference.class)); + elements.addAll(PsiTreeUtil.findChildrenOfType(psiFile, ClassConstantReference.class)); + elements.addAll(PsiTreeUtil.findChildrenOfType(psiFile, MethodReference.class)); + elements.addAll(PsiTreeUtil.findChildrenOfType(psiFile, AssignmentExpression.class)); + elements.addAll(PsiTreeUtil.findChildrenOfType(psiFile, ClassReference.class)); + elements.addAll(PsiTreeUtil.findChildrenOfType(psiFile, FieldReference.class)); return elements; } diff --git a/src/com/magento/idea/magento2uct/settings/UctSettingsService.java b/src/com/magento/idea/magento2uct/settings/UctSettingsService.java index 4b2e41528..dea0e0b5c 100644 --- a/src/com/magento/idea/magento2uct/settings/UctSettingsService.java +++ b/src/com/magento/idea/magento2uct/settings/UctSettingsService.java @@ -257,7 +257,7 @@ public void setIgnoreCurrentVersion(final boolean ignoreCurrentVersion) { } /** - * Set if show additional path + * Set if show additional path. * * @param hasAdditionalPath boolean */ @@ -266,12 +266,12 @@ public void setHasAdditionalPath(final boolean hasAdditionalPath) { } /** - * Check if show additional path + * Check if show additional path. * * @return boolean */ - public @Nullable Boolean getHasAdditionalPath() { - return hasAdditionalPath; + public @NotNull Boolean getHasAdditionalPath() { + return hasAdditionalPath != null && hasAdditionalPath; } /** diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectArrayValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectArrayValue/di.xml new file mode 100644 index 000000000..0b8341ed4 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectArrayValue/di.xml @@ -0,0 +1,15 @@ + + + + + + QW1 + QW2 + QW3 + QW4 + QW5 + + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectBooleanValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectBooleanValue/di.xml new file mode 100644 index 000000000..731759df9 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectBooleanValue/di.xml @@ -0,0 +1,9 @@ + + + + + false + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectConstValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectConstValue/di.xml new file mode 100644 index 000000000..f26db45d3 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectConstValue/di.xml @@ -0,0 +1,9 @@ + + + + + Foo\Bar\Model\ServiceTest::DEFAULT_SERVICE + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectInitParameterValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectInitParameterValue/di.xml new file mode 100644 index 000000000..f25afb7cb --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectInitParameterValue/di.xml @@ -0,0 +1,9 @@ + + + + + Foo\Bar\Model\AreaTest::DEFAULT_AREA + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNestedArrayValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNestedArrayValue/di.xml new file mode 100644 index 000000000..fda440cbd --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNestedArrayValue/di.xml @@ -0,0 +1,19 @@ + + + + + + QW1 + QW2 + QW3 + QW4 + + NT1 + true + + + + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNullValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNullValue/di.xml new file mode 100644 index 000000000..28e6599cd --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNullValue/di.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNumberValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNumberValue/di.xml new file mode 100644 index 000000000..1e1cfe5f7 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectNumberValue/di.xml @@ -0,0 +1,9 @@ + + + + + 12 + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectObjectValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectObjectValue/di.xml new file mode 100644 index 000000000..7c85bc54c --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectObjectValue/di.xml @@ -0,0 +1,9 @@ + + + + + Foo\Bar\Model\Service + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/injectStringValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectStringValue/di.xml new file mode 100644 index 000000000..b8f8d9d30 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/injectStringValue/di.xml @@ -0,0 +1,9 @@ + + + + + test + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithFactoryValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithFactoryValue/di.xml new file mode 100644 index 000000000..645bbe721 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithFactoryValue/di.xml @@ -0,0 +1,11 @@ + + + + + 1111 + Foo\Bar\Model\ServiceFactory + Just another argument + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithNullValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithNullValue/di.xml new file mode 100644 index 000000000..206074d73 --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithNullValue/di.xml @@ -0,0 +1,11 @@ + + + + + 1111 + + Just another argument + + + diff --git a/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithProxyValue/di.xml b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithProxyValue/di.xml new file mode 100644 index 000000000..1e708e60a --- /dev/null +++ b/testData/actions/generation/generator/ArgumentInjectionGenerator/replaceObjectValueWithProxyValue/di.xml @@ -0,0 +1,11 @@ + + + + + 1111 + Foo\Bar\Model\Service\Proxy + Just another argument + + + diff --git a/testData/project/magento2/app/code/Foo/Bar/etc/crontab/di.xml b/testData/project/magento2/app/code/Foo/Bar/etc/crontab/di.xml new file mode 100644 index 000000000..55c772245 --- /dev/null +++ b/testData/project/magento2/app/code/Foo/Bar/etc/crontab/di.xml @@ -0,0 +1,11 @@ + + + + + 1111 + Foo\Bar\Model\Service + Just another argument + + + diff --git a/tests/com/magento/idea/magento2plugin/actions/generation/generator/ArgumentInjectionGeneratorTest.java b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ArgumentInjectionGeneratorTest.java new file mode 100644 index 000000000..496a86f5d --- /dev/null +++ b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ArgumentInjectionGeneratorTest.java @@ -0,0 +1,338 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator; + +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArgumentData; +import com.magento.idea.magento2plugin.actions.generation.data.xml.DiArrayValueData; +import com.magento.idea.magento2plugin.actions.generation.generator.code.ArgumentInjectionGenerator; +import com.magento.idea.magento2plugin.magento.files.ModuleDiXml; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.DiArgumentType; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("PMD.TooManyMethods") +public class ArgumentInjectionGeneratorTest extends BaseGeneratorTestCase { + + private static final String EXPECTED_DIRECTORY = "src/app/code/Foo/Bar/etc/frontend"; + private static final String EXPECTED_DIR_FOR_REPLACING = "src/app/code/Foo/Bar/etc/crontab"; + private static final String MODULE_NAME = "Foo_Bar"; + private static final String TARGET_CLASS = "Foo\\Bar\\Model\\Test"; + private static final String STRING_PARAMETER = "name"; + private static final String STRING_VALUE = "test"; + private static final String BOOL_PARAMETER = "isEmpty"; + private static final String BOOL_VALUE = "false"; + private static final String NUMBER_PARAMETER = "age"; + private static final String NUMBER_VALUE = "12"; + private static final String INIT_PARAM_PARAMETER = "defaultArea"; + private static final String INIT_PARAM_VALUE = "Foo\\Bar\\Model\\AreaTest::DEFAULT_AREA"; + private static final String CONST_PARAMETER = "defaultService"; + private static final String CONST_VALUE = "Foo\\Bar\\Model\\ServiceTest::DEFAULT_SERVICE"; + private static final String NULL_PARAMETER = "object"; + private static final String NULL_VALUE = ""; + private static final String OBJECT_PARAMETER = "object"; + private static final String OBJECT_VALUE = "Foo\\Bar\\Model\\Service"; + private static final String ARRAY_PARAMETER = "methods"; + private static final Areas TEST_AREA = Areas.frontend; + + /** + * Tested string value injection. + */ + public void testInjectStringValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + STRING_PARAMETER, + TEST_AREA, + DiArgumentType.STRING, + STRING_VALUE + ) + ) + ); + } + + /** + * Tested boolean value injection. + */ + public void testInjectBooleanValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + BOOL_PARAMETER, + TEST_AREA, + DiArgumentType.BOOLEAN, + BOOL_VALUE + ) + ) + ); + } + + /** + * Tested number value injection. + */ + public void testInjectNumberValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + NUMBER_PARAMETER, + TEST_AREA, + DiArgumentType.NUMBER, + NUMBER_VALUE + ) + ) + ); + } + + /** + * Tested init_parameter value injection. + */ + public void testInjectInitParameterValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + INIT_PARAM_PARAMETER, + TEST_AREA, + DiArgumentType.INIT_PARAMETER, + INIT_PARAM_VALUE + ) + ) + ); + } + + /** + * Tested constant value injection. + */ + public void testInjectConstValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + CONST_PARAMETER, + TEST_AREA, + DiArgumentType.CONST, + CONST_VALUE + ) + ) + ); + } + + /** + * Tested null value injection. + */ + public void testInjectNullValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + NULL_PARAMETER, + TEST_AREA, + DiArgumentType.NULL, + NULL_VALUE + ) + ) + ); + } + + /** + * Tested object value injection. + */ + public void testInjectObjectValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + OBJECT_PARAMETER, + TEST_AREA, + DiArgumentType.OBJECT, + OBJECT_VALUE + ) + ) + ); + } + + /** + * Tested array value injection. + */ + public void testInjectArrayValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + ARRAY_PARAMETER, + TEST_AREA, + DiArgumentType.ARRAY, + getArrayValue() + ) + ) + ); + } + + /** + * Tested nested array value injection. + */ + public void testInjectNestedArrayValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIRECTORY, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + ARRAY_PARAMETER, + TEST_AREA, + DiArgumentType.ARRAY, + getNestedArrayValue() + ) + ) + ); + } + + /** + * Tested object value replacing with the null value injection. + */ + public void testReplaceObjectValueWithNullValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIR_FOR_REPLACING, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + OBJECT_PARAMETER, + Areas.crontab, + DiArgumentType.NULL, + NULL_VALUE + ) + ) + ); + } + + /** + * Tested object value replacing with the object proxy value injection. + */ + public void testReplaceObjectValueWithProxyValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIR_FOR_REPLACING, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + OBJECT_PARAMETER, + Areas.crontab, + DiArgumentType.OBJECT, + OBJECT_VALUE + "\\Proxy" + ) + ) + ); + } + + /** + * Tested object value replacing with the object factory value injection. + */ + public void testReplaceObjectValueWithFactoryValue() { + assertGeneratedFileIsCorrect( + myFixture.configureByFile(getFixturePath(ModuleDiXml.FILE_NAME)), + EXPECTED_DIR_FOR_REPLACING, + injectConstructorArgument( + new DiArgumentData( + MODULE_NAME, + TARGET_CLASS, + OBJECT_PARAMETER, + Areas.crontab, + DiArgumentType.OBJECT, + OBJECT_VALUE + "Factory" + ) + ) + ); + } + + private PsiFile injectConstructorArgument( + final DiArgumentData data + ) { + final ArgumentInjectionGenerator generator = new ArgumentInjectionGenerator( + data, + myFixture.getProject() + ); + + return generator.generate("test"); + } + + private String getArrayValue() { + final List items = new ArrayList<>(); + items.add(new DiArrayValueData.DiArrayItemData("method1", DiArgumentType.STRING, "QW1")); + items.add(new DiArrayValueData.DiArrayItemData("method2", DiArgumentType.STRING, "QW2")); + items.add(new DiArrayValueData.DiArrayItemData("method3", DiArgumentType.STRING, "QW3")); + items.add(new DiArrayValueData.DiArrayItemData("method4", DiArgumentType.STRING, "QW4")); + items.add(new DiArrayValueData.DiArrayItemData("method5", DiArgumentType.STRING, "QW5")); + final DiArrayValueData arrayValueData = new DiArrayValueData(); + arrayValueData.setItems(items); + + return arrayValueData.convertToXml(arrayValueData); + } + + private String getNestedArrayValue() { + final List items = new ArrayList<>(); + items.add(new DiArrayValueData.DiArrayItemData("method1", DiArgumentType.STRING, "QW1")); + items.add(new DiArrayValueData.DiArrayItemData("method2", DiArgumentType.STRING, "QW2")); + items.add(new DiArrayValueData.DiArrayItemData("method3", DiArgumentType.STRING, "QW3")); + items.add(new DiArrayValueData.DiArrayItemData("method4", DiArgumentType.STRING, "QW4")); + final DiArrayValueData.DiArrayItemData nestedItem = new DiArrayValueData.DiArrayItemData( + "nested", + DiArgumentType.ARRAY, + "" + ); + + final DiArrayValueData nestedItemsHolder = new DiArrayValueData(); + final List nestedItems = new ArrayList<>(); + nestedItems.add( + new DiArrayValueData.DiArrayItemData("nested1", DiArgumentType.STRING, "NT1") + ); + nestedItems.add( + new DiArrayValueData.DiArrayItemData("nested2", DiArgumentType.BOOLEAN, "true") + ); + nestedItems.add( + new DiArrayValueData.DiArrayItemData("nested3", DiArgumentType.NULL, "") + ); + nestedItemsHolder.setItems(nestedItems); + nestedItem.setChildren(nestedItemsHolder); + items.add(nestedItem); + + final DiArrayValueData arrayValueData = new DiArrayValueData(); + arrayValueData.setItems(items); + + return arrayValueData.convertToXml(arrayValueData); + } +}