/* * 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; import com.goide.psi.*; import com.goide.psi.impl.GoElementFactory; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.quickfix.GoDeleteQuickFix; import com.intellij.codeInspection.*; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class GoDeferGoInspection extends GoInspectionBase { public static final String ADD_CALL_QUICK_FIX_NAME = "Add function call"; public static final String UNWRAP_PARENTHESES_QUICK_FIX_NAME = "Unwrap parentheses"; public static final String REPLACE_WITH_CORRECT_DEFER_RECOVER = "Replace with correct defer construct"; @NotNull @Override protected GoVisitor buildGoVisitor(@NotNull ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { return new GoVisitor() { @SuppressWarnings("DialogTitleCapitalization") @Override public void visitDeferStatement(@NotNull GoDeferStatement o) { super.visitDeferStatement(o); GoExpression expression = o.getExpression(); if (expression instanceof GoCallExpr) { GoCallExpr callExpr = (GoCallExpr)expression; if (GoPsiImplUtil.isPanic(callExpr)) { holder.registerProblem(o, "defer should not call panic() directly #loc", ProblemHighlightType.WEAK_WARNING, new GoDeleteQuickFix("Delete statement", GoDeferStatement.class)); return; } if (GoPsiImplUtil.isRecover(callExpr)) { holder.registerProblem(o, "defer should not call recover() directly #loc", ProblemHighlightType.WEAK_WARNING, new GoReplaceWithCorrectDeferRecoverQuickFix()); return; } } checkExpression(expression, "defer"); } @SuppressWarnings("DialogTitleCapitalization") @Override public void visitGoStatement(@NotNull GoGoStatement o) { super.visitGoStatement(o); GoExpression expression = o.getExpression(); if (expression instanceof GoCallExpr) { GoCallExpr callExpr = (GoCallExpr)expression; if (GoPsiImplUtil.isPanic(callExpr)) { holder.registerProblem(o, "go should not call panic() directly #loc", ProblemHighlightType.WEAK_WARNING, new GoDeleteQuickFix("Delete statement", GoGoStatement.class)); return; } if (GoPsiImplUtil.isRecover(callExpr)) { holder.registerProblem(o, "go should not call recover() directly #loc", ProblemHighlightType.WEAK_WARNING, new GoDeleteQuickFix("Delete statement", GoGoStatement.class)); return; } } checkExpression(expression, "go"); } private void checkExpression(@Nullable GoExpression o, @NotNull String who) { if (o == null) return; if (o instanceof GoCallExpr || o instanceof GoBuiltinCallExpr) { if (GoPsiImplUtil.isConversionExpression(o)) { holder.registerProblem(o, who + " requires function call, not conversion #loc", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } return; } if (o instanceof GoParenthesesExpr) { holder.registerProblem(o, "Expression in " + who + " must not be parenthesized", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new GoUnwrapParensExpression()); } LocalQuickFix[] fixes = o.getGoType(null) instanceof GoFunctionType ? new LocalQuickFix[]{new GoAddParensQuickFix()} : LocalQuickFix.EMPTY_ARRAY; holder.registerProblem(o, "Expression in " + who + " must be function call", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fixes); } }; } public static class GoAddParensQuickFix extends LocalQuickFixBase { public GoAddParensQuickFix() { super(ADD_CALL_QUICK_FIX_NAME); } @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { addParensIfNeeded(project, descriptor.getStartElement()); } public static PsiElement addParensIfNeeded(@NotNull Project project, @Nullable PsiElement element) { if (element instanceof GoExpression && !(element instanceof GoCallExpr || element instanceof GoBuiltinCallExpr)) { if (((GoExpression)element).getGoType(null) instanceof GoFunctionType) { return element.replace(GoElementFactory.createExpression(project, element.getText() + "()")); } } return null; } } private static class GoUnwrapParensExpression extends LocalQuickFixBase { public GoUnwrapParensExpression() { super(UNWRAP_PARENTHESES_QUICK_FIX_NAME); } @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { PsiElement element = descriptor.getStartElement(); if (element instanceof GoParenthesesExpr) { GoExpression innerExpression = ((GoParenthesesExpr)element).getExpression(); while (innerExpression instanceof GoParenthesesExpr) { innerExpression = ((GoParenthesesExpr)innerExpression).getExpression(); } if (innerExpression != null) { element.replace(GoElementFactory.createExpression(project, innerExpression.getText())); } } } } public static class GoReplaceWithCorrectDeferRecoverQuickFix extends LocalQuickFixBase { public GoReplaceWithCorrectDeferRecoverQuickFix() { super(REPLACE_WITH_CORRECT_DEFER_RECOVER); } @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { PsiElement element = descriptor.getPsiElement(); if (element == null || !element.isValid()) return; if (element instanceof GoStatement) { element.replace(GoElementFactory.createDeferStatement(project, "func() {\n" + "\tif r := recover(); r != nil {\n" + "\t\t\n" + "\t}\n" + "\t}()}")); } } } }