package com.intellij.lang.javascript.intentions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.lang.ASTNode; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.lang.javascript.JSBundle; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.lang.javascript.generation.JSNamedElementNode; import com.intellij.lang.javascript.generation.JavaScriptImplementMethodsHandler; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.JSFunction; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSPackageStatement; import com.intellij.lang.javascript.psi.ecmal4.JSReferenceList; import com.intellij.lang.javascript.validation.fixes.CreateClassOrInterfaceFix; import com.intellij.lang.javascript.validation.fixes.CreateClassParameters; import com.intellij.lang.javascript.validation.fixes.ImplementMethodsFix; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.util.*; public class CreateJSSubclassIntention extends PsiElementBaseIntentionAction { private @NonNls static final String IMPL_SUFFIX = "Impl"; public CreateJSSubclassIntention() { setText(CodeInsightBundle.message("intention.implement.abstract.class.subclass.text")); } @NotNull public String getFamilyName() { return FlexBundle.message("intention.create.subclass.or.implement.interface"); } public boolean isAvailable(final @NotNull Project project, final Editor editor, final @NotNull PsiElement element) { final PsiFile psiFile = element.getContainingFile(); if (!(psiFile instanceof JSFile) || InjectedLanguageManager.getInstance(project).getInjectionHost(psiFile) != null || !psiFile.getLanguage().isKindOf(JavaScriptSupportLoader.ECMA_SCRIPT_L4)) { return false; } final JSClass jsClass = PsiTreeUtil.getParentOfType(element, JSClass.class); if (jsClass == null || !(jsClass.getParent() instanceof JSPackageStatement)) { return false; } if (!jsClass.isInterface()) { final JSAttributeList attributeList = jsClass.getAttributeList(); if (attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.FINAL)) { return false; } } final TextRange declarationRange = getClassDeclarationTextRange(jsClass); final int offset = editor.getCaretModel().getOffset(); if (offset < declarationRange.getStartOffset() || offset > declarationRange.getEndOffset()) { // not the same as TextRange.contains() return false; } setText(getTitle(jsClass)); return true; } private static String getTitle(final JSClass jsClass) { return jsClass.isInterface() ? CodeInsightBundle.message("intention.implement.abstract.class.interface.text") : CodeInsightBundle.message("intention.implement.abstract.class.subclass.text"); } public static TextRange getClassDeclarationTextRange(final JSClass jsClass) { int start = jsClass.getTextRange().getStartOffset(); final JSAttributeList attributeList = jsClass.getAttributeList(); if (attributeList != null) { final PsiElement accessTypeElement = attributeList.findAccessTypeElement(); if (accessTypeElement != null) { start = accessTypeElement.getTextRange().getStartOffset(); } else { final ASTNode node = jsClass.getNode(); final ASTNode classKeyWordNode = node == null ? null : node.findChildByType(JSTokenTypes.CLASS_KEYWORD); if (classKeyWordNode != null) { start = classKeyWordNode.getTextRange().getStartOffset(); } } } int end = start; JSReferenceList jsReferenceList = jsClass.getImplementsList(); if (jsReferenceList == null) { jsReferenceList = jsClass.getExtendsList(); } if (jsReferenceList != null) { end = jsReferenceList.getTextRange().getEndOffset(); } else { final PsiElement nameIdentifier = jsClass.getNameIdentifier(); if (nameIdentifier != null) { end = nameIdentifier.getTextRange().getEndOffset(); } } return new TextRange(start, end); } @Override public void invoke(@NotNull final Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { final JSClass jsClass = PsiTreeUtil.getParentOfType(element, JSClass.class); if (jsClass == null) return; final PsiElement parent = jsClass.getParent(); if (!(parent instanceof JSPackageStatement)) return; final JSPackageStatement jsPackageStatement = (JSPackageStatement)parent; final String defaultTemplateName = CreateClassOrInterfaceFix.ACTION_SCRIPT_CLASS_WITH_SUPERS_TEMPLATE_NAME; final String className; final String packageName; final String templateName; final PsiDirectory targetDirectory; final Collection<String> interfaces; final Map<String, Object> templateAttributes; final JSClass superClass; if (ApplicationManager.getApplication().isUnitTestMode()) { className = suggestSubclassName(jsClass.getName()); packageName = "foo"; templateName = defaultTemplateName; targetDirectory = WriteAction .compute(() -> CreateClassOrInterfaceFix.findOrCreateDirectory(packageName, jsPackageStatement)); interfaces = jsClass.isInterface() ? Collections.singletonList(jsClass.getQualifiedName()) : Collections.emptyList(); templateAttributes = Collections.emptyMap(); superClass = jsClass.isInterface() ? null : jsClass; } else { CreateClassParameters p = CreateClassOrInterfaceFix .createAndShow(defaultTemplateName, jsClass, suggestSubclassName(jsClass.getName()), true, jsPackageStatement.getQualifiedName(), jsClass, JSBundle.message("new.actionscript.class.dialog.title"), () -> CreateClassOrInterfaceFix.getApplicableTemplates(CreateClassOrInterfaceFix.ACTIONSCRIPT_TEMPLATES_EXTENSIONS, project)); if (p == null) return; className = p.getClassName(); packageName = p.getPackageName(); templateName = p.getTemplateName(); targetDirectory = p.getTargetDirectory(); superClass = CreateClassOrInterfaceFix.calcClass(p.getSuperclassFqn(), element); interfaces = p.getInterfacesFqns(); templateAttributes = new HashMap<>(p.getTemplateAttributes()); } JSClass createdClass = CreateClassOrInterfaceFix .createClass(templateName, className, packageName, superClass, interfaces, targetDirectory, getTitle(jsClass), true, templateAttributes, aClass -> { if (aClass != null && !aClass.isInterface() && (jsClass.isInterface() || !interfaces.isEmpty())) { new MyImplementMethodsHandler(aClass).execute(); } }); if (createdClass != null) { createdClass.navigate(true); } } public boolean startInWriteAction() { return false; } private static String suggestSubclassName(final String name) { return name + IMPL_SUFFIX; } private static class MyImplementMethodsHandler extends JavaScriptImplementMethodsHandler { private final JSClass myClass; public MyImplementMethodsHandler(JSClass aClass) { myClass = aClass; } public void execute() { Collection<JSNamedElementNode> candidates = new ArrayList<>(); collectCandidates(myClass, candidates); ImplementMethodsFix fix = new ImplementMethodsFix(myClass); for(JSNamedElementNode el: candidates) { fix.addElementToProcess((JSFunction)el.getPsiElement()); } fix.invoke(myClass.getProject(), null, myClass.getContainingFile()); } } }