Angular/angular-backend/src/org/angular2/web/Angular2SymbolQueryConfigurator.kt (322 lines of code) (raw):
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.angular2.web
import com.intellij.lang.javascript.psi.*
import com.intellij.lang.javascript.psi.ecma6.ES6Decorator
import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass
import com.intellij.lang.javascript.psi.ecma6.TypeScriptField
import com.intellij.model.Pointer
import com.intellij.openapi.project.Project
import com.intellij.polySymbols.PolySymbolKind
import com.intellij.polySymbols.PolySymbolProperty
import com.intellij.polySymbols.context.PolyContext
import com.intellij.polySymbols.css.CSS_CLASS_LIST
import com.intellij.polySymbols.framework.framework
import com.intellij.polySymbols.html.HTML_ATTRIBUTES
import com.intellij.polySymbols.html.NAMESPACE_HTML
import com.intellij.polySymbols.js.JS_PROPERTIES
import com.intellij.polySymbols.js.JS_STRING_LITERALS
import com.intellij.polySymbols.js.NAMESPACE_JS
import com.intellij.polySymbols.js.css.CssClassListInJSLiteralInHtmlAttributeScope
import com.intellij.polySymbols.js.css.CssClassListInJSLiteralInHtmlAttributeScope.Companion.isJSLiteralContextFromEmbeddedContent
import com.intellij.polySymbols.query.*
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.css.CssElement
import com.intellij.psi.html.HtmlTag
import com.intellij.psi.util.parentOfType
import com.intellij.psi.util.parentOfTypes
import com.intellij.psi.util.siblings
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.psi.xml.XmlElement
import com.intellij.psi.xml.XmlTag
import com.intellij.util.asSafely
import org.angular2.*
import org.angular2.Angular2DecoratorUtil.COMPONENT_DEC
import org.angular2.Angular2DecoratorUtil.DIRECTIVE_DEC
import org.angular2.Angular2DecoratorUtil.HOST_BINDING_DEC
import org.angular2.Angular2DecoratorUtil.VIEW_CHILDREN_DEC
import org.angular2.Angular2DecoratorUtil.VIEW_CHILD_DEC
import org.angular2.Angular2DecoratorUtil.getDecoratorForLiteralParameter
import org.angular2.Angular2DecoratorUtil.isHostBindingClassValueLiteral
import org.angular2.Angular2DecoratorUtil.isHostListenerDecoratorEventLiteral
import org.angular2.codeInsight.Angular2DeclarationsScope
import org.angular2.codeInsight.attributes.isNgClassOrAnimateAttribute
import org.angular2.codeInsight.blocks.Angular2HtmlBlockReferenceExpressionCompletionProvider
import org.angular2.codeInsight.blocks.isDeferOnTriggerParameterReference
import org.angular2.codeInsight.blocks.isDeferOnTriggerReference
import org.angular2.codeInsight.blocks.isJSReferenceAfterEqInForBlockLetParameterAssignment
import org.angular2.entities.Angular2Directive
import org.angular2.entities.Angular2EntitiesProvider
import org.angular2.index.getFunctionNameFromIndex
import org.angular2.lang.expr.psi.*
import org.angular2.lang.html.parser.Angular2AttributeNameParser
import org.angular2.lang.html.parser.Angular2AttributeType
import org.angular2.lang.html.psi.Angular2HtmlBlock
import org.angular2.lang.html.psi.Angular2HtmlPropertyBinding
import org.angular2.signals.Angular2SignalUtils.getPossibleSignalFunNameForLiteralParameter
import org.angular2.signals.Angular2SignalUtils.isViewChildSignalCall
import org.angular2.signals.Angular2SignalUtils.isViewChildrenSignalCall
import org.angular2.web.scopes.*
class Angular2SymbolQueryScopeContributor : PolySymbolQueryScopeContributor {
override fun registerProviders(registrar: PolySymbolQueryScopeProviderRegistrar) {
registrar
.inContext { it.framework == Angular2Framework.ID }
.apply {
forAnyPsiLocationInFile()
.contributeScopeProvider {
listOf(Angular2CustomCssPropertiesScope(it.containingFile))
}
forPsiLocations(XmlElement::class.java, CssElement::class.java)
.contributeScopeProvider {
listOf(DirectiveElementSelectorsScope(it.containingFile),
DirectiveAttributeSelectorsScope(it.containingFile))
}
forPsiLocation(CssElement::class.java)
.contributeScopeProvider {
listOf(HtmlAttributesCustomCssPropertiesScope(it))
}
forPsiLocations<XmlElement>(XmlAttributeValue::class.java, XmlAttribute::class.java, HtmlTag::class.java)
.contributeScopeProvider(AngularTemplateXmlSymbolScopeProvider)
forPsiLocation(Angular2HtmlPropertyBinding::class.java)
.contributeScopeProvider {
if (Angular2AttributeNameParser.parse(it.name).type == Angular2AttributeType.REGULAR)
listOf(AttributeWithInterpolationsScope)
else
emptyList()
}
forPsiLocation(JSReferenceExpression::class.java)
.contributeScopeProvider(JSReferenceSymbolScopeProvider)
forPsiLocation(JSLiteralExpression::class.java)
.contributeScopeProvider(JSLiteralExpressionScopeProvider)
forPsiLocation(JSObjectLiteralExpression::class.java)
.contributeScopeProvider(JSObjectLiteralExpressionScopeProvider)
forPsiLocation(Angular2TemplateBindingKey::class.java)
.contributeScopeProvider { element ->
listOfNotNull(
TemplateBindingKeyScope(element),
TemplateBindingKeywordsScope.takeIf { (element.parent as? Angular2TemplateBinding)?.keyIsVar() == false }
)
}
}
}
private object AngularTemplateXmlSymbolScopeProvider : PolySymbolLocationQueryScopeProvider<XmlElement> {
override fun getScopes(location: XmlElement): List<PolySymbolScope> =
location.parentOfType<XmlTag>(withSelf = true)?.let {
listOf(
OneTimeBindingsScope(it),
StandardPropertyAndEventsScope(it.containingFile),
NgContentSelectorsScope(it),
MatchedDirectivesScope.createFor(it),
I18NAttributesScope(it),
HtmlAttributesCustomCssPropertiesScope(it),
)
} ?: emptyList()
}
private object JSReferenceSymbolScopeProvider : PolySymbolLocationQueryScopeProvider<JSReferenceExpression> {
override fun getScopes(location: JSReferenceExpression): List<PolySymbolScope> =
when {
Angular2HtmlBlockReferenceExpressionCompletionProvider.canAddCompletions(location) ->
emptyList()
isJSReferenceAfterEqInForBlockLetParameterAssignment(location) ->
listOfNotNull(location.parentOfType<Angular2HtmlBlock>()?.definition)
isDeferOnTriggerReference(location) ->
listOfNotNull(location.parentOfType<Angular2BlockParameter>()?.definition)
isDeferOnTriggerParameterReference(location) ->
listOfNotNull(location.parentOfType<Angular2BlockParameter>()?.let { DeferOnTriggerParameterScope(it) })
isTemplateBindingKeywordLocation(location) ->
listOf(TemplateBindingKeywordsScope)
else ->
listOfNotNull(DirectivePropertyMappingCompletionScope(location),
getCssClassesInJSLiteralInHtmlAttributeScope(location),
location.parentOfType<Angular2EmbeddedExpression>()?.let { Angular2TemplateScope(it) })
}
private fun isTemplateBindingKeywordLocation(element: JSReferenceExpression): Boolean =
element.qualifier == null
&& element.parent is Angular2TemplateBinding
&& element.siblings(forward = false, withSelf = false).filter { it !is PsiWhiteSpace }.firstOrNull() !is Angular2TemplateBindingKey
}
private object JSObjectLiteralExpressionScopeProvider : PolySymbolLocationQueryScopeProvider<JSObjectLiteralExpression> {
override fun getScopes(location: JSObjectLiteralExpression): List<PolySymbolScope> {
var decorator: ES6Decorator? = null
if (location.parent.asSafely<JSProperty>()?.name == Angular2DecoratorUtil.HOST_PROP
&& location.parentOfType<ES6Decorator>()
?.takeIf { Angular2DecoratorUtil.isAngularEntityDecorator(it, true, COMPONENT_DEC, DIRECTIVE_DEC) }
?.also { decorator = it } != null
)
return listOf(HostBindingsScope(mapOf(JS_PROPERTIES to HTML_ATTRIBUTES), decorator!!))
else
return listOfNotNull(getCssClassesInJSLiteralInHtmlAttributeScope(location))
}
}
private object JSLiteralExpressionScopeProvider : PolySymbolLocationQueryScopeProvider<JSLiteralExpression> {
override fun getScopes(location: JSLiteralExpression): List<PolySymbolScope> =
listOfNotNull(
DirectivePropertyMappingCompletionScope(location),
getHostBindingsScopeForLiteral(location),
getCssClassesInJSLiteralInHtmlAttributeScope(location),
getViewChildrenScopeForLiteral(location),
location.parentOfType<Angular2EmbeddedExpression>()?.let { Angular2TemplateScope(it) },
) + getCreateComponentBindingsScopeForLiteral(location)
private fun getHostBindingsScopeForLiteral(element: JSLiteralExpression): PolySymbolScope? {
val mapping = when {
getDecoratorForLiteralParameter(element)?.decoratorName == HOST_BINDING_DEC -> NG_PROPERTY_BINDINGS
isHostListenerDecoratorEventLiteral(element) -> NG_EVENT_BINDINGS
isHostBindingClassValueLiteral(element) -> CSS_CLASS_LIST
else -> return null
}
return element
.parentOfType<TypeScriptClass>()
?.let { Angular2DecoratorUtil.findDecorator(it, true, COMPONENT_DEC, DIRECTIVE_DEC) }
?.let { HostBindingsScope(mapOf(JS_STRING_LITERALS to mapping), it) }
}
private fun getViewChildrenScopeForLiteral(element: JSLiteralExpression): PolySymbolScope? {
val signalCallInfo = getPossibleSignalFunNameForLiteralParameter(element)
val decorator = getDecoratorForLiteralParameter(element)
val isChildViewCall = decorator?.decoratorName == VIEW_CHILD_DEC || isViewChildSignalCall(signalCallInfo)
val isChildrenViewCall = decorator?.decoratorName == VIEW_CHILDREN_DEC || isViewChildrenSignalCall(signalCallInfo)
if (!isChildViewCall && !isChildrenViewCall) return null
return element
.parentOfType<TypeScriptClass>()
?.let { Angular2DecoratorUtil.findDecorator(it, true, COMPONENT_DEC, DIRECTIVE_DEC) }
?.let { ViewChildrenScope(it, isChildrenViewCall) }
}
private fun getCreateComponentBindingsScopeForLiteral(element: JSLiteralExpression): List<PolySymbolScope> {
val callExpr =
element.context
?.let { if (it is JSArgumentList) it.parent else it }
?.asSafely<JSCallExpression>()
?: return emptyList()
val functionName = getFunctionNameFromIndex(callExpr)
?.takeIf { it == TWO_WAY_BINDING_FUN || it == OUTPUT_BINDING_FUN || it == INPUT_BINDING_FUN }
?: return emptyList()
val objectLiteral =
callExpr.context
?.let { if (it is JSArrayLiteralExpression) it.parent else it }
?.asSafely<JSProperty>()
?.takeIf { it.name == BINDINGS_PROP }
?.context?.asSafely<JSObjectLiteralExpression>()
?: return emptyList()
return listOf(
CreateComponentDirectiveBindingScope(objectLiteral),
when (functionName) {
INPUT_BINDING_FUN -> CreateComponentDirectiveBindingScope.INPUTS_SCOPE
OUTPUT_BINDING_FUN -> CreateComponentDirectiveBindingScope.OUTPUTS_SCOPE
TWO_WAY_BINDING_FUN -> CreateComponentDirectiveBindingScope.IN_OUTS_SCOPE
else -> throw IllegalStateException("Unexpected function name: $functionName")
}
)
}
}
}
class Angular2SymbolQueryConfigurator : PolySymbolQueryConfigurator {
override fun getNameConversionRulesProviders(
project: Project,
element: PsiElement?,
context: PolyContext,
): List<PolySymbolNameConversionRulesProvider> {
if (context.framework == Angular2Framework.ID && element != null) {
// possibly the input definition
if (element is JSLiteralExpression || element is TypeScriptField) {
val parent = element
.parentOfTypes(TypeScriptClass::class, ES6Decorator::class)
?.let {
if (it is ES6Decorator && !Angular2DecoratorUtil.isAngularEntityDecorator(it, true, COMPONENT_DEC, DIRECTIVE_DEC))
it.parentOfType<TypeScriptClass>()
else
it
}
if (parent == null || parent is TypeScriptClass
&& Angular2DecoratorUtil.findDecorator(parent, false,
COMPONENT_DEC, DIRECTIVE_DEC) == null)
return emptyList()
val attrSelectors by lazy(LazyThreadSafetyMode.PUBLICATION) {
Angular2EntitiesProvider.getDirective(parent)?.selector
?.simpleSelectors
?.flatMapTo(mutableSetOf()) { it.attrNames }
?: emptyList()
}
return listOf(object : PolySymbolNameConversionRulesProvider {
override fun getNameConversionRules(): PolySymbolNameConversionRules =
PolySymbolNameConversionRules.builder()
.addMatchNamesRule(NG_DIRECTIVE_INPUTS) { name ->
attrSelectors.mapNotNull {
if (isTemplateBindingDirectiveInput(name, it))
directiveInputToTemplateBindingVar(name, it)
else null
} + name
}
.build()
override fun createPointer(): Pointer<out PolySymbolNameConversionRulesProvider> =
Pointer.hardPointer(this)
override fun getModificationCount(): Long = 0
})
}
else if (element is Angular2TemplateBindingKey) {
val templateName = element.parentOfType<Angular2TemplateBindings>()?.templateName
if (templateName != null)
return listOf(object : PolySymbolNameConversionRulesProvider {
override fun getNameConversionRules(): PolySymbolNameConversionRules =
PolySymbolNameConversionRules.builder()
.addMatchNamesRule(NG_DIRECTIVE_INPUTS) {
listOf(templateBindingVarToDirectiveInput(it, templateName))
}
.addRenameRule(NG_DIRECTIVE_INPUTS) {
listOf(directiveInputToTemplateBindingVar(it, templateName))
}
.addCanonicalNamesRule(NG_DIRECTIVE_OUTPUTS) {
listOf(templateBindingVarToDirectiveInput(it, templateName))
}
.addCompletionVariantsRule(NG_DIRECTIVE_INPUTS) {
listOf(directiveInputToTemplateBindingVar(it, templateName))
}
.build()
override fun createPointer(): Pointer<out PolySymbolNameConversionRulesProvider> =
Pointer.hardPointer(this)
override fun getModificationCount(): Long = 0
})
}
}
return emptyList()
}
}
private fun getCssClassesInJSLiteralInHtmlAttributeScope(element: PsiElement): PolySymbolScope? =
element.takeIf { isNgClassOrAnimateLiteralContext(it) }
?.parentOfType<XmlAttribute>()
?.let { CssClassListInJSLiteralInHtmlAttributeScope(it) }
@JvmField
val PROP_BINDING_PATTERN: PolySymbolProperty<Boolean> = PolySymbolProperty["ng-binding-pattern"]
@JvmField
val PROP_ERROR_SYMBOL: PolySymbolProperty<Boolean> = PolySymbolProperty["ng-error-symbol"]
@JvmField
val PROP_SYMBOL_DIRECTIVE: PolySymbolProperty<Angular2Directive> = PolySymbolProperty["ng-symbol-directive"]
@JvmField
val PROP_SCOPE_PROXIMITY: PolySymbolProperty<Angular2DeclarationsScope.DeclarationProximity> = PolySymbolProperty["scope-proximity"]
@JvmField
val PROP_HOST_BINDING: PolySymbolProperty<Boolean> = PolySymbolProperty["ng-host-binding"]
const val EVENT_ATTR_PREFIX: String = "on"
const val ATTR_NG_NON_BINDABLE: String = "ngNonBindable"
const val ATTR_SELECT: String = "select"
const val ELEMENT_NG_CONTAINER: String = "ng-container"
const val ELEMENT_NG_CONTENT: String = "ng-content"
const val ELEMENT_NG_TEMPLATE: String = "ng-template"
val NG_PROPERTY_BINDINGS: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-property-bindings"]
val NG_EVENT_BINDINGS: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-event-bindings"]
val NG_STRUCTURAL_DIRECTIVES: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-structural-directives"]
val NG_DIRECTIVE_ONE_TIME_BINDINGS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-one-time-bindings"]
val NG_DIRECTIVE_INPUTS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-inputs"]
val NG_DIRECTIVE_OUTPUTS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-outputs"]
val NG_DIRECTIVE_IN_OUTS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-in-outs"]
val NG_DIRECTIVE_ATTRIBUTES: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-attributes"]
val NG_DIRECTIVE_EXPORTS_AS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-exports-as"]
val NG_DIRECTIVE_ELEMENT_SELECTORS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-element-selectors"]
val NG_DIRECTIVE_ATTRIBUTE_SELECTORS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-directive-attribute-selectors"]
val NG_I18N_ATTRIBUTES: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-i18n-attributes"]
val NG_BLOCKS: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-blocks"]
val NG_BLOCK_PARAMETERS: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-block-parameters"]
val NG_BLOCK_PARAMETER_PREFIXES: PolySymbolKind = PolySymbolKind[NAMESPACE_HTML, "ng-block-parameter-prefixes"]
val NG_DEFER_ON_TRIGGERS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-defer-on-triggers"]
val NG_TEMPLATE_BINDING_KEYWORDS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-template-binding-keywords"]
val NG_TEMPLATE_BINDINGS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-template-bindings"]
val NG_KEY_EVENT_MODIFIERS: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "key-event-modifiers"]
val NG_CUSTOM_PROPERTY: PolySymbolKind = PolySymbolKind[NAMESPACE_JS, "ng-custom-property"]
fun isNgClassOrAnimateLiteralContext(literal: PsiElement): Boolean =
isJSLiteralContextFromEmbeddedContent(literal, Angular2Binding::class.java, ::isNgClassOrAnimateAttribute)