Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved support of the Impex nested attributes #339

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/META-INF/plugin-release-info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<![CDATA[
<h3>2023.1.5</h3>
<ul>
<li><i>Feature:</i> Improved support of the Impex nested attributes (<a href="https://github.com/epam/sap-commerce-intellij-idea-plugin/pull/339" target="_blank" rel="nofollow">#339</a>)</li>
<li><i>Feature:</i> Added new <strong>Impex</strong> inspection rules
<ul>
<li>Type attribute validation for complex header (<a href="https://github.com/epam/sap-commerce-intellij-idea-plugin/pull/338" target="_blank" rel="nofollow">#338</a>)</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ package com.intellij.idea.plugin.hybris.impex.completion.provider
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.idea.plugin.hybris.impex.psi.ImpexAnyHeaderParameterName
import com.intellij.idea.plugin.hybris.impex.psi.ImpexFullHeaderParameter
import com.intellij.idea.plugin.hybris.impex.psi.ImpexParameter
import com.intellij.idea.plugin.hybris.system.type.codeInsight.lookup.TSLookupElementFactory
import com.intellij.idea.plugin.hybris.system.type.meta.TSMetaModelAccess
import com.intellij.idea.plugin.hybris.system.type.meta.model.TSGlobalMetaItem
import com.intellij.idea.plugin.hybris.system.type.model.EnumType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.xml.XmlTag
import com.intellij.util.ProcessingContext
Expand All @@ -48,7 +49,9 @@ class ImpexHeaderItemTypeParameterNameCompletionProvider : CompletionProvider<Co
Validate.notNull(result)

val project = parameters.position.project
val psiElementUnderCaret = parameters.position
val psiElementUnderCaret = if (parameters.position is LeafPsiElement)
parameters.position.parent
else parameters.position
val typeName = findItemTypeReference(psiElementUnderCaret) ?: return

val metaService = TSMetaModelAccess.getInstance(project)
Expand Down Expand Up @@ -84,14 +87,15 @@ class ImpexHeaderItemTypeParameterNameCompletionProvider : CompletionProvider<Co
?.let { resultSet.addElement(it) }
}

private fun findItemTypeReference(element: PsiElement): String? {
val parent = PsiTreeUtil.getParentOfType(element, ImpexFullHeaderParameter::class.java)
val parameterName = PsiTreeUtil.findChildOfType(parent, ImpexAnyHeaderParameterName::class.java) ?: return null

return parameterName.references
.mapNotNull { it.resolve() }
.firstNotNullOfOrNull { obtainTypeName(it) }
}
private fun findItemTypeReference(element: PsiElement) = (
PsiTreeUtil.getParentOfType(element, ImpexParameter::class.java)
?: PsiTreeUtil.getParentOfType(element, ImpexFullHeaderParameter::class.java)
?.anyHeaderParameterName
)
?.references
?.firstOrNull()
?.resolve()
?.let { obtainTypeName(it) }

private fun obtainTypeName(reference: PsiElement): String? {
val typeTag = PsiTreeUtil.findFirstParent(reference) { value -> value is XmlTag }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,33 @@

package com.intellij.idea.plugin.hybris.impex.psi.references

import com.intellij.idea.plugin.hybris.impex.psi.ImpexAnyHeaderParameterName
import com.intellij.codeInsight.completion.CompletionUtilCore
import com.intellij.idea.plugin.hybris.impex.psi.ImpexFullHeaderParameter
import com.intellij.idea.plugin.hybris.impex.psi.ImpexParameter
import com.intellij.idea.plugin.hybris.psi.reference.TSReferenceBase
import com.intellij.idea.plugin.hybris.psi.utils.PsiUtils
import com.intellij.idea.plugin.hybris.system.type.meta.TSMetaItemService
import com.intellij.idea.plugin.hybris.system.type.meta.TSMetaModelAccess
import com.intellij.idea.plugin.hybris.system.type.psi.reference.result.AttributeResolveResult
import com.intellij.idea.plugin.hybris.system.type.psi.reference.result.EnumResolveResult
import com.intellij.idea.plugin.hybris.system.type.psi.reference.result.RelationEndResolveResult
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.ResolveResult
import com.intellij.psi.util.*
import com.intellij.psi.xml.XmlTag

class FunctionTSAttributeReference(owner: ImpexParameter) : TSReferenceBase<ImpexParameter>(owner) {

override fun calculateDefaultRangeInElement(): TextRange {
val alias = element.text
.substringBefore("(")
.substringBefore("[")
.trim()
return TextRange.from(element.text.indexOf(alias), alias.length)
}

override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
val indicator = ProgressManager.getInstance().progressIndicator
if (indicator != null && indicator.isCanceled) return ResolveResult.EMPTY_ARRAY
Expand All @@ -51,25 +60,28 @@ class FunctionTSAttributeReference(owner: ImpexParameter) : TSReferenceBase<Impe

private val provider = ParameterizedCachedValueProvider<Array<ResolveResult>, FunctionTSAttributeReference> { ref ->
val metaService = TSMetaModelAccess.getInstance(ref.project)
val featureName = ref.element.text.trim()
val featureName = ref.element.text
.replace(CompletionUtilCore.DUMMY_IDENTIFIER, "")
.substringBefore("(")
.substringBefore("[")
.trim()
val typeName = findItemTypeReference(ref.element)
val metaItem = metaService.findMetaItemByName(typeName)
val result: Array<ResolveResult> = if (metaItem == null) {
metaService.findMetaEnumByName(typeName)
?.let { arrayOf(EnumResolveResult(it)) }
?: ResolveResult.EMPTY_ARRAY
} else {
val itemService = TSMetaItemService.getInstance(ref.project)
val attributes = itemService
.findAttributesByName(metaItem, featureName, true)
.map { AttributeResolveResult(it) }

val relations = itemService
.findRelationEndsByQualifier(metaItem, featureName, true)
.map { RelationEndResolveResult(it) }

(attributes + relations).toTypedArray()
}
val result: Array<ResolveResult> = (
metaService.findMetaEnumByName(typeName)
?.let { EnumResolveResult(it) }
?: metaService.findMetaItemByName(typeName)
?.let {
it.allAttributes
.find { attr -> attr.name == featureName }
?.let { attr -> AttributeResolveResult(attr) }
?: it.allRelationEnds
.find { relationEnd -> relationEnd.name == featureName }
?.let { relationEnd -> RelationEndResolveResult(relationEnd) }
}
)
?.let { arrayOf(it) }
?: ResolveResult.EMPTY_ARRAY

// no need to track with PsiModificationTracker.MODIFICATION_COUNT due manual cache reset via custom Mixin
CachedValueProvider.Result.create(
Expand All @@ -78,18 +90,15 @@ class FunctionTSAttributeReference(owner: ImpexParameter) : TSReferenceBase<Impe
)
}

private fun findItemTypeReference(element: PsiElement): String {
val parent = element.parent.parent
val parameterName = PsiTreeUtil.findChildOfType(parent, ImpexAnyHeaderParameterName::class.java)
if (parameterName != null) {
val references = parameterName.references
if (references.isNotEmpty()) {
val reference = references.first().resolve()
return obtainTypeName(reference)
}
}
return ""
}
private fun findItemTypeReference(element: PsiElement) = (
PsiTreeUtil.getParentOfType(element, ImpexParameter::class.java)
?: PsiTreeUtil.getParentOfType(element, ImpexFullHeaderParameter::class.java)
?.anyHeaderParameterName
)
?.references
?.firstOrNull()
?.resolve()
?.let { obtainTypeName(it) }

private fun obtainTypeName(reference: PsiElement?): String {
val typeTag = PsiTreeUtil.findFirstParent(reference) { value -> value is XmlTag }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.intellij.idea.plugin.hybris.impex.psi.ImpexAnyHeaderParameterName
import com.intellij.idea.plugin.hybris.impex.utils.ImpexPsiUtils
import com.intellij.idea.plugin.hybris.psi.reference.TSReferenceBase
import com.intellij.idea.plugin.hybris.psi.utils.PsiUtils
import com.intellij.idea.plugin.hybris.system.type.meta.TSMetaItemService
import com.intellij.idea.plugin.hybris.system.type.meta.TSMetaModelAccess
import com.intellij.idea.plugin.hybris.system.type.meta.model.TSGlobalMetaItem
import com.intellij.idea.plugin.hybris.system.type.psi.reference.result.AttributeResolveResult
Expand All @@ -37,6 +36,7 @@ import com.intellij.psi.util.ParameterizedCachedValue
import com.intellij.psi.util.ParameterizedCachedValueProvider

internal class ImpexTSAttributeReference(owner: ImpexAnyHeaderParameterNameMixin) : TSReferenceBase<ImpexAnyHeaderParameterName>(owner) {

override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
val indicator = ProgressManager.getInstance().progressIndicator
if (indicator != null && indicator.isCanceled) return ResolveResult.EMPTY_ARRAY
Expand All @@ -52,18 +52,14 @@ internal class ImpexTSAttributeReference(owner: ImpexAnyHeaderParameterNameMixin

private val provider = ParameterizedCachedValueProvider<Array<ResolveResult>, ImpexTSAttributeReference> { ref ->
val metaModelAccess = TSMetaModelAccess.getInstance(ref.project)
val metaItemService = TSMetaItemService.getInstance(ref.project)
val featureName = ref.value
var result = tryResolveForItemType(ref.element, metaModelAccess, metaItemService, featureName)
if (result == null) {
result = tryResolveForRelationType(ref.element, metaModelAccess, metaItemService, featureName)
}
if (result == null) {
result = tryResolveForEnumType(ref.element, metaModelAccess, featureName)
}
if (result == null) {
result = ResolveResult.EMPTY_ARRAY
}
val result = (
tryResolveForItemType(ref.element, metaModelAccess, featureName)
?: tryResolveForRelationType(ref.element, metaModelAccess, featureName)
?: tryResolveForEnumType(ref.element, metaModelAccess, featureName)
)
?.let { arrayOf(it) }
?: ResolveResult.EMPTY_ARRAY

// no need to track with PsiModificationTracker.MODIFICATION_COUNT due manual cache reset via custom Mixin
CachedValueProvider.Result.create(
Expand All @@ -76,53 +72,50 @@ internal class ImpexTSAttributeReference(owner: ImpexAnyHeaderParameterNameMixin
element: ImpexAnyHeaderParameterName,
metaService: TSMetaModelAccess,
featureName: String
): Array<ResolveResult>? = if (HybrisConstants.CODE_ATTRIBUTE_NAME == featureName || HybrisConstants.NAME_ATTRIBUTE_NAME == featureName)
): ResolveResult? = if (HybrisConstants.CODE_ATTRIBUTE_NAME == featureName || HybrisConstants.NAME_ATTRIBUTE_NAME == featureName)
ImpexPsiUtils.findHeaderItemTypeName(element)
?.text
?.let { metaService.findMetaEnumByName(it) }
?.let { arrayOf(EnumResolveResult(it)) }
?.let { EnumResolveResult(it) }
else null

private fun tryResolveForItemType(
element: ImpexAnyHeaderParameterName,
metaModelService: TSMetaModelAccess,
metaItemService: TSMetaItemService,
featureName: String
): Array<ResolveResult>? = ImpexPsiUtils.findHeaderItemTypeName(element)
): ResolveResult? = ImpexPsiUtils.findHeaderItemTypeName(element)
?.text
?.let { metaModelService.findMetaItemByName(it) }
?.let {
val attributes = resolveMetaItemAttributes(metaItemService, featureName, it)
val relationEnds = metaItemService.findRelationEndsByQualifier(it, featureName, true)
.map { relation -> RelationEndResolveResult(relation) }
(attributes + relationEnds)
resolveMetaItemAttribute(featureName, it)
?: it.allRelationEnds
.find { relationEnd -> relationEnd.name == featureName }
?.let { relationEnd -> RelationEndResolveResult(relationEnd) }
}

private fun tryResolveForRelationType(
element: ImpexAnyHeaderParameterName,
metaService: TSMetaModelAccess,
metaItemService: TSMetaItemService,
featureName: String
): Array<ResolveResult>? = ImpexPsiUtils.findHeaderItemTypeName(element)
): ResolveResult? = ImpexPsiUtils.findHeaderItemTypeName(element)
?.text
?.let { metaService.findMetaRelationByName(it) }
?.let {
if (HybrisConstants.SOURCE_ATTRIBUTE_NAME.equals(featureName, ignoreCase = true)) {
return@let arrayOf(RelationEndResolveResult(it.source))
return@let RelationEndResolveResult(it.source)
} else if (HybrisConstants.TARGET_ATTRIBUTE_NAME.equals(featureName, ignoreCase = true)) {
return@let arrayOf(RelationEndResolveResult(it.target))
return@let RelationEndResolveResult(it.target)
}

return@let metaService.findMetaItemByName(HybrisConstants.TS_TYPE_LINK)
?.let { metaLink -> resolveMetaItemAttributes(metaItemService, featureName, metaLink) }
?.let { metaLink -> resolveMetaItemAttribute(featureName, metaLink) }
}

private fun resolveMetaItemAttributes(
metaItemService: TSMetaItemService,
private fun resolveMetaItemAttribute(
featureName: String,
metaItem: TSGlobalMetaItem
): Array<ResolveResult> = metaItemService.findAttributesByName(metaItem, featureName, true)
.map { AttributeResolveResult(it) }
.toTypedArray()
): ResolveResult? = metaItem.allAttributes
.find { it.name == featureName }
?.let { AttributeResolveResult(it) }
}
}