diff --git a/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java b/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java new file mode 100644 index 000000000000..b1495dea325f --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java @@ -0,0 +1,40 @@ +// Copyright 2000-2020 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.intellij.slicer; + +import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.types.DfConstantType; +import com.intellij.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiLiteralValue; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class JavaDfaSliceValueFilter implements SliceValueFilter { + private final @NotNull DfType myDfType; + + public JavaDfaSliceValueFilter(@NotNull DfType type) { + myDfType = type; + } + + @Override + public boolean allowed(PsiElement element) { + if (myDfType instanceof DfConstantType && element instanceof PsiLiteralValue) { + Object constValue = ((DfConstantType)myDfType).getValue(); + if (!(constValue instanceof PsiElement)) { + Object value = ((PsiLiteralValue)element).getValue(); + return Objects.equals(value, constValue); + } + } + if (!(element instanceof PsiExpression)) return true; + DfType dfType = CommonDataflow.getDfType((PsiExpression)element); + return dfType.meet(myDfType) != DfTypes.BOTTOM; + } + + @Override + public String toString() { + return myDfType.toString(); + } +} diff --git a/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java b/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java index 89f7271e30d7..791977581d08 100644 --- a/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java +++ b/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java @@ -15,11 +15,18 @@ */ package com.intellij.slicer; +import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.codeInspection.dataFlow.value.RelationType; import com.intellij.ide.util.treeView.AbstractTreeStructure; +import com.intellij.java.JavaBundle; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.IncorrectOperationException; +import com.siyeh.ig.psiutils.ExpressionUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -103,4 +110,95 @@ public class JavaSliceProvider implements SliceLanguageSupportProvider, SliceUsa actionGroup.add(new CanItBeNullAction(sliceTreeBuilder)); } } + + @Override + public boolean supportValueFilters(@NotNull PsiElement expression) { + PsiType type = getType(expression); + return type != null && !PsiType.VOID.equals(type) && !PsiType.NULL.equals(type); + } + + @Override + public @NotNull SliceValueFilter parseFilter(@NotNull PsiElement expression, @NotNull String filter) throws SliceFilterParseException { + PsiType type = getType(expression); + if (type == null) { + return SliceLanguageSupportProvider.super.parseFilter(expression, filter); + } + if (filter.equals("null")) { + if (type instanceof PsiPrimitiveType) { + throw new SliceFilterParseException( + JavaBundle.message("slice.filter.parse.error.null.filter.not.applicable.for.primitive.type", type.getPresentableText())); + } + return new JavaDfaSliceValueFilter(DfTypes.NULL); + } + if (filter.equals("!null")) { + if (type instanceof PsiPrimitiveType) { + throw new SliceFilterParseException( + JavaBundle.message("slice.filter.parse.error.not.null.filter.not.applicable.for.primitive.type", type.getPresentableText())); + } + return new JavaDfaSliceValueFilter(DfTypes.NOT_NULL_OBJECT); + } + RelationType relationType = RelationType.EQ; + if (PsiType.BYTE.equals(type) || + PsiType.CHAR.equals(type) || + PsiType.SHORT.equals(type) || + PsiType.INT.equals(type) || + PsiType.LONG.equals(type)) { + for (RelationType relType : RelationType.values()) { + if (filter.startsWith(relType.toString())) { + relationType = relType; + filter = filter.substring(relType.toString().length()).trim(); + break; + } + } + } + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (psiClass != null && psiClass.isEnum()) { + PsiField enumConstant = psiClass.findFieldByName(filter, false); + if (enumConstant instanceof PsiEnumConstant) { + return new JavaDfaSliceValueFilter(DfTypes.constant(enumConstant, type)); + } else { + throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.enum.constant.not.found", filter)); + } + } + PsiExpression constant; + try { + constant = JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(filter, expression); + } + catch (IncorrectOperationException ignore) { + throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.incorrect.expression", filter)); + } + PsiType constantType = constant.getType(); + if (constantType == null || !type.isAssignableFrom(constantType)) { + throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.incorrect.constant.type", type.getPresentableText())); + } + Object o = ExpressionUtils.computeConstantExpression(constant); + if (o == null) { + throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.expression.must.evaluate.to.constant", filter)); + } + if (relationType != RelationType.EQ) { + if (!(o instanceof Number)) { + throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.incorrect.constant.expected.number", filter)); + } + if (PsiType.LONG.equals(type)) { + LongRangeSet rangeSet = LongRangeSet.point(((Number)o).longValue()).fromRelation(relationType); + return new JavaDfaSliceValueFilter(DfTypes.longRange(rangeSet)); + } + LongRangeSet rangeSet = LongRangeSet.point(((Number)o).intValue()).fromRelation(relationType); + return new JavaDfaSliceValueFilter(DfTypes.intRangeClamped(rangeSet)); + } + return new JavaDfaSliceValueFilter(DfTypes.constant(o, type)); + } + + private static @Nullable PsiType getType(@NotNull PsiElement expression) { + if (expression instanceof PsiExpression) { + return ((PsiExpression)expression).getType(); + } + else if (expression instanceof PsiVariable) { + return ((PsiVariable)expression).getType(); + } + else if (expression instanceof PsiMethod) { + return ((PsiMethod)expression).getReturnType(); + } + return null; + } } diff --git a/java/java-tests/testData/codeInsight/slice/backward/FilterIntRange.java b/java/java-tests/testData/codeInsight/slice/backward/FilterIntRange.java new file mode 100644 index 000000000000..4ae41706b9ca --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/FilterIntRange.java @@ -0,0 +1,21 @@ +class Test { + void test(int x) { + System.out.println(x); + } + + void use(int x) { + if (x > 0) { + throw new IllegalArgumentException(); + } + test(x); + } + + void use2(int x) { + if (x >= 0) { + throw new IllegalArgumentException(); + } + test(x); + test(12); + test(-12); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/slice/backward/FilterNull.java b/java/java-tests/testData/codeInsight/slice/backward/FilterNull.java new file mode 100644 index 000000000000..f54552b0fc86 --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/FilterNull.java @@ -0,0 +1,15 @@ +class Test { + void test(String x) { + System.out.println(x); + } + + void use(String x) { + test(x); + System.out.println(x.trim()); + test(x); + } + + void use2(String x) { + test(x.trim()); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java index 79023b1e8f17..5d3688f77ec7 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java @@ -21,6 +21,7 @@ import com.intellij.openapi.editor.RangeMarker; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.slicer.*; +import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Map; @@ -30,10 +31,14 @@ import java.util.Map; */ public class SliceBackwardTest extends SliceTestCase { private void doTest() throws Exception { + doTest(""); + } + + private void doTest(@NotNull String filter) throws Exception { configureByFile("/codeInsight/slice/backward/"+getTestName(false)+".java"); Map sliceUsageName2Offset = SliceTestUtil.extractSliceOffsetsFromDocument(getEditor().getDocument()); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - PsiElement element = new SliceHandler(true).getExpressionAtCaret(getEditor(), getFile()); + PsiElement element = SliceHandler.create(true).getExpressionAtCaret(getEditor(), getFile()); assertNotNull(element); SliceTestUtil.Node tree = SliceTestUtil.buildTree(element, sliceUsageName2Offset); Collection errors = highlightErrors(); @@ -41,8 +46,12 @@ public class SliceBackwardTest extends SliceTestCase { SliceAnalysisParams params = new SliceAnalysisParams(); params.scope = new AnalysisScope(getProject()); params.dataFlowToThis = true; + SliceLanguageSupportProvider provider = LanguageSlicing.getProvider(element); + if (!filter.isEmpty()) { + params.valueFilter = provider.parseFilter(element, filter); + } - SliceUsage usage = LanguageSlicing.getProvider(element).createRootUsage(element, params); + SliceUsage usage = provider.createRootUsage(element, params); SliceTestUtil.checkUsages(usage, tree); } @@ -83,4 +92,6 @@ public class SliceBackwardTest extends SliceTestCase { public void testSearchOverriddenMethodsInThisClassHierarchy() throws Exception { doTest();} public void testAppend() throws Exception { doTest();} public void testRequireNonNull() throws Exception { doTest();} + public void testFilterIntRange() throws Exception { doTest(">=0");} + public void testFilterNull() throws Exception { doTest("null");} } diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/SliceForwardTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/SliceForwardTest.java index 65b942176148..47ae9ab21813 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/SliceForwardTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/SliceForwardTest.java @@ -33,7 +33,7 @@ public class SliceForwardTest extends SliceTestCase { configureByFile("/codeInsight/slice/forward/"+getTestName(false)+".java"); Map sliceUsageName2Offset = SliceTestUtil.extractSliceOffsetsFromDocument(getEditor().getDocument()); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - PsiElement element = new SliceForwardHandler().getExpressionAtCaret(getEditor(), getFile()); + PsiElement element = SliceHandler.create(false).getExpressionAtCaret(getEditor(), getFile()); assertNotNull(element); SliceTestUtil.Node tree = SliceTestUtil.buildTree(element, sliceUsageName2Offset); Collection errors = highlightErrors(); diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/SliceTreeTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/SliceTreeTest.java index 21998748f2b3..03e8292b3516 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/SliceTreeTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/SliceTreeTest.java @@ -23,7 +23,7 @@ public class SliceTreeTest extends SliceTestCase { private SliceTreeStructure configureTree(@NonNls final String name) throws Exception { configureByFile("/codeInsight/slice/backward/"+ name +".java"); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - PsiElement element = new SliceHandler(true).getExpressionAtCaret(getEditor(), getFile()); + PsiElement element = SliceHandler.create(true).getExpressionAtCaret(getEditor(), getFile()); assertNotNull(element); Collection errors = highlightErrors(); assertEmpty(errors); diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 797f4df4ccb5..9678144996c9 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -1235,4 +1235,11 @@ inspection.excessive.range.check.display.name=Excessive range check inspection.condition.covered.by.further.condition.display.name=Condition is covered by further condition inspection.move.field.assignment.to.initializer.display.name=Field assignment can be moved to initializer inspection.test.failed.line.display.name=Highlight problem line in test -inspection.frequently.used.inheritor.inspection.display.name=Class may extend a commonly used base class \ No newline at end of file +inspection.frequently.used.inheritor.inspection.display.name=Class may extend a commonly used base class +slice.filter.parse.error.null.filter.not.applicable.for.primitive.type=''null'' filter is not applicable for primitive type {0} +slice.filter.parse.error.not.null.filter.not.applicable.for.primitive.type=''!null'' filter is not applicable for primitive type {0} +slice.filter.parse.error.enum.constant.not.found=Enum constant not found: {0} +slice.filter.parse.error.incorrect.expression=Incorrect expression: {0} +slice.filter.parse.error.incorrect.constant.type=Incorrect constant type (required: {0}) +slice.filter.parse.error.expression.must.evaluate.to.constant=Expression must evaluate to a constant: {0} +slice.filter.parse.error.incorrect.constant.expected.number=Incorrect constant (expected number): {0} \ No newline at end of file diff --git a/platform/lang-api/resources/messages/LangBundle.properties b/platform/lang-api/resources/messages/LangBundle.properties index b560cc35d4b6..527218361075 100644 --- a/platform/lang-api/resources/messages/LangBundle.properties +++ b/platform/lang-api/resources/messages/LangBundle.properties @@ -301,3 +301,6 @@ action.UpDownAction.up.text=Up action.UpDownAction.down.text=Down button.override=Override button.do.not.override=Do Not Override +label.filter.value=Filter value: +slice.filter.not.supported=Filtering is not supported +slice.analysis.title.filter=(filter: {0}) diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceAnalysisParams.java b/platform/lang-impl/src/com/intellij/slicer/SliceAnalysisParams.java index c9ded4e76b77..412fddc955a7 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceAnalysisParams.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceAnalysisParams.java @@ -16,11 +16,22 @@ package com.intellij.slicer; import com.intellij.analysis.AnalysisScope; +import org.jetbrains.annotations.Nullable; public class SliceAnalysisParams { - public boolean dataFlowToThis = true; // to/from this - public boolean showInstanceDereferences = true; // show method calls or field access on the variable being analysed + /** + * Direction of flow: true = backward; false = forward + */ + public boolean dataFlowToThis = true; + /** + * show method calls or field access on the variable being analysed + */ + public boolean showInstanceDereferences = true; public AnalysisScope scope; + /** + * If present filters the occurrences + */ + public @Nullable SliceValueFilter valueFilter; public SliceAnalysisParams() { } diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceBackwardAction.java b/platform/lang-impl/src/com/intellij/slicer/SliceBackwardAction.java index 1705abc32d91..bbba347dae6a 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceBackwardAction.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceBackwardAction.java @@ -30,7 +30,7 @@ public class SliceBackwardAction extends CodeInsightAction { @NotNull @Override protected SliceHandler getHandler() { - return new SliceHandler(true); + return SliceHandler.create(true); } @Override diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceBackwardHandler.java b/platform/lang-impl/src/com/intellij/slicer/SliceBackwardHandler.java new file mode 100644 index 000000000000..8c556638dbc8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/slicer/SliceBackwardHandler.java @@ -0,0 +1,102 @@ +// Copyright 2000-2020 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.intellij.slicer; + +import com.intellij.analysis.AnalysisScope; +import com.intellij.analysis.AnalysisUIOptions; +import com.intellij.analysis.BaseAnalysisActionDialog; +import com.intellij.analysis.dialog.ModelScopeItem; +import com.intellij.lang.LangBundle; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBTextField; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.util.List; + +class SliceBackwardHandler extends SliceHandler { + SliceBackwardHandler() { + super(true); + } + + @Override + public SliceAnalysisParams askForParams(PsiElement element, + SliceManager.StoredSettingsBean storedSettingsBean, + String dialogTitle) { + AnalysisScope analysisScope = new AnalysisScope(element.getContainingFile()); + Module module = ModuleUtilCore.findModuleForPsiElement(element); + + Project myProject = element.getProject(); + AnalysisUIOptions analysisUIOptions = new AnalysisUIOptions(); + analysisUIOptions.loadState(storedSettingsBean.analysisUIOptions); + + List items = BaseAnalysisActionDialog.standardItems(myProject, analysisScope, module, element); + SliceLanguageSupportProvider provider = LanguageSlicing.getProvider(element); + boolean supportFilter = provider.supportValueFilters(element); + class BackwardHandlerDialog extends BaseAnalysisActionDialog { + JBTextField field; + + BackwardHandlerDialog() { + super(dialogTitle, "Analyze scope", myProject, items, analysisUIOptions, true); + } + + @Override + protected @Nullable JComponent getAdditionalActionSettings(Project project) { + if (!supportFilter) return null; + JPanel panel = new JPanel(new GridBagLayout()); + JBLabel label = new JBLabel(LangBundle.message("label.filter.value") + " "); + panel.add(label); + field = new JBTextField(); + Dimension size = field.getPreferredSize(); + size.width = 400; + field.setPreferredSize(size); + panel.add(field); + label.setLabelFor(field); + field.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + try { + getFilter(); + setErrorText(null, field); + } + catch (SliceFilterParseException exception) { + setErrorText(exception.getMessage(), field); + } + } + }); + return panel; + } + + private @Nullable SliceValueFilter getFilter() throws SliceFilterParseException { + String text = field.getText().trim(); + if (!text.isEmpty()) { + return provider.parseFilter(element, text); + } + return null; + } + } + BackwardHandlerDialog dialog = new BackwardHandlerDialog(); + if (!dialog.showAndGet()) { + return null; + } + + AnalysisScope scope = dialog.getScope(analysisScope); + storedSettingsBean.analysisUIOptions.loadState(analysisUIOptions); + + SliceAnalysisParams params = new SliceAnalysisParams(); + params.scope = scope; + params.dataFlowToThis = myDataFlowToThis; + try { + params.valueFilter = dialog.getFilter(); + } + catch (SliceFilterParseException ignored) { } + return params; + } +} diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceFilterParseException.java b/platform/lang-impl/src/com/intellij/slicer/SliceFilterParseException.java new file mode 100644 index 000000000000..0099dce8905e --- /dev/null +++ b/platform/lang-impl/src/com/intellij/slicer/SliceFilterParseException.java @@ -0,0 +1,10 @@ +// Copyright 2000-2020 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.intellij.slicer; + +import com.intellij.openapi.util.NlsContexts; + +public class SliceFilterParseException extends Exception { + public SliceFilterParseException(@NlsContexts.DialogMessage String message) { + super(message); + } +} diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceForwardAction.java b/platform/lang-impl/src/com/intellij/slicer/SliceForwardAction.java index a9e59e32823c..b934717fac9b 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceForwardAction.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceForwardAction.java @@ -31,7 +31,7 @@ public class SliceForwardAction extends CodeInsightAction { @NotNull @Override protected CodeInsightActionHandler getHandler() { - return new SliceForwardHandler(); + return SliceHandler.create(false); } @Override diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceForwardHandler.java b/platform/lang-impl/src/com/intellij/slicer/SliceForwardHandler.java index be8dae8ed290..9864f765fc71 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceForwardHandler.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceForwardHandler.java @@ -30,13 +30,15 @@ import java.util.List; /** * @author cdr */ -public class SliceForwardHandler extends SliceHandler { - public SliceForwardHandler() { +class SliceForwardHandler extends SliceHandler { + SliceForwardHandler() { super(false); } @Override - public SliceAnalysisParams askForParams(PsiElement element, boolean dataFlowToThis, SliceManager.StoredSettingsBean storedSettingsBean, String dialogTitle) { + public SliceAnalysisParams askForParams(PsiElement element, + SliceManager.StoredSettingsBean storedSettingsBean, + String dialogTitle) { AnalysisScope analysisScope = new AnalysisScope(element.getContainingFile()); Module module = ModuleUtilCore.findModuleForPsiElement(element); @@ -66,7 +68,7 @@ public class SliceForwardHandler extends SliceHandler { SliceAnalysisParams params = new SliceAnalysisParams(); params.scope = scope; - params.dataFlowToThis = dataFlowToThis; + params.dataFlowToThis = myDataFlowToThis; params.showInstanceDereferences = form.isToShowDerefs(); return params; } diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceHandler.java b/platform/lang-impl/src/com/intellij/slicer/SliceHandler.java index 91de48f3be57..7bffa3fc11b9 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceHandler.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceHandler.java @@ -3,34 +3,26 @@ */ package com.intellij.slicer; -import com.intellij.analysis.AnalysisScope; -import com.intellij.analysis.AnalysisUIOptions; -import com.intellij.analysis.BaseAnalysisActionDialog; -import com.intellij.analysis.dialog.ModelScopeItem; import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.TargetElementUtil; import com.intellij.codeInsight.hint.HintManager; import com.intellij.lang.LangBundle; import com.intellij.openapi.diagnostic.Logger; 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.psi.PsiElement; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - /** * @author cdr */ -public class SliceHandler implements CodeInsightActionHandler { +public abstract class SliceHandler implements CodeInsightActionHandler { private static final Logger LOG = Logger.getInstance(SliceHandler.class); - private final boolean myDataFlowToThis; + final boolean myDataFlowToThis; - public SliceHandler(boolean dataFlowToThis) { + SliceHandler(boolean dataFlowToThis) { myDataFlowToThis = dataFlowToThis; } @@ -66,34 +58,17 @@ public class SliceHandler implements CodeInsightActionHandler { PsiElement atCaret = file.findElementAt(offset); SliceLanguageSupportProvider provider = LanguageSlicing.getProvider(file); - if(provider == null || atCaret == null) { + if (provider == null || atCaret == null) { return null; } return provider.getExpressionAtCaret(atCaret, myDataFlowToThis); } - public SliceAnalysisParams askForParams(PsiElement element, boolean dataFlowToThis, SliceManager.StoredSettingsBean storedSettingsBean, String dialogTitle) { - AnalysisScope analysisScope = new AnalysisScope(element.getContainingFile()); - Module module = ModuleUtilCore.findModuleForPsiElement(element); + public abstract SliceAnalysisParams askForParams(PsiElement element, + SliceManager.StoredSettingsBean storedSettingsBean, + String dialogTitle); - Project myProject = element.getProject(); - AnalysisUIOptions analysisUIOptions = new AnalysisUIOptions(); - analysisUIOptions.loadState(storedSettingsBean.analysisUIOptions); - - List items = BaseAnalysisActionDialog.standardItems(myProject, analysisScope, - module, element); - BaseAnalysisActionDialog dialog = - new BaseAnalysisActionDialog(dialogTitle, "Analyze scope", myProject, items, analysisUIOptions, true); - if (!dialog.showAndGet()) { - return null; - } - - AnalysisScope scope = dialog.getScope(analysisScope); - storedSettingsBean.analysisUIOptions.loadState(analysisUIOptions); - - SliceAnalysisParams params = new SliceAnalysisParams(); - params.scope = scope; - params.dataFlowToThis = dataFlowToThis; - return params; + public static SliceHandler create(boolean dataFlowToThis) { + return dataFlowToThis ? new SliceBackwardHandler() : new SliceForwardHandler(); } } diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceLanguageSupportProvider.java b/platform/lang-impl/src/com/intellij/slicer/SliceLanguageSupportProvider.java index c2b0e39adb04..449538b4c28c 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceLanguageSupportProvider.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceLanguageSupportProvider.java @@ -16,6 +16,7 @@ package com.intellij.slicer; import com.intellij.ide.util.treeView.AbstractTreeStructure; +import com.intellij.lang.LangBundle; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,25 @@ public interface SliceLanguageSupportProvider { @NotNull PsiElement getElementForDescription(@NotNull PsiElement element); + /** + * @param expression expression (previously returned from {@link #getExpressionAtCaret(PsiElement, boolean)}. + * @return true if value filters are supported + */ + default boolean supportValueFilters(@NotNull PsiElement expression) { + return false; + } + + /** + * @param expression expression (previously returned from {@link #getExpressionAtCaret(PsiElement, boolean)}. + * @param filter user-entered filter string + * @return parsed {@link SliceValueFilter} + * @throws SliceFilterParseException if string cannot be parsed or filtering is not supported + */ + default @NotNull SliceValueFilter parseFilter(@NotNull PsiElement expression, @NotNull String filter) + throws SliceFilterParseException { + throw new SliceFilterParseException(LangBundle.message("slice.filter.not.supported")); + } + @NotNull SliceUsageCellRendererBase getRenderer(); diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceManager.java b/platform/lang-impl/src/com/intellij/slicer/SliceManager.java index 21a606c58b0f..840054d1a15a 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceManager.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceManager.java @@ -3,6 +3,7 @@ package com.intellij.slicer; import com.intellij.analysis.AnalysisUIOptions; import com.intellij.ide.impl.ContentManagerWatcher; +import com.intellij.lang.LangBundle; import com.intellij.openapi.components.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; @@ -62,13 +63,16 @@ public class SliceManager implements PersistentStateComponent.*)|<[^<>]*>", Pattern.DOTALL).matcher(dialogTitle).replaceAll(""); - SliceAnalysisParams params = handler.askForParams(element, dataFlowToThis, myStoredSettings, StringUtil.unescapeXmlEntities(dialogTitle)); + SliceAnalysisParams params = handler.askForParams(element, myStoredSettings, StringUtil.unescapeXmlEntities(dialogTitle)); if (params == null) return; SliceRootNode rootNode = new SliceRootNode(myProject, new DuplicateMap(), LanguageSlicing.getProvider(element).createRootUsage(element, params)); - - createToolWindow(dataFlowToThis, rootNode, false, getElementDescription(null, element, null)); + String suffix = null; + if (params.valueFilter != null) { + suffix = " " + StringUtil.escapeXmlEntities(LangBundle.message("slice.analysis.title.filter", params.valueFilter)); + } + createToolWindow(dataFlowToThis, rootNode, false, getElementDescription(null, element, suffix)); } public void createToolWindow(boolean dataFlowToThis, @NotNull SliceRootNode rootNode, boolean splitByLeafExpressions, @NotNull String displayName) { diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceUsage.java b/platform/lang-impl/src/com/intellij/slicer/SliceUsage.java index 0371bed97db1..fadd413a10be 100644 --- a/platform/lang-impl/src/com/intellij/slicer/SliceUsage.java +++ b/platform/lang-impl/src/com/intellij/slicer/SliceUsage.java @@ -81,6 +81,10 @@ public abstract class SliceUsage extends UsageInfo2UsageAdapter { }) { @Override public boolean process(SliceUsage usage) { + SliceValueFilter filter = usage.params.valueFilter; + if (filter != null && !filter.allowed(usage.getElement())) { + return true; + } return transformToLanguageSpecificUsage(usage).stream().allMatch(super::process); } }; diff --git a/platform/lang-impl/src/com/intellij/slicer/SliceValueFilter.java b/platform/lang-impl/src/com/intellij/slicer/SliceValueFilter.java new file mode 100644 index 000000000000..2eb4405a6359 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/slicer/SliceValueFilter.java @@ -0,0 +1,18 @@ +// Copyright 2000-2020 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.intellij.slicer; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nls; + +public interface SliceValueFilter { + /** + * @param element to test + * @return true if this element passes the filter + */ + boolean allowed(PsiElement element); + + /** + * @return String representation + */ + @Nls String toString(); +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTestCase.kt b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTestCase.kt index bab24c0fb81a..6ad361ee44e6 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTestCase.kt +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTestCase.kt @@ -51,7 +51,7 @@ abstract class GroovySliceTestCase(private val isDataFlowToThis: Boolean) : Daem psiDocumentManager.commitAllDocuments() - val element = SliceHandler(isDataFlowToThis).getExpressionAtCaret(editor, file)!! + val element = SliceHandler.create(isDataFlowToThis).getExpressionAtCaret(editor, file)!! val tree = SliceTestUtil.buildTree(element, sliceUsageName2Offset) val errors = highlightErrors() diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTreeGroupingTest.kt b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTreeGroupingTest.kt index 03252cd63a99..b29408b9d6c9 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTreeGroupingTest.kt +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/slicer/GroovySliceTreeGroupingTest.kt @@ -37,7 +37,7 @@ class GroovySliceTreeGroupingTest : DaemonAnalyzerTestCase() { configureByFiles(null, *getTestFiles(getTestName(false)).toTypedArray()) PsiDocumentManager.getInstance(project).commitAllDocuments() - val element = SliceHandler(true).getExpressionAtCaret(editor, file)!! + val element = SliceHandler.create(true).getExpressionAtCaret(editor, file)!! val errors = highlightErrors() UsefulTestCase.assertEmpty(errors)