package com.intellij.javascript.flex.mxml.schema; import com.intellij.codeInsight.daemon.IdeValidationHost; import com.intellij.codeInsight.daemon.Validator; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.ide.IconProvider; import com.intellij.javascript.flex.FlexAnnotationNames; import com.intellij.javascript.flex.FlexMxmlLanguageAttributeNames; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.javascript.flex.FlexReferenceContributor; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.javascript.flex.mxml.FlexNameAlias; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.ASTNode; import com.intellij.lang.LanguageNamesValidation; import com.intellij.lang.javascript.JSBundle; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.AnnotationBackedDescriptor; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.flex.XmlBackedJSClassImpl; import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils; import com.intellij.lang.javascript.index.JSTypeEvaluateManager; import com.intellij.lang.javascript.index.JavaScriptIndex; import com.intellij.lang.javascript.inspections.actionscript.ActionScriptAnnotatingVisitor; import com.intellij.lang.javascript.psi.*; import com.intellij.lang.javascript.psi.ecmal4.*; import com.intellij.lang.javascript.psi.impl.JSPsiImplUtils; import com.intellij.lang.javascript.psi.resolve.ActionScriptResolveUtil; import com.intellij.lang.javascript.psi.resolve.JSImportHandlingUtil; import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.validation.JSAnnotatingVisitor; import com.intellij.lang.refactoring.NamesValidator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.xml.*; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.*; import com.intellij.xml.impl.schema.AnyXmlAttributeDescriptor; import com.intellij.xml.util.XmlUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import static com.intellij.lang.javascript.psi.JSCommonTypeNames.*; /** * @author Maxim.Mossienko */ public class ClassBackedElementDescriptor extends IconProvider implements XmlElementDescriptor, Validator<XmlTag>, XmlElementDescriptorWithCDataContent, XmlElementDescriptorAwareAboutChildren { protected final @NonNls String className; protected final @NonNls String name; protected final @NonNls String iconPath; protected final Project project; protected final CodeContext context; private final boolean predefined; private Map<String, AnnotationBackedDescriptor> myDescriptors; // can be both XML attributes and elements private Map<String, Map<String, AnnotationBackedDescriptor>> myPackageToInternalDescriptors; // These descriptors are resolved only if MXML file is in the same package as descriptor originating element. Can be both XML attributes and elements. private Map<String, AnnotationBackedDescriptor> myPredefinedDescriptors; // can be XML attributes, but not elements @NonNls private static final String ARRAY_TYPE_ANNOTATION_PARAMETER = "arrayType"; @NonNls private static final String RADIO_BUTTON_GROUP_CLASS = "mx.controls.RadioButtonGroup"; private final static XmlUtil.DuplicationInfoProvider<XmlElement> myDuplicationInfoProvider = new XmlUtil.DuplicationInfoProvider<XmlElement>() { @Override public String getName(@NotNull final XmlElement xmlElement) { if (xmlElement instanceof XmlTag) return ((XmlTag)xmlElement).getLocalName(); return ((XmlAttribute)xmlElement).getName(); } @Override @NotNull public String getNameKey(@NotNull final XmlElement xmlElement, final @NotNull String name) { return name; } @Override @NotNull public PsiElement getNodeForMessage(@NotNull final XmlElement xmlElement) { return xmlElement; } }; private static final String IMPLEMENTS_ATTR_NAME = "implements"; private AnnotationBackedDescriptor defaultPropertyDescriptor; private boolean defaultPropertyDescriptorInitialized; private boolean isContainerClass; private boolean isContainerClassInitialized; private static final String CONTAINER_CLASS_NAME_2 = "mx.core.IVisualElementContainer"; @NonNls static final String IFACTORY_SHORT_CLASS_NAME = "IFactory"; private static final String PRIMITIVE_GRAPHIC_ELEMENT_BASE_CLASS = "spark.primitives.supportClasses.GraphicElement"; ClassBackedElementDescriptor(String name, String _classname, CodeContext _context, Project _project) { this(name, _classname,_context,_project, false, null); } ClassBackedElementDescriptor(String _classname, CodeContext _context, Project _project, boolean _predefined) { this(null, _classname, _context, _project, _predefined, null); } ClassBackedElementDescriptor(String _name, String _classname, CodeContext _context, Project _project, boolean _predefined, String _iconPath) { context = _context; className = _classname; project = _project; predefined = _predefined; iconPath = _iconPath; name = _name; } @Override public String getQualifiedName() { return className; } @Override public String getDefaultName() { return getName(); } public boolean isPredefined(){ return predefined; } @Override public XmlElementDescriptor[] getElementsDescriptors(final XmlTag _context) { if (MxmlJSClass.isTagOrInsideTagThatAllowsAnyXmlContent(_context) || MxmlLanguageTagsUtil.isFxReparentTag(_context)) { return EMPTY_ARRAY; } if (MxmlLanguageTagsUtil.isFxLibraryTag(_context)) { final XmlElementDescriptor definitionDescriptor = context.getElementDescriptor(CodeContext.DEFINITION_TAG_NAME, (XmlTag)null); return definitionDescriptor == null ? EMPTY_ARRAY : new XmlElementDescriptor[]{definitionDescriptor}; } final XmlElementDescriptor _parentDescriptor = _context.getDescriptor(); if (_parentDescriptor instanceof AnnotationBackedDescriptorImpl) { final String arrayType = ((AnnotationBackedDescriptorImpl)_parentDescriptor).getArrayType(); if (arrayType != null) { return getElementDescriptorsInheritedFromGivenType(arrayType); } final String type = ((AnnotationBackedDescriptorImpl)_parentDescriptor).getType(); if (type != null && isAdequateType(type)) { return getElementDescriptorsInheritedFromGivenType(type); } if (IFACTORY_SHORT_CLASS_NAME.equals(className(type)) && JavaScriptSupportLoader.isLanguageNamespace(context.namespace)) { final XmlElementDescriptor descriptor = context.getElementDescriptor(FlexNameAlias.COMPONENT_TYPE_NAME, (XmlTag)null); return descriptor == null ? EMPTY_ARRAY : new XmlElementDescriptor[]{descriptor}; } } if (!(_parentDescriptor instanceof ClassBackedElementDescriptor)) { return EMPTY_ARRAY; } final ClassBackedElementDescriptor parentDescriptor = (ClassBackedElementDescriptor)_parentDescriptor; getAttributesDescriptors(_context); List<XmlElementDescriptor> resultList = new ArrayList<>(myDescriptors == null ? 0 : myDescriptors.size() + context.getAllDescriptorsSize()); final boolean isComponentTag = MxmlLanguageTagsUtil.isComponentTag(_context); boolean includeProperties = (parentDescriptor == this) && !isComponentTag; if (isComponentTag) { ContainerUtil.addAll(resultList, getElementDescriptorsInheritedFromGivenType(FlexCommonTypeNames.IUI_COMPONENT)); ContainerUtil.addAll(resultList, getElementDescriptorsInheritedFromGivenType(PRIMITIVE_GRAPHIC_ELEMENT_BASE_CLASS)); } else if (parentDescriptor.getDefaultPropertyDescriptor() != null && parentDescriptor.defaultPropertyDescriptor.getType() != null) { final PsiElement contextParent = _context.getParent(); if (contextParent instanceof XmlDocument && JavaScriptSupportLoader.isLanguageNamespace(_context.getNamespace())) { // Predefined tags like <fx:Declaration/> can be children of a tag with [DefaultProperty] annotation if this tag is root tag in the mxml file for (XmlElementDescriptor descriptor : context.getDescriptorsWithAllowedDeclaration()) { if (descriptor instanceof ClassBackedElementDescriptor && ((ClassBackedElementDescriptor)descriptor).predefined) { resultList.add(descriptor); } } } final String type = parentDescriptor.getDefaultPropertyType(); ContainerUtil.addAll(resultList, isAdequateType(type) ? getElementDescriptorsInheritedFromGivenType(type) : context.getDescriptorsWithAllowedDeclaration()); if (JavaScriptSupportLoader.isLanguageNamespace(context.namespace)) { ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(FlexPredefinedTagNames.SCRIPT, (XmlTag)null)); ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(CodeContext.REPARENT_TAG_NAME, (XmlTag)null)); if (contextParent instanceof XmlTag && MxmlLanguageTagsUtil.isComponentTag((XmlTag)contextParent)) { ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(FlexPredefinedTagNames.DECLARATIONS, (XmlTag)null)); ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(FlexPredefinedTagNames.BINDING, (XmlTag)null)); ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(FlexPredefinedTagNames.STYLE, (XmlTag)null)); ContainerUtil.addIfNotNull(resultList, context.getElementDescriptor(FlexPredefinedTagNames.METADATA, (XmlTag)null)); } } } else if (parentDescriptor.predefined || isContainerClass(parentDescriptor)) { context.appendDescriptorsWithAllowedDeclaration(resultList); } if (includeProperties && myDescriptors != null) { resultList.addAll(myDescriptors.values()); if (!myPackageToInternalDescriptors.isEmpty()) { final String contextPackage = JSResolveUtil.getPackageNameFromPlace(_context); final Map<String, AnnotationBackedDescriptor> internalDescriptors = myPackageToInternalDescriptors.get(contextPackage); if (internalDescriptors != null) { resultList.addAll(internalDescriptors.values()); } } } return resultList.toArray(new XmlElementDescriptor[resultList.size()]); } private static boolean isContainerClass(final ClassBackedElementDescriptor descriptor) { if (!descriptor.isContainerClassInitialized) { if (descriptor.predefined) { descriptor.isContainerClass = false; } else { final PsiElement declaration = descriptor.getDeclaration(); descriptor.isContainerClass = JSResolveUtil.isAssignableType(FlexCommonTypeNames.ICONTAINER, descriptor.className, declaration) || JSResolveUtil.isAssignableType(CONTAINER_CLASS_NAME_2, descriptor.className, declaration); } descriptor.isContainerClassInitialized = true; } return descriptor.isContainerClass; } private String getDefaultPropertyType() { return defaultPropertyDescriptor.getArrayType() != null ? defaultPropertyDescriptor.getArrayType() : defaultPropertyDescriptor.getType(); } @Override @Nullable public XmlElementDescriptor getElementDescriptor(final XmlTag childTag, final XmlTag contextTag) { if (MxmlJSClass.isTagThatAllowsAnyXmlContent(contextTag)) { return new AnyXmlElementWithAnyChildrenDescriptor(); } if (MxmlLanguageTagsUtil.isFxReparentTag(contextTag)) { return null; } final XmlElementDescriptor descriptor = _getElementDescriptor(childTag, contextTag); if (descriptor instanceof AnnotationBackedDescriptorImpl && ((AnnotationBackedDescriptorImpl)descriptor).isPredefined()) { return null; } if (MxmlLanguageTagsUtil.isComponentTag(contextTag) && descriptor instanceof ClassBackedElementDescriptor && ((ClassBackedElementDescriptor)descriptor).isPredefined()) { return null; } if (MxmlLanguageTagsUtil.isFxReparentTag(childTag) || MxmlLanguageTagsUtil.isScriptTag(childTag) || MxmlLanguageTagsUtil.isDesignLayerTag(childTag)) { return descriptor; } if (descriptor == null && JavaScriptSupportLoader.MXML_URI3.equals(childTag.getNamespace()) && contextTag != null) { return FxDefinitionBackedDescriptor.getFxDefinitionBackedDescriptor(context.module, childTag); } final XmlElementDescriptor parentDescriptor = contextTag.getDescriptor(); if (MxmlLanguageTagsUtil.isComponentTag(contextTag)) { final XmlElementDescriptor checkedDescriptor = checkValidDescriptorAccordingToType(FlexCommonTypeNames.IUI_COMPONENT, descriptor); return checkedDescriptor != null ? checkedDescriptor : checkValidDescriptorAccordingToType(PRIMITIVE_GRAPHIC_ELEMENT_BASE_CLASS, descriptor); } else if (getDefaultPropertyDescriptor() != null && defaultPropertyDescriptor.getType() != null) { if (descriptor instanceof ClassBackedElementDescriptor && ((ClassBackedElementDescriptor)descriptor).predefined) { final PsiElement contextParent = contextTag.getParent(); if (contextParent instanceof XmlDocument) { return MxmlLanguageTagsUtil.isLanguageTagAllowedUnderRootTag(childTag) ? descriptor : null; } else if (contextParent instanceof XmlTag && MxmlLanguageTagsUtil.isComponentTag((XmlTag)contextParent)) { return MxmlLanguageTagsUtil.isLanguageTagAllowedUnderInlineComponentRootTag(childTag) ? descriptor : null; } } return checkValidDescriptorAccordingToType(getDefaultPropertyType(), descriptor); } else if (needToCheckThatChildIsUiComponent(contextTag, parentDescriptor, descriptor)) { return checkValidDescriptorAccordingToType(FlexCommonTypeNames.IUI_COMPONENT, descriptor); } // Ideally all not-null results should be returned in if's above and the last statement should be 'return null'. return descriptor; } private static boolean needToCheckThatChildIsUiComponent(final XmlTag parentTag, final XmlElementDescriptor _parentDescriptor, final XmlElementDescriptor _childDescriptor) { // non-visual components in Flex 3 are allowed only under document root tag and under inline component root tag; // in Flex 4 - only inside <fx:Declarations> or <fx:Definition> // mx.controls.RadioButtonGroup is a compiler-level exception if (!(_childDescriptor instanceof ClassBackedElementDescriptor) || !(_parentDescriptor instanceof ClassBackedElementDescriptor)) { return false; } final ClassBackedElementDescriptor childDescriptor = (ClassBackedElementDescriptor)_childDescriptor; final ClassBackedElementDescriptor parentDescriptor = (ClassBackedElementDescriptor)_parentDescriptor; if (childDescriptor.predefined) { return false; } if (MxmlLanguageTagsUtil.isFxDeclarationsTag(parentTag) || MxmlLanguageTagsUtil.isFxDefinitionTag(parentTag)) { return false; } final PsiElement parent = parentTag.getParent(); if (!ArrayUtil.contains(JavaScriptSupportLoader.MXML_URI3, parentTag.knownNamespaces()) && (parent instanceof XmlDocument || (parent instanceof XmlTag && MxmlLanguageTagsUtil.isComponentTag((XmlTag)parent)))) { return false; } if (JSResolveUtil.isAssignableType(RADIO_BUTTON_GROUP_CLASS, childDescriptor.className, parentTag)) { return false; } return isContainerClass(parentDescriptor); } @Nullable private XmlElementDescriptor _getElementDescriptor(XmlTag childTag, XmlTag contextTag) { final String childNs = childTag.getNamespace(); if (!sameNs(childNs, context.namespace)) { final XmlNSDescriptor descriptor = childTag.getNSDescriptor(childNs, true); return descriptor != null ? descriptor.getElementDescriptor(childTag):null; } getAttributesDescriptors(childTag); @NonNls String localName = childTag.getLocalName(); localName = skipStateNamePart(localName); XmlElementDescriptor descriptor = myDescriptors != null ? myDescriptors.get(localName) : null; if (descriptor == null && myPackageToInternalDescriptors != null && !myPackageToInternalDescriptors.isEmpty()) { final String contextPackage = JSResolveUtil.getPackageNameFromPlace(childTag); final Map<String, AnnotationBackedDescriptor> internalDescriptors = myPackageToInternalDescriptors.get(contextPackage); if (internalDescriptors != null) { descriptor = internalDescriptors.get(localName); } } if (descriptor != null) return descriptor; final @NonNls String name = getName(); if ("WebService".equals(name) && "operation".equals(localName)) { return context.getElementDescriptor("WebServiceOperation", (XmlTag)null); } else if ("RemoteObject".equals(name) && "method".equals(localName)) { return context.getElementDescriptor("RemoteObjectOperation", (XmlTag)null); } XmlElementDescriptor xmlElementDescriptor = context.getElementDescriptor(localName, childTag); if (xmlElementDescriptor == null && !predefined) { xmlElementDescriptor = getClassIfDynamic(localName, getDeclaration()); } return xmlElementDescriptor; } static boolean sameNs(final String childNs, final String namespace) { final boolean b = childNs.equals(namespace); if (!b) { return JavaScriptSupportLoader.isLanguageNamespace(childNs) && JavaScriptSupportLoader.isLanguageNamespace(namespace); } return b; } @Nullable XmlElementDescriptor getClassIfDynamic(final String localName, PsiElement element) { if (isDynamicClass(element)) { return new ClassBackedElementDescriptor( localName.length() > 0 && Character.isUpperCase(localName.charAt(0)) ? localName : OBJECT_CLASS_NAME, context, project, true); } return null; } static boolean isDynamicClass(PsiElement element) { JSAttributeList attrList; return element instanceof JSClass && (attrList = ((JSClass)element).getAttributeList()) != null && attrList.hasModifier(JSAttributeList.ModifierType.DYNAMIC); } @Override public XmlAttributeDescriptor[] getAttributesDescriptors(final @Nullable XmlTag _context) { if (MxmlLanguageTagsUtil.isFxPrivateTag(_context) || MxmlLanguageTagsUtil.isFxLibraryTag(_context)) { return XmlAttributeDescriptor.EMPTY; } if (MxmlLanguageTagsUtil.isFxDefinitionTag(_context)) { return new XmlAttributeDescriptor[]{ new AnnotationBackedDescriptorImpl(MxmlLanguageTagsUtil.NAME_ATTRIBUTE, this, true, null, null, null)}; } if (_context != null && MxmlLanguageTagsUtil.isComponentTag(_context)) { return new XmlAttributeDescriptor[]{ new ClassNameAttributeDescriptor(this), new AnnotationBackedDescriptorImpl(FlexMxmlLanguageAttributeNames.ID, this, true, null, null, null) }; } if (myDescriptors == null || myPackageToInternalDescriptors == null) { PsiElement element = getDeclaration(); if (element == null) { myDescriptors = Collections.emptyMap(); myPackageToInternalDescriptors = Collections.emptyMap(); } else { ensureDescriptorsMapsInitialized(element, null); } } final Collection<AnnotationBackedDescriptor> descriptors = new ArrayList<>(myDescriptors.values()); if (_context != null && !myPackageToInternalDescriptors.isEmpty()) { final String contextPackage = JSResolveUtil.getPackageNameFromPlace(_context); final Map<String, AnnotationBackedDescriptor> internalDescriptors = myPackageToInternalDescriptors.get(contextPackage); if (internalDescriptors != null) { descriptors.addAll(internalDescriptors.values()); } } if (_context != null && MxmlLanguageTagsUtil.isComponentTag(_context.getParentTag())) { descriptors.add(new AnnotationBackedDescriptorImpl(IMPLEMENTS_ATTR_NAME, this, true, null, null, null)); } if (_context != null && _context.getParent() instanceof XmlDocument) { final AnnotationBackedDescriptor idDescriptor = myDescriptors.get(FlexMxmlLanguageAttributeNames.ID); if (idDescriptor != null) { descriptors.remove(idDescriptor); } descriptors.add(new AnnotationBackedDescriptorImpl(IMPLEMENTS_ATTR_NAME, this, true, null, null, null)); // do not include id, includeIn, excludeFrom, itemCreationPolicy, itemDestructionPolicy attributes for root tag if (myPredefinedDescriptors != null) { for (final AnnotationBackedDescriptor descriptor : myPredefinedDescriptors.values()) { if (!FlexMxmlLanguageAttributeNames.ID.equals(descriptor.getName()) && !ArrayUtil.contains(descriptor.getName(), CodeContext.GUMBO_ATTRIBUTES)) { descriptors.add(descriptor); } } } } else if (myPredefinedDescriptors != null) { descriptors.addAll(myPredefinedDescriptors.values()); } return descriptors.toArray(new XmlAttributeDescriptor[descriptors.size()]); } public void addPredefinedMemberDescriptor(@NotNull AnnotationBackedDescriptor descriptor) { if (predefined) { if (myDescriptors == null) { myDescriptors = new THashMap<>(); myPackageToInternalDescriptors = Collections.emptyMap(); } myDescriptors.put(descriptor.getName(), descriptor); } else { if (myPredefinedDescriptors == null) myPredefinedDescriptors = new THashMap<>(); myPredefinedDescriptors.put(descriptor.getName(), descriptor); } } private void ensureDescriptorsMapsInitialized(PsiElement element, @Nullable Set<JSClass> visited) { Map<String, AnnotationBackedDescriptor> map; Map<String, Map<String, AnnotationBackedDescriptor>> packageToInternalDescriptors; synchronized (CodeContext.class) { map = myDescriptors; packageToInternalDescriptors = myPackageToInternalDescriptors; if (map != null && packageToInternalDescriptors != null) return; map = new THashMap<>(); packageToInternalDescriptors = new THashMap<>(); Set<PsiElement> processedElements = null; if (element instanceof XmlBackedJSClassImpl) { element = element.getParent().getContainingFile(); // TODO: make this code and following loop better } if (element instanceof XmlFile && MxmlJSClass.isFxgFile((PsiFile)element)) { element = XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)element); } while (element instanceof XmlFile) { final XmlDocument document = ((XmlFile)element).getDocument(); final XmlTag rootTag = document != null ? document.getRootTag():null; final XmlElementDescriptor descriptor = rootTag != null ? rootTag.getDescriptor():null; if (processedElements == null) processedElements = new THashSet<>(); processedElements.add(element); element = descriptor != null ? descriptor.getDeclaration():null; if (processedElements.contains(element)) break; collectMxmlAttributes(map, packageToInternalDescriptors, rootTag); } if (element instanceof JSNamedElement) { JSNamedElement jsClass = (JSNamedElement)element; if (visited == null || !visited.contains(jsClass)) { if (!MxmlJSClass.XML_TAG_NAME.equals(jsClass.getName()) && !MxmlJSClass.XMLLIST_TAG_NAME.equals(jsClass.getName())) { JSReferenceList extendsList = jsClass instanceof JSClass ? ((JSClass)jsClass).getExtendsList():null; if (extendsList != null) { final JSClass clazz = (JSClass)jsClass; if (visited == null) { visited = new THashSet<>(); } visited.add(clazz); for(JSClass superClazz: clazz.getSuperClasses()) { appendSuperClassDescriptors(map, packageToInternalDescriptors, superClazz, visited); } } else if (!OBJECT_CLASS_NAME.equals(jsClass.getName()) && CodeContext.isStdNamespace(context.namespace)) { appendSuperClassDescriptors( map, packageToInternalDescriptors, ActionScriptClassResolver.findClassByQNameStatic(OBJECT_CLASS_NAME, jsClass), visited); } } collectMyAttributes(jsClass, map, packageToInternalDescriptors); } } myDescriptors = map; myPackageToInternalDescriptors = packageToInternalDescriptors; } } private void collectMxmlAttributes(final Map<String, AnnotationBackedDescriptor> map, final Map<String, Map<String, AnnotationBackedDescriptor>> packageToInternalDescriptors, final XmlTag rootTag) { if (rootTag != null) { final JSResolveUtil.JSInjectedFilesVisitor injectedFilesVisitor = new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(final JSFile file) { collectMyAttributes(file, map, packageToInternalDescriptors); } }; FlexUtils.processMxmlTags(rootTag, true, injectedFilesVisitor); processClassBackedTagsWithIdAttribute(rootTag, idAttributeAndItsType -> { final XmlAttribute idAttribute = idAttributeAndItsType.first; final String idAttributeValue = idAttribute.getValue(); final String type = idAttributeAndItsType.second; map.put(idAttributeValue, new AnnotationBackedDescriptorImpl(idAttributeValue, this, false, type, null, idAttribute)); return true; }); } } static boolean processClassBackedTagsWithIdAttribute(final @NotNull XmlTag tag, final Processor<Pair<XmlAttribute, String>> processor) { boolean toContinue = true; final XmlElementDescriptor tagDescriptor = tag.getDescriptor(); if (tagDescriptor instanceof ClassBackedElementDescriptor) { final XmlAttribute idAttribute = tag.getAttribute(FlexMxmlLanguageAttributeNames.ID); if (idAttribute != null && !StringUtil.isEmpty(idAttribute.getValue())) { toContinue = processor.process(Pair.create(idAttribute, tagDescriptor.getQualifiedName())); } if (toContinue) { for (final XmlTag childTag : tag.getSubTags()) { if (toContinue) { toContinue = processClassBackedTagsWithIdAttribute(childTag, processor); } } } } return toContinue; } private void collectMyAttributes(final PsiElement jsClass, final Map<String, AnnotationBackedDescriptor> map, final Map<String, Map<String, AnnotationBackedDescriptor>> packageToInternalDescriptors) { processAttributes(jsClass, new AttributedItemsProcessor() { @Override public boolean process(final JSNamedElement jsNamedElement, final boolean isPackageLocalVisibility) { String name = jsNamedElement.getName(); if (name != null) { if (jsNamedElement instanceof JSVariable && ((JSVariable)jsNamedElement).isConst()) return true; String propertyType = getPropertyType(jsNamedElement); boolean deferredInstance = false; final JSAttributeList attributeList = ((JSAttributeListOwner)jsNamedElement).getAttributeList(); if (attributeList != null) { String instanceType = JSPsiImplUtils.getTypeFromAnnotationParameter(attributeList, "InstanceType", null); if (instanceType != null) { deferredInstance = true; propertyType = instanceType; } } if (propertyType != null && propertyType.length() > 0 && ( Character.isUpperCase(propertyType.charAt(0)) || propertyType.indexOf('.') >= 0 ) && !STRING_CLASS_NAME.equals(propertyType) && !NUMBER_CLASS_NAME.equals(propertyType) && !BOOLEAN_CLASS_NAME.equals(propertyType)) { String arrayType = null; if(JSTypeEvaluateManager.isArrayType(propertyType)) { arrayType = JSTypeEvaluateManager.getComponentType(propertyType); } else if (ARRAY_CLASS_NAME.equals(propertyType)) { if (attributeList != null) { arrayType = JSPsiImplUtils.getTypeFromAnnotationParameter(attributeList, FlexAnnotationNames.INSPECTABLE, ARRAY_TYPE_ANNOTATION_PARAMETER); if (arrayType == null) { arrayType = JSPsiImplUtils.getArrayElementTypeFromAnnotation(attributeList); } } // getter may have annotation which is applicable for setter if (arrayType == null && jsClass instanceof JSClass && jsNamedElement instanceof JSFunction && ((JSFunction)jsNamedElement).isSetProperty()) { final JSFunction getter = ((JSClass)jsClass).findFunctionByNameAndKind(jsNamedElement.getName(), JSFunction.FunctionKind.GETTER); final JSAttributeList getterAttributeList = getter == null ? null : getter.getAttributeList(); if (getterAttributeList != null) { arrayType = JSPsiImplUtils .getTypeFromAnnotationParameter(getterAttributeList, FlexAnnotationNames.INSPECTABLE, ARRAY_TYPE_ANNOTATION_PARAMETER); if (arrayType == null) { arrayType = JSPsiImplUtils.getArrayElementTypeFromAnnotation(getterAttributeList); } } } } else { propertyType = JSImportHandlingUtil.resolveTypeName(propertyType, jsNamedElement); } if (arrayType != null) { arrayType = JSImportHandlingUtil.resolveTypeName(arrayType, jsNamedElement); } putDescriptor(jsNamedElement, name, propertyType, arrayType, deferredInstance, isPackageLocalVisibility, map, packageToInternalDescriptors); } else { putDescriptor(jsNamedElement, name, propertyType, null, deferredInstance, isPackageLocalVisibility, map, packageToInternalDescriptors); } } return true; } @Override public boolean process(final JSAttributeNameValuePair pair, final String annotationName, final boolean included) { String name = pair.getSimpleValue(); if (name != null) { if (included) { final AnnotationBackedDescriptorImpl previousDescriptor = (AnnotationBackedDescriptorImpl)map.get(name); final AnnotationBackedDescriptorImpl descriptor = new AnnotationBackedDescriptorImpl(name, ClassBackedElementDescriptor.this, false, null, null, pair); if (previousDescriptor == null || !previousDescriptor.isPreferredTo(descriptor)) { map.put(name, descriptor); } } else map.remove(name); } return true; } }); if (predefined && (FlexPredefinedTagNames.SCRIPT.equals(className) || FlexPredefinedTagNames.STYLE.equals(className))) { map.put(FlexReferenceContributor.SOURCE_ATTR_NAME, new AnnotationBackedDescriptorImpl(FlexReferenceContributor.SOURCE_ATTR_NAME, this, true, null, null, null)); } if (!predefined && map.get(FlexMxmlLanguageAttributeNames.ID) == null) { addPredefinedMemberDescriptor(new AnnotationBackedDescriptorImpl(FlexMxmlLanguageAttributeNames.ID, this, true, null, null, null)); } } private void putDescriptor(final JSNamedElement jsNamedElement, final String name, final String propertyType, final String arrayType, final boolean deferredInstance, final boolean isPackageLocalVisibility, final Map<String, AnnotationBackedDescriptor> map, final Map<String, Map<String, AnnotationBackedDescriptor>> packageToInternalDescriptors) { AnnotationBackedDescriptorImpl previousDescriptor = (AnnotationBackedDescriptorImpl)map.get(name); AnnotationBackedDescriptorImpl descriptor; if (previousDescriptor != null && previousDescriptor.parentDescriptor != this && previousDescriptor.getOriginatingElementType() == AnnotationBackedDescriptorImpl.OriginatingElementType.VarOrFunction && jsNamedElement instanceof JSAttributeListOwner && AnnotationBackedDescriptorImpl.findInspectableAttr(jsNamedElement) == null ) { descriptor = new AnnotationBackedDescriptorImpl(name, this, previousDescriptor, jsNamedElement); } else { descriptor = new AnnotationBackedDescriptorImpl(name, this, false, propertyType, arrayType, deferredInstance, jsNamedElement); } if (previousDescriptor == null || !previousDescriptor.isPreferredTo(descriptor)) { if (isPackageLocalVisibility) { final String packageName = JSResolveUtil.getPackageNameFromPlace(jsNamedElement); if (packageName != null) { Map<String, AnnotationBackedDescriptor> descriptorMap = packageToInternalDescriptors.get(packageName); if (descriptorMap == null) { descriptorMap = new THashMap<>(); packageToInternalDescriptors.put(packageName, descriptorMap); } descriptorMap.put(name, descriptor); } } else { map.put(name, descriptor); } } } static String getPropertyType(final JSNamedElement jsNamedElement) { if (jsNamedElement instanceof JSVariable) { return ((JSVariable)jsNamedElement).getTypeString(); } else if (jsNamedElement instanceof JSFunctionItem) { final JSType type = JSResolveUtil.getTypeFromSetAccessor((JSFunctionItem)jsNamedElement); if (type != null) { return type.getTypeText(); } } return null; } private void appendSuperClassDescriptors(final Map<String, AnnotationBackedDescriptor> map, final Map<String, Map<String, AnnotationBackedDescriptor>> packageToInternalDescriptors, final PsiElement _clazz, @Nullable Set<JSClass> visited) { if (_clazz instanceof JSClass) { final JSClass clazz = (JSClass)_clazz; ClassBackedElementDescriptor parentDescriptor = context.getElementDescriptor(clazz.getName(), clazz.getQualifiedName()); if (parentDescriptor == null) { parentDescriptor = new ClassBackedElementDescriptor(null, clazz.getQualifiedName(), context, project); } parentDescriptor.ensureDescriptorsMapsInitialized(clazz, visited); map.putAll(parentDescriptor.myDescriptors); for (final Map.Entry<String, Map<String, AnnotationBackedDescriptor>> entry : parentDescriptor.myPackageToInternalDescriptors .entrySet()) { Map<String, AnnotationBackedDescriptor> descriptorMap = packageToInternalDescriptors.get(entry.getKey()); if (descriptorMap == null) { descriptorMap = new THashMap<>(); packageToInternalDescriptors.put(entry.getKey(), descriptorMap); } descriptorMap.putAll(entry.getValue()); } } } @Override public void validate(@NotNull final XmlTag tag, @NotNull final ValidationHost host) { if (FlexSdkUtils.isFlex4Sdk(FlexUtils.getSdkForActiveBC(context.module))) { MxmlLanguageTagsUtil.checkFlex4Attributes(tag, host, true); } if (MxmlLanguageTagsUtil.isFxPrivateTag(tag)) { MxmlLanguageTagsUtil.validateFxPrivateTag(tag, host); return; } if (MxmlLanguageTagsUtil.isFxLibraryTag(tag)) { MxmlLanguageTagsUtil.validateFxLibraryTag(tag, host); return; } if (MxmlLanguageTagsUtil.isFxDefinitionTag(tag)) { MxmlLanguageTagsUtil.validateFxDefinitionTag(tag, host); return; } if (MxmlLanguageTagsUtil.isFxReparentTag(tag)) { MxmlLanguageTagsUtil.validateFxReparentTag(tag, host); return; } for(XmlTag subtag:tag.getSubTags()) { final String s = subtag.getLocalName(); if (s.length() > 0 && Character.isLowerCase(s.charAt(0))) { if (tag.getAttributeValue(s) != null) { final List<XmlElement> elements = new ArrayList<>(); collectLowerCasedElements(elements, tag.getAttributes()); collectLowerCasedElements(elements, tag.getSubTags()); XmlUtil.doDuplicationCheckForElements( elements.toArray(new XmlElement[elements.size()]), new THashMap<>(elements.size()), myDuplicationInfoProvider, host ); break; } } } final PsiElement declaration = getDeclaration(); if ((declaration instanceof JSClass) && !CodeContext.hasDefaultConstructor((JSClass)declaration)) { host.addMessage(tag, JSBundle.message("class.0.does.not.have.default.constructor", ((JSClass)declaration).getQualifiedName()), Validator.ValidationHost.ErrorType.ERROR); } if (tag.getParent() instanceof XmlDocument) { final JSClass jsClass = XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)tag.getContainingFile()); final JSReferenceList list = jsClass.getImplementsList(); final MxmlErrorReportingClient reportingClient = new MxmlErrorReportingClient(host); if (list != null) { ActionScriptAnnotatingVisitor.checkActionScriptImplementedMethods(jsClass, reportingClient); for (JSClass element : list.getReferencedClasses()) { if (element == jsClass) { reportingClient.reportError( refToImplementsNode(tag), // TODO: list is artificial node without context, cannot bind to it JSBundle.message("javascript.validation.message.circular.dependency"), JSAnnotatingVisitor.ErrorReportingClient.ProblemKind.ERROR); continue; } if (element != null && !element.isInterface()) { reportingClient.reportError( refToImplementsNode(tag), JSBundle.message("javascript.validation.message.interface.name.expected.here"), JSAnnotatingVisitor.ErrorReportingClient.ProblemKind.ERROR); } } } JSClass[] classes = jsClass.getSuperClasses(); if (classes.length > 0 && classes[0] == jsClass) { reportingClient.reportError( tag.getNode(), JSBundle.message("javascript.validation.message.circular.dependency"), JSAnnotatingVisitor.ErrorReportingClient.ProblemKind.ERROR); } ActionScriptAnnotatingVisitor.checkFileUnderSourceRoot(jsClass, reportingClient); } if (MxmlLanguageTagsUtil.isComponentTag(tag)) { if (tag.getSubTags().length == 0) { host.addMessage( tag, JSBundle.message("javascript.validation.empty.component.type"), ValidationHost.ErrorType.ERROR ); } } //if (defaultPropertyDescriptor != null && defaultPropertyDescriptor.getArrayType() == null) { // // TODO // for(XmlTag subtag:tag.getSubTags()) { // final String s = subtag.getLocalName(); // } //} } private static ASTNode refToImplementsNode(XmlTag tag) { return tag.getAttribute("implements").getValueElement().getChildren()[1].getNode(); } private static <T extends XmlElement & PsiNamedElement> void collectLowerCasedElements(final List<XmlElement> elements, final T[] xmlAttributes) { for(T a: xmlAttributes) { if (a instanceof XmlAttribute && FlexMxmlLanguageAttributeNames.ID.equals(a.getName())) continue; final String name = a instanceof XmlTag ? ((XmlTag)a).getLocalName() : a.getName(); if (name != null && name.length() > 0 && Character.isLowerCase(name.charAt(0))) elements.add(a); } } @Override public boolean requiresCdataBracesInContext(@NotNull final XmlTag context) { return predefined && XmlBackedJSClassImpl.SCRIPT_TAG_NAME.equals(context.getLocalName()); } @Override public Icon getIcon(@NotNull final PsiElement element, final int flags) { if (iconPath != null) { final PsiFile containingFile = element.getContainingFile(); final VirtualFile file = containingFile.getVirtualFile(); if (file != null) { final VirtualFile relativeFile = VfsUtil.findRelativeFile(iconPath, file.getParent()); if (relativeFile != null) { try { return new ImageIcon(new URL(VfsUtil.fixIDEAUrl(relativeFile.getUrl()))); } catch (MalformedURLException ignored) {} } } } return null; } XmlElementDescriptor[] getElementDescriptorsInheritedFromGivenType(@NotNull String arrayElementType) { final PsiElement declaration = getDeclaration(); if (declaration == null) { return EMPTY_ARRAY; } final PsiElement clazz = ActionScriptClassResolver.findClassByQNameStatic(arrayElementType, declaration); if (!(clazz instanceof JSClass)) { return EMPTY_ARRAY; } final JSClass jsClass = (JSClass)clazz; final JSAttributeList attributeList = jsClass.getAttributeList(); final boolean isFinalClass = attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.FINAL); final List<XmlElementDescriptor> result = new ArrayList<>(); if (isFinalClass) { final String jsClassName = jsClass.getName(); if (jsClassName != null) { final XmlElementDescriptor descriptor = context.getElementDescriptor(jsClassName, jsClass.getQualifiedName()); if (descriptor instanceof ClassBackedElementDescriptor && CodeContext.checkDeclaration((ClassBackedElementDescriptor)descriptor)) { result.add(descriptor); } } } else { for (final XmlElementDescriptor descriptor : context.getDescriptorsWithAllowedDeclaration()) { if (descriptor instanceof ClassBackedElementDescriptor && !((ClassBackedElementDescriptor)descriptor).predefined) { final PsiElement decl = descriptor.getDeclaration(); if (decl instanceof JSClass && JSInheritanceUtil.isParentClass((JSClass)decl, jsClass, false)) { result.add(descriptor); } } } } return result.toArray(new XmlElementDescriptor[result.size()]); } @Nullable static XmlElementDescriptor checkValidDescriptorAccordingToType(String arrayElementType, XmlElementDescriptor descriptor) { // no need to check AnnotationBackedDescriptors if (descriptor instanceof ClassBackedElementDescriptor) { if (isAdequateType(arrayElementType)) { PsiElement declaration = descriptor.getDeclaration(); if (declaration instanceof XmlFile) { declaration = XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)declaration); } if (declaration == null) { return null; } if (declaration instanceof JSClass) { if (!JSResolveUtil.isAssignableType(arrayElementType, ((JSClass)declaration).getQualifiedName(), declaration)) { return null; } } } } return descriptor; } @Nullable public AnnotationBackedDescriptor getDefaultPropertyDescriptor() { if (!defaultPropertyDescriptorInitialized) { PsiElement element = predefined ? null : getDeclaration(); if (element instanceof XmlFile) { element = XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)element); } if (element instanceof JSClass) { JSClass jsClass = (JSClass)element; do { AnnotationBackedDescriptor descriptor = getDefaultPropertyDescriptor(jsClass); if (descriptor == null) { jsClass = ArrayUtil.getFirstElement(jsClass.getSuperClasses()); } else { defaultPropertyDescriptor = descriptor; break; } } while (jsClass != null); } defaultPropertyDescriptorInitialized = true; } return defaultPropertyDescriptor; } @Nullable private AnnotationBackedDescriptor getDefaultPropertyDescriptor(final JSClass jsClass) { final Ref<JSAttribute> dpAttributeRef = Ref.create(); if (jsClass instanceof XmlBackedJSClassImpl) { final XmlFile xmlFile = (XmlFile)jsClass.getContainingFile(); final XmlTag rootTag = xmlFile.getRootTag(); if (rootTag != null) { for (XmlTag metadataTag : rootTag.findSubTags(FlexPredefinedTagNames.METADATA, JavaScriptSupportLoader.MXML_URI3)) { JSResolveUtil.processInjectedFileForTag(metadataTag, new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(final JSFile file) { for (PsiElement elt : file.getChildren()) { if (elt instanceof JSAttributeList) { final JSAttribute dpAttribute = ((JSAttributeList)elt).findAttributeByName("DefaultProperty"); if (dpAttributeRef.isNull() && dpAttribute != null) { dpAttributeRef.set(dpAttribute); return; } } } } }); } } } else { final JSAttributeList attributeList = jsClass.getAttributeList(); if (attributeList != null) { dpAttributeRef.set(attributeList.findAttributeByName("DefaultProperty")); } } final JSAttribute attribute = dpAttributeRef.get(); if (attribute != null) { JSAttributeNameValuePair pair = attribute.getValueByName(null); if (pair != null) { String s = pair.getSimpleValue(); if (s != null) { getAttributesDescriptors(null); XmlAttributeDescriptor descriptor = getAttributeDescriptor(s, null); if (descriptor instanceof AnnotationBackedDescriptor) { return (AnnotationBackedDescriptor)descriptor; } else { return new AnnotationBackedDescriptorImpl("any", this, true, "NonExistentClass!", null, null); } } } } return null; } @Override public boolean allowElementsFromNamespace(final String namespace, final XmlTag context) { if (MxmlJSClass.isTagOrInsideTagThatAllowsAnyXmlContent(context)) { return false; } if (MxmlLanguageTagsUtil.isFxLibraryTag(context)) { return JavaScriptSupportLoader.MXML_URI3.equals(namespace); } final XmlElementDescriptor _descriptor = context.getDescriptor(); if (_descriptor instanceof ClassBackedElementDescriptor) { final ClassBackedElementDescriptor descriptor = (ClassBackedElementDescriptor)_descriptor; if (context.getParent() instanceof XmlDocument && JavaScriptSupportLoader.isLanguageNamespace(namespace)) { return true; } if (MxmlLanguageTagsUtil.isComponentTag(context) || (descriptor.getDefaultPropertyDescriptor() != null && descriptor.defaultPropertyDescriptor.getType() != null)) { return true; } if (!descriptor.predefined && !isContainerClass(descriptor)) { return false; } } return true; } static boolean isAdequateType(final String type) { if (ARRAY_CLASS_NAME.equals(type) || OBJECT_CLASS_NAME.equals(type) || ANY_TYPE.equals(type)) { return false; } final String className = className(type); if ("IDeferredInstance".equals(className)) { return false; } return true; } static String className(String type) { return type != null ? type.substring(type.lastIndexOf('.') + 1):null; } private static class MxmlErrorReportingClient implements JSAnnotatingVisitor.ErrorReportingClient { private final Validator.ValidationHost myHost; public MxmlErrorReportingClient(final Validator.ValidationHost host) { myHost = host; } @Override public void reportError(final ASTNode nameIdentifier, final String s, ProblemKind kind, @NotNull final IntentionAction... fixes) { final ValidationHost.ErrorType errorType = kind == ProblemKind.ERROR ? ValidationHost.ErrorType.ERROR: ValidationHost.ErrorType.WARNING; if (myHost instanceof IdeValidationHost) { ((IdeValidationHost) myHost).addMessageWithFixes(nameIdentifier.getPsi(), s, errorType, fixes); } else { myHost.addMessage(nameIdentifier.getPsi(), s, errorType); } } } interface AttributedItemsProcessor { boolean process(JSNamedElement element, boolean isPackageLocalVisibility); boolean process(JSAttributeNameValuePair pair, final String annotationName, final boolean included); } static boolean processAttributes(PsiElement jsClass, AttributedItemsProcessor processor) { return doProcess(jsClass, processor); } private static boolean doProcess(final PsiElement jsClass, final AttributedItemsProcessor processor) { return ActionScriptResolveUtil.processMetaAttributesForClass(jsClass, new ActionScriptResolveUtil.MetaDataProcessor() { @Override public boolean process(final @NotNull JSAttribute attr) { final String attrName = attr.getName(); boolean skipped = false; if (FlexAnnotationNames.EVENT.equals(attrName) || FlexAnnotationNames.STYLE.equals(attrName) || FlexAnnotationNames.EFFECT.equals(attrName) || (skipped = FlexAnnotationNames.EXCLUDE.equals(attrName))) { JSAttributeNameValuePair jsAttributeNameValuePair = attr.getValueByName("name"); // [Event("someEvent")] equivalent to [Event(name="someEvent")] if (jsAttributeNameValuePair == null) jsAttributeNameValuePair = attr.getValueByName(null); if (jsAttributeNameValuePair != null && !processor.process(jsAttributeNameValuePair, attrName, !skipped)) return false; } return true; } @Override public boolean handleOtherElement(final PsiElement el, final PsiElement context, Ref<PsiElement> continuePassElement) { if (continuePassElement != null) { if (el instanceof JSVarStatement) { JSVariable[] jsVariables = ((JSVarStatement)el).getVariables(); if(jsVariables.length > 0) continuePassElement.set(jsVariables[0]); } else { continuePassElement.set(el); } } else { final JSAttributeList.AccessType accessType = ((JSAttributeList)el).getAccessType(); if ( (accessType == JSAttributeList.AccessType.PUBLIC || accessType == JSAttributeList.AccessType.PACKAGE_LOCAL) && !((JSAttributeList)el).hasModifier(JSAttributeList.ModifierType.STATIC) && (context instanceof JSVariable || (context instanceof JSFunction && ((JSFunction)context).isSetProperty()))) { if(!processor.process((JSNamedElement)context, accessType == JSAttributeList.AccessType.PACKAGE_LOCAL)) return false; } } return true; } }); } @Override public XmlAttributeDescriptor getAttributeDescriptor(String attributeName, final @Nullable XmlTag context) { if (isPrivateAttribute(attributeName, context)) { return new AnyXmlAttributeDescriptor(attributeName); } if (context != null && context.getParent() instanceof XmlDocument) { if (FlexMxmlLanguageAttributeNames.ID.equals(attributeName) || ArrayUtil.contains(attributeName, CodeContext.GUMBO_ATTRIBUTES)) { // id, includeIn, excludeFrom, itemCreationPolicy, itemDestructionPolicy attributes are not allowed for the root tag return null; } } PsiElement element = getDeclaration(); if (element == null) return null; attributeName = skipStateNamePart(attributeName); ensureDescriptorsMapsInitialized(element, null); AnnotationBackedDescriptor descriptor = myDescriptors.get(attributeName); if (descriptor == null && context != null) { final String prefix = context.getNamespacePrefix(); if (StringUtil.isNotEmpty(prefix) && attributeName.startsWith(prefix + ":")){ descriptor = myDescriptors.get(attributeName.substring(prefix.length() + 1)); } } if (descriptor == null && context != null && !myPackageToInternalDescriptors.isEmpty()) { final String contextPackage = JSResolveUtil.getPackageNameFromPlace(context); final Map<String, AnnotationBackedDescriptor> internalDescriptors = myPackageToInternalDescriptors.get(contextPackage); if (internalDescriptors != null) { descriptor = internalDescriptors.get(attributeName); } } if (descriptor == null && myPredefinedDescriptors != null) { descriptor = myPredefinedDescriptors.get(attributeName); } if (descriptor == null) { if (IMPLEMENTS_ATTR_NAME.equals(attributeName) && context != null) { final PsiElement parent = context.getParent(); if (parent instanceof XmlDocument || (parent instanceof XmlTag && MxmlLanguageTagsUtil.isComponentTag((XmlTag)parent))) { descriptor = new AnnotationBackedDescriptorImpl(IMPLEMENTS_ATTR_NAME, this, true, null, null, null); } } else if (XmlBackedJSClassImpl.CLASS_NAME_ATTRIBUTE_NAME.equals(attributeName) && MxmlLanguageTagsUtil.isComponentTag(context)) { descriptor = new ClassNameAttributeDescriptor(this); } else if (!predefined && !FlexMxmlLanguageAttributeNames.ID.equals(attributeName) && !MxmlLanguageTagsUtil.isXmlOrXmlListTag(context) && isDynamicClass(element)) { return new AnyXmlAttributeDescriptor(attributeName); } } return descriptor; } static boolean isPrivateAttribute(final String attributeName, final XmlTag context) { final int colonIndex = attributeName.indexOf(':'); final String namespacePrefix = colonIndex == -1 ? null : attributeName.substring(0, colonIndex); if (namespacePrefix != null && context != null && ArrayUtil.contains(JavaScriptSupportLoader.MXML_URI3, context.knownNamespaces())) { final String namespace = context.getNamespaceByPrefix(namespacePrefix); if (!StringUtil.isEmpty(namespace) && !namespace.equals(JavaScriptSupportLoader.MXML_URI3) && !namespace.equals(context.getNamespace())) { return true; } } return false; } private static String skipStateNamePart(final String attributeName) { int dotPos = attributeName.lastIndexOf('.'); if (dotPos != -1 && !attributeName.startsWith("id.")) { return attributeName.substring(0, dotPos); } return attributeName; } @Override public XmlAttributeDescriptor getAttributeDescriptor(final XmlAttribute attribute) { return getAttributeDescriptor(attribute.getName(), attribute.getParent()); } @Override public XmlNSDescriptor getNSDescriptor() { return null; } @Override public XmlElementsGroup getTopGroup() { return null; } @Override public int getContentType() { return CONTENT_TYPE_UNKNOWN; } @Override public String getDefaultValue() { return null; } @Override @Nullable public PsiElement getDeclaration() { String className = predefined ? OBJECT_CLASS_NAME :this.className; if (className.equals(CodeContext.AS3_VEC_VECTOR_QUALIFIED_NAME)) className = VECTOR_CLASS_NAME; final PsiElement jsClass = ActionScriptClassResolver.findClassByQName(className, JavaScriptIndex.getInstance(project), context.module); final PsiFile file = jsClass == null ? null : jsClass.getContainingFile(); // can be MXML file listed as a component in the manifest file return (file != null && JavaScriptSupportLoader.isMxmlOrFxgFile(file)) ? file : jsClass; } @Override @NonNls public String getName(final PsiElement context) { String prefix = null; XmlTag tag = (XmlTag)context; String name = getName(); int packageIndex = className.lastIndexOf(name); String packageName = packageIndex > 0? className.substring(0, packageIndex - 1):""; if (packageName.length() > 0) packageName += ".*"; else packageName = "*"; XmlNSDescriptor nsDescriptor = tag.getPrefixByNamespace(packageName) != null ? tag.getNSDescriptor(packageName, true):null; if (nsDescriptor instanceof FlexMxmlNSDescriptor) { if(((FlexMxmlNSDescriptor)nsDescriptor).hasElementDescriptorWithName(name, className)) { prefix = tag.getPrefixByNamespace(packageName); } } if (prefix == null) { final String preferredNamespace = this.context.namespace; final String[] knownNamespaces = tag.knownNamespaces(); if (preferredNamespace.indexOf('*') == -1 && ArrayUtil.contains(preferredNamespace, knownNamespaces)) { nsDescriptor = tag.getNSDescriptor(preferredNamespace, true); if (nsDescriptor instanceof FlexMxmlNSDescriptor && ((FlexMxmlNSDescriptor)nsDescriptor).hasElementDescriptorWithName(name, className)) { prefix = tag.getPrefixByNamespace(preferredNamespace); } } if (prefix == null) { for (String namespace : knownNamespaces) { if (namespace.equals(preferredNamespace) || namespace.indexOf('*') != -1) { continue; } nsDescriptor = tag.getNSDescriptor(namespace, true); if (nsDescriptor instanceof FlexMxmlNSDescriptor && ((FlexMxmlNSDescriptor)nsDescriptor).hasElementDescriptorWithName(name, className)) { prefix = tag.getPrefixByNamespace(namespace); break; } } } } if (prefix == null || prefix.length() == 0) return name; return prefix + ":" + name; } @Override @NonNls public String getName() { return name == null ? getNameFromQName():name; } private String getNameFromQName() { return className(className); } @Override public void init(final PsiElement element) { } @NotNull @Override public Object[] getDependences() { return ArrayUtil.EMPTY_OBJECT_ARRAY; } private static class ClassNameAttributeDescriptor extends AnnotationBackedDescriptorImpl { protected ClassNameAttributeDescriptor(ClassBackedElementDescriptor parentDescriptor) { super(XmlBackedJSClassImpl.CLASS_NAME_ATTRIBUTE_NAME, parentDescriptor, true, null, null, null); } @Override public String validateValue(XmlElement context, String value) { final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(JavaScriptSupportLoader.JAVASCRIPT.getLanguage()); if (!namesValidator.isIdentifier(value, context.getProject())) { return JSBundle.message("invalid.identifier.value"); } return null; } } }