1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-29 19:45:01 +02:00

Bug 355174 - Added quickfix for miss cases/default

Change-Id: I4c815bd55e55d81456efa796453dd2f69a7c876a
Signed-off-by: Marco Stornelli <marco.stornelli@gmail.com>
This commit is contained in:
Marco Stornelli 2019-05-26 20:38:03 +02:00
parent a009b41021
commit 3890eec7b7
11 changed files with 396 additions and 3 deletions

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.eclipse.cdt.codan.checkers.ui;singleton:=true
Bundle-Version: 3.2.2.qualifier
Bundle-Version: 3.2.3.qualifier
Bundle-Activator: org.eclipse.cdt.codan.internal.checkers.ui.CheckersUiActivator
Require-Bundle: org.eclipse.core.resources,
org.eclipse.core.runtime,

View file

@ -3,6 +3,14 @@
<plugin>
<extension
point="org.eclipse.cdt.codan.ui.codanMarkerResolution">
<resolution
class="org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixAddDefaultSwitch"
problemId="com.baldapps.artemis.checkers.MissDefaultProblem">
</resolution>
<resolution
class="org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixAddCaseSwitch"
problemId="com.baldapps.artemis.checkers.MissCaseProblem">
</resolution>
<resolution
class="org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixForFixit"
messagePattern=".*">

View file

@ -0,0 +1,163 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers.ui.quickfix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.cdt.codan.internal.checkers.ui.CheckersUiActivator;
import org.eclipse.cdt.codan.ui.AbstractAstRewriteQuickFix;
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.INodeFactory;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration;
import org.eclipse.cdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.dom.parser.ValueFactory;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
@SuppressWarnings("restriction")
public class QuickFixAddCaseSwitch extends AbstractAstRewriteQuickFix {
@Override
public String getLabel() {
return QuickFixMessages.QuickFixAddCaseSwitch_add_cases_to_switch;
}
@Override
public void modifyAST(IIndex index, IMarker marker) {
IASTTranslationUnit ast;
try {
ITranslationUnit tu = getTranslationUnitViaEditor(marker);
ast = tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS);
} catch (CoreException e) {
CheckersUiActivator.log(e);
return;
}
IASTNode astNode = null;
if (isCodanProblem(marker)) {
astNode = getASTNodeFromMarker(marker, ast);
}
if (astNode == null || !(astNode instanceof IASTSwitchStatement)) {
return;
}
ASTRewrite r = ASTRewrite.create(ast);
INodeFactory factory = ast.getASTNodeFactory();
Map<String, Number> missingEnums = getMissingCases((IASTSwitchStatement) astNode);
Set<Number> existing = new HashSet<>();
missingEnums = missingEnums.entrySet().stream().filter(entry -> existing.add(entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
List<IASTCaseStatement> caseStatements = new ArrayList<>();
for (Map.Entry<String, Number> e : missingEnums.entrySet()) {
IASTName newName = factory.newName(e.getKey());
caseStatements.add(factory.newCaseStatement(factory.newIdExpression(newName)));
}
IASTBreakStatement breakStatement = factory.newBreakStatement();
IASTNode[] children = astNode.getChildren();
IASTCompoundStatement compound = null;
for (int i = 0; i < children.length; ++i) {
if (children[i] instanceof IASTCompoundStatement) {
compound = (IASTCompoundStatement) children[i];
break;
}
}
if (compound == null)
return;
for (IASTCaseStatement caseStatement : caseStatements)
r.insertBefore(compound, null, caseStatement, null);
r.insertBefore(compound, null, breakStatement, null);
Change c = r.rewriteAST();
try {
c.perform(new NullProgressMonitor());
} catch (CoreException e) {
CheckersUiActivator.log(e);
return;
}
try {
marker.delete();
} catch (CoreException e) {
CheckersUiActivator.log(e);
}
}
private Map<String, Number> getMissingCases(IASTSwitchStatement statement) {
IASTExpression controller = statement.getControllerExpression();
IASTStatement bodyStmt = statement.getBody();
IType type = SemanticUtil.getUltimateType(controller.getExpressionType(), true);
Map<String, Number> enumValues = new HashMap<>();
if (type instanceof IEnumeration) {
IEnumerator[] enums = ((IEnumeration) type).getEnumerators();
String prefix = "";
if (type instanceof ICPPEnumeration) {
String[] qualName = CPPVisitor.getQualifiedName((IEnumeration) type);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < qualName.length - 1; ++i) {
builder.append(qualName[i]);
builder.append("::");
}
prefix = builder.toString();
}
for (IEnumerator e : enums) {
enumValues.put(prefix + e.getName(), e.getValue().numberValue());
}
} else
return enumValues;
final List<IASTStatement> statements;
if (bodyStmt instanceof IASTCompoundStatement) {
statements = Arrays.asList(((IASTCompoundStatement) bodyStmt).getStatements());
} else {
statements = Collections.singletonList(bodyStmt);
}
for (IASTStatement s : statements) {
if (s instanceof IASTCaseStatement && ((IASTCaseStatement) s).getExpression() instanceof IASTIdExpression) {
IASTName name = ((IASTIdExpression) ((IASTCaseStatement) s).getExpression()).getName();
IBinding binding = name.resolveBinding();
if (binding instanceof IEnumerator) {
enumValues.entrySet().removeIf(
entry -> entry.getValue().equals(((IEnumerator) binding).getValue().numberValue()));
}
} else if (s instanceof IASTCaseStatement
&& ((IASTCaseStatement) s).getExpression() instanceof IASTLiteralExpression) {
Number value = ValueFactory.getConstantNumericalValue(((IASTCaseStatement) s).getExpression());
if (value != null)
enumValues.entrySet().removeIf(entry -> entry.getValue().equals(value));
}
}
return enumValues;
}
}

View file

@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers.ui.quickfix;
import org.eclipse.cdt.codan.internal.checkers.ui.CheckersUiActivator;
import org.eclipse.cdt.codan.ui.AbstractAstRewriteQuickFix;
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.INodeFactory;
import org.eclipse.cdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
public class QuickFixAddDefaultSwitch extends AbstractAstRewriteQuickFix {
@Override
public String getLabel() {
return QuickFixMessages.QuickFixAddDefaultSwitch_add_default_to_switch;
}
@Override
public void modifyAST(IIndex index, IMarker marker) {
IASTTranslationUnit ast;
try {
ITranslationUnit tu = getTranslationUnitViaEditor(marker);
ast = tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS);
} catch (CoreException e) {
CheckersUiActivator.log(e);
return;
}
IASTNode astNode = null;
if (isCodanProblem(marker)) {
astNode = getASTNodeFromMarker(marker, ast);
}
if (astNode == null || !(astNode instanceof IASTSwitchStatement)) {
return;
}
ASTRewrite r = ASTRewrite.create(ast);
INodeFactory factory = ast.getASTNodeFactory();
IASTDefaultStatement defStatement = factory.newDefaultStatement();
IASTBreakStatement breakStatement = factory.newBreakStatement();
IASTNode[] children = astNode.getChildren();
IASTCompoundStatement compound = null;
for (int i = 0; i < children.length; ++i) {
if (children[i] instanceof IASTCompoundStatement) {
compound = (IASTCompoundStatement) children[i];
break;
}
}
if (compound == null)
return;
r.insertBefore(compound, null, defStatement, null);
r.insertBefore(compound, null, breakStatement, null);
Change c = r.rewriteAST();
try {
c.perform(new NullProgressMonitor());
} catch (CoreException e) {
CheckersUiActivator.log(e);
return;
}
try {
marker.delete();
} catch (CoreException e) {
CheckersUiActivator.log(e);
}
}
}

View file

@ -29,6 +29,8 @@ public class QuickFixMessages extends NLS {
public static String QuickFixUseDotOperator_replace_ptr;
public static String QuickFixForFixit_apply_fixit;
public static String QuickFixSuppressProblem_Label;
public static String QuickFixAddDefaultSwitch_add_default_to_switch;
public static String QuickFixAddCaseSwitch_add_cases_to_switch;
static {
NLS.initializeMessages(QuickFixMessages.class.getName(), QuickFixMessages.class);

View file

@ -23,4 +23,6 @@ QuickFixAddSemicolon_add_semicolon=Add semicolon
QuickFixUsePointer_replace_dot=Replace '.' with '->'
QuickFixUseDotOperator_replace_ptr=Replace '->' with '.'
QuickFixForFixit_apply_fixit=Apply compiler recommended fix-it
QuickFixSuppressProblem_Label=Suppress problem "%s"
QuickFixSuppressProblem_Label=Suppress problem "%s"
QuickFixAddDefaultSwitch_add_default_to_switch=Add default to switch
QuickFixAddCaseSwitch_add_cases_to_switch=Add missing cases to switch

View file

@ -43,6 +43,8 @@ import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CaseBreakQuickFixComm
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CaseBreakQuickFixFallthroughAttributeTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CatchByReferenceQuickFixTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CreateLocalVariableQuickFixTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixAddCaseTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixAddDefaultTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.QuickFixSuppressProblemTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.SuggestedParenthesisQuickFixTest;
@ -103,6 +105,8 @@ public class AutomatedIntegrationSuite extends TestSuite {
suite.addTestSuite(CaseBreakQuickFixFallthroughAttributeTest.class);
suite.addTestSuite(AssignmentInConditionQuickFixTest.class);
suite.addTestSuite(QuickFixSuppressProblemTest.class);
suite.addTestSuite(QuickFixAddDefaultTest.class);
suite.addTestSuite(QuickFixAddCaseTest.class);
return suite;
}
}

View file

@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marco Stornelli - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers.ui.quickfix;
import org.eclipse.cdt.codan.internal.checkers.SwitchCaseChecker;
import org.eclipse.cdt.codan.ui.AbstractCodanCMarkerResolution;
public class QuickFixAddCaseTest extends QuickFixTestCase {
@Override
protected AbstractCodanCMarkerResolution createQuickFix() {
return new QuickFixAddCaseSwitch();
}
@Override
public boolean isCpp() {
return true;
}
@Override
public void setUp() throws Exception {
super.setUp();
enableProblems(SwitchCaseChecker.MISS_CASE_ID);
}
//enum FRUIT {
// APPLE, PEAR, BANANA
//};
//void func() {
// FRUIT f = APPLE;
// switch(f) {
// case APPLE:
// break;
// }
//}
public void testAddCase() throws Exception {
loadcode(getAboveComment());
String result = runQuickFixOneFile();
assertContainedIn("PEAR:", result); //$NON-NLS-1$
assertContainedIn("BANANA:", result); //$NON-NLS-1$
}
}

View file

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marco Stornelli - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers.ui.quickfix;
import org.eclipse.cdt.codan.internal.checkers.SwitchCaseChecker;
import org.eclipse.cdt.codan.ui.AbstractCodanCMarkerResolution;
public class QuickFixAddDefaultTest extends QuickFixTestCase {
@Override
protected AbstractCodanCMarkerResolution createQuickFix() {
return new QuickFixAddDefaultSwitch();
}
@Override
public boolean isCpp() {
return true;
}
@Override
public void setUp() throws Exception {
super.setUp();
enableProblems(SwitchCaseChecker.MISS_DEFAULT_ID);
}
//void func() {
// int a = 0;
// switch(a) {
// case 0:
// break;
// }
//}
public void testAddDefault() throws Exception {
loadcode(getAboveComment());
String result = runQuickFixOneFile();
assertContainedIn("default:", result); //$NON-NLS-1$
}
}

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.eclipse.cdt.codan.ui.cxx;singleton:=true
Bundle-Version: 3.4.1.qualifier
Bundle-Version: 3.5.0.qualifier
Bundle-Activator: org.eclipse.cdt.codan.internal.ui.cxx.Activator
Bundle-Vendor: %Bundle-Vendor
Require-Bundle: org.eclipse.ui,

View file

@ -21,6 +21,7 @@ import org.eclipse.cdt.codan.internal.core.model.CodanProblemMarker;
import org.eclipse.cdt.codan.internal.ui.CodanUIActivator;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.CModelException;
@ -309,4 +310,30 @@ public abstract class AbstractCodanCMarkerResolution implements ICodanMarkerReso
public String getProblemMessage(IMarker marker) {
return CodanProblemMarker.getMessage(marker);
}
/**
* Get the enclosing node using the input position
* @param ast The AST
* @param charStart Start position
* @param length Length
* @return The node or null
* @since 3.5
*/
protected IASTNode getASTNodeFromPosition(IASTTranslationUnit ast, final int charStart, final int length) {
IASTNode node = ast.getNodeSelector(null).findEnclosingNode(charStart, length);
return node;
}
/**
* Get the enclosing node using the input marker
* @param marker The marker
* @param ast The AST
* @return The node or null
* @since 3.5
*/
protected IASTNode getASTNodeFromMarker(IMarker marker, IASTTranslationUnit ast) {
final int charStart = marker.getAttribute(IMarker.CHAR_START, -1);
final int length = marker.getAttribute(IMarker.CHAR_END, -1) - charStart;
return getASTNodeFromPosition(ast, charStart, length);
}
}