package net.jangaroo.ide.idea.exml.intentions; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.psi.JSAssignmentExpression; import com.intellij.lang.javascript.psi.JSExpression; import com.intellij.lang.javascript.psi.JSFunction; import com.intellij.lang.javascript.psi.JSLocalVariable; import com.intellij.lang.javascript.psi.JSNewExpression; import com.intellij.lang.javascript.psi.JSObjectLiteralExpression; import com.intellij.lang.javascript.psi.JSProperty; import com.intellij.lang.javascript.psi.JSType; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.impl.JSChangeUtil; import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import netscape.javascript.JSUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; /** * This intention replaces an invocation of an EXML config class constructor using an object literal * by a no-arg constructor invocation, followed by assigning each property separately. * The advantage is that in contrast to the object literal, the usage of properties is type-safe * and can be refactored. */ public class ReplaceObjectLiteralByPropertyAssignment extends PsiElementBaseIntentionAction implements IntentionAction { public ReplaceObjectLiteralByPropertyAssignment() { setText("Replace EXML config class object literals by property assignments"); } @Nls @NotNull @Override public String getFamilyName() { return getText(); } @Override public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { if (!element.isWritable()) return false; JSLocalVariable localVariable = PsiTreeUtil.getParentOfType(element, JSLocalVariable.class, true); if (localVariable != null && localVariable.getName() != null && localVariable.getType() != null) { JSExpression initializer = localVariable.getInitializer(); if (initializer instanceof JSNewExpression) { JSExpression[] arguments = ((JSNewExpression)initializer).getArguments(); return arguments.length == 1 && arguments[0] instanceof JSObjectLiteralExpression; } } return false; } @Override public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException { JSLocalVariable localVariable = PsiTreeUtil.getParentOfType(psiElement, JSLocalVariable.class, true); if (localVariable == null) { return; } String localVariableName = localVariable.getName(); JSType type = localVariable.getType(); if (type == null) { return; } JSExpression newExpression = localVariable.getInitializer(); if (!(newExpression instanceof JSNewExpression)) { return; } JSExpression[] arguments = ((JSNewExpression)newExpression).getArguments(); if (arguments.length != 1 || !(arguments[0] instanceof JSObjectLiteralExpression)) { return; } JSObjectLiteralExpression objectLiteralExpression = (JSObjectLiteralExpression)arguments[0]; JSProperty[] properties = objectLiteralExpression.getProperties(); JSClass configClass = type.resolveClass(); for (JSProperty property : properties) { String propertyName = property.getName(); JSQualifiedNamedElement member = configClass == null ? null : JSInheritanceUtil.findMember(propertyName, configClass, JSInheritanceUtil.SearchedMemberType.Methods, JSFunction.FunctionKind.SETTER, true); String accessPattern = member == null ? "%s['%s']" : "%s.%s"; String code = String.format(accessPattern + "=null;%n", localVariableName, propertyName); PsiElement propertyAssignment = JSChangeUtil.createStatementFromText(project, code, JavaScriptSupportLoader.ECMA_SCRIPT_L4).getPsi(); JSAssignmentExpression assignment = (JSAssignmentExpression)propertyAssignment.getFirstChild(); assignment.getROperand().replace(property.getValue()); JSChangeUtil.doAddAfter(localVariable.getParent(), propertyAssignment, null); } arguments[0].delete(); CodeStyleManager.getInstance(project).reformat(localVariable.getParent(), true); } }