diff --git a/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java b/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java index 66148bcac3e4..4dbcaba2eba6 100644 --- a/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java +++ b/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java @@ -236,7 +236,7 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase { myFixture.addClass("package p;\n" + "public class Root extends javafx.scene.layout.GridPane{\n" + " public Root(@javafx.beans.NamedArg(\"axis\") javafx.scene.Node node ) {\n" + - " super(node)\n" + + " super(node);\n" + " }\n" + " public javafx.beans.property.Property axis;" + " public void setAxis() {}" + @@ -346,16 +346,40 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase { doTest(); } + public void testPropertyTagCompatibleClass() throws Exception { + doTest(); + } + + public void testPropertyTagCompatiblePrimitive() throws Exception { + doTest(); + } + + public void testPropertyTagIncompatibleClass() throws Exception { + doTest(); + } + + public void testPropertyTagIncompatiblePrimitive() throws Exception { + doTest(); + } + + public void testPropertyTagUnrelatedClass() throws Exception { + doWarningsTest(); + } + + public void testPropertyTagUnrelatedPrimitive() throws Exception { + doWarningsTest(); + } + public void testCharsetInInclude() throws Exception { myFixture.addFileToProject("sample.fxml", "\n" + "\n"); - myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml"); + doWarningsTest(); } public void testIncludedForm() throws Exception { myFixture.addFileToProject("sample.fxml", "\n" + "\n"); - myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml"); + doWarningsTest(); } public void testInjectedControllerFields() throws Exception { @@ -372,6 +396,10 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase { myFixture.testHighlighting(false, false, false, getTestName(true) + ".fxml"); } + private void doWarningsTest() { + myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml"); + } + @Override protected void enableInspections() { myFixture.enableInspections(new XmlPathReferenceInspection(), diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java index 55278757aa95..b53f0edb79a8 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java @@ -16,6 +16,7 @@ package org.jetbrains.plugins.javaFX.fxml; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.daemon.Validator; import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.lang.ASTNode; @@ -36,6 +37,8 @@ import com.intellij.util.Processor; import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlElementDescriptor; import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxClassBackedElementDescriptor; @@ -43,6 +46,9 @@ import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxDefaultPropertyElemen import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxPropertyElementDescriptor; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; /** * User: anna @@ -307,9 +313,18 @@ public class JavaFxPsiUtil { return null; } - public static PsiClass getTagClass(XmlTag xmlTag) { + @Nullable + public static PsiClass getTagClass(@NotNull XmlTag xmlTag) { final XmlElementDescriptor descriptor = xmlTag.getDescriptor(); if (descriptor != null) { + if (descriptor instanceof JavaFxDefaultPropertyElementDescriptor) { + final JavaFxClassBackedElementDescriptor rootTagDescriptor = + ((JavaFxDefaultPropertyElementDescriptor)descriptor).getFxRootTagDescriptor(xmlTag); + if (rootTagDescriptor != null) { + final PsiElement declaration = rootTagDescriptor.getDeclaration(); + return declaration instanceof PsiClass ? (PsiClass)declaration : null; + } + } final PsiElement declaration = descriptor.getDeclaration(); if (declaration instanceof PsiClass) { return (PsiClass)declaration; @@ -318,20 +333,27 @@ public class JavaFxPsiUtil { return null; } - public static boolean isVisibleInFxml(PsiMember psiMember) { + public static boolean isVisibleInFxml(@NotNull PsiMember psiMember) { return psiMember.hasModifierProperty(PsiModifier.PUBLIC) || AnnotationUtil.isAnnotated(psiMember, JavaFxCommonNames.JAVAFX_FXML_ANNOTATION, false); } - public static PsiMethod findValueOfMethod(@NotNull final PsiClass tagClass) { - final PsiMethod[] methods = tagClass.findMethodsByName(JavaFxCommonNames.VALUE_OF, false); + @Nullable + public static PsiMethod findValueOfMethod(@NotNull final PsiType psiType) { + final PsiClass psiClass = PsiTypesUtil.getPsiClass(psiType); + return psiClass != null ? findValueOfMethod(psiClass) : null; + } + + @Nullable + public static PsiMethod findValueOfMethod(@NotNull final PsiClass psiClass) { + final PsiMethod[] methods = psiClass.findMethodsByName(JavaFxCommonNames.VALUE_OF, true); for (PsiMethod method : methods) { if (method.hasModifierProperty(PsiModifier.STATIC)) { final PsiParameter[] parameters = method.getParameterList().getParameters(); if (parameters.length == 1) { final PsiType type = parameters[0].getType(); if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) || type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { - if (method.hasModifierProperty(PsiModifier.STATIC) && tagClass.equals(PsiUtil.resolveClassInType(method.getReturnType()))) { + if (psiClass.equals(PsiUtil.resolveClassInType(method.getReturnType()))) { return method; } } @@ -407,11 +429,16 @@ public class JavaFxPsiUtil { return null; } - public static String isAbleToInstantiate(final PsiClass psiClass) { + public static boolean isAbleToInstantiate(final PsiClass psiClass) { + return isAbleToInstantiate(psiClass, message -> { + }); + } + + public static boolean isAbleToInstantiate(final PsiClass psiClass, @NotNull Consumer messageConsumer) { if (psiClass.getConstructors().length > 0) { - for (PsiMethod constr : psiClass.getConstructors()) { - final PsiParameter[] parameters = constr.getParameterList().getParameters(); - if (parameters.length == 0) return null; + for (PsiMethod constructor : psiClass.getConstructors()) { + final PsiParameter[] parameters = constructor.getParameterList().getParameters(); + if (parameters.length == 0) return true; boolean annotated = true; for (PsiParameter parameter : parameters) { if (!AnnotationUtil.isAnnotated(parameter, JavaFxCommonNames.JAVAFX_BEANS_NAMED_ARG, false)) { @@ -419,14 +446,17 @@ public class JavaFxPsiUtil { break; } } - if (annotated) return null; + if (annotated) return true; } final PsiMethod valueOf = findValueOfMethod(psiClass); if (valueOf == null) { - if (!hasBuilder(psiClass)) return "Unable to instantiate"; + if (!hasBuilder(psiClass)) { + messageConsumer.accept("Unable to instantiate"); + return false; + } } } - return null; + return true; } public static boolean hasBuilder(@NotNull final PsiClass psiClass) { @@ -460,57 +490,187 @@ public class JavaFxPsiUtil { }); } - public static String isClassAcceptable(@Nullable XmlTag parentTag, @Nullable final PsiClass aClass) { - if (parentTag == null) { - return null; - } - if (aClass != null && aClass.isValid()) { - XmlElementDescriptor descriptor = parentTag.getDescriptor(); - if (descriptor instanceof JavaFxDefaultPropertyElementDescriptor) { - descriptor = ((JavaFxDefaultPropertyElementDescriptor)descriptor).getFxRootTagDescriptor(parentTag); - } + public static boolean isClassAcceptable(@Nullable XmlTag targetTag, @Nullable final PsiClass fromClass) { + return isClassAcceptable(targetTag, fromClass, (message, type) -> { + }); + } - if (descriptor instanceof JavaFxPropertyElementDescriptor) { - final PsiClass containingClass = ((JavaFxPropertyElementDescriptor)descriptor).getPsiClass(); - final PsiElement declaration = descriptor.getDeclaration(); - final PsiType propertyType = getWritablePropertyType(containingClass, declaration); - return canCoerce(aClass, propertyType); - } - else if (descriptor instanceof JavaFxClassBackedElementDescriptor) { - final PsiElement declaration = descriptor.getDeclaration(); - if (declaration instanceof PsiClass) { - final PsiType type = getDefaultPropertyExpectedType((PsiClass)declaration); - if (type != null) { - return canCoerce(aClass, type); + public static boolean isClassAcceptable(@Nullable XmlTag targetTag, @Nullable final PsiClass fromClass, + @NotNull BiConsumer messageConsumer) { + if (targetTag == null || fromClass == null || !fromClass.isValid()) { + return true; + } + XmlElementDescriptor tagDescriptor = targetTag.getDescriptor(); + if (tagDescriptor instanceof JavaFxDefaultPropertyElementDescriptor) { + tagDescriptor = ((JavaFxDefaultPropertyElementDescriptor)tagDescriptor).getFxRootTagDescriptor(targetTag); + } + + if (tagDescriptor instanceof JavaFxPropertyElementDescriptor) { + final PsiClass containingClass = ((JavaFxPropertyElementDescriptor)tagDescriptor).getPsiClass(); + final PsiType targetType = getWritablePropertyType(containingClass, tagDescriptor.getDeclaration()); + return canCoerce(targetType, fromClass, targetTag, messageConsumer); + } + else if (tagDescriptor instanceof JavaFxClassBackedElementDescriptor) { + final PsiElement tagDeclaration = tagDescriptor.getDeclaration(); + if (tagDeclaration instanceof PsiClass) { + PsiClass defaultPropertyOwnerClass = (PsiClass)tagDeclaration; + final XmlAttribute factoryAttr = targetTag.getAttribute(FxmlConstants.FX_FACTORY); + if (factoryAttr != null) { + final PsiClass factoryReturnClass = getFactoryProducedClass((PsiClass)tagDeclaration, factoryAttr.getValue()); + if (factoryReturnClass == null) { + return true; } + defaultPropertyOwnerClass = factoryReturnClass; + } + final PsiType targetType = getDefaultPropertyExpectedType(defaultPropertyOwnerClass); + if (targetType != null) { + return canCoerce(targetType, fromClass, targetTag, messageConsumer); + } + if (!isObservableCollection(defaultPropertyOwnerClass)) { + return noDefaultPropertyError(messageConsumer); } } } - return null; + return true; } - @Nullable - private static String canCoerce(@NotNull PsiClass aClass, @Nullable PsiType type) { - PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(type, aClass.getResolveScope()); - if (collectionItemType == null && InheritanceUtil.isInheritor(type, JavaFxCommonNames.JAVAFX_BEANS_PROPERTY)) { - collectionItemType = getWritablePropertyType(type, aClass.getProject()); + private static boolean noDefaultPropertyError(@NotNull BiConsumer messageConsumer) { + messageConsumer.accept("Parent tag has no default property", + Validator.ValidationHost.ErrorType.ERROR); + return false; + } + + private static boolean canCoerce(@Nullable PsiType targetType, @NotNull PsiClass fromClass, @NotNull PsiElement context, + @NotNull BiConsumer messageConsumer) { + if (targetType == null) return true; + targetType = eraseFreeTypeParameters(targetType); + PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(targetType, fromClass.getResolveScope()); + if (collectionItemType == null && InheritanceUtil.isInheritor(targetType, JavaFxCommonNames.JAVAFX_BEANS_PROPERTY)) { + collectionItemType = getWritablePropertyType(targetType, fromClass.getProject()); } - if (collectionItemType != null && PsiPrimitiveType.getUnboxedType(collectionItemType) == null) { - final PsiClass baseClass = PsiUtil.resolveClassInType(collectionItemType); - if (baseClass != null) { - final String qualifiedName = baseClass.getQualifiedName(); - if (qualifiedName != null && !Comparing.strEqual(qualifiedName, CommonClassNames.JAVA_LANG_STRING)) { - if (!InheritanceUtil.isInheritor(aClass, qualifiedName)) { - return unableToCoerceMessage(aClass, qualifiedName); - } + if (collectionItemType != null) { + return canCoerceImpl(collectionItemType, fromClass, context, messageConsumer); + } + return canCoerceImpl(targetType, fromClass, context, messageConsumer); + } + + /** + * Similar to {@link GenericsUtil#getVariableTypeByExpressionType(PsiType)} and {@link TypeConversionUtil#erasure(PsiType)} + */ + @NotNull + public static PsiType eraseFreeTypeParameters(@NotNull PsiType psiType) { + return psiType.accept(new PsiTypeVisitor() { + @Nullable + @Override + public PsiType visitType(PsiType type) { + return type; + } + + @Nullable + @Override + public PsiType visitClassType(PsiClassType classType) { + final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + final PsiClass aClass = resolveResult.getElement(); + if (aClass == null) return classType; + if (aClass instanceof PsiTypeParameter) return null; + PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; + boolean unchanged = true; + for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(aClass)) { + final PsiType typeArgument = resolveResult.getSubstitutor().substitute(typeParameter); + if (typeArgument == null) return classType.rawType(); + final PsiType toPut = typeArgument.accept(this); + if (toPut == null) return classType.rawType(); + unchanged &= toPut == typeArgument; + substitutor = substitutor.put(typeParameter, toPut); } + if (unchanged) return classType; + final PsiManager manager = aClass.getManager(); + final PsiAnnotation[] applicableAnnotations = classType.getApplicableAnnotations(); + return JavaPsiFacade.getInstance(manager.getProject()).getElementFactory() + .createType(aClass, substitutor, PsiUtil.getLanguageLevel(aClass), applicableAnnotations); + } + + @Override + public PsiType visitWildcardType(PsiWildcardType wildcardType) { + final PsiType bound = wildcardType.getBound(); + return bound != null ? bound.accept(this) : null; + } + + @Nullable + @Override + public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) { + return capturedWildcardType.getUpperBound().accept(this); + } + + @Override + public PsiType visitEllipsisType(PsiEllipsisType ellipsisType) { + return visitArrayType(ellipsisType); + } + + @Override + public PsiType visitArrayType(PsiArrayType arrayType) { + final PsiType componentType = arrayType.getComponentType(); + final PsiType newComponentType = componentType.accept(this); + if (newComponentType == componentType) return arrayType; + return newComponentType != null ? newComponentType.createArrayType() : null; + } + + @Override + public PsiType visitDisjunctionType(PsiDisjunctionType disjunctionType) { + final PsiClassType lub = PsiTypesUtil.getLowestUpperBoundClassType(disjunctionType); + return lub != null ? lub.accept(this) : null; + } + }); + } + + private static boolean canCoerceImpl(@NotNull PsiType targetType, @NotNull PsiClass fromClass, @NotNull PsiElement context, + @NotNull BiConsumer messageConsumer) { + if (targetType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || + targetType.equalsToText(CommonClassNames.JAVA_LANG_STRING) || + targetType.isAssignableFrom(PsiTypesUtil.getClassType(fromClass))) { + return true; + } + final PsiClassType boxedTargetClass = + targetType instanceof PsiPrimitiveType ? ((PsiPrimitiveType)targetType).getBoxedType(context) : null; + if (boxedTargetClass != null && InheritanceUtil.isInheritor(boxedTargetClass, CommonClassNames.JAVA_LANG_NUMBER) || + InheritanceUtil.isInheritor(targetType, CommonClassNames.JAVA_LANG_NUMBER)) { + if (Comparing.strEqual(fromClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING) || + InheritanceUtil.isInheritor(fromClass, CommonClassNames.JAVA_LANG_NUMBER)) { + return true; + } + return unrelatedTypesWarning(targetType, fromClass, messageConsumer); + } + final PsiMethod valueOfMethod = findValueOfMethod(targetType); + final PsiType valueOfParameterType = valueOfMethod != null && valueOfMethod.getParameterList().getParametersCount() == 1 ? + valueOfMethod.getParameterList().getParameters()[0].getType() : null; + if (valueOfParameterType != null && valueOfParameterType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + return true; + } + if (Comparing.strEqual(fromClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING)) { + if (isPrimitiveOrBoxed(targetType) || + valueOfParameterType != null && valueOfParameterType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return true; } } - return null; + if (valueOfMethod != null) { + return unrelatedTypesWarning(targetType, fromClass, messageConsumer); + } + return unableToCoerceError(targetType, fromClass, messageConsumer); } - private static String unableToCoerceMessage(PsiClass aClass, String qualifiedName) { - return "Unable to coerce " + HighlightUtil.formatClass(aClass) + " to " + qualifiedName; + private static boolean unableToCoerceError(@NotNull PsiType targetType, @NotNull PsiClass fromClass, + @NotNull BiConsumer messageConsumer) { + messageConsumer.accept("Unable to coerce " + HighlightUtil.formatClass(fromClass) + " to " + targetType.getCanonicalText(), + Validator.ValidationHost.ErrorType.ERROR); + return false; + } + + private static boolean unrelatedTypesWarning(@NotNull PsiType targetType, @NotNull PsiClass fromClass, + @NotNull BiConsumer messageConsumer) { + messageConsumer.accept("Conversion between unrelated types, " + HighlightUtil.formatClass(fromClass) + + " to " + targetType.getCanonicalText(), + Validator.ValidationHost.ErrorType.WARNING); + return true; } public static boolean isOutOfHierarchy(final XmlAttributeValue element) { @@ -667,7 +827,7 @@ public class JavaFxPsiUtil { } @Nullable - public static PsiClass getPropertyClass(PsiType propertyType, XmlAttributeValue context) { + public static PsiClass getPropertyClass(PsiType propertyType, PsiElement context) { if (propertyType instanceof PsiPrimitiveType) { PsiClassType boxedType = ((PsiPrimitiveType)propertyType).getBoxedType(context); return boxedType != null ? boxedType.resolve() : null; @@ -818,13 +978,17 @@ public class JavaFxPsiUtil { } private static boolean isWritablePropertyType(@NotNull PsiClass psiClass, @NotNull PsiType fieldType) { - return (InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_LIST) || - InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_SET) || - InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_ARRAY)) && + return isObservableCollection(PsiUtil.resolveClassInType(fieldType)) && JavaGenericsUtil.getCollectionItemType(fieldType, psiClass.getResolveScope()) != null || InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_MAP); } + private static boolean isObservableCollection(@Nullable PsiClass psiClass) { + return InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_LIST) || + InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_SET) || + InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_ARRAY); + } + @Nullable private static PsiSubstitutor getTagClassSubstitutor(@NotNull XmlAttribute xmlAttribute, @NotNull PsiClass controllerClass) { final XmlTag xmlTag = xmlAttribute.getParent(); @@ -914,6 +1078,35 @@ public class JavaFxPsiUtil { return null; } + @Nullable + public static PsiClass getFactoryProducedClass(@NotNull PsiClass aClass, @Nullable String factoryMethodName) { + if (factoryMethodName == null) return null; + final PsiMethod[] methods = aClass.findMethodsByName(factoryMethodName, true); + for (PsiMethod method : methods) { + if (method.getParameterList().getParametersCount() == 0 && + method.hasModifierProperty(PsiModifier.STATIC)) { + return PsiUtil.resolveClassInType(method.getReturnType()); + } + } + return null; + } + + @Nullable + public static String validateEnumConstant(@NotNull PsiClass enumClass, @NonNls @Nullable String name) { + if (!enumClass.isEnum() || name == null) return null; + final Set constantNames = CachedValuesManager.getCachedValue(enumClass, () -> + CachedValueProvider.Result.create(Arrays.stream(enumClass.getFields()) + .filter(PsiEnumConstant.class::isInstance) + .map(PsiField::getName) + .map(String::toUpperCase) + .collect(Collectors.toCollection(THashSet::new)), + PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT)); + if (!constantNames.contains(name.toUpperCase())) { + return "No enum constant '" + name + "' in " + enumClass.getQualifiedName(); + } + return null; + } + private static class JavaFxControllerCachedValueProvider implements CachedValueProvider { private final Project myProject; private final PsiFile myContainingFile; diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxColorRgbInspection.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxColorRgbInspection.java index 1477b1395fcc..091738929e19 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxColorRgbInspection.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxColorRgbInspection.java @@ -36,7 +36,7 @@ public class JavaFxColorRgbInspection extends XmlSuppressableInspectionTool { final XmlAttributeDescriptor descriptor = attribute.getDescriptor(); if (descriptor instanceof JavaFxPropertyAttributeDescriptor) { final PsiClass psiClass = ((JavaFxPropertyAttributeDescriptor)descriptor).getPsiClass(); - if (JavaFxCommonNames.JAVAFX_SCENE_COLOR.equals(psiClass.getQualifiedName())) { + if (psiClass != null && JavaFxCommonNames.JAVAFX_SCENE_COLOR.equals(psiClass.getQualifiedName())) { final XmlAttributeValue valueElement = attribute.getValueElement(); final PsiElement location = valueElement != null ? valueElement : attribute; validateColorComponent(psiClass, attribute.getName(), attributeValue, location); diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/intentions/JavaFxExpandAttributeIntention.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/intentions/JavaFxExpandAttributeIntention.java index f81a91ef3c9f..68532b4a473b 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/intentions/JavaFxExpandAttributeIntention.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/intentions/JavaFxExpandAttributeIntention.java @@ -87,7 +87,7 @@ public class JavaFxExpandAttributeIntention extends PsiElementBaseIntentionActio tagType = PropertyUtil.getPropertyType((PsiMember)declaration); } PsiClass tagClass = PsiUtil.resolveClassInType(tagType instanceof PsiPrimitiveType ? ((PsiPrimitiveType)tagType).getBoxedType(parent) : tagType); - if ((tagClass != null && JavaFxPsiUtil.isAbleToInstantiate(tagClass) == null) || descriptor instanceof JavaFxStaticSetterAttributeDescriptor) { + if ((tagClass != null && JavaFxPsiUtil.isAbleToInstantiate(tagClass)) || descriptor instanceof JavaFxStaticSetterAttributeDescriptor) { setText("Expand '" + ((XmlAttribute)parent).getName() + "' to tag"); return true; } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java index 9a66ff40adea..d1c4250aea29 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java @@ -9,7 +9,6 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.*; import com.intellij.psi.xml.XmlAttribute; -import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; @@ -322,27 +321,14 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor, } else { final XmlAttribute factoryAttr = context.getAttribute(FxmlConstants.FX_FACTORY); if (factoryAttr != null) { - final XmlAttributeValue valueElement = factoryAttr.getValueElement(); - if (valueElement != null) { - final PsiReference reference = valueElement.getReference(); - final PsiElement staticFactoryMethod = reference != null ? reference.resolve() : null; - if (staticFactoryMethod instanceof PsiMethod && - ((PsiMethod)staticFactoryMethod).getParameterList().getParametersCount() == 0 && - ((PsiMethod)staticFactoryMethod).hasModifierProperty(PsiModifier.STATIC)) { - aClass = PsiUtil.resolveClassInType(((PsiMethod)staticFactoryMethod).getReturnType()); - } - } + aClass = JavaFxPsiUtil.getFactoryProducedClass(myPsiClass, factoryAttr.getValue()); } } - final String canCoerceError = JavaFxPsiUtil.isClassAcceptable(parentTag, aClass); - if (canCoerceError != null) { - host.addMessage(context.getNavigationElement(), canCoerceError, ValidationHost.ErrorType.ERROR); - } + JavaFxPsiUtil.isClassAcceptable(parentTag, aClass, (errorMessage, errorType) -> + host.addMessage(context.getNavigationElement(), errorMessage, errorType)); if (aClass != null && aClass.isValid()) { - final String message = JavaFxPsiUtil.isAbleToInstantiate(aClass); - if (message != null) { - host.addMessage(context, message, ValidationHost.ErrorType.ERROR); - } + JavaFxPsiUtil.isAbleToInstantiate(aClass, errorMessage -> + host.addMessage(context, errorMessage, ValidationHost.ErrorType.ERROR)); } } } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyAttributeDescriptor.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyAttributeDescriptor.java index bda5fbd25baf..bd0d7f81cbd2 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyAttributeDescriptor.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyAttributeDescriptor.java @@ -82,6 +82,9 @@ public class JavaFxDefaultPropertyAttributeDescriptor extends JavaFxPropertyAttr if (FxmlConstants.FX_VALUE.equals(attributeName)) { final PsiClass tagClass = JavaFxPsiUtil.getTagClass((XmlAttributeValue)context); if (tagClass != null) { + if (tagClass.isEnum()) { + return JavaFxPsiUtil.validateEnumConstant(tagClass, value); + } final PsiMethod method = JavaFxPsiUtil.findValueOfMethod(tagClass); if (method == null) { return "Unable to coerce '" + value + "' to " + tagClass.getQualifiedName() + "."; diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyElementDescriptor.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyElementDescriptor.java index 5f11f4baf40b..21acb4d4048d 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyElementDescriptor.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxDefaultPropertyElementDescriptor.java @@ -297,10 +297,8 @@ public class JavaFxDefaultPropertyElementDescriptor implements XmlElementDescrip final PsiElement declaration = descriptor.getDeclaration(); if (declaration instanceof PsiClass) { final PsiClass psiClass = (PsiClass)declaration; - final String canCoerceError = JavaFxPsiUtil.isClassAcceptable(context.getParentTag(), psiClass); - if (canCoerceError != null) { - host.addMessage(context.getNavigationElement(), canCoerceError, ValidationHost.ErrorType.ERROR); - } + JavaFxPsiUtil.isClassAcceptable(context.getParentTag(), psiClass, (errorMessage, errorType) -> + host.addMessage(context.getNavigationElement(), errorMessage, errorType)); if (FxmlConstants.FX_COPY.equals(contextName)) { boolean copyConstructorFound = false; for (PsiMethod constructor : psiClass.getConstructors()) { diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java index 0ce46522c627..9ccd13c6e056 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java @@ -99,7 +99,7 @@ class JavaFxComponentIdReferenceProvider extends PsiReferenceProvider { final XmlTag parentTag = currentTag.getParentTag(); for (final String id : fileIds.keySet()) { final XmlAttributeValue resolvedAttrValue = fileIds.get(id); - if (JavaFxPsiUtil.isClassAcceptable(parentTag, JavaFxPsiUtil.getTagClass(resolvedAttrValue)) == null) { + if (JavaFxPsiUtil.isClassAcceptable(parentTag, JavaFxPsiUtil.getTagClass(resolvedAttrValue))) { acceptableIds.add(id); } } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxImportClassFix.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxImportClassFix.java index 701142d06331..a49c247fe755 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxImportClassFix.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxImportClassFix.java @@ -66,7 +66,7 @@ abstract class JavaFxImportClassFix extends ImportClassFixBase + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/javaFX/testData/highlighting/propertyTagCompatiblePrimitive.fxml b/plugins/javaFX/testData/highlighting/propertyTagCompatiblePrimitive.fxml new file mode 100644 index 000000000000..c9a21ece816a --- /dev/null +++ b/plugins/javaFX/testData/highlighting/propertyTagCompatiblePrimitive.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/javaFX/testData/highlighting/propertyTagIncompatibleClass.fxml b/plugins/javaFX/testData/highlighting/propertyTagIncompatibleClass.fxml new file mode 100644 index 000000000000..ca83d66eef67 --- /dev/null +++ b/plugins/javaFX/testData/highlighting/propertyTagIncompatibleClass.fxml @@ -0,0 +1,15 @@ + + + + + + + + + <Color green="1"/> + + + + <Priority fx:value="always"/> + + \ No newline at end of file diff --git a/plugins/javaFX/testData/highlighting/propertyTagIncompatiblePrimitive.fxml b/plugins/javaFX/testData/highlighting/propertyTagIncompatiblePrimitive.fxml new file mode 100644 index 000000000000..2696e8f71b82 --- /dev/null +++ b/plugins/javaFX/testData/highlighting/propertyTagIncompatiblePrimitive.fxml @@ -0,0 +1,14 @@ + + + + + + + <Integer fx:value="10"/> + + + + <Double fx:value="20"/> + + + \ No newline at end of file diff --git a/plugins/javaFX/testData/highlighting/propertyTagUnrelatedClass.fxml b/plugins/javaFX/testData/highlighting/propertyTagUnrelatedClass.fxml new file mode 100644 index 000000000000..c535983a4cd5 --- /dev/null +++ b/plugins/javaFX/testData/highlighting/propertyTagUnrelatedClass.fxml @@ -0,0 +1,14 @@ + + + + + + + + <Insets top="10"/> + + + + <Color green="1"/> + + \ No newline at end of file diff --git a/plugins/javaFX/testData/highlighting/propertyTagUnrelatedPrimitive.fxml b/plugins/javaFX/testData/highlighting/propertyTagUnrelatedPrimitive.fxml new file mode 100644 index 000000000000..32adb01fe593 --- /dev/null +++ b/plugins/javaFX/testData/highlighting/propertyTagUnrelatedPrimitive.fxml @@ -0,0 +1,10 @@ + + + + + + + <Double fx:value="1"/> + + + \ No newline at end of file