mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Javafx: Validate property tags. Check that value provided by property's nested tag(s) is compatible with or convertible to the property type. Show warning for conversion of unrelated types: technically it might work, but usually not in the way the user expects. Validate enum constants provided in fx:value attribute. Tests added. (IDEA-103596)
This commit is contained in:
+31
-3
@@ -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<javafx.scene.Node> 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", "<?import javafx.scene.layout.GridPane?>\n" +
|
||||
"<fx:root type=\"javafx.scene.layout.GridPane\" xmlns:fx=\"http://javafx.com/fxml\"/>\n");
|
||||
myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml");
|
||||
doWarningsTest();
|
||||
}
|
||||
|
||||
public void testIncludedForm() throws Exception {
|
||||
myFixture.addFileToProject("sample.fxml", "<?import javafx.scene.layout.GridPane?>\n" +
|
||||
"<fx:root type=\"javafx.scene.layout.GridPane\" xmlns:fx=\"http://javafx.com/fxml\"/>\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(),
|
||||
|
||||
@@ -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<String> 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<? super String, Validator.ValidationHost.ErrorType> 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<? super String, Validator.ValidationHost.ErrorType> 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<? super String, Validator.ValidationHost.ErrorType> 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<PsiType>() {
|
||||
@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<? super String, Validator.ValidationHost.ErrorType> 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<? super String, Validator.ValidationHost.ErrorType> 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<? super String, Validator.ValidationHost.ErrorType> 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<String> 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<PsiClass> {
|
||||
private final Project myProject;
|
||||
private final PsiFile myContainingFile;
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+5
-19
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -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() + ".";
|
||||
|
||||
+2
-4
@@ -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()) {
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ abstract class JavaFxImportClassFix extends ImportClassFixBase<XmlTag, JavaFxTag
|
||||
|
||||
@Override
|
||||
protected boolean isAccessible(PsiMember member, XmlTag reference) {
|
||||
return member instanceof PsiClass && JavaFxPsiUtil.isClassAcceptable(reference.getParentTag(), (PsiClass)member) == null;
|
||||
return member instanceof PsiClass && JavaFxPsiUtil.isClassAcceptable(reference.getParentTag(), (PsiClass)member);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.paint.Color?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
|
||||
<Rectangle width="100" height="100">
|
||||
<fill>
|
||||
<Color green="1"/>
|
||||
</fill>
|
||||
<VBox.margin>
|
||||
<Insets top="10"/>
|
||||
</VBox.margin>
|
||||
</Rectangle>
|
||||
<padding>
|
||||
<Insets left="20"/>
|
||||
</padding>
|
||||
</fx:root>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?import javafx.scene.layout.Priority?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import java.lang.Double?>
|
||||
<?import java.lang.Integer?>
|
||||
<?import java.lang.String?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml">
|
||||
<Rectangle width="100" height="100" fill="darkcyan">
|
||||
<arcWidth>
|
||||
<Double fx:value="10"/>
|
||||
</arcWidth>
|
||||
<arcHeight>
|
||||
<Integer fx:value="40"/>
|
||||
</arcHeight>
|
||||
<VBox.vgrow>
|
||||
<Priority fx:value="always"/>
|
||||
</VBox.vgrow>
|
||||
<opacity>
|
||||
<String fx:value="0.5"/>
|
||||
</opacity>
|
||||
</Rectangle>
|
||||
</VBox>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.Priority?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.paint.Color?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml">
|
||||
<Rectangle width="100" height="100">
|
||||
<VBox.margin>
|
||||
<<error descr="Unable to coerce javafx.scene.paint.Color to javafx.geometry.Insets">Color</error> green="1"/>
|
||||
</VBox.margin>
|
||||
</Rectangle>
|
||||
<padding>
|
||||
<<error descr="Unable to coerce javafx.scene.layout.Priority to javafx.geometry.Insets">Priority</error> fx:value="always"/>
|
||||
</padding>
|
||||
</VBox>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import java.lang.Double?>
|
||||
<?import java.lang.Integer?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
|
||||
<padding>
|
||||
<<error descr="Unable to coerce java.lang.Integer to javafx.geometry.Insets">Integer</error> fx:value="10"/>
|
||||
</padding>
|
||||
<Rectangle width="100" height="100">
|
||||
<VBox.vgrow>
|
||||
<<error descr="Unable to coerce java.lang.Double to javafx.scene.layout.Priority">Double</error> fx:value="20"/>
|
||||
</VBox.vgrow>
|
||||
</Rectangle>
|
||||
</fx:root>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.paint.Color?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml">
|
||||
<Rectangle width="100" height="100">
|
||||
<fill>
|
||||
<<warning descr="Conversion between unrelated types, javafx.geometry.Insets to javafx.scene.paint.Paint">Insets</warning> top="10"/>
|
||||
</fill>
|
||||
</Rectangle>
|
||||
<spacing>
|
||||
<<warning descr="Conversion between unrelated types, javafx.scene.paint.Color to double">Color</warning> green="1"/>
|
||||
</spacing>
|
||||
</VBox>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import java.lang.Double?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
|
||||
<Rectangle width="100" height="100">
|
||||
<fill>
|
||||
<<warning descr="Conversion between unrelated types, java.lang.Double to javafx.scene.paint.Paint">Double</warning> fx:value="1"/>
|
||||
</fill>
|
||||
</Rectangle>
|
||||
</fx:root>
|
||||
Reference in New Issue
Block a user