Provide fixes for missing branches in switch expression (create 'default', create missing branches)

Fixes IDEA-203071 Switch expressions: provide fix to generate missed branches for enum switches
Fixes IDEA-203449 Switch statement without default branch: provide a quick-fix
This commit is contained in:
Tagir Valeev
2018-12-03 18:47:26 +07:00
parent 8905ab83ae
commit 8a1302c2a4
24 changed files with 670 additions and 372 deletions

View File

@@ -19,6 +19,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author cdr
@@ -460,4 +461,8 @@ public abstract class QuickFixFactory {
public IntentionAction createSameErasureButDifferentMethodsFix(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
throw new AbstractMethodError();
}
public abstract IntentionAction createAddMissingEnumBranchesFix(@NotNull PsiSwitchBlock switchBlock, @NotNull Set<String> missingCases);
public abstract IntentionAction createAddSwitchDefaultFix(@NotNull PsiSwitchBlock switchBlock);
}

View File

@@ -1989,29 +1989,30 @@ public class HighlightUtil extends HighlightUtilBase {
}
if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) {
if (values.isEmpty()) {
String message = JavaErrorMessages.message("switch.expr.empty");
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(switchBlock).descriptionAndTooltip(message).create());
}
else if (!hasDefaultCase) {
Set<String> missingConstants = new HashSet<>();
boolean exhaustive = hasDefaultCase;
if (!exhaustive) {
if (selectorType instanceof PsiClassType) {
PsiClass type = ((PsiClassType)selectorType).resolve();
if (type != null && type.isEnum()) {
Set<Object> constants = new HashSet<>();
for (PsiField field : type.getFields()) {
if (field instanceof PsiEnumConstant) {
constants.add(field.getName());
if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) {
missingConstants.add(field.getName());
}
}
constants.removeAll(values.keySet());
hasDefaultCase = constants.isEmpty();
exhaustive = missingConstants.isEmpty();
}
}
if (!hasDefaultCase) {
PsiElement range = ObjectUtils.notNull(selectorExpression, switchBlock);
String message = JavaErrorMessages.message("switch.expr.incomplete");
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create());
}
if (!exhaustive) {
PsiElement range = ObjectUtils.notNull(selectorExpression, switchBlock);
String message = JavaErrorMessages.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete");
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create();
if (!missingConstants.isEmpty()) {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddMissingEnumBranchesFix(switchBlock, missingConstants));
}
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddSwitchDefaultFix(switchBlock));
results.add(info);
}
}

View File

@@ -47,6 +47,8 @@ import com.intellij.psi.util.PropertyMemberType;
import com.intellij.refactoring.memberPushDown.JavaPushDownHandler;
import com.intellij.util.DocumentUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.fixes.CreateDefaultBranchFix;
import com.siyeh.ig.fixes.CreateMissingSwitchBranchesFix;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -933,4 +935,14 @@ public class QuickFixFactoryImpl extends QuickFixFactory {
public IntentionAction createSameErasureButDifferentMethodsFix(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
return new SameErasureButDifferentMethodsFix(method, superMethod);
}
@Override
public IntentionAction createAddMissingEnumBranchesFix(@NotNull PsiSwitchBlock switchBlock, @NotNull Set<String> missingCases) {
return new CreateMissingSwitchBranchesFix(switchBlock, missingCases);
}
@Override
public IntentionAction createAddSwitchDefaultFix(@NotNull PsiSwitchBlock switchBlock) {
return new CreateDefaultBranchFix(switchBlock);
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2003-2017 Dave Griffith, Bas Leijdekkers
*
* 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.siyeh.ig.controlflow;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.SwitchUtils;
import one.util.streamex.StreamEx;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.List;
import java.util.Set;
public class SwitchStatementsWithoutDefaultInspection extends BaseInspection {
@SuppressWarnings("PublicField")
public boolean m_ignoreFullyCoveredEnums = true;
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message("switch.statements.without.default.display.name");
}
@Pattern(VALID_ID_PATTERN)
@Override
@NotNull
public String getID() {
return "SwitchStatementWithoutDefaultBranch";
}
@Override
@NotNull
protected String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message("switch.statements.without.default.problem.descriptor");
}
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(InspectionGadgetsBundle.message("switch.statement.without.default.ignore.option"),
this, "m_ignoreFullyCoveredEnums");
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new SwitchStatementsWithoutDefaultVisitor();
}
private class SwitchStatementsWithoutDefaultVisitor extends BaseInspectionVisitor {
@Override
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);
final int count = SwitchUtils.calculateBranchCount(statement);
if (count <= 0) {
return;
}
if (m_ignoreFullyCoveredEnums && switchStatementIsFullyCoveredEnum(statement)) {
return;
}
registerStatementError(statement);
}
private boolean switchStatementIsFullyCoveredEnum(PsiSwitchStatement statement) {
final PsiExpression expression = statement.getExpression();
if (expression == null) {
return true; // don't warn on incomplete code
}
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
if (aClass == null || !aClass.isEnum()) return false;
List<PsiSwitchLabelStatementBase> labels = PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatementBase.class);
Set<PsiEnumConstant> constants = StreamEx.of(labels).flatCollection(SwitchUtils::findEnumConstants).toSet();
for (PsiField field : aClass.getFields()) {
if (field instanceof PsiEnumConstant && !constants.remove(field)) {
return false;
}
}
return true;
}
}
}

View File

@@ -754,8 +754,8 @@
implementationClass="com.siyeh.ig.controlflow.SwitchStatementWithTooManyBranchesInspection"/>
<localInspection groupPath="Java" language="JAVA" suppressId="SwitchStatementWithoutDefaultBranch" shortName="SwitchStatementsWithoutDefault"
bundle="com.siyeh.InspectionGadgetsBundle" key="switch.statements.without.default.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.control.flow.issues" enabledByDefault="false"
level="WARNING" implementationClass="com.siyeh.ig.controlflow.SwitchStatementsWithoutDefaultInspection"/>
groupBundle="messages.InspectionsBundle" groupKey="group.names.control.flow.issues" enabledByDefault="true"
level="INFORMATION" implementationClass="com.siyeh.ig.controlflow.SwitchStatementsWithoutDefaultInspection"/>
<localInspection groupPath="Java" language="JAVA" suppressId="RedundantIfStatement" shortName="TrivialIf" bundle="com.siyeh.InspectionGadgetsBundle"
key="trivial.if.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.control.flow.issues"
enabledByDefault="true" level="WARNING" implementationClass="com.siyeh.ig.controlflow.TrivialIfInspection" cleanupTool="true"/>

View File

@@ -15,39 +15,27 @@
*/
package com.siyeh.ig.controlflow;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.template.TemplateBuilder;
import com.intellij.codeInsight.template.TemplateBuilderFactory;
import com.intellij.codeInsight.template.impl.ConstantNode;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.TextRange;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.fixes.CreateMissingSwitchBranchesFix;
import com.siyeh.ig.psiutils.SwitchUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.Joining;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class EnumSwitchStatementWhichMissesCasesInspection extends AbstractBaseJavaLocalInspectionTool {
@@ -66,15 +54,10 @@ public class EnumSwitchStatementWhichMissesCasesInspection extends AbstractBaseJ
return InspectionGadgetsBundle
.message("enum.switch.statement.which.misses.cases.problem.descriptor.single", enumName, names.iterator().next());
}
String namesString = formatMissingBranches(names);
String namesString = CreateMissingSwitchBranchesFix.formatMissingBranches(names);
return InspectionGadgetsBundle.message("enum.switch.statement.which.misses.cases.problem.descriptor", enumName, namesString);
}
static String formatMissingBranches(Set<String> names) {
return StreamEx.of(names).map(name -> "'" + name + "'").mapLast("and "::concat)
.collect(Joining.with(", ").maxChars(50).cutAfterDelimiter());
}
@Override
@Nullable
public JComponent createOptionsPanel() {
@@ -116,7 +99,7 @@ public class EnumSwitchStatementWhichMissesCasesInspection extends AbstractBaseJ
}
continue;
}
List<PsiEnumConstant> enumConstants = findEnumConstants(child);
List<PsiEnumConstant> enumConstants = SwitchUtils.findEnumConstants(child);
if (enumConstants.isEmpty()) {
// Syntax error or unresolved constant: do not report anything on incomplete code
return;
@@ -162,233 +145,4 @@ public class EnumSwitchStatementWhichMissesCasesInspection extends AbstractBaseJ
}
};
}
private static class CreateMissingSwitchBranchesFix implements LocalQuickFix, IntentionAction {
private final Set<String> myNames;
private final SmartPsiElementPointer<PsiSwitchBlock> myBlock;
private CreateMissingSwitchBranchesFix(@NotNull PsiSwitchBlock block, Set<String> names) {
myBlock = SmartPointerManager.createPointer(block);
myNames = names;
}
@NotNull
@Override
public String getText() {
return getName();
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
if (myNames.size() == 1) {
return "Create missing switch branch '" + myNames.iterator().next() + "'";
}
return "Create missing branches: " + formatMissingBranches(myNames);
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return "Create enum switch branches";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
invoke();
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
invoke();
}
@Override
public boolean startInWriteAction() {
return true;
}
public void invoke() {
PsiSwitchBlock switchBlock = myBlock.getElement();
if (switchBlock == null) return;
final PsiCodeBlock body = switchBlock.getBody();
final PsiExpression switchExpression = switchBlock.getExpression();
if (switchExpression == null) return;
final PsiClassType switchType = (PsiClassType)switchExpression.getType();
if (switchType == null) return;
final PsiClass enumClass = switchType.resolve();
if (enumClass == null) return;
boolean isRuleBasedFormat = SwitchUtils.isRuleFormatSwitch(switchBlock);
if (body == null) {
// replace entire switch statement if no code block is present
@NonNls final StringBuilder newStatementText = new StringBuilder();
CommentTracker commentTracker = new CommentTracker();
newStatementText.append("switch(").append(commentTracker.text(switchExpression)).append("){");
for (String missingName : myNames) {
newStatementText.append(String.join("", generateStatements(missingName, switchBlock, isRuleBasedFormat)));
}
newStatementText.append('}');
commentTracker.replaceAndRestoreComments(switchBlock, newStatementText.toString());
createTemplate(switchBlock);
return;
}
List<PsiEnumConstant> allEnumConstants = StreamEx.of(enumClass.getAllFields()).select(PsiEnumConstant.class).toList();
Map<PsiEnumConstant, PsiEnumConstant> nextEnumConstants =
StreamEx.of(allEnumConstants).pairMap(Couple::of).toMap(c -> c.getFirst(), c -> c.getSecond());
List<PsiEnumConstant> missingEnumElements = StreamEx.of(allEnumConstants).filter(c -> myNames.contains(c.getName())).toList();
PsiEnumConstant nextEnumConstant = getNextEnumConstant(nextEnumConstants, missingEnumElements);
PsiElement bodyElement = body.getFirstBodyElement();
while (bodyElement != null) {
List<PsiEnumConstant> constants = findEnumConstants(bodyElement);
while (nextEnumConstant != null && constants.contains(nextEnumConstant)) {
addSwitchLabelStatementBefore(missingEnumElements.get(0), bodyElement, switchBlock, isRuleBasedFormat);
missingEnumElements.remove(0);
if (missingEnumElements.isEmpty()) {
break;
}
nextEnumConstant = getNextEnumConstant(nextEnumConstants, missingEnumElements);
}
if (isDefaultSwitchLabelStatement(bodyElement)) {
for (PsiEnumConstant missingEnumElement : missingEnumElements) {
addSwitchLabelStatementBefore(missingEnumElement, bodyElement, switchBlock, isRuleBasedFormat);
}
missingEnumElements.clear();
break;
}
bodyElement = bodyElement.getNextSibling();
}
if (!missingEnumElements.isEmpty()) {
final PsiElement lastChild = body.getLastChild();
for (PsiEnumConstant missingEnumElement : missingEnumElements) {
addSwitchLabelStatementBefore(missingEnumElement, lastChild, switchBlock, isRuleBasedFormat);
}
}
createTemplate(switchBlock);
}
private void createTemplate(@NotNull PsiSwitchBlock block) {
if (!(block instanceof PsiSwitchExpression)) return;
PsiFile file = block.getContainingFile();
Project project = block.getProject();
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor == null) return;
Document document = editor.getDocument();
PsiFile topLevelFile = InjectedLanguageManager.getInstance(project).getTopLevelFile(file);
if (topLevelFile == null || document != topLevelFile.getViewProvider().getDocument()) return;
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document);
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(block);
List<PsiSwitchLabelStatementBase> labels = PsiTreeUtil.getChildrenOfTypeAsList(block.getBody(), PsiSwitchLabelStatementBase.class);
List<PsiExpression> elementsToReplace = getElementsToReplace(labels);
for (PsiExpression expression : elementsToReplace) {
builder.replaceElement(expression, new ConstantNode(expression.getText()));
}
builder.run(editor, true);
}
@NotNull
private List<PsiExpression> getElementsToReplace(@NotNull List<PsiSwitchLabelStatementBase> labels) {
List<PsiExpression> elementsToReplace = new ArrayList<>();
for (PsiSwitchLabelStatementBase label : labels) {
List<PsiEnumConstant> constants = findEnumConstants(label);
if (constants.size() == 1 && myNames.contains(constants.get(0).getName())) {
if (label instanceof PsiSwitchLabeledRuleStatement) {
PsiStatement body = ((PsiSwitchLabeledRuleStatement)label).getBody();
if (body instanceof PsiExpressionStatement) {
ContainerUtil.addIfNotNull(elementsToReplace, ((PsiExpressionStatement)body).getExpression());
}
} else {
PsiElement next = PsiTreeUtil.skipWhitespacesAndCommentsForward(label);
if(next instanceof PsiBreakStatement) {
ContainerUtil.addIfNotNull(elementsToReplace, ((PsiBreakStatement)next).getValueExpression());
}
}
}
}
return elementsToReplace;
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
PsiSwitchBlock startSwitch = myBlock.getElement();
if (startSwitch == null) return false;
int offset = Math.min(editor.getCaretModel().getOffset(), startSwitch.getTextRange().getEndOffset() - 1);
PsiSwitchBlock currentSwitch = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(offset), PsiSwitchBlock.class);
return currentSwitch == startSwitch;
}
private static List<String> generateStatements(String name, PsiSwitchBlock switchBlock, boolean isRuleBasedFormat) {
if (switchBlock instanceof PsiSwitchExpression) {
String value = TypeUtils.getDefaultValue(((PsiSwitchExpression)switchBlock).getType());
if (isRuleBasedFormat) {
return Collections.singletonList("case "+name+" -> " + value + ";");
} else {
return Arrays.asList("case "+name+":", "break " + value + ";");
}
} else {
if (isRuleBasedFormat) {
return Collections.singletonList("case "+name+" -> {}");
} else {
return Arrays.asList("case "+name+":", "break;");
}
}
}
private static void addSwitchLabelStatementBefore(PsiEnumConstant missingEnumElement,
PsiElement anchor,
PsiSwitchBlock switchBlock,
boolean isRuleBasedFormat) {
if (anchor instanceof PsiSwitchLabelStatement) {
PsiElement sibling = PsiTreeUtil.skipWhitespacesBackward(anchor);
while (sibling instanceof PsiSwitchLabelStatement) {
anchor = sibling;
sibling = PsiTreeUtil.skipWhitespacesBackward(anchor);
}
}
PsiElement correctedAnchor = anchor;
final PsiElement parent = anchor.getParent();
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject());
generateStatements(missingEnumElement.getName(), switchBlock, isRuleBasedFormat).stream()
.map(text -> factory.createStatementFromText(text, parent))
.forEach(statement -> parent.addBefore(statement, correctedAnchor));
}
private static PsiEnumConstant getNextEnumConstant(Map<PsiEnumConstant, PsiEnumConstant> nextEnumConstants,
List<PsiEnumConstant> missingEnumElements) {
PsiEnumConstant nextEnumConstant = nextEnumConstants.get(missingEnumElements.get(0));
while (missingEnumElements.contains(nextEnumConstant)) {
nextEnumConstant = nextEnumConstants.get(nextEnumConstant);
}
return nextEnumConstant;
}
private static boolean isDefaultSwitchLabelStatement(PsiElement element) {
return element instanceof PsiSwitchLabelStatementBase && ((PsiSwitchLabelStatementBase)element).isDefaultCase();
}
}
@NotNull
private static List<PsiEnumConstant> findEnumConstants(PsiElement element) {
if (!(element instanceof PsiSwitchLabelStatementBase)) {
return Collections.emptyList();
}
final PsiSwitchLabelStatementBase switchLabelStatement = (PsiSwitchLabelStatementBase)element;
final PsiExpressionList list = switchLabelStatement.getCaseValues();
if (list == null) {
return Collections.emptyList();
}
List<PsiEnumConstant> constants = new ArrayList<>();
for (PsiExpression value : list.getExpressions()) {
if (value instanceof PsiReferenceExpression) {
final PsiElement target = ((PsiReferenceExpression)value).resolve();
if (target instanceof PsiEnumConstant) {
constants.add((PsiEnumConstant)target);
continue;
}
}
return Collections.emptyList();
}
return constants;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2003-2017 Dave Griffith, Bas Leijdekkers
*
* 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.siyeh.ig.controlflow;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.fixes.CreateDefaultBranchFix;
import com.siyeh.ig.psiutils.SwitchUtils;
import one.util.streamex.StreamEx;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.List;
import java.util.Set;
public class SwitchStatementsWithoutDefaultInspection extends AbstractBaseJavaLocalInspectionTool {
@SuppressWarnings("PublicField")
public boolean m_ignoreFullyCoveredEnums = true;
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message("switch.statements.without.default.display.name");
}
@Pattern(VALID_ID_PATTERN)
@Override
@NotNull
public String getID() {
return "SwitchStatementWithoutDefaultBranch";
}
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(InspectionGadgetsBundle.message("switch.statement.without.default.ignore.option"),
this, "m_ignoreFullyCoveredEnums");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);
final int count = SwitchUtils.calculateBranchCount(statement);
if (count < 0 || statement.getBody() == null) {
return;
}
boolean infoMode = false;
if (count == 0 || m_ignoreFullyCoveredEnums && switchStatementIsFullyCoveredEnum(statement)) {
if (!isOnTheFly) return;
infoMode = true;
}
String message = InspectionGadgetsBundle.message("switch.statements.without.default.problem.descriptor");
if (infoMode || (isOnTheFly && InspectionProjectProfileManager.isInformationLevel(getShortName(), statement))) {
holder.registerProblem(statement, message, ProblemHighlightType.INFORMATION, new CreateDefaultBranchFix(statement));
} else {
holder.registerProblem(statement.getFirstChild(), message, new CreateDefaultBranchFix(statement));
}
}
private boolean switchStatementIsFullyCoveredEnum(PsiSwitchStatement statement) {
final PsiExpression expression = statement.getExpression();
if (expression == null) {
return true; // don't warn on incomplete code
}
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
if (aClass == null || !aClass.isEnum()) return false;
List<PsiSwitchLabelStatementBase> labels = PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatementBase.class);
Set<PsiEnumConstant> constants = StreamEx.of(labels).flatCollection(SwitchUtils::findEnumConstants).toSet();
for (PsiField field : aClass.getFields()) {
if (field instanceof PsiEnumConstant && !constants.remove(field)) {
return false;
}
}
return true;
}
};
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.fixes;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class BaseSwitchFix implements LocalQuickFix, IntentionAction {
protected final SmartPsiElementPointer<PsiSwitchBlock> myBlock;
public BaseSwitchFix(@NotNull PsiSwitchBlock block) {
myBlock = SmartPointerManager.createPointer(block);
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
invoke();
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
invoke();
}
@Override
public boolean startInWriteAction() {
return true;
}
abstract protected void invoke();
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
PsiSwitchBlock startSwitch = myBlock.getElement();
if (startSwitch == null) return false;
int offset = Math.min(editor.getCaretModel().getOffset(), startSwitch.getTextRange().getEndOffset() - 1);
PsiSwitchBlock currentSwitch = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(offset), PsiSwitchBlock.class);
return currentSwitch == startSwitch;
}
@Nullable
static Editor prepareForTemplateAndObtainEditor(@NotNull PsiElement element) {
PsiFile file = element.getContainingFile();
Project project = element.getProject();
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor == null) return null;
Document document = editor.getDocument();
PsiFile topLevelFile = InjectedLanguageManager.getInstance(project).getTopLevelFile(file);
if (topLevelFile == null || document != topLevelFile.getViewProvider().getDocument()) return null;
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document);
return editor;
}
}

View File

@@ -0,0 +1,108 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.fixes;
import com.intellij.codeInsight.template.TemplateBuilder;
import com.intellij.codeInsight.template.TemplateBuilderFactory;
import com.intellij.codeInsight.template.impl.ConstantNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.*;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.psiutils.SwitchUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CreateDefaultBranchFix extends BaseSwitchFix {
public CreateDefaultBranchFix(@NotNull PsiSwitchBlock block) {
super(block);
}
@NotNull
@Override
public String getText() {
return getName();
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return "Insert 'default' branch";
}
@Override
protected void invoke() {
PsiSwitchBlock switchBlock = myBlock.getElement();
if (switchBlock == null) return;
PsiCodeBlock body = switchBlock.getBody();
if (body == null) return;
if (SwitchUtils.calculateBranchCount(switchBlock) < 0) {
// Default already present for some reason
return;
}
PsiExpression switchExpression = switchBlock.getExpression();
if (switchExpression == null) return;
boolean isRuleBasedFormat = SwitchUtils.isRuleFormatSwitch(switchBlock);
PsiElement anchor = body.getLastChild();
if (anchor == null) return;
PsiElement parent = anchor.getParent();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject());
generateStatements(switchBlock, isRuleBasedFormat).stream()
.map(text -> factory.createStatementFromText(text, parent))
.forEach(statement -> parent.addBefore(statement, anchor));
adjustEditor(switchBlock);
}
private static void adjustEditor(@NotNull PsiSwitchBlock block) {
PsiCodeBlock body = block.getBody();
if (body == null) return;
Editor editor = prepareForTemplateAndObtainEditor(block);
if (editor == null) return;
PsiStatement lastStatement = ArrayUtil.getLastElement(body.getStatements());
PsiExpression expression = null;
int offset = -1;
if (lastStatement instanceof PsiBreakStatement) {
expression = ((PsiBreakStatement)lastStatement).getExpression();
} else if (lastStatement instanceof PsiSwitchLabeledRuleStatement) {
PsiStatement ruleBody = ((PsiSwitchLabeledRuleStatement)lastStatement).getBody();
if (ruleBody instanceof PsiExpressionStatement) {
expression = ((PsiExpressionStatement)ruleBody).getExpression();
} else if (ruleBody instanceof PsiBlockStatement) {
offset = ruleBody.getTextRange().getStartOffset()+1;
}
} else if (lastStatement instanceof PsiSwitchLabelStatement) {
offset = lastStatement.getTextRange().getEndOffset();
}
if (expression == null) {
if (offset != -1) {
editor.getCaretModel().moveToOffset(offset);
}
} else {
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(block);
builder.replaceElement(expression, new ConstantNode(expression.getText()));
builder.run(editor, true);
}
}
private static List<String> generateStatements(PsiSwitchBlock switchBlock, boolean isRuleBasedFormat) {
if (switchBlock instanceof PsiSwitchExpression) {
String value = TypeUtils.getDefaultValue(((PsiSwitchExpression)switchBlock).getType());
if (isRuleBasedFormat) {
return Collections.singletonList("default -> " + value + ";");
} else {
return Arrays.asList("default:", "break " + value + ";");
}
} else {
if (isRuleBasedFormat) {
return Collections.singletonList("default -> {}");
} else {
return Collections.singletonList("default:");
}
}
}
}

View File

@@ -0,0 +1,201 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.fixes;
import com.intellij.codeInsight.template.TemplateBuilder;
import com.intellij.codeInsight.template.TemplateBuilderFactory;
import com.intellij.codeInsight.template.impl.ConstantNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Couple;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.SwitchUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.Joining;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class CreateMissingSwitchBranchesFix extends BaseSwitchFix {
private final Set<String> myNames;
public CreateMissingSwitchBranchesFix(@NotNull PsiSwitchBlock block, Set<String> names) {
super(block);
myNames = names;
}
@NotNull
@Override
public String getText() {
return getName();
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
if (myNames.size() == 1) {
return "Create missing switch branch '" + myNames.iterator().next() + "'";
}
return "Create missing branches: " + formatMissingBranches(myNames);
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return "Create enum switch branches";
}
@Override
protected void invoke() {
PsiSwitchBlock switchBlock = myBlock.getElement();
if (switchBlock == null) return;
final PsiCodeBlock body = switchBlock.getBody();
final PsiExpression switchExpression = switchBlock.getExpression();
if (switchExpression == null) return;
final PsiClassType switchType = (PsiClassType)switchExpression.getType();
if (switchType == null) return;
final PsiClass enumClass = switchType.resolve();
if (enumClass == null) return;
boolean isRuleBasedFormat = SwitchUtils.isRuleFormatSwitch(switchBlock);
if (body == null) {
// replace entire switch statement if no code block is present
@NonNls final StringBuilder newStatementText = new StringBuilder();
CommentTracker commentTracker = new CommentTracker();
newStatementText.append("switch(").append(commentTracker.text(switchExpression)).append("){");
for (String missingName : myNames) {
newStatementText.append(String.join("", generateStatements(missingName, switchBlock, isRuleBasedFormat)));
}
newStatementText.append('}');
commentTracker.replaceAndRestoreComments(switchBlock, newStatementText.toString());
createTemplate(switchBlock);
return;
}
List<PsiEnumConstant> allEnumConstants = StreamEx.of(enumClass.getAllFields()).select(PsiEnumConstant.class).toList();
Map<PsiEnumConstant, PsiEnumConstant> nextEnumConstants =
StreamEx.of(allEnumConstants).pairMap(Couple::of).toMap(c -> c.getFirst(), c -> c.getSecond());
List<PsiEnumConstant> missingEnumElements = StreamEx.of(allEnumConstants).filter(c -> myNames.contains(c.getName())).toList();
PsiEnumConstant nextEnumConstant = getNextEnumConstant(nextEnumConstants, missingEnumElements);
PsiElement bodyElement = body.getFirstBodyElement();
while (bodyElement != null) {
List<PsiEnumConstant> constants = SwitchUtils.findEnumConstants(bodyElement);
while (nextEnumConstant != null && constants.contains(nextEnumConstant)) {
addSwitchLabelStatementBefore(missingEnumElements.get(0), bodyElement, switchBlock, isRuleBasedFormat);
missingEnumElements.remove(0);
if (missingEnumElements.isEmpty()) {
break;
}
nextEnumConstant = getNextEnumConstant(nextEnumConstants, missingEnumElements);
}
if (isDefaultSwitchLabelStatement(bodyElement)) {
for (PsiEnumConstant missingEnumElement : missingEnumElements) {
addSwitchLabelStatementBefore(missingEnumElement, bodyElement, switchBlock, isRuleBasedFormat);
}
missingEnumElements.clear();
break;
}
bodyElement = bodyElement.getNextSibling();
}
if (!missingEnumElements.isEmpty()) {
final PsiElement lastChild = body.getLastChild();
for (PsiEnumConstant missingEnumElement : missingEnumElements) {
addSwitchLabelStatementBefore(missingEnumElement, lastChild, switchBlock, isRuleBasedFormat);
}
}
createTemplate(switchBlock);
}
private void createTemplate(@NotNull PsiSwitchBlock block) {
if (!(block instanceof PsiSwitchExpression)) return;
Editor editor = BaseSwitchFix.prepareForTemplateAndObtainEditor(block);
if (editor == null) return;
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(block);
List<PsiSwitchLabelStatementBase> labels = PsiTreeUtil.getChildrenOfTypeAsList(block.getBody(), PsiSwitchLabelStatementBase.class);
List<PsiExpression> elementsToReplace = getElementsToReplace(labels);
for (PsiExpression expression : elementsToReplace) {
builder.replaceElement(expression, new ConstantNode(expression.getText()));
}
builder.run(editor, true);
}
@NotNull
private List<PsiExpression> getElementsToReplace(@NotNull List<PsiSwitchLabelStatementBase> labels) {
List<PsiExpression> elementsToReplace = new ArrayList<>();
for (PsiSwitchLabelStatementBase label : labels) {
List<PsiEnumConstant> constants = SwitchUtils.findEnumConstants(label);
if (constants.size() == 1 && myNames.contains(constants.get(0).getName())) {
if (label instanceof PsiSwitchLabeledRuleStatement) {
PsiStatement body = ((PsiSwitchLabeledRuleStatement)label).getBody();
if (body instanceof PsiExpressionStatement) {
ContainerUtil.addIfNotNull(elementsToReplace, ((PsiExpressionStatement)body).getExpression());
}
} else {
PsiElement next = PsiTreeUtil.skipWhitespacesAndCommentsForward(label);
if(next instanceof PsiBreakStatement) {
ContainerUtil.addIfNotNull(elementsToReplace, ((PsiBreakStatement)next).getValueExpression());
}
}
}
}
return elementsToReplace;
}
private static List<String> generateStatements(String name, PsiSwitchBlock switchBlock, boolean isRuleBasedFormat) {
if (switchBlock instanceof PsiSwitchExpression) {
String value = TypeUtils.getDefaultValue(((PsiSwitchExpression)switchBlock).getType());
if (isRuleBasedFormat) {
return Collections.singletonList("case " + name + " -> " + value + ";");
} else {
return Arrays.asList("case "+name+":", "break " + value + ";");
}
} else {
if (isRuleBasedFormat) {
return Collections.singletonList("case "+name+" -> {}");
} else {
return Arrays.asList("case "+name+":", "break;");
}
}
}
private static void addSwitchLabelStatementBefore(PsiEnumConstant missingEnumElement,
PsiElement anchor,
PsiSwitchBlock switchBlock,
boolean isRuleBasedFormat) {
if (anchor instanceof PsiSwitchLabelStatement) {
PsiElement sibling = PsiTreeUtil.skipWhitespacesBackward(anchor);
while (sibling instanceof PsiSwitchLabelStatement) {
anchor = sibling;
sibling = PsiTreeUtil.skipWhitespacesBackward(anchor);
}
}
PsiElement correctedAnchor = anchor;
final PsiElement parent = anchor.getParent();
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject());
generateStatements(missingEnumElement.getName(), switchBlock, isRuleBasedFormat).stream()
.map(text -> factory.createStatementFromText(text, parent))
.forEach(statement -> parent.addBefore(statement, correctedAnchor));
}
private static PsiEnumConstant getNextEnumConstant(Map<PsiEnumConstant, PsiEnumConstant> nextEnumConstants,
List<PsiEnumConstant> missingEnumElements) {
PsiEnumConstant nextEnumConstant = nextEnumConstants.get(missingEnumElements.get(0));
while (missingEnumElements.contains(nextEnumConstant)) {
nextEnumConstant = nextEnumConstants.get(nextEnumConstant);
}
return nextEnumConstant;
}
private static boolean isDefaultSwitchLabelStatement(PsiElement element) {
return element instanceof PsiSwitchLabelStatementBase && ((PsiSwitchLabelStatementBase)element).isDefaultCase();
}
public static String formatMissingBranches(Set<String> names) {
return StreamEx.of(names).map(name -> "'" + name + "'").mapLast("and "::concat)
.collect(Joining.with(", ").maxChars(50).cutAfterDelimiter());
}
}

View File

@@ -0,0 +1,9 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
default:
}
}
}

View File

@@ -0,0 +1,10 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
default -> {
}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Insert 'default' branch" "true"
class X {
int test(int i) {
return switch(i) {
case 1 -> 2;
default -> 0;
};
}
}

View File

@@ -0,0 +1,13 @@
// "Insert 'default' branch" "true"
class X {
void test(int i, int j) {
switch(i) {
case 0:
switch (j) {
default: break;
}
default:
}
}
}

View File

@@ -0,0 +1,10 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
case 0 -> System.out.println("oops");
default -> {
}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
case 0:break;
default:
}
}
}

View File

@@ -0,0 +1,8 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
<caret>
}
}
}

View File

@@ -0,0 +1,8 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
<caret>
}
}
}

View File

@@ -0,0 +1,8 @@
// "Insert 'default' branch" "true"
class X {
int test(int i) {
return switch(<caret>i) {
case 1 -> 2;
};
}
}

View File

@@ -0,0 +1,12 @@
// "Insert 'default' branch" "false"
class X {
void test(int i, int j) {
switch(i) {
case 0:
switch (j) {
<caret>
default: break;
}
}
}
}

View File

@@ -0,0 +1,12 @@
// "Insert 'default' branch" "true"
class X {
void test(int i, int j) {
switch(i) {
case 0:
<caret>
switch (j) {
default: break;
}
}
}
}

View File

@@ -0,0 +1,8 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
case 0 -> System.out.println("oops");<caret>
}
}
}

View File

@@ -0,0 +1,8 @@
// "Insert 'default' branch" "true"
class X {
void test(int i) {
switch(i) {
case 0:<caret>break;
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.fixes.controlflow;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.siyeh.ig.LightInspectionTestCase;
import com.siyeh.ig.controlflow.SwitchStatementsWithoutDefaultInspection;
import org.jetbrains.annotations.NotNull;
public class CreateDefaultBranchFixTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new SwitchStatementsWithoutDefaultInspection[]{new SwitchStatementsWithoutDefaultInspection()};
}
@Override
protected String getBasePath() {
return "/com/siyeh/igfixes/controlflow/create_default";
}
@NotNull
@Override
protected String getTestDataPath() {
return PathManagerEx.getCommunityHomePath() + LightInspectionTestCase.INSPECTION_GADGETS_TEST_DATA_PATH;
}
}