From df995c8bf3fca458532e5195e48b660ebd2083ad Mon Sep 17 00:00:00 2001 From: bohdan-harniuk Date: Fri, 23 Oct 2020 16:13:54 +0300 Subject: [PATCH] Added index, completion and references for table names, column names in db_schema.xml file --- resources/META-INF/plugin.xml | 1 + .../ColumnNameCompletionProvider.java | 115 +++++++++++ .../provider/TableNameCompletionProvider.java | 46 +++++ .../xml/XmlCompletionContributor.java | 58 ++++++ .../magento2plugin/indexes/IndexManager.java | 2 + .../magento/files/ModuleDbSchemaXml.java | 47 +++++ .../TableColumnNamesReferenceProvider.java | 190 ++++++++++++++++++ .../xml/XmlReferenceContributor.java | 69 +++++++ .../indexes/xml/TableAndColumnNameIndex.java | 93 +++++++++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../tableNameMustHaveCompletion/db_schema.xml | 11 + .../magento/module-catalog/etc/db_schema.xml | 63 ++++++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../db_schema.xml | 13 ++ .../tableTagMustHaveReference/db_schema.xml | 11 + ...eAndColumnNameCompletionRegistrarTest.java | 60 ++++++ .../reference/BaseReferenceTestCase.java | 32 ++- ...leNameAndColumnReferenceRegistrarTest.java | 69 +++++++ 23 files changed, 964 insertions(+), 7 deletions(-) create mode 100644 src/com/magento/idea/magento2plugin/completion/provider/ColumnNameCompletionProvider.java create mode 100644 src/com/magento/idea/magento2plugin/completion/provider/TableNameCompletionProvider.java create mode 100644 src/com/magento/idea/magento2plugin/magento/files/ModuleDbSchemaXml.java create mode 100644 src/com/magento/idea/magento2plugin/reference/provider/TableColumnNamesReferenceProvider.java create mode 100644 src/com/magento/idea/magento2plugin/stubs/indexes/xml/TableAndColumnNameIndex.java create mode 100644 testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintColumnNameMustHaveCompletion/db_schema.xml create mode 100644 testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintReferenceColumnNameMustHaveCompletion/db_schema.xml create mode 100644 testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagReferenceTableMustHaveCompletion/db_schema.xml create mode 100644 testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagTableMustHaveCompletion/db_schema.xml create mode 100644 testData/completion/xml/TableAndColumnNameCompletionRegistrar/tableNameMustHaveCompletion/db_schema.xml create mode 100644 testData/project/magento2/vendor/magento/module-catalog/etc/db_schema.xml create mode 100644 testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintColumnTagMustHaveReference/db_schema.xml create mode 100644 testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceColumnTagMustHaveReference/db_schema.xml create mode 100644 testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceTableTagMustHaveReference/db_schema.xml create mode 100644 testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintTableTagMustHaveReference/db_schema.xml create mode 100644 testData/reference/xml/TableNameAndColumnReferenceRegistrar/tableTagMustHaveReference/db_schema.xml create mode 100644 tests/com/magento/idea/magento2plugin/completion/xml/TableAndColumnNameCompletionRegistrarTest.java create mode 100644 tests/com/magento/idea/magento2plugin/reference/xml/TableNameAndColumnReferenceRegistrarTest.java diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 11e9c1026..d48b8b512 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -125,6 +125,7 @@ + diff --git a/src/com/magento/idea/magento2plugin/completion/provider/ColumnNameCompletionProvider.java b/src/com/magento/idea/magento2plugin/completion/provider/ColumnNameCompletionProvider.java new file mode 100644 index 000000000..04039759a --- /dev/null +++ b/src/com/magento/idea/magento2plugin/completion/provider/ColumnNameCompletionProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.completion.provider; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.xml.XmlAttributeImpl; +import com.intellij.psi.impl.source.xml.XmlTagImpl; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.ProcessingContext; +import com.intellij.util.indexing.FileBasedIndex; +import com.magento.idea.magento2plugin.magento.files.ModuleDbSchemaXml; +import com.magento.idea.magento2plugin.stubs.indexes.xml.TableAndColumnNameIndex; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; + +/** + * Provides column names for completion. + */ +public class ColumnNameCompletionProvider extends CompletionProvider { + @Override + protected void addCompletions( + final @NotNull CompletionParameters parameters, + final @NotNull ProcessingContext context, + final @NotNull CompletionResultSet result + ) { + final PsiElement position = parameters.getPosition().getOriginalElement(); + final String currentAttrName = getCurrentAttributeName(position); + if (position == null || currentAttrName == null) { + return; + } + String targetTableAttrName; + + switch (currentAttrName) { + case ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_COLUMN_NAME: + targetTableAttrName = ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_TABLE_NAME; + break; + case ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_COLUMN_NAME: + targetTableAttrName = ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_TABLE_NAME; + break; + default: + return; + } + final String targetTableName = getTargetTableFromPositionAndAttrName( + position, + targetTableAttrName + ); + + if (targetTableName == null) { + return; + } + final Collection tableAndColumnNames = FileBasedIndex.getInstance().getAllKeys( + TableAndColumnNameIndex.KEY, position.getProject() + ); + final List filteredColumnNames = tableAndColumnNames.stream() + .filter(name -> name.contains(targetTableName + ".")).collect(Collectors.toList()) + .stream().map(name -> name.substring(name.indexOf(".") + 1)) + .collect(Collectors.toList()); + + for (final String columnName: filteredColumnNames) { + result.addElement(LookupElementBuilder.create(columnName)); + } + } + + /** + * Get attribute name from position. + * + * @param position PsiElement + * + * @return String + */ + private String getCurrentAttributeName(final PsiElement position) { + if (position instanceof XmlAttributeImpl) { + return ((XmlAttributeImpl) position).getName(); + } else { + return getCurrentAttributeName(position.getParent()); + } + } + + /** + * Get reference table name from current position. + * + * @param position PsiElement + * @param targetTableAttrName String + * + * @return String + */ + private String getTargetTableFromPositionAndAttrName( + final PsiElement position, + final String targetTableAttrName + ) { + if (targetTableAttrName == null) { + return null; + } + + if (position instanceof XmlTagImpl + && ((XmlTag) position).getName().equals(ModuleDbSchemaXml.XML_TAG_CONSTRAINT)) { + return ((XmlTag) position) + .getAttributeValue(targetTableAttrName); + } else { + return getTargetTableFromPositionAndAttrName( + position.getParent(), + targetTableAttrName + ); + } + } +} diff --git a/src/com/magento/idea/magento2plugin/completion/provider/TableNameCompletionProvider.java b/src/com/magento/idea/magento2plugin/completion/provider/TableNameCompletionProvider.java new file mode 100644 index 000000000..1a4f625f4 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/completion/provider/TableNameCompletionProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.completion.provider; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import com.intellij.util.indexing.FileBasedIndex; +import com.magento.idea.magento2plugin.stubs.indexes.xml.TableAndColumnNameIndex; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; + +/** + * Provides table names for completion. + */ +public class TableNameCompletionProvider extends CompletionProvider { + @Override + protected void addCompletions( + final @NotNull CompletionParameters parameters, + final @NotNull ProcessingContext context, + final @NotNull CompletionResultSet result + ) { + final PsiElement position = parameters.getPosition().getOriginalElement(); + if (position == null) { + return; + } + + final Collection tableNames = FileBasedIndex.getInstance().getAllKeys( + TableAndColumnNameIndex.KEY, position.getProject() + ); + final List filteredTableNames = tableNames.stream() + .filter(name -> !name.contains(".")).collect(Collectors.toList()); + + for (final String tableName: filteredTableNames) { + result.addElement(LookupElementBuilder.create(tableName)); + } + } +} diff --git a/src/com/magento/idea/magento2plugin/completion/xml/XmlCompletionContributor.java b/src/com/magento/idea/magento2plugin/completion/xml/XmlCompletionContributor.java index 9e4b53b8c..7589da76a 100644 --- a/src/com/magento/idea/magento2plugin/completion/xml/XmlCompletionContributor.java +++ b/src/com/magento/idea/magento2plugin/completion/xml/XmlCompletionContributor.java @@ -317,6 +317,64 @@ public XmlCompletionContributor() { new MenuCompletionProvider() ); + // in db_schema.xml + extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) + .inside(XmlPatterns.xmlAttribute().withName(ModuleDbSchemaXml.XML_ATTR_TABLE_NAME) + .withParent(XmlPatterns.xmlTag().withName(ModuleDbSchemaXml.XML_TAG_TABLE) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_SCHEMA)))) + .inFile(xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME))), + new TableNameCompletionProvider() + ); + + // in db_schema.xml + extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) + .inside(XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_TABLE_NAME) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_TABLE)))) + .inFile(xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME))), + new TableNameCompletionProvider() + ); + + // in db_schema.xml + extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) + .inside(XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_TABLE_NAME) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_TABLE)))) + .inFile(xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME))), + new TableNameCompletionProvider() + ); + + // in db_schema.xml + extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) + .inside(XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_COLUMN_NAME) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_TABLE)))) + .inFile(xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME))), + new ColumnNameCompletionProvider() + ); + + // in db_schema.xml + extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) + .inside(XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_COLUMN_NAME) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + .withParent(XmlPatterns.xmlTag() + .withName(ModuleDbSchemaXml.XML_TAG_TABLE)))) + .inFile(xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME))), + new ColumnNameCompletionProvider() + ); + registerCompletionsForDifferentNesting(); } diff --git a/src/com/magento/idea/magento2plugin/indexes/IndexManager.java b/src/com/magento/idea/magento2plugin/indexes/IndexManager.java index f235eb5e6..2c821464d 100644 --- a/src/com/magento/idea/magento2plugin/indexes/IndexManager.java +++ b/src/com/magento/idea/magento2plugin/indexes/IndexManager.java @@ -27,6 +27,7 @@ import com.magento.idea.magento2plugin.stubs.indexes.xml.AclResourceIndex; import com.magento.idea.magento2plugin.stubs.indexes.xml.MenuIndex; import com.magento.idea.magento2plugin.stubs.indexes.xml.PhpClassNameIndex; +import com.magento.idea.magento2plugin.stubs.indexes.xml.TableAndColumnNameIndex; @SuppressWarnings({"PMD.ClassNamingConventions", "PMD.UseUtilityClass"}) public class IndexManager { @@ -41,6 +42,7 @@ public static void manualReindex() { // xml|di configuration PluginIndex.KEY, VirtualTypeIndex.KEY, + TableAndColumnNameIndex.KEY, // layouts BlockNameIndex.KEY, ContainerNameIndex.KEY, diff --git a/src/com/magento/idea/magento2plugin/magento/files/ModuleDbSchemaXml.java b/src/com/magento/idea/magento2plugin/magento/files/ModuleDbSchemaXml.java new file mode 100644 index 000000000..8c2a68ff2 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/magento/files/ModuleDbSchemaXml.java @@ -0,0 +1,47 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.magento.files; + +import com.intellij.lang.Language; +import com.intellij.lang.xml.XMLLanguage; + +public class ModuleDbSchemaXml implements ModuleFileInterface { + private static final ModuleDbSchemaXml INSTANCE = new ModuleDbSchemaXml(); + public static final String FILE_NAME = "db_schema.xml"; + public static final String TEMPLATE = "Magento Module Declarative Schema XML"; + + //attributes + public static final String XML_ATTR_TABLE_NAME = "name"; + public static final String XML_ATTR_COLUMN_NAME = "name"; + public static final String XML_ATTR_CONSTRAINT_TABLE_NAME = "table"; + public static final String XML_ATTR_CONSTRAINT_REFERENCE_TABLE_NAME = "referenceTable"; + public static final String XML_ATTR_CONSTRAINT_COLUMN_NAME = "column"; + public static final String XML_ATTR_CONSTRAINT_REFERENCE_COLUMN_NAME = "referenceColumn"; + //tags + public static final String XML_TAG_SCHEMA = "schema"; + public static final String XML_TAG_TABLE = "table"; + public static final String XML_TAG_COLUMN = "column"; + public static final String XML_TAG_CONSTRAINT = "constraint"; + + public static ModuleDbSchemaXml getInstance() { + return INSTANCE; + } + + @Override + public String getFileName() { + return FILE_NAME; + } + + @Override + public String getTemplate() { + return TEMPLATE; + } + + @Override + public Language getLanguage() { + return XMLLanguage.INSTANCE; + } +} diff --git a/src/com/magento/idea/magento2plugin/reference/provider/TableColumnNamesReferenceProvider.java b/src/com/magento/idea/magento2plugin/reference/provider/TableColumnNamesReferenceProvider.java new file mode 100644 index 000000000..81d74fcf1 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/reference/provider/TableColumnNamesReferenceProvider.java @@ -0,0 +1,190 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.reference.provider; + +import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.impl.source.xml.XmlAttributeImpl; +import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl; +import com.intellij.psi.impl.source.xml.XmlTagImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.ProcessingContext; +import com.intellij.util.indexing.FileBasedIndex; +import com.magento.idea.magento2plugin.magento.files.ModuleDbSchemaXml; +import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase; +import com.magento.idea.magento2plugin.stubs.indexes.xml.TableAndColumnNameIndex; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + + +/** + * Reference provider for table and column names in the db_schema.xml files. + */ +public class TableColumnNamesReferenceProvider extends PsiReferenceProvider { + @NotNull + @Override + @SuppressWarnings({"PMD.NPathComplexity", "PMD.CyclomaticComplexity", + "PMD.AvoidDeeplyNestedIfStmts"}) + public PsiReference[] getReferencesByElement( + final @NotNull PsiElement element, + final @NotNull ProcessingContext context + ) { + final List psiReferences = new ArrayList<>(); + String identifier = ((XmlAttributeValueImpl) element).getValue(); + String columnIdentifier = null; + String tableNameIdentifier = null; + + final String parentTag = getParentTagName(element); + if (parentTag != null && parentTag.equals(ModuleDbSchemaXml.XML_TAG_CONSTRAINT)) { + final XmlAttribute attribute = getParentAttribute(element); + final String attributeName = attribute.getName(); + + if (attributeName.equals(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_COLUMN_NAME)) { + columnIdentifier = identifier; + tableNameIdentifier = getNearAttributeValueForElement( + attribute, + ModuleDbSchemaXml.XML_TAG_CONSTRAINT, + ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_TABLE_NAME + ); + + if (tableNameIdentifier != null) { + identifier = tableNameIdentifier + "." + columnIdentifier; + } + } + if (attributeName.equals( + ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_COLUMN_NAME)) { + columnIdentifier = identifier; + tableNameIdentifier = getNearAttributeValueForElement( + attribute, + ModuleDbSchemaXml.XML_TAG_CONSTRAINT, + ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_TABLE_NAME + ); + + if (tableNameIdentifier != null) { + identifier = tableNameIdentifier + "." + columnIdentifier; + } + } + } + final Collection files = FileBasedIndex.getInstance() + .getContainingFiles(TableAndColumnNameIndex.KEY, identifier, + GlobalSearchScope.getScopeRestrictedByFileTypes( + GlobalSearchScope.allScope(element.getProject()), + XmlFileType.INSTANCE + ) + ); + + final List psiElements = new ArrayList<>(); + final PsiManager psiManager = PsiManager.getInstance(element.getProject()); + + for (final VirtualFile virtualFile : files) { + final XmlDocument xmlDocument = ((XmlFile) Objects.requireNonNull( + psiManager.findFile(virtualFile)) + ).getDocument(); + + if (xmlDocument != null) { + final XmlTag xmlRootTag = xmlDocument.getRootTag(); + + if (xmlRootTag != null) { + final XmlTag[] tableTags = xmlRootTag.getSubTags(); + for (final XmlTag tableTag : tableTags) { + final String tableName = + tableTag.getAttributeValue(ModuleDbSchemaXml.XML_ATTR_TABLE_NAME); + if (tableTag.getName().equals(ModuleDbSchemaXml.XML_TAG_TABLE) + && tableName != null + && tableName.equals(identifier) + && columnIdentifier == null + ) { + psiElements.add(tableTag); + } + + if (tableName != null && tableName.equals(tableNameIdentifier)) { + final XmlTag[] columnTags = tableTag + .findSubTags(ModuleDbSchemaXml.XML_TAG_COLUMN); + for (final XmlTag columnTag : columnTags) { + final String columnName = + columnTag.getAttributeValue( + ModuleDbSchemaXml.XML_ATTR_COLUMN_NAME + ); + if (columnName != null + && columnName.equals(columnIdentifier)) { + psiElements.add(columnTag); + } + } + } + } + } + } + } + + if (!psiElements.isEmpty()) { + psiReferences.add(new PolyVariantReferenceBase(element, psiElements)); + } + + return psiReferences.toArray(new PsiReference[0]); + } + + /** + * Get parent tag of element. + * + * @param element PsiElement + * + * @return String + */ + private String getParentTagName(final PsiElement element) { + if (element instanceof XmlTagImpl) { + return ((XmlTagImpl) element).getName(); + } else { + return getParentTagName(element.getParent()); + } + } + + /** + * Get parent attribute from element. + * + * @param element PsiElement + * + * @return XmlAttribute + */ + private XmlAttribute getParentAttribute(final PsiElement element) { + if (element instanceof XmlAttributeImpl) { + return (XmlAttribute) element; + } else { + return getParentAttribute(element.getParent()); + } + } + + /** + * Get near attribute value. + * + * @param element XmlAttribute + * @param parentTagName String + * @param targetAttributeName String + * + * @return String + */ + private String getNearAttributeValueForElement( + final XmlAttribute element, + final String parentTagName, + final String targetAttributeName + ) { + final XmlTag parentTag = element.getParent(); + if (parentTag.getName().equals(parentTagName)) { + return parentTag.getAttributeValue(targetAttributeName); + } + return null; + } +} diff --git a/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java b/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java index c4bde6ca0..2c072dc7c 100644 --- a/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java +++ b/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java @@ -11,6 +11,7 @@ import com.intellij.psi.xml.XmlTokenType; import com.magento.idea.magento2plugin.magento.files.MftfActionGroup; import com.magento.idea.magento2plugin.magento.files.MftfTest; +import com.magento.idea.magento2plugin.magento.files.ModuleDbSchemaXml; import com.magento.idea.magento2plugin.magento.files.ModuleMenuXml; import com.magento.idea.magento2plugin.magento.files.UiComponentXml; // CHECKSTYLE IGNORE check FOR NEXT 5 LINES @@ -290,6 +291,74 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) new MenuReferenceProvider() ); + //
in db_schema.xml + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute().withName(ModuleDbSchemaXml.XML_ATTR_TABLE_NAME) + .withParent(XmlPatterns.xmlTag().withName(ModuleDbSchemaXml.XML_TAG_TABLE) + ) + ).inFile( + xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME)) + ), + new TableColumnNamesReferenceProvider() + ); + + // in db_schema.xml + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_TABLE_NAME) + .withParent(XmlPatterns.xmlTag().withName( + ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + ) + ).inFile( + xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME)) + ), + new TableColumnNamesReferenceProvider() + ); + + // in db_schema.xml + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_TABLE_NAME) + .withParent(XmlPatterns.xmlTag().withName( + ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + ) + ).inFile( + xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME)) + ), + new TableColumnNamesReferenceProvider() + ); + + // in db_schema.xml + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_COLUMN_NAME) + .withParent(XmlPatterns.xmlTag().withName( + ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + ) + ).inFile( + xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME)) + ), + new TableColumnNamesReferenceProvider() + ); + + // in db_schema.xml + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute() + .withName(ModuleDbSchemaXml.XML_ATTR_CONSTRAINT_REFERENCE_COLUMN_NAME) + .withParent(XmlPatterns.xmlTag().withName( + ModuleDbSchemaXml.XML_TAG_CONSTRAINT) + ) + ).inFile( + xmlFile().withName(string().matches(ModuleDbSchemaXml.FILE_NAME)) + ), + new TableColumnNamesReferenceProvider() + ); + registerReferenceForDifferentNesting(registrar); } diff --git a/src/com/magento/idea/magento2plugin/stubs/indexes/xml/TableAndColumnNameIndex.java b/src/com/magento/idea/magento2plugin/stubs/indexes/xml/TableAndColumnNameIndex.java new file mode 100644 index 000000000..51396e0b1 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/stubs/indexes/xml/TableAndColumnNameIndex.java @@ -0,0 +1,93 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.stubs.indexes.xml; + +import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.indexing.DataIndexer; +import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.FileBasedIndexExtension; +import com.intellij.util.indexing.FileContent; +import com.intellij.util.indexing.ID; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.KeyDescriptor; +import com.intellij.util.io.VoidDataExternalizer; +import com.magento.idea.magento2plugin.project.Settings; +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +/** + * Index to store table names and column names from db_schema.xml files. + */ +public class TableAndColumnNameIndex extends FileBasedIndexExtension { + public static final ID KEY = ID.create( + "com.magento.idea.magento2plugin.stubs.indexes.db_schema.tables_and_columns"); + private final KeyDescriptor myKeyDescriptor = new EnumeratorStringDescriptor(); + + @Override + public @NotNull DataIndexer getIndexer() { + return inputData -> { + final Map map = new HashMap<>(); + final PsiFile psiFile = inputData.getPsiFile(); + final XmlDocument document = ((XmlFile) psiFile).getDocument(); + final XmlTag root = ((XmlFile) psiFile).getRootTag(); + + if (!Settings.isEnabled(psiFile.getProject()) || document == null || root == null) { + return map; + } + + for (final XmlTag tableTag : root.getSubTags()) { + if (tableTag.getName().equals("table")) { + final String tableName = tableTag.getAttributeValue("name"); + map.put(tableName, null); + + for (final XmlTag columnTag : tableTag.getSubTags()) { + if (columnTag.getName().equals("column")) { + map.put(tableName + "." + columnTag.getAttributeValue("name"), null); + } + } + } + } + return map; + }; + } + + @Override + public @NotNull ID getName() { + return KEY; + } + + @Override + public @NotNull KeyDescriptor getKeyDescriptor() { + return myKeyDescriptor; + } + + @Override + public @NotNull DataExternalizer getValueExternalizer() { + return VoidDataExternalizer.INSTANCE; + } + + @Override + public int getVersion() { + return 1; + } + + @Override + public FileBasedIndex.@NotNull InputFilter getInputFilter() { + return virtualFile -> (virtualFile.getFileType() == XmlFileType.INSTANCE + && virtualFile.getNameWithoutExtension().equals("db_schema")); + } + + @Override + public boolean dependsOnFileContent() { + return true; + } +} diff --git a/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintColumnNameMustHaveCompletion/db_schema.xml b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintColumnNameMustHaveCompletion/db_schema.xml new file mode 100644 index 000000000..651d6c4cc --- /dev/null +++ b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintColumnNameMustHaveCompletion/db_schema.xml @@ -0,0 +1,13 @@ + + + +
+ +
+ \ No newline at end of file diff --git a/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintReferenceColumnNameMustHaveCompletion/db_schema.xml b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintReferenceColumnNameMustHaveCompletion/db_schema.xml new file mode 100644 index 000000000..1f0a1b40f --- /dev/null +++ b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintReferenceColumnNameMustHaveCompletion/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
\ No newline at end of file diff --git a/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagReferenceTableMustHaveCompletion/db_schema.xml b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagReferenceTableMustHaveCompletion/db_schema.xml new file mode 100644 index 000000000..fdc19be4b --- /dev/null +++ b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagReferenceTableMustHaveCompletion/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
\ No newline at end of file diff --git a/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagTableMustHaveCompletion/db_schema.xml b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagTableMustHaveCompletion/db_schema.xml new file mode 100644 index 000000000..267c35b09 --- /dev/null +++ b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/constraintTagTableMustHaveCompletion/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
diff --git a/testData/completion/xml/TableAndColumnNameCompletionRegistrar/tableNameMustHaveCompletion/db_schema.xml b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/tableNameMustHaveCompletion/db_schema.xml new file mode 100644 index 000000000..c2837d76f --- /dev/null +++ b/testData/completion/xml/TableAndColumnNameCompletionRegistrar/tableNameMustHaveCompletion/db_schema.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/testData/project/magento2/vendor/magento/module-catalog/etc/db_schema.xml b/testData/project/magento2/vendor/magento/module-catalog/etc/db_schema.xml new file mode 100644 index 000000000..0a81ef492 --- /dev/null +++ b/testData/project/magento2/vendor/magento/module-catalog/etc/db_schema.xml @@ -0,0 +1,63 @@ + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
diff --git a/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintColumnTagMustHaveReference/db_schema.xml b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintColumnTagMustHaveReference/db_schema.xml new file mode 100644 index 000000000..a4162ce64 --- /dev/null +++ b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintColumnTagMustHaveReference/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
\ No newline at end of file diff --git a/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceColumnTagMustHaveReference/db_schema.xml b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceColumnTagMustHaveReference/db_schema.xml new file mode 100644 index 000000000..41041e621 --- /dev/null +++ b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceColumnTagMustHaveReference/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
\ No newline at end of file diff --git a/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceTableTagMustHaveReference/db_schema.xml b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceTableTagMustHaveReference/db_schema.xml new file mode 100644 index 000000000..c065029a0 --- /dev/null +++ b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintReferenceTableTagMustHaveReference/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
\ No newline at end of file diff --git a/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintTableTagMustHaveReference/db_schema.xml b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintTableTagMustHaveReference/db_schema.xml new file mode 100644 index 000000000..21a41cda5 --- /dev/null +++ b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/constraintTableTagMustHaveReference/db_schema.xml @@ -0,0 +1,13 @@ + + + + + +
+
diff --git a/testData/reference/xml/TableNameAndColumnReferenceRegistrar/tableTagMustHaveReference/db_schema.xml b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/tableTagMustHaveReference/db_schema.xml new file mode 100644 index 000000000..5c6915496 --- /dev/null +++ b/testData/reference/xml/TableNameAndColumnReferenceRegistrar/tableTagMustHaveReference/db_schema.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/tests/com/magento/idea/magento2plugin/completion/xml/TableAndColumnNameCompletionRegistrarTest.java b/tests/com/magento/idea/magento2plugin/completion/xml/TableAndColumnNameCompletionRegistrarTest.java new file mode 100644 index 000000000..0156b97ee --- /dev/null +++ b/tests/com/magento/idea/magento2plugin/completion/xml/TableAndColumnNameCompletionRegistrarTest.java @@ -0,0 +1,60 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.completion.xml; + +import com.magento.idea.magento2plugin.magento.files.ModuleDbSchemaXml; + +/** + * Test table and column names completion in the db_schema.xml file. + */ +public class TableAndColumnNameCompletionRegistrarTest extends CompletionXmlFixtureTestCase { + private static final String CATALOG_PRODUCT_ENTITY_TABLE_NAME = "catalog_product_entity"; + + /** + * The `name` attribute of the `table` tag in `db_schema.xml` file must + * have completion based on table and column names index. + */ + public void testTableNameMustHaveCompletion() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + assertCompletionContains(filePath, "catalog_category_entity"); + } + + /** + * The `table` attribute of the `constraint` tag in `db_schema.xml` file must + * have completion based on table and column names index. + */ + public void testConstraintTagTableMustHaveCompletion() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + assertCompletionContains(filePath, CATALOG_PRODUCT_ENTITY_TABLE_NAME); + } + + /** + * The `referenceTable` attribute of the `constraint` tag in `db_schema.xml` file must + * have completion based on table and column names index. + */ + public void testConstraintTagReferenceTableMustHaveCompletion() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + assertCompletionContains(filePath, CATALOG_PRODUCT_ENTITY_TABLE_NAME); + } + + /** + * The `column` attribute of the `constraint` tag in `db_schema.xml` file must + * have completion based on table and column names index. + */ + public void testConstraintColumnNameMustHaveCompletion() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + assertCompletionContains(filePath, "children_count"); + } + + /** + * The `referenceColumn` attribute of the `constraint` tag in `db_schema.xml` file must + * have completion based on table and column names index. + */ + public void testConstraintReferenceColumnNameMustHaveCompletion() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + assertCompletionContains(filePath, "attribute_set_id"); + } +} diff --git a/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java b/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java index 481d1c5c9..dca734783 100644 --- a/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java +++ b/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java @@ -7,11 +7,13 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.ResolveResult; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlTag; import com.jetbrains.php.lang.psi.elements.PhpClass; import com.magento.idea.magento2plugin.inspections.BaseInspectionsTestCase; import com.magento.idea.magento2plugin.magento.packages.File; +import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase; abstract public class BaseReferenceTestCase extends BaseInspectionsTestCase { @@ -46,13 +48,29 @@ protected void assertHasReferenceToXmlTag(String tagName) { PsiElement element = getElementFromCaret(); for (PsiReference psiReference: element.getReferences()) { - PsiElement resolved = psiReference.resolve(); - if (!(resolved instanceof XmlTag)) { - continue; - } - - if (((XmlTag) resolved).getName().equals(tagName)) { - return; + if (psiReference instanceof PolyVariantReferenceBase) { + ResolveResult[] resolveResults + = ((PolyVariantReferenceBase) psiReference).multiResolve(true); + + for (ResolveResult resolveResult : resolveResults) { + PsiElement resolved = resolveResult.getElement(); + if (!(resolved instanceof XmlTag)) { + continue; + } + + if (((XmlTag) resolved).getName().equals(tagName)) { + return; + } + } + } else { + PsiElement resolved = psiReference.resolve(); + if (!(resolved instanceof XmlTag)) { + continue; + } + + if (((XmlTag) resolved).getName().equals(tagName)) { + return; + } } } diff --git a/tests/com/magento/idea/magento2plugin/reference/xml/TableNameAndColumnReferenceRegistrarTest.java b/tests/com/magento/idea/magento2plugin/reference/xml/TableNameAndColumnReferenceRegistrarTest.java new file mode 100644 index 000000000..97adcd998 --- /dev/null +++ b/tests/com/magento/idea/magento2plugin/reference/xml/TableNameAndColumnReferenceRegistrarTest.java @@ -0,0 +1,69 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.reference.xml; + +import com.magento.idea.magento2plugin.magento.files.ModuleDbSchemaXml; + +/** + * Test table and columns name references in the db_schema.xml file. + */ +public class TableNameAndColumnReferenceRegistrarTest extends ReferenceXmlFixtureTestCase { + + /** + * The `name` attribute of the `table` tag in a `db_schema.xml` must + * have reference to the `name` attribute of the another `table` tag. + */ + public void testTableTagMustHaveReference() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + myFixture.configureByFile(filePath); + + assertHasReferenceToXmlTag(ModuleDbSchemaXml.XML_TAG_TABLE); + } + + /** + * The `table` attribute of the `constraint` tag in a `db_schema.xml` must + * have reference to the `name` attribute of the another `table` tag. + */ + public void testConstraintTableTagMustHaveReference() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + myFixture.configureByFile(filePath); + + assertHasReferenceToXmlTag(ModuleDbSchemaXml.XML_TAG_TABLE); + } + + /** + * The `referenceTable` attribute of the `constraint` tag in a `db_schema.xml` must + * have reference to the `name` attribute of the another `table` tag. + */ + public void testConstraintReferenceTableTagMustHaveReference() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + myFixture.configureByFile(filePath); + + assertHasReferenceToXmlTag(ModuleDbSchemaXml.XML_TAG_TABLE); + } + + /** + * The `column` attribute of the `constraint` tag in a `db_schema.xml` must + * have reference to the `name` attribute of the `column` tag. + */ + public void testConstraintColumnTagMustHaveReference() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + myFixture.configureByFile(filePath); + + assertHasReferenceToXmlTag(ModuleDbSchemaXml.XML_TAG_COLUMN); + } + + /** + * The `referenceColumn` attribute of the `constraint` tag in a `db_schema.xml` must + * have reference to the `name` attribute of the `column` tag. + */ + public void testConstraintReferenceColumnTagMustHaveReference() { + final String filePath = this.getFixturePath(ModuleDbSchemaXml.FILE_NAME); + myFixture.configureByFile(filePath); + + assertHasReferenceToXmlTag(ModuleDbSchemaXml.XML_TAG_COLUMN); + } +}