/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.goide.inspections.unresolved; import com.goide.GoConstants; import com.goide.GoDocumentationProvider; import com.goide.project.GoVendoringUtil; import com.goide.psi.*; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.psi.impl.GoTypeUtil; import com.goide.refactor.GoRefactoringUtil; import com.goide.util.GoPathScopeHelper; import com.goide.util.GoUtil; import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInsight.template.Template; import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInsight.template.impl.ConstantNode; import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; import com.intellij.diagnostic.AttachmentFactory; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; public class GoIntroduceFunctionFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { private final String myName; private static final String FAMILY_NAME = "Create function"; public GoIntroduceFunctionFix(@NotNull PsiElement element, @NotNull String name) { super(element); myName = name; } @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { if (editor == null) { LOG.error("Cannot run quick fix without editor: " + getClass().getSimpleName(), AttachmentFactory.createAttachment(file.getVirtualFile())); return; } if (!(startElement instanceof GoCallExpr)) return; GoCallExpr call = (GoCallExpr)startElement; List<GoExpression> args = call.getArgumentList().getExpressionList(); GoType resultType = ContainerUtil.getFirstItem(GoTypeUtil.getExpectedTypes(call)); PsiElement anchor = PsiTreeUtil.findPrevParent(file, call); Template template = TemplateManager.getInstance(project).createTemplate("", ""); template.addTextSegment("\nfunc " + myName); setupFunctionParameters(template, args, file); setupFunctionResult(template, resultType); template.addTextSegment(" {\n\t"); template.addEndVariable(); template.addTextSegment("\n}"); int offset = anchor.getTextRange().getEndOffset(); editor.getCaretModel().moveToOffset(offset); startTemplate(editor, template, project); } @NotNull private static String convertType(@NotNull PsiFile file, @Nullable GoType type, @NotNull Map<String, GoImportSpec> importMap) { if (type == null) return GoConstants.INTERFACE_TYPE; Module module = ModuleUtilCore.findModuleForPsiElement(file); boolean vendoringEnabled = GoVendoringUtil.isVendoringEnabled(module); return GoDocumentationProvider.getTypePresentation(type, element -> { if (element instanceof GoTypeSpec) { GoTypeSpec spec = (GoTypeSpec)element; if (GoPsiImplUtil.builtin(spec)) return spec.getIdentifier().getText(); GoFile typeFile = spec.getContainingFile(); if (file.isEquivalentTo(typeFile) || GoUtil.inSamePackage(typeFile, file)) { return spec.getIdentifier().getText(); } if (!spec.isPublic()) { return GoConstants.INTERFACE_TYPE; } GoPathScopeHelper scopeHelper = GoPathScopeHelper.fromReferenceFile(file.getProject(), module, file.getVirtualFile()); boolean isAllowed = scopeHelper.couldBeReferenced(typeFile.getVirtualFile(), file.getVirtualFile()); if (!isAllowed) return GoConstants.INTERFACE_TYPE; String importPath = typeFile.getImportPath(vendoringEnabled); GoImportSpec importSpec = importMap.get(importPath); String packageName = StringUtil.notNullize(typeFile.getPackageName()); String qualifier = StringUtil.notNullize(GoPsiImplUtil.getImportQualifierToUseInFile(importSpec, packageName), packageName); // todo: add import package fix if getImportQualifierToUseInFile is null? return GoPsiImplUtil.getFqn(qualifier, spec.getIdentifier().getText()); } return GoConstants.INTERFACE_TYPE; }); } private static void setupFunctionResult(@NotNull Template template, @Nullable GoType type) { if (type instanceof GoTypeList) { template.addTextSegment(" ("); List<GoType> list = ((GoTypeList)type).getTypeList(); for (int i = 0; i < list.size(); i++) { template.addVariable(new ConstantNode(list.get(i).getText()), true); if (i < list.size() - 1) template.addTextSegment(", "); } template.addTextSegment(")"); return; } if (type != null) { template.addTextSegment(" "); template.addVariable(new ConstantNode(type.getText()), true); } } private static void setupFunctionParameters(@NotNull Template template, @NotNull List<GoExpression> args, PsiFile file) { Map<String, GoImportSpec> importMap = ((GoFile)file).getImportedPackagesMap(); template.addTextSegment("("); for (int i = 0; i < args.size(); i++) { GoExpression e = args.get(i); template.addVariable(GoRefactoringUtil.createParameterNameSuggestedExpression(e), true); template.addTextSegment(" "); String type = convertType(file, e.getGoType(null), importMap); template.addVariable(new ConstantNode(type), true); if (i != args.size() - 1) template.addTextSegment(", "); } template.addTextSegment(")"); } private static void startTemplate(@NotNull Editor editor, @NotNull Template template, @NotNull Project project) { Runnable runnable = () -> { if (project.isDisposed() || editor.isDisposed()) return; CommandProcessor.getInstance().executeCommand(project, () -> TemplateManager.getInstance(project).startTemplate(editor, template, null), "Introduce function", null); }; if (ApplicationManager.getApplication().isUnitTestMode()) { runnable.run(); } else { ApplicationManager.getApplication().invokeLater(runnable); } } @NotNull @Override public String getText() { return FAMILY_NAME + " " + myName; } @Nls @NotNull @Override public String getFamilyName() { return FAMILY_NAME; } }