mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Javafx: FXML inspection that validates event handler methods' signature. Use full generic event type in quickfix that creates event handler method. Use multi-reference for navigating to ambiguous event handler methods. (IDEA-151694)
This commit is contained in:
+8
@@ -60,6 +60,14 @@ public class JavaFXQuickfixTest extends LightCodeInsightFixtureTestCase {
|
||||
doTest("Create method 'void bar(ActionEvent)'", ".groovy");
|
||||
}
|
||||
|
||||
public void testCreateControllerMethodGeneric() throws Exception {
|
||||
doTest("Create method 'void onSort(SortEvent)'", ".java");
|
||||
}
|
||||
|
||||
public void testCreateControllerMethodHalfRaw() throws Exception {
|
||||
doTest("Create method 'void onSort(SortEvent)'", ".java");
|
||||
}
|
||||
|
||||
public void testCreateFieldPublicVisibility() throws Exception {
|
||||
doTestWithDefaultVisibility("Create field 'btn'", "CreateField", PsiModifier.PUBLIC, ".java");
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package org.jetbrains.plugins.javaFX.fxml;
|
||||
|
||||
import com.intellij.openapi.application.PluginPathManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxEventHandlerInspection;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class JavaFxEventHandlerInspectionTest extends AbstractJavaFXTestCase {
|
||||
|
||||
@Override
|
||||
protected void enableInspections() {
|
||||
myFixture.enableInspections(new JavaFxEventHandlerInspection());
|
||||
}
|
||||
|
||||
|
||||
public void testHighlightExact() throws Exception {
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
public void testHighlightNonVoid() throws Exception {
|
||||
final JavaFxEventHandlerInspection inspection = new JavaFxEventHandlerInspection();
|
||||
inspection.myDetectNonVoidReturnType = true;
|
||||
myFixture.enableInspections(inspection);
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
public void testHighlightAmbiguous() throws Exception {
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
public void testHighlightSimpleGeneric() throws Exception {
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
public void testHighlightMixedGeneric() throws Exception {
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
public void testHighlightWildcard() throws Exception {
|
||||
doHighlightingTest();
|
||||
}
|
||||
|
||||
private void doHighlightingTest() throws Exception {
|
||||
myFixture.configureByFiles(getTestName(true) + ".fxml", getTestName(false) + "Controller.java");
|
||||
myFixture.checkHighlighting();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PluginPathManager.getPluginHomePath("javaFX") + "/testData/inspections/eventHandler/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports event handler issues, including incompatible argument type, ambiguous event handler method, and incorrect return type.
|
||||
</body>
|
||||
</html>
|
||||
@@ -26,6 +26,8 @@
|
||||
enabledByDefault="true" level="WARNING" displayName="JavaFX redundant property values" groupName="JavaFX"/>
|
||||
<localInspection language="XML" shortName="JavaFxColorRgb" displayName="Color component is out of range" groupName="JavaFX"
|
||||
enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxColorRgbInspection"/>
|
||||
<localInspection language="XML" shortName="JavaFxEventHandler" displayName="Event handler method signature problem" groupName="JavaFX"
|
||||
enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxEventHandlerInspection"/>
|
||||
|
||||
<applicationService serviceInterface="org.jetbrains.plugins.javaFX.JavaFxSettings" serviceImplementation="org.jetbrains.plugins.javaFX.JavaFxSettings"/>
|
||||
<applicationConfigurable groupId="language" displayName="JavaFX" id="preferences.JavaFX" instance="org.jetbrains.plugins.javaFX.JavaFxSettingsConfigurable"/>
|
||||
|
||||
@@ -820,6 +820,40 @@ public class JavaFxPsiUtil {
|
||||
InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_MAP);
|
||||
}
|
||||
|
||||
public static boolean isNotFullyResolvedGeneric(@NotNull PsiClassType classType) {
|
||||
final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics();
|
||||
final PsiClass psiClass = resolveResult.getElement();
|
||||
if (psiClass == null || psiClass instanceof PsiTypeParameter) return true;
|
||||
final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
|
||||
for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(psiClass)) {
|
||||
final PsiType substitute = substitutor.substitute(parameter);
|
||||
if (substitute == null || substitute instanceof PsiClassType && isNotFullyResolvedGeneric((PsiClassType)substitute)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiSubstitutor getTagClassSubstitutor(@NotNull XmlTag xmlTag,
|
||||
@Nullable PsiClass fieldClass,
|
||||
@Nullable PsiClass controllerClass) {
|
||||
if (fieldClass != null && controllerClass != null) {
|
||||
final String tagFieldName = xmlTag.getAttributeValue(FxmlConstants.FX_ID);
|
||||
if (tagFieldName != null) {
|
||||
final PsiField tagField = controllerClass.findFieldByName(tagFieldName, true);
|
||||
if (tagField != null && !tagField.hasModifierProperty(PsiModifier.STATIC) && isVisibleInFxml(tagField)) {
|
||||
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(tagField.getType());
|
||||
final PsiClass resolvedClass = resolveResult.getElement();
|
||||
if (resolvedClass != null) {
|
||||
return TypeConversionUtil.getClassSubstitutor(fieldClass, resolvedClass, resolveResult.getSubstitutor());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class JavaFxControllerCachedValueProvider implements CachedValueProvider<PsiClass> {
|
||||
private final Project myProject;
|
||||
private final PsiFile myContainingFile;
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections;
|
||||
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
|
||||
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.xml.XmlElementDescriptor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.plugins.javaFX.fxml.JavaFxCommonNames;
|
||||
import org.jetbrains.plugins.javaFX.fxml.JavaFxFileTypeFactory;
|
||||
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil.isNotFullyResolvedGeneric;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class JavaFxEventHandlerInspection extends XmlSuppressableInspectionTool {
|
||||
public boolean myDetectNonVoidReturnType;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new XmlElementVisitor() {
|
||||
@Override
|
||||
public void visitXmlFile(XmlFile file) {
|
||||
if (!JavaFxFileTypeFactory.isFxml(file)) return;
|
||||
super.visitXmlFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitXmlAttribute(XmlAttribute attribute) {
|
||||
super.visitXmlAttribute(attribute);
|
||||
|
||||
final PsiFile containingFile = attribute.getContainingFile();
|
||||
final PsiClass controllerClass = JavaFxPsiUtil.getControllerClass(containingFile);
|
||||
if (controllerClass == null) return;
|
||||
|
||||
final String valueText = attribute.getValue();
|
||||
if (valueText == null || !valueText.startsWith("#")) return;
|
||||
final String eventHandlerMethodName = valueText.substring(1);
|
||||
|
||||
List<PsiMethod> eventHandlerMethods =
|
||||
Arrays.stream(controllerClass.findMethodsByName(eventHandlerMethodName, true))
|
||||
.filter(method -> !method.hasModifierProperty(PsiModifier.STATIC) && JavaFxPsiUtil.isVisibleInFxml(method))
|
||||
.filter(JavaFxEventHandlerInspection::hasEventArgument)
|
||||
.collect(Collectors.toList());
|
||||
if (eventHandlerMethods.size() == 0) return;
|
||||
if (eventHandlerMethods.size() != 1) {
|
||||
holder.registerProblem(getHighlightedElement(attribute), "Ambiguous event handler name: more than one matching method found");
|
||||
}
|
||||
|
||||
if (myDetectNonVoidReturnType) {
|
||||
eventHandlerMethods.stream()
|
||||
.map(PsiMethod::getReturnType)
|
||||
.filter(returnType -> !PsiType.VOID.equals(returnType))
|
||||
.findAny()
|
||||
.ifPresent(ignored -> holder.registerProblem(getHighlightedElement(attribute), "Return type of event handler should be void"));
|
||||
}
|
||||
|
||||
final PsiClassType eventClassType = getHandlerArgumentClassType(attribute, controllerClass);
|
||||
if (eventClassType == null) return;
|
||||
final boolean eventClassTypeNotFullyResolved = isNotFullyResolvedGeneric(eventClassType);
|
||||
|
||||
eventHandlerMethods.stream()
|
||||
.map(method -> method.getParameterList().getParameters())
|
||||
.filter(parameters -> parameters.length == 1)
|
||||
.map(parameters -> parameters[0].getType())
|
||||
.forEach(argType -> {
|
||||
if (argType instanceof PsiClassType) {
|
||||
final boolean assignable;
|
||||
if (eventClassTypeNotFullyResolved || isNotFullyResolvedGeneric((PsiClassType)argType)) {
|
||||
assignable = isAssignableFromIgnoringUnresolved((PsiClassType)argType, eventClassType);
|
||||
}
|
||||
else {
|
||||
assignable = argType.isAssignableFrom(eventClassType);
|
||||
}
|
||||
if (!assignable) {
|
||||
holder.registerProblem(getHighlightedElement(attribute),
|
||||
"Incompatible event handler argument: " + argType.getPresentableText() +
|
||||
" is not assignable from " + eventClassType.getPresentableText());
|
||||
}
|
||||
}
|
||||
else {
|
||||
holder.registerProblem(getHighlightedElement(attribute),
|
||||
"Unresolved event handler argument type " + argType.getCanonicalText());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isAssignableFromIgnoringUnresolved(@Nullable PsiClassType left, @Nullable PsiClassType right) {
|
||||
if (left == null || right == null) return true;
|
||||
final PsiClassType.ClassResolveResult leftResolveResult = left.resolveGenerics();
|
||||
final PsiClassType.ClassResolveResult rightResolveResult = right.resolveGenerics();
|
||||
|
||||
final PsiClass leftClass = leftResolveResult.getElement();
|
||||
final PsiClass rightClass = rightResolveResult.getElement();
|
||||
|
||||
if (leftClass == null || rightClass == null || leftClass instanceof PsiTypeParameter || rightClass instanceof PsiTypeParameter) {
|
||||
return true;
|
||||
}
|
||||
if (leftClass.getManager().areElementsEquivalent(leftClass, rightClass)) {
|
||||
// generic args are assignable or unresolved
|
||||
if (!leftClass.hasTypeParameters()) return true;
|
||||
final PsiSubstitutor leftSubstitutor = leftResolveResult.getSubstitutor();
|
||||
final PsiSubstitutor rightSubstitutor = rightResolveResult.getSubstitutor();
|
||||
final PsiTypeParameter[] typeParameters = leftClass.getTypeParameters();
|
||||
for (PsiTypeParameter typeParameter : typeParameters) {
|
||||
final PsiType leftParameter = leftSubstitutor.substitute(typeParameter);
|
||||
if (leftParameter == null || leftParameter instanceof PsiTypeParameter) continue;
|
||||
final PsiType rightParameter = rightSubstitutor.substitute(typeParameter);
|
||||
if (rightParameter == null || rightParameter instanceof PsiTypeParameter) continue;
|
||||
final PsiClassType leftParameterClass = getBoundClassType(leftParameter, true);
|
||||
final PsiClassType rightParameterClass = getBoundClassType(rightParameter, false);
|
||||
if (!isAssignableFromIgnoringUnresolved(leftParameterClass, rightParameterClass)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (leftClass.isInheritor(rightClass, true)) {
|
||||
// generic args are equal
|
||||
final PsiType rightSubstitute = leftResolveResult.getSubstitutor().substitute(right);
|
||||
return rightSubstitute.isAssignableFrom(left);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClassType getBoundClassType(PsiType psiType, boolean isSuper) {
|
||||
if (psiType instanceof PsiClassType) {
|
||||
return (PsiClassType)psiType;
|
||||
}
|
||||
if (psiType instanceof PsiWildcardType) {
|
||||
PsiWildcardType wildcardType = (PsiWildcardType)psiType;
|
||||
if (isSuper && wildcardType.isSuper() || !isSuper && wildcardType.isExtends()) {
|
||||
final PsiType bound = wildcardType.getBound();
|
||||
if (bound instanceof PsiClassType) return (PsiClassType)bound;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiElement getHighlightedElement(XmlAttribute attribute) {
|
||||
final XmlAttributeValue valueElement = attribute.getValueElement();
|
||||
return valueElement != null ? valueElement : attribute;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClassType getHandlerArgumentClassType(@NotNull XmlAttribute attribute, PsiClass controllerClass) {
|
||||
final XmlTag xmlTag = attribute.getParent();
|
||||
final XmlElementDescriptor descriptor = xmlTag.getDescriptor();
|
||||
if (descriptor == null) return null;
|
||||
final PsiElement declaration = descriptor.getDeclaration();
|
||||
if (!(declaration instanceof PsiClass)) return null;
|
||||
final PsiClass tagClass = (PsiClass)declaration;
|
||||
|
||||
final String handlerSetterName = PropertyUtil.suggestSetterName(attribute.getName());
|
||||
final PsiMethod[] handlerSetterCandidates = tagClass.findMethodsByName(handlerSetterName, true);
|
||||
if (handlerSetterCandidates.length == 0) return null;
|
||||
|
||||
final Project project = attribute.getProject();
|
||||
final PsiClass javaFxEventHandlerClass = JavaPsiFacade.getInstance(project).findClass(JavaFxCommonNames.JAVAFX_EVENT_EVENT_HANDLER,
|
||||
GlobalSearchScope.allScope(project));
|
||||
if (javaFxEventHandlerClass != null) {
|
||||
final PsiTypeParameter[] typeParameters = javaFxEventHandlerClass.getTypeParameters();
|
||||
if (typeParameters.length == 1) {
|
||||
final PsiTypeParameter javaFxEventHandlerTypeParameter = typeParameters[0];
|
||||
final PsiSubstitutor tagClassSubstitutor = JavaFxPsiUtil.getTagClassSubstitutor(xmlTag, tagClass, controllerClass);
|
||||
for (PsiMethod handlerSetter : handlerSetterCandidates) {
|
||||
if (handlerSetter.hasModifierProperty(PsiModifier.STATIC) || !handlerSetter.hasModifierProperty(PsiModifier.PUBLIC)) continue;
|
||||
final PsiParameter[] parameters = handlerSetter.getParameterList().getParameters();
|
||||
if (parameters.length == 1) {
|
||||
PsiType handlerParameterType = parameters[0].getType();
|
||||
if (tagClassSubstitutor != null) {
|
||||
handlerParameterType = tagClassSubstitutor.substitute(handlerParameterType);
|
||||
}
|
||||
if (handlerParameterType instanceof PsiClassType &&
|
||||
InheritanceUtil.isInheritorOrSelf(((PsiClassType)handlerParameterType).resolve(), javaFxEventHandlerClass, true)) {
|
||||
final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)handlerParameterType).resolveGenerics();
|
||||
final PsiType eventType = resolveResult.getSubstitutor().substitute(javaFxEventHandlerTypeParameter);
|
||||
PsiClassType eventClassType = getBoundClassType(eventType, true);
|
||||
if (eventClassType != null) return eventClassType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasEventArgument(@NotNull PsiMethod method) {
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
return parameters.length == 0 ||
|
||||
parameters.length == 1 && InheritanceUtil.isInheritor(parameters[0].getType(), JavaFxCommonNames.JAVAFX_EVENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createOptionsPanel() {
|
||||
return new SingleCheckboxOptionsPanel("Detect event handler method having non-void return type", this, "myDetectNonVoidReturnType");
|
||||
}
|
||||
}
|
||||
+27
-11
@@ -26,6 +26,7 @@ import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -71,7 +72,8 @@ public class JavaFxEventHandlerReference extends PsiReferenceBase<XmlAttributeVa
|
||||
}
|
||||
|
||||
public static boolean isHandlerMethod(PsiMethod psiMethod) {
|
||||
if (!psiMethod.hasModifierProperty(PsiModifier.STATIC) && PsiType.VOID.equals(psiMethod.getReturnType())) {
|
||||
if (!psiMethod.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
JavaFxPsiUtil.isVisibleInFxml(psiMethod)) {
|
||||
final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
if (parameters.length == 1) {
|
||||
final PsiType parameterType = parameters[0].getType();
|
||||
@@ -108,19 +110,33 @@ public class JavaFxEventHandlerReference extends PsiReferenceBase<XmlAttributeVa
|
||||
final PsiElement parent = element.getParent();
|
||||
if (parent instanceof XmlAttribute) {
|
||||
final XmlAttribute xmlAttribute = (XmlAttribute)parent;
|
||||
final Project project = element.getProject();
|
||||
final PsiField handlerField = ref.myCurrentTagClass.findFieldByName(xmlAttribute.getName(), true);
|
||||
if (handlerField != null) {
|
||||
final PsiClassType classType = JavaFxPsiUtil.getPropertyClassType(handlerField);
|
||||
if (classType != null) {
|
||||
final XmlTag xmlTag = xmlAttribute.getParent();
|
||||
final PsiSubstitutor currentTagClassSubstitutor =
|
||||
JavaFxPsiUtil.getTagClassSubstitutor(xmlTag, ref.myCurrentTagClass, ref.myController);
|
||||
PsiType handlerType = JavaFxPsiUtil.getPropertyClassType(handlerField);
|
||||
if (currentTagClassSubstitutor != null) {
|
||||
handlerType = currentTagClassSubstitutor.substitute(handlerType);
|
||||
}
|
||||
if (handlerType instanceof PsiClassType) {
|
||||
final Project project = element.getProject();
|
||||
final PsiClass eventHandlerClass = JavaPsiFacade.getInstance(project).findClass(JavaFxCommonNames.JAVAFX_EVENT_EVENT_HANDLER, GlobalSearchScope.allScope(project));
|
||||
final PsiTypeParameter[] typeParameters = eventHandlerClass != null ? eventHandlerClass.getTypeParameters() : null;
|
||||
if (typeParameters != null && typeParameters.length == 1) {
|
||||
final PsiTypeParameter typeParameter = typeParameters[0];
|
||||
final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(eventHandlerClass, classType);
|
||||
final PsiType eventType = substitutor.substitute(typeParameter);
|
||||
if (eventType != null) {
|
||||
canonicalText = eventType.getCanonicalText();
|
||||
if (eventHandlerClass != null) {
|
||||
final PsiTypeParameter[] typeParameters = eventHandlerClass.getTypeParameters();
|
||||
if (typeParameters.length == 1) {
|
||||
final PsiTypeParameter typeParameter = typeParameters[0];
|
||||
final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(eventHandlerClass,
|
||||
(PsiClassType)handlerType);
|
||||
final PsiType eventType = substitutor.substitute(typeParameter);
|
||||
if (eventType != null) {
|
||||
if (eventType instanceof PsiClassType && JavaFxPsiUtil.isNotFullyResolvedGeneric((PsiClassType)eventType)) {
|
||||
canonicalText = ((PsiClassType)eventType).rawType().getCanonicalText();
|
||||
}
|
||||
else {
|
||||
canonicalText = eventType.getCanonicalText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-9
@@ -20,6 +20,7 @@ import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
@@ -30,6 +31,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
|
||||
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* User: anna
|
||||
* Date: 1/16/13
|
||||
@@ -49,23 +52,30 @@ class JavaFxEventHandlerReferenceProvider extends JavaFxControllerBasedReference
|
||||
if (!JavaFxPsiUtil.checkIfAttributeHandler(attribute)) return PsiReference.EMPTY_ARRAY;
|
||||
final XmlElementDescriptor descriptor = attribute.getParent().getDescriptor();
|
||||
if (descriptor == null) return PsiReference.EMPTY_ARRAY;
|
||||
final PsiElement currentTagClass = descriptor.getDeclaration();
|
||||
final PsiElement declaration = descriptor.getDeclaration();
|
||||
if (!(declaration instanceof PsiClass)) return PsiReference.EMPTY_ARRAY;
|
||||
final PsiClass currentTagClass = ((PsiClass)declaration);
|
||||
final String eventHandlerName = attValueString.substring(1);
|
||||
final PsiMethod[] methods = controllerClass.findMethodsByName(eventHandlerName, true);
|
||||
|
||||
PsiMethod handlerMethod = null;
|
||||
for (PsiMethod psiMethod : methods) {
|
||||
if (JavaFxEventHandlerReference.isHandlerMethod(psiMethod)) {
|
||||
handlerMethod = psiMethod;
|
||||
break;
|
||||
}
|
||||
final PsiReference[] references = Arrays.stream(methods)
|
||||
.filter(JavaFxEventHandlerReference::isHandlerMethod)
|
||||
.map(handlerMethod -> new JavaFxEventHandlerReference(xmlAttributeValue, currentTagClass, handlerMethod, controllerClass))
|
||||
.toArray(PsiReference.ARRAY_FACTORY::create);
|
||||
|
||||
if (references.length == 1) {
|
||||
return references;
|
||||
}
|
||||
if (handlerMethod == null) {
|
||||
if (references.length > 1) {
|
||||
return new PsiReference[]{new PsiMultiReference(references, xmlAttributeValue)};
|
||||
}
|
||||
|
||||
if (references.length == 0) {
|
||||
final XmlTag rootTag = ((XmlFile)xmlAttributeValue.getContainingFile()).getRootTag();
|
||||
if (rootTag == null || FxmlConstants.FX_ROOT.equals(rootTag.getName())) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
return new PsiReference[]{new JavaFxEventHandlerReference(xmlAttributeValue, (PsiClass)currentTagClass, handlerMethod, controllerClass)};
|
||||
return new PsiReference[]{new JavaFxEventHandlerReference(xmlAttributeValue, currentTagClass, null, controllerClass)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
|
||||
public class HighlightAmbiguousController {
|
||||
public void onArgType(ActionEvent e) {}
|
||||
public void onArgType(Event e) {}
|
||||
public void onNoArg(ActionEvent e) {}
|
||||
public void onNoArg() {}
|
||||
public void onNotEvent(Event e) {}
|
||||
public void onNotEvent(String notEvent) {}
|
||||
public void onNotEvent(int notEvent) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
|
||||
public class HighlightExactController {
|
||||
public void onSameArg(ActionEvent e) {}
|
||||
public void onSuperArg(Event e) {}
|
||||
public void onNoArg() {}
|
||||
public String onSameArgNotVoid(ActionEvent e) {return "";}
|
||||
public Boolean onSuperArgNotVoid(Event e) {return false;}
|
||||
public int onNoArgNotVoid() {return 1;}
|
||||
public void onIncompatible(ScrollEvent e) {}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.util.Pair;
|
||||
import java.util.Map;
|
||||
|
||||
public class HighlightMixedGenericController {
|
||||
@FXML TableView<Pair<Integer, String>> specificTable;
|
||||
@FXML TableView<Pair<Integer, String>> specificTableHalfRawArg;
|
||||
@FXML TableView<Pair<Integer, String>> specificTableIncompatibleArg;
|
||||
@FXML TableView<Pair<Integer, String>> specificTableIncompatibleHalfRawArg;
|
||||
|
||||
@FXML TableView<Pair> halfRawTable;
|
||||
@FXML TableView<Pair> halfRawTableHalfRawArg;
|
||||
@FXML TableView<Pair> halfRawTableIncompatibleArg;
|
||||
@FXML TableView<Pair> halfRawTableIncompatibleHalfRawArg;
|
||||
|
||||
@FXML TableView rawTable;
|
||||
@FXML TableView rawTableHalfRawArg;
|
||||
@FXML TableView rawTableIncompatibleArg;
|
||||
@FXML TableView rawTableIncompatibleHalfRawArg;
|
||||
|
||||
public void onSort(SortEvent<TableView<Pair<Integer, String>>> e) {}
|
||||
public void onScrollTo(ScrollToEvent<Integer> e) {}
|
||||
public void onScrollToColumn(ScrollToEvent<TableColumn<Pair<Integer, String>, ?>> e) {}
|
||||
|
||||
public void onSortHalfRawArg(SortEvent<TableView> e) {}
|
||||
public void onScrollToHalfRawArg(ScrollToEvent<? extends Number> e) {}
|
||||
public void onScrollToColumnHalfRawArg(ScrollToEvent<TableColumn> e) {}
|
||||
|
||||
public void onSortIncompatibleArg(SortEvent<TableView<Pair<Double, String>>> e) {}
|
||||
public void onScrollToIncompatibleArg(ScrollToEvent<Double> e) {}
|
||||
public void onScrollToColumnIncompatibleArg(ScrollToEvent<TableColumn<Map.Entry<Integer, String>, ?>> e) {}
|
||||
|
||||
public void onSortIncompatibleHalfRawArg(SortEvent<Pair<?, String>> e) {}
|
||||
public void onScrollToIncompatibleHalfRawArg(ScrollToEvent<? super Number> e) {}
|
||||
public void onScrollToColumnIncompatibleHalfRawArg(ScrollToEvent<TableCell<Pair, ?>> e) {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
|
||||
public class HighlightNonVoidController {
|
||||
public String onSameArgNotVoid(ActionEvent e) {return "";}
|
||||
public Boolean onSuperArgNotVoid(Event e) {return false;}
|
||||
public int onNoArgNotVoid() {return 1;}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
|
||||
public class HighlightSimpleGenericController {
|
||||
@FXML ListView<String> list;
|
||||
|
||||
public void onEditStart(ListView.EditEvent<?> e) {}
|
||||
public void onEditCancel(ListView.EditEvent<String> e) {}
|
||||
public void onEditCommit(ListView.EditEvent e) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import javafx.event.Event;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.SortEvent;
|
||||
import javafx.scene.input.MouseDragEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
public class HighlightWildcardController {
|
||||
@FXML private void onSameArg(MouseEvent e) {}
|
||||
@FXML private void onSuperArg(Event e) {}
|
||||
@FXML private void onNoArg() {}
|
||||
@FXML private void onNotSuper(MouseDragEvent e) {}
|
||||
@FXML private void onNotRelated(SortEvent e) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightAmbiguousController">
|
||||
<Button text="ArgType" onAction=<warning descr="Ambiguous event handler name: more than one matching method found">"#onArgType"</warning>/>
|
||||
<Button text="NoArg" onAction=<warning descr="Ambiguous event handler name: more than one matching method found">"#onNoArg"</warning>/>
|
||||
<Button text="NotEvent" onAction="#onNotEvent"/>
|
||||
</HBox>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightExactController">
|
||||
<Button text="SameArg" onAction="#onSameArg"/>
|
||||
<Button text="SuperArg" onAction="#onSuperArg"/>
|
||||
<Button text="NoArg" onAction="#onNoArg"/>
|
||||
<Button text="SameArgNotVoid" onAction="#onSameArgNotVoid"/>
|
||||
<Button text="SuperArgNotVoid" onAction="#onSuperArgNotVoid"/>
|
||||
<Button text="NoArgNotVoid" onAction="#onNoArgNotVoid"/>
|
||||
<Button text="Incompatible" onAction=<warning descr="Incompatible event handler argument: ScrollEvent is not assignable from ActionEvent">"#onIncompatible"</warning>/>
|
||||
</HBox>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightMixedGenericController">
|
||||
<TableView fx:id="specificTable"
|
||||
onSort="#onSort"
|
||||
onScrollTo="#onScrollTo"
|
||||
onScrollToColumn="#onScrollToColumn"/>
|
||||
<TableView fx:id="specificTableHalfRawArg"
|
||||
onSort="#onSortHalfRawArg"
|
||||
onScrollTo="#onScrollToHalfRawArg"
|
||||
onScrollToColumn="#onScrollToColumnHalfRawArg"/>
|
||||
<TableView fx:id="specificTableIncompatibleArg"
|
||||
onSort=<warning descr="Incompatible event handler argument: SortEvent<TableView<Pair<Double, String>>> is not assignable from SortEvent<TableView<Pair<Integer, String>>>">"#onSortIncompatibleArg"</warning>
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<Double> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableColumn<Entry<Integer, String>, ?>> is not assignable from ScrollToEvent<TableColumn<Pair<Integer, String>, ?>>">"#onScrollToColumnIncompatibleArg"</warning>/>
|
||||
<TableView fx:id="specificTableIncompatibleHalfRawArg"
|
||||
onSort=<warning descr="Incompatible event handler argument: SortEvent<Pair<?, String>> is not assignable from SortEvent<TableView<Pair<Integer, String>>>">"#onSortIncompatibleHalfRawArg"</warning>
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<? super Number> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleHalfRawArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableCell<Pair, ?>> is not assignable from ScrollToEvent<TableColumn<Pair<Integer, String>, ?>>">"#onScrollToColumnIncompatibleHalfRawArg"</warning>/>
|
||||
|
||||
<TableView fx:id="halfRawTable"
|
||||
onSort="#onSort"
|
||||
onScrollTo="#onScrollTo"
|
||||
onScrollToColumn="#onScrollToColumn"/>
|
||||
<TableView fx:id="halfRawTableHalfRawArg"
|
||||
onSort="#onSortHalfRawArg"
|
||||
onScrollTo="#onScrollToHalfRawArg"
|
||||
onScrollToColumn="#onScrollToColumnHalfRawArg"/>
|
||||
<TableView fx:id="halfRawTableIncompatibleArg"
|
||||
onSort="#onSortIncompatibleArg"
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<Double> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableColumn<Entry<Integer, String>, ?>> is not assignable from ScrollToEvent<TableColumn<Pair, ?>>">"#onScrollToColumnIncompatibleArg"</warning>/>
|
||||
<TableView fx:id="halfRawTableIncompatibleHalfRawArg"
|
||||
onSort=<warning descr="Incompatible event handler argument: SortEvent<Pair<?, String>> is not assignable from SortEvent<TableView<Pair>>">"#onSortIncompatibleHalfRawArg"</warning>
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<? super Number> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleHalfRawArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableCell<Pair, ?>> is not assignable from ScrollToEvent<TableColumn<Pair, ?>>">"#onScrollToColumnIncompatibleHalfRawArg"</warning>/>
|
||||
|
||||
<TableView fx:id="rawTable"
|
||||
onSort="#onSort"
|
||||
onScrollTo="#onScrollTo"
|
||||
onScrollToColumn="#onScrollToColumn"/>
|
||||
<TableView fx:id="rawTableHalfRawArg"
|
||||
onSort="#onSortHalfRawArg"
|
||||
onScrollTo="#onScrollToHalfRawArg"
|
||||
onScrollToColumn="#onScrollToColumnHalfRawArg"/>
|
||||
<TableView fx:id="rawTableIncompatibleArg"
|
||||
onSort="#onSortIncompatibleArg"
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<Double> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleArg"</warning>
|
||||
onScrollToColumn="#onScrollToColumnIncompatibleArg"/>
|
||||
<TableView fx:id="rawTableIncompatibleHalfRawArg"
|
||||
onSort=<warning descr="Incompatible event handler argument: SortEvent<Pair<?, String>> is not assignable from SortEvent<TableView>">"#onSortIncompatibleHalfRawArg"</warning>
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<? super Number> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleHalfRawArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableCell<Pair, ?>> is not assignable from ScrollToEvent<TableColumn>">"#onScrollToColumnIncompatibleHalfRawArg"</warning>/>
|
||||
|
||||
<TableView
|
||||
onSort="#onSort"
|
||||
onScrollTo="#onScrollTo"
|
||||
onScrollToColumn="#onScrollToColumn"/>
|
||||
<TableView
|
||||
onSort="#onSortHalfRawArg"
|
||||
onScrollTo="#onScrollToHalfRawArg"
|
||||
onScrollToColumn="#onScrollToColumnHalfRawArg"/>
|
||||
<TableView
|
||||
onSort="#onSortIncompatibleArg"
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<Double> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleArg"</warning>
|
||||
onScrollToColumn="#onScrollToColumnIncompatibleArg"/>
|
||||
<TableView
|
||||
onSort=<warning descr="Incompatible event handler argument: SortEvent<Pair<?, String>> is not assignable from SortEvent<TableView<S>>">"#onSortIncompatibleHalfRawArg"</warning>
|
||||
onScrollTo=<warning descr="Incompatible event handler argument: ScrollToEvent<? super Number> is not assignable from ScrollToEvent<Integer>">"#onScrollToIncompatibleHalfRawArg"</warning>
|
||||
onScrollToColumn=<warning descr="Incompatible event handler argument: ScrollToEvent<TableCell<Pair, ?>> is not assignable from ScrollToEvent<TableColumn<S, ?>>">"#onScrollToColumnIncompatibleHalfRawArg"</warning>/>
|
||||
</VBox>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightNonVoidController">
|
||||
<Button text="SameArgNotVoid" onAction=<warning descr="Return type of event handler should be void">"#onSameArgNotVoid"</warning>/>
|
||||
<Button text="SuperArgNotVoid" onAction=<warning descr="Return type of event handler should be void">"#onSuperArgNotVoid"</warning>/>
|
||||
<Button text="NoArgNotVoid" onAction=<warning descr="Return type of event handler should be void">"#onNoArgNotVoid"</warning>/>
|
||||
</HBox>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightSimpleGenericController">
|
||||
<ListView fx:id="list" onEditStart="#onEditStart" onEditCancel="#onEditCancel" onEditCommit="#onEditCommit"/>
|
||||
</VBox>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml" fx:controller="HighlightWildcardController">
|
||||
<Label text=" SameArg " onMouseClicked="#onSameArg"/>
|
||||
<Label text=" SuperArg " onMouseClicked="#onSuperArg"/>
|
||||
<Label text=" NoArg " onMouseClicked="#onNoArg"/>
|
||||
<Label text=" NotSuper " onMouseClicked=<warning descr="Incompatible event handler argument: MouseDragEvent is not assignable from MouseEvent">"#onNotSuper"</warning>/>
|
||||
<Label text=" NotRelated " onMouseClicked=<warning descr="Incompatible event handler argument: SortEvent is not assignable from MouseEvent">"#onNotRelated"</warning>/>
|
||||
</HBox>
|
||||
@@ -0,0 +1,9 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableView;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CreateControllerMethodGeneric {
|
||||
@FXML
|
||||
TableView<Map<Integer, String>> table;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.SortEvent;
|
||||
import javafx.scene.control.TableView;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CreateControllerMethodGeneric {
|
||||
@FXML
|
||||
TableView<Map<Integer, String>> table;
|
||||
|
||||
public void onSort(SortEvent<TableView<Map<Integer, String>>> tableViewSortEvent) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableView;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CreateControllerMethodHalfRaw {
|
||||
@FXML
|
||||
TableView<Map> table;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.SortEvent;
|
||||
import javafx.scene.control.TableView;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CreateControllerMethodHalfRaw {
|
||||
@FXML
|
||||
TableView<Map> table;
|
||||
|
||||
public void onSort(SortEvent sortEvent) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane xmlns:fx="http://javafx.com/fxml" fx:controller="CreateControllerMethodGeneric">
|
||||
<TableView fx:id="table" onSort="#o<caret>nSort"/>
|
||||
</GridPane>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane xmlns:fx="http://javafx.com/fxml" fx:controller="CreateControllerMethodHalfRaw">
|
||||
<TableView fx:id="table" onSort="#o<caret>nSort"/>
|
||||
</GridPane>
|
||||
Reference in New Issue
Block a user