package com.intellij.lang.javascript.inspections.actionscript.fixes;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.lang.javascript.DialectDetector;
import com.intellij.lang.javascript.JSTokenTypes;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.formatter.JSCodeStyleSettings;
import com.intellij.lang.javascript.inspections.actionscript.JSFieldCanBeLocalInspection;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.impl.JSChangeUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
public class ConvertToLocalFix implements LocalQuickFix {
private static final Logger LOG = Logger.getInstance(JSFieldCanBeLocalInspection.class);
private final JSVariable myField;
private final Map<JSFunction, Collection<PsiReference>> myFunctionToReferences;
public ConvertToLocalFix(final JSVariable field, Map<JSFunction, Collection<PsiReference>> functionToReferences) {
myField = field;
myFunctionToReferences = functionToReferences;
}
@NotNull
@Override
public String getFamilyName() {
return FlexBundle.message("js.convert.to.local.quick.fix");
}
@Override
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
if (!FileModificationService.getInstance().preparePsiElementForWrite(myField)) return;
for (Map.Entry<JSFunction, Collection<PsiReference>> entry : myFunctionToReferences.entrySet()) {
if (!applyFixForFunction(entry.getValue())) return;
}
deleteField();
}
private boolean applyFixForFunction(final Collection<PsiReference> references) {
final Project project = myField.getProject();
final JSBlockStatement anchorBlock = findAnchorBlock(references);
if (anchorBlock == null) return false;
final PsiElement firstElement = getFirstElement(references);
final PsiElement anchorElement = getAnchorElement(anchorBlock, firstElement);
JSType type = myField.getType();
final String typeString = type == null ? null : type.getTypeText(JSType.TypeTextFormat.CODE);
StringBuilder text = new StringBuilder("var ").append(myField.getName());
final boolean assignment = isAssignment(anchorElement, firstElement);
if (!StringUtil.isEmpty(typeString) && !(DialectDetector.isTypeScript(myField) && assignment)) {
text.append(":").append(typeString);
}
if (assignment) {
final JSExpression expression = ((JSExpressionStatement)anchorElement).getExpression();
final JSExpression rOperand = ((JSAssignmentExpression)expression).getROperand();
text.append("=").append(rOperand.getText());
}
else {
final JSExpression initializer = myField.getInitializer();
if (initializer != null) {
text.append("=").append(initializer.getText());
}
}
text.append(JSCodeStyleSettings.getSemicolon(anchorBlock.getContainingFile()));
final PsiElement varStatement =
JSChangeUtil.createJSTreeFromText(project, text.toString(), JavaScriptSupportLoader.ECMA_SCRIPT_L4).getPsi();
if (varStatement == null) return false;
final PsiElement newDeclaration;
if (assignment) {
newDeclaration = anchorElement.replace(varStatement);
}
else {
newDeclaration = anchorBlock.addBefore(varStatement, anchorElement);
}
CodeStyleManager.getInstance(project).reformatNewlyAddedElement(anchorBlock.getParent().getNode(), anchorBlock.getNode());
if (newDeclaration != null) {
PsiFile psiFile = myField.getContainingFile();
int offset = newDeclaration.getTextOffset();
final PsiElement context = psiFile.getContext();
if (context != null) {
psiFile = context.getContainingFile();
offset = InjectedLanguageManager.getInstance(project).injectedToHost(newDeclaration, offset);
}
final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor != null) {
final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == psiFile) {
editor.getCaretModel().moveToOffset(offset);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
}
return true;
}
private void deleteField() {
final PsiElement varStatement = myField.getParent();
LOG.assertTrue(varStatement instanceof JSVarStatement);
final PsiElement cl = varStatement.getParent();
final PsiElement first = varStatement.getPrevSibling();
if (first instanceof PsiWhiteSpace) {
cl.deleteChildRange(first, varStatement);
}
else {
myField.delete();
}
}
private static boolean isAssignment(final PsiElement anchorElement, PsiElement ref) {
if (anchorElement instanceof JSExpressionStatement) {
final JSExpressionStatement expressionStatement = (JSExpressionStatement)anchorElement;
final JSExpression expression = expressionStatement.getExpression();
if (expression instanceof JSAssignmentExpression) {
return ((JSAssignmentExpression)expression).getOperationSign() == JSTokenTypes.EQ &&
PsiTreeUtil.isAncestor(((JSAssignmentExpression)expression).getLOperand(), ref, true);
}
}
return false;
}
@Nullable
private static JSBlockStatement findAnchorBlock(final Collection<PsiReference> references) {
JSBlockStatement result = null;
for (PsiReference psiReference : references) {
final PsiElement element = psiReference.getElement();
JSBlockStatement block = PsiTreeUtil.getParentOfType(element, JSBlockStatement.class);
if (result == null || block == null) {
result = block;
}
else {
final PsiElement commonParent = PsiTreeUtil.findCommonParent(result, block);
result = PsiTreeUtil.getParentOfType(commonParent, JSBlockStatement.class, false);
}
}
return result;
}
@NotNull
private static PsiElement getFirstElement(final Collection<PsiReference> references) {
PsiElement firstElement = null;
for (PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (firstElement == null || firstElement.getTextRange().getStartOffset() > element.getTextRange().getStartOffset()) {
firstElement = element;
}
}
LOG.assertTrue(firstElement != null);
return firstElement;
}
@NotNull
private static PsiElement getAnchorElement(final JSBlockStatement anchorBlock, @NotNull final PsiElement firstElement) {
PsiElement element = firstElement;
while (element != null && element.getParent() != anchorBlock) {
element = element.getParent();
}
LOG.assertTrue(element != null);
return element;
}
}