package com.intellij.lang.javascript.inspections.actionscript;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.javascript.flex.mxml.FlexCommonTypeNames;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.javascript.JSBundle;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.ActionScriptSmartCompletionContributor;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.impl.JSChangeUtil;
import com.intellij.lang.javascript.psi.resolve.ActionScriptResolveUtil;
import com.intellij.lang.javascript.psi.resolve.JSClassResolver;
import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil;
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil;
import com.intellij.lang.javascript.validation.JSProblemReporter;
import com.intellij.lang.javascript.validation.JSTypeChecker;
import com.intellij.lang.javascript.validation.fixes.ChangeSignatureFix;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.Map;
import static com.intellij.lang.javascript.psi.JSCommonTypeNames.FUNCTION_CLASS_NAME;
/**
* @author Konstantin.Ulitin
*/
public class ActionScriptTypeChecker extends JSTypeChecker<Annotation> {
private final JSProblemReporter<Annotation> myReporter;
public ActionScriptTypeChecker(JSProblemReporter<Annotation> reporter) {
myReporter = reporter;
}
@Override
public Annotation registerProblem(PsiElement place,
String message,
@Nullable ProblemHighlightType highlightType,
LocalQuickFix... fixes) {
return myReporter
.registerProblem(place, message, highlightType, getValidateTypesInspectionId(), fixes);
}
@Override
public void checkExpressionIsAssignableToVariable(JSVariable p,
final JSExpression expr,
PsiFile containingFile,
@PropertyKey(resourceBundle = JSBundle.BUNDLE) String problemKey,
boolean allowChangeVariableTypeFix) {
final JSType type = p.getType();
Pair<Annotation, String> annotationAndExprType =
checkExpressionIsAssignableToType(expr, type, problemKey,
allowChangeVariableTypeFix ? p : null);
if (annotationAndExprType != null &&
p.getParent() instanceof JSParameterList &&
expr.getParent() instanceof JSArgumentList &&
!JSCommonTypeNames.VOID_TYPE_NAME.equals(annotationAndExprType.second)) {
JSFunction method = (JSFunction)p.getParent().getParent();
JSFunction topMethod = JSInheritanceUtil.findTopMethods(method).iterator().next();
annotationAndExprType.first.registerFix(new ChangeSignatureFix(topMethod, (JSArgumentList)expr.getParent()));
}
PsiElement _fun;
if (annotationAndExprType == null &&
type != null && FUNCTION_CLASS_NAME.equals(type.getResolvedTypeText()) &&
p instanceof JSParameter &&
isAddEventListenerMethod((JSFunction)p.getParent().getParent()) &&
(( expr instanceof JSReferenceExpression &&
(_fun = ((JSReferenceExpression)expr).resolve()) instanceof JSFunction
) ||
(
expr instanceof JSFunctionExpression &&
(_fun = expr) != null
)
)) {
JSFunction fun = (JSFunction)_fun;
JSParameterList parameterList = fun.getParameterList();
if (parameterList != null) {
JSParameter[] parameters = parameterList.getParameterVariables();
boolean invalidArgs = parameters.length == 0;
if (!invalidArgs && parameters.length > 1) {
for(int i = parameters.length - 1; i > 0; --i) {
if (!parameters[i].isRest() && parameters[i].getInitializer() == null) {
invalidArgs = true;
break;
}
}
}
Computable.NotNullCachedComputable<JSParameterList> expectedParameterListForEventListener =
new Computable.NotNullCachedComputable<JSParameterList>() {
@NotNull
@Override
protected JSParameterList internalCompute() {
JSClass jsClass = calcNontrivialExpectedEventType(expr);
ASTNode treeFromText =
JSChangeUtil.createJSTreeFromText(
expr.getProject(),
"function f(event:" + (jsClass != null ? jsClass.getQualifiedName() : FlexCommonTypeNames.FLASH_EVENT_FQN) + ") {}",
JavaScriptSupportLoader.ECMA_SCRIPT_L4
);
return ((JSFunction)treeFromText.getPsi()).getParameterList();
}
};
if (invalidArgs) {
PsiElement expr_;
if (expr instanceof JSFunctionExpression) {
expr_ = ((JSFunctionExpression)expr).getParameterList();
}
else {
expr_ = expr;
}
registerProblem(
expr_,
JSBundle.message("javascript.callback.signature.mismatch"),
ProblemHighlightType.WEAK_WARNING,
new ChangeSignatureFix(fun, expectedParameterListForEventListener)
);
} else {
final JSClass expectedEventClass = calcNontrivialExpectedEventType(expr);
JSType paramType = parameters[0].getType();
final String actualParameterType = paramType != null ? paramType.getResolvedTypeText() : null;
if (expectedEventClass == null) {
if (!JSResolveUtil.isAssignableType(FlexCommonTypeNames.FLASH_EVENT_FQN, actualParameterType, parameters[0]) &&
!JSResolveUtil.isAssignableType(FlexCommonTypeNames.STARLING_EVENT_FQN, actualParameterType, parameters[0])) {
registerProblem(
expr instanceof JSFunctionExpression ? parameters[0] : expr,
JSBundle.message("javascript.callback.signature.mismatch"),
ProblemHighlightType.WEAK_WARNING,
new ChangeSignatureFix(fun, expectedParameterListForEventListener)
);
}
}
else {
if (!JSResolveUtil.isAssignableType(actualParameterType, expectedEventClass.getQualifiedName(), parameters[0])) {
registerProblem(
expr instanceof JSFunctionExpression ? parameters[0] : expr,
JSBundle.message("javascript.callback.signature.mismatch.event.class", expectedEventClass.getQualifiedName()),
ProblemHighlightType.WEAK_WARNING,
new ChangeSignatureFix(fun, expectedParameterListForEventListener)
);
}
}
}
}
}
}
private static boolean isAddEventListenerMethod(final JSFunction method) {
if (ActionScriptResolveUtil.ADD_EVENT_LISTENER_METHOD.equals(method.getName())) {
PsiElement methodParent = method.getParent();
if (methodParent instanceof JSClass) {
JSClass declaringClass = (JSClass)methodParent;
if (JSResolveUtil.isAssignableType(FlexCommonTypeNames.FLASH_IEVENT_DISPATCHER_FQN, declaringClass.getQualifiedName(), method)
|| ActionScriptClassResolver.isParentClass(declaringClass, FlexCommonTypeNames.STARLING_EVENT_DISPATCHER_FQN, false)) {
return true;
}
}
}
return false;
}
@Nullable
private static JSClass calcNontrivialExpectedEventType(JSExpression expr) {
JSExpression prevExpr = PsiTreeUtil.findChildOfAnyType(expr.getParent(), JSExpression.class);
String type = null;
JSExpression adHocQualifierExpr = null;
if (prevExpr instanceof JSReferenceExpression && prevExpr != expr) {
PsiElement constantRef = ((JSReferenceExpression)prevExpr).resolve();
if (constantRef instanceof JSVariable) {
final String initializerText = ((JSVariable)constantRef).getLiteralOrReferenceInitializerText();
if (initializerText != null &&
(StringUtil.startsWith(initializerText, "\'") ||
StringUtil.startsWith(initializerText, "\"")
)) {
type = StringUtil.stripQuotesAroundValue(initializerText);
}
}
adHocQualifierExpr = ((JSReferenceExpression)prevExpr).getQualifier();
} else if (prevExpr instanceof JSLiteralExpression) {
type = StringUtil.stripQuotesAroundValue(prevExpr.getText());
}
if (type != null) {
JSExpression methodExpression = ((JSCallExpression)expr.getParent().getParent()).getMethodExpression();
if (methodExpression instanceof JSReferenceExpression) {
JSClass clazz = ActionScriptSmartCompletionContributor.findClassOfQualifier((JSReferenceExpression)methodExpression);
if (clazz != null) {
Map<String,String> eventsMap = ActionScriptSmartCompletionContributor.getEventsMap(clazz);
String qName = eventsMap.get(type);
if (qName != null) {
PsiElement classFromNamespace = JSClassResolver.findClassFromNamespace(qName, clazz);
if (classFromNamespace instanceof JSClass) return (JSClass)classFromNamespace;
// if uncomment next 2 lines then the following event listener parameter won't be highlighted with warning
// new Sprite().addEventListener(ErrorEvent.ERROR, function(e:AccelerometerEvent):void{})
//} else if (JSInheritanceUtil.isParentClass(clazz, "flash.events.EventDispatcher", true)) {
// adHocQualifierExpr = null;
}
}
}
}
if (adHocQualifierExpr instanceof JSReferenceExpression) {
PsiElement resolve = ((JSReferenceExpression)adHocQualifierExpr).resolve();
if (resolve instanceof JSClass) {
JSClass clazz = (JSClass)resolve;
if (ActionScriptClassResolver.isParentClass((JSClass)resolve, FlexCommonTypeNames.FLASH_EVENT_FQN, false) ||
ActionScriptClassResolver.isParentClass((JSClass)resolve, FlexCommonTypeNames.STARLING_EVENT_FQN, false)) {
return clazz;
}
}
}
return null;
}
}