From 6fb1e974392c089ddb8d57dbbb5b46c32c4ce2df Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 2 Sep 2014 19:11:57 +0400 Subject: [PATCH 1/3] Cherry-picked static call hierarchy for Python from novokrest/intellij-idea:master --- python/src/META-INF/python-core.xml | 26 ++ .../PyFunctionFindUsagesHandler.java | 4 +- .../hierarchy/call/PyCallDataManager.java | 32 +++ .../call/PyCallHierarchyBrowser.java | 115 +++++++++ .../call/PyCallHierarchyNodeDescriptor.java | 240 ++++++++++++++++++ .../call/PyCallHierarchyProvider.java | 76 ++++++ .../call/PyCalleeFunctionTreeStructure.java | 88 +++++++ .../call/PyCallerFunctionTreeStructure.java | 87 +++++++ .../call/PyStaticCallDataManager.java | 28 ++ .../call/PyStaticCallDataManagerImpl.java | 165 ++++++++++++ .../ArgumentList_callee_verification.xml | 1 + .../ArgumentList_caller_verification.xml | 1 + .../call/Static/ArgumentList/file_1.py | 8 + .../call/Static/ArgumentList/main.py | 8 + .../Constructor_callee_verification.xml | 8 + .../Constructor_caller_verification.xml | 6 + .../hierarchy/call/Static/Constructor/main.py | 32 +++ .../DefaultValue_callee_verification.xml | 1 + .../DefaultValue_caller_verification.xml | 4 + .../call/Static/DefaultValue/main.py | 30 +++ .../Inheritance_callee_verification.xml | 1 + .../Inheritance_caller_verification.xml | 4 + .../hierarchy/call/Static/Inheritance/main.py | 36 +++ .../InnerFunction_callee_verification.xml | 5 + .../InnerFunction_caller_verification.xml | 1 + .../call/Static/InnerFunction/main.py | 9 + .../Lambda/Lambda_callee_verification.xml | 8 + .../Lambda/Lambda_caller_verification.xml | 1 + .../hierarchy/call/Static/Lambda/file_1.py | 18 ++ .../hierarchy/call/Static/Lambda/main.py | 18 ++ .../NestedCall_callee_verification.xml | 12 + .../NestedCall_caller_verification.xml | 1 + .../call/Static/NestedCall/file_1.py | 10 + .../hierarchy/call/Static/NestedCall/main.py | 10 + .../OverriddenMethod_callee_verification.xml | 3 + .../OverriddenMethod_caller_verification.xml | 5 + .../call/Static/OverriddenMethod/file_1.py | 8 + .../call/Static/OverriddenMethod/main.py | 29 +++ .../Parentheses_callee_verification.xml | 3 + .../Parentheses_caller_verification.xml | 3 + .../call/Static/Parentheses/file_1.py | 20 ++ .../hierarchy/call/Static/Parentheses/main.py | 7 + .../Simple/Simple_callee_verification.xml | 3 + .../Simple/Simple_caller_verification.xml | 3 + .../hierarchy/call/Static/Simple/main.py | 10 + .../HierarchyTreeStructureViewer.java | 160 ++++++++++++ .../python/hierarchy/PyCallHierarchyTest.java | 121 +++++++++ 47 files changed, 1467 insertions(+), 2 deletions(-) create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java create mode 100644 python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java create mode 100644 python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/ArgumentList/file_1.py create mode 100644 python/testData/hierarchy/call/Static/ArgumentList/main.py create mode 100644 python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Constructor/main.py create mode 100644 python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/DefaultValue/main.py create mode 100644 python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Inheritance/main.py create mode 100644 python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/InnerFunction/main.py create mode 100644 python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Lambda/file_1.py create mode 100644 python/testData/hierarchy/call/Static/Lambda/main.py create mode 100644 python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/NestedCall/file_1.py create mode 100644 python/testData/hierarchy/call/Static/NestedCall/main.py create mode 100644 python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py create mode 100644 python/testData/hierarchy/call/Static/OverriddenMethod/main.py create mode 100644 python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Parentheses/file_1.py create mode 100644 python/testData/hierarchy/call/Static/Parentheses/main.py create mode 100644 python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml create mode 100644 python/testData/hierarchy/call/Static/Simple/main.py create mode 100644 python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java create mode 100644 python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 7adbc46c189c..261300a81cd7 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -137,6 +137,7 @@ + @@ -476,6 +477,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + myAllElements; - protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) { + public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) { super(psiElement); myAllElements = null; } - protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List allElements) { + public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List allElements) { super(psiElement); myAllElements = allElements; } diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java new file mode 100644 index 000000000000..08fd1f2bde89 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.PyElement; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + + +/** + * @author novokrest + */ +public interface PyCallDataManager { + Collection getCallees(@NotNull PyElement pyElement); + + Collection getCallers(@NotNull PyElement pyElement); +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java new file mode 100644 index 000000000000..69d2f8c31d6d --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java @@ -0,0 +1,115 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.intellij.ide.hierarchy.CallHierarchyBrowserBase; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.PsiElement; +import com.intellij.ui.PopupHandler; +import com.jetbrains.python.hierarchy.PyHierarchyUtils; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Comparator; +import java.util.Map; + +/** + * @author novokrest + */ +public class PyCallHierarchyBrowser extends CallHierarchyBrowserBase { + private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.hierarchy.call.PyCallHierarchyBrowser"); + private static final String GROUP_PY_CALL_HIERARCHY_POPUP = "PyCallHierarchyPopupMenu"; + + public PyCallHierarchyBrowser(PsiElement function) { + super(function.getProject(), function); + } + + @Nullable + @Override + protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) { + if (descriptor instanceof PyCallHierarchyNodeDescriptor) { + PyCallHierarchyNodeDescriptor nodeDescriptor = (PyCallHierarchyNodeDescriptor)descriptor; + return nodeDescriptor.getEnclosingElement(); + } + return null; + } + + @Override + protected PsiElement getOpenFileElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) { + if (descriptor instanceof PyCallHierarchyNodeDescriptor) { + PyCallHierarchyNodeDescriptor nodeDescriptor = (PyCallHierarchyNodeDescriptor)descriptor; + return nodeDescriptor.getTargetElement(); + } + return null; + } + + @Override + protected void createTrees(@NotNull Map type2TreeMap) { + ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(GROUP_PY_CALL_HIERARCHY_POPUP); + final JTree tree1 = createTree(false); + PopupHandler.installPopupHandler(tree1, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance()); + final BaseOnThisFunctionAction baseOnThisFunctionAction = new BaseOnThisFunctionAction(); + baseOnThisFunctionAction + .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).getShortcutSet(), tree1); + type2TreeMap.put(CALLER_TYPE, tree1); + + final JTree tree2 = createTree(false); + PopupHandler.installPopupHandler(tree2, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance()); + baseOnThisFunctionAction + .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).getShortcutSet(), tree2); + type2TreeMap.put(CALLEE_TYPE, tree2); + } + + @Override + protected boolean isApplicableElement(@NotNull PsiElement element) { + return element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile; + } + + @Nullable + @Override + protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) { + if (CALLER_TYPE.equals(typeName)) { + return new PyCallerFunctionTreeStructure(myProject, psiElement, getCurrentScopeType()); + } + else if (CALLEE_TYPE.equals(typeName)) { + return new PyCalleeFunctionTreeStructure(myProject, psiElement, getCurrentScopeType()); + } + else { + LOG.error("unexpected type: " + typeName); + return null; + } + } + + @Nullable + @Override + protected Comparator getComparator() { + return PyHierarchyUtils.getComparator(myProject); + } + + public static final class BaseOnThisFunctionAction extends CallHierarchyBrowserBase.BaseOnThisMethodAction { + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java new file mode 100644 index 000000000000..2aff7a06e4e7 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java @@ -0,0 +1,240 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.icons.AllIcons; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ui.util.CompositeAppearance; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.TextRange; +import com.intellij.pom.Navigatable; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.LayeredIcon; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author novokrest + */ +public class PyCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable { + private int myUsageCount = 1; + private final List myReferences = new ArrayList(); + private final boolean myNavigateToReference; + + protected PyCallHierarchyNodeDescriptor(@NotNull Project project, + @Nullable NodeDescriptor parentDescriptor, + @NotNull PsiElement element, + boolean isBase, + boolean navigateToReference) { + super(project, parentDescriptor, element, isBase); + myNavigateToReference = navigateToReference; + } + + public final void incrementCount() { + myUsageCount++; + } + + public final PyFunction getPyFunction() { + return (PyFunction)myElement; + } + + public final PyElement getEnclosingElement() { + return PsiTreeUtil.getNonStrictParentOfType(myElement, PyFunction.class, PyClass.class, PyFile.class); + } + + public final PsiElement getTargetElement() { + return myElement; + } + + @Override + public boolean isValid() { + return myElement != null && myElement.isValid(); + } + + @Override + public boolean update() { + final CompositeAppearance oldText = myHighlightedText; + final Icon oldIcon = getIcon(); + + int flags = Iconable.ICON_FLAG_VISIBILITY; + if (isMarkReadOnly()) { + flags |= Iconable.ICON_FLAG_READ_STATUS; + } + + final PsiElement element = getEnclosingElement(); + if (element == null) { + final String invalidPrefix = IdeBundle.message("node.hierarchy.invalid"); + if (!myHighlightedText.getText().startsWith(invalidPrefix)) { + myHighlightedText.getBeginning().addText(invalidPrefix, HierarchyNodeDescriptor.getInvalidPrefixAttributes()); + } + return true; + } + + boolean changes = super.update(); + + Icon newIcon = element.getIcon(flags); + if (changes && myIsBase) { + final LayeredIcon icon = new LayeredIcon(2); + icon.setIcon(newIcon, 0); + icon.setIcon(AllIcons.Hierarchy.Base, 1, -AllIcons.Hierarchy.Base.getIconWidth() / 2, 0); + newIcon = icon; + } + setIcon(newIcon); + + myHighlightedText = new CompositeAppearance(); + final TextAttributes mainTextAttributes = myColor != null ? null : new TextAttributes(myColor, null, null, null, Font.PLAIN); + + PyElementVisitor visitor = new PyElementVisitor() { + @Override + public void visitPyClass(PyClass pyClass) { + myHighlightedText.getEnding().addText(pyClass.getName(), mainTextAttributes); + myHighlightedText.getEnding().addText("(" + pyClass.getContainingFile().getName() + ")", + HierarchyNodeDescriptor.getPackageNameAttributes()); + } + + @Override + public void visitPyFunction(PyFunction function) { + final StringBuilder buffer = new StringBuilder(); + final PyClass pyClass = function.getContainingClass(); + if (pyClass != null) { + buffer.append(pyClass.getName()); + buffer.append('.'); + } + buffer.append(function.getName()); + + myHighlightedText.getEnding().addText(buffer.toString(), mainTextAttributes); + myHighlightedText.getEnding().addText("(" + function.getContainingFile().getName() + ")", + HierarchyNodeDescriptor.getPackageNameAttributes()); + } + + @Override + public void visitPyLambdaExpression(PyLambdaExpression node) { + } + + @Override + public void visitPyFile(PyFile pyFile) { + myHighlightedText.getEnding().addText(pyFile.getName(), mainTextAttributes); + } + }; + + element.accept(visitor); + + if (myUsageCount > 1) { + myHighlightedText.getEnding() + .addText(IdeBundle.message("node.call.hierarchy.N.usages", myUsageCount), HierarchyNodeDescriptor.getUsageCountPrefixAttributes()); + } + + myName = myHighlightedText.getText(); + if (!Comparing.equal(myHighlightedText, oldText) + || !Comparing.equal(getIcon(), oldIcon)) { + return true; + } + + return changes; + } + + public void addReference(final PsiReference reference) { + myReferences.add(reference); + } + + public boolean hasReference(final PsiReference reference) { + return myReferences.contains(reference); + } + + + @Override + public void navigate(boolean requestFocus) { + if (!myNavigateToReference) { + if (myElement instanceof Navigatable && ((Navigatable)myElement).canNavigate()) { + ((Navigatable)myElement).navigate(requestFocus); + } + return; + } + final PsiReference firstReference = myReferences.get(0); + final PsiElement element = firstReference.getElement(); + if (element == null) return; + final PsiElement callElement = element.getParent(); + if (callElement instanceof Navigatable && ((Navigatable)callElement).canNavigate()) { + ((Navigatable)callElement).navigate(requestFocus); + } + else { + final PsiFile file = callElement.getContainingFile(); + if (file == null || file.getVirtualFile() == null) return; + FileEditorManager.getInstance(myProject).openFile(file.getVirtualFile(), requestFocus); + } + + Editor editor = PsiUtilBase.findEditor(callElement); + if (editor != null) { + HighlightManager highlightManager = HighlightManager.getInstance(myProject); + EditorColorsManager colorsManager = EditorColorsManager.getInstance(); + TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); + List highlighters = new ArrayList(); + for (PsiReference reference : myReferences) { + final PsiElement eachElement = reference.getElement(); + if (eachElement != null) { + PsiElement eachCallElement = eachElement.getParent(); + if (eachCallElement != null) { + final TextRange textRange = eachCallElement.getTextRange(); + highlightManager + .addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, false, highlighters); + } + } + } + } + } + + @Override + public boolean canNavigate() { + if (!myNavigateToReference) { + return myElement instanceof Navigatable && ((Navigatable)myElement).canNavigate(); + } + if (myReferences.isEmpty()) return false; + final PsiReference reference = myReferences.get(0); + final PsiElement callElement = reference.getElement().getParent(); + if (callElement == null || !callElement.isValid()) return false; + if (!(callElement instanceof Navigatable) || !((Navigatable)callElement).canNavigate()) { + final PsiFile file = callElement.getContainingFile(); + if (file == null) return false; + } + return true; + } + + @Override + public boolean canNavigateToSource() { + return canNavigate(); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java new file mode 100644 index 000000000000..67c0d6ddfa0b --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.ide.hierarchy.CallHierarchyBrowserBase; +import com.intellij.ide.hierarchy.HierarchyBrowser; +import com.intellij.ide.hierarchy.HierarchyProvider; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author novokrest + */ +public class PyCallHierarchyProvider implements HierarchyProvider { + @Nullable + @Override + public PsiElement getTarget(@NotNull DataContext dataContext) { + Project project = CommonDataKeys.PROJECT.getData(dataContext); + if (project == null) return null; + + PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); + if (element == null) { + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (editor != null) { + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == null) return null; + + element = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | + TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED | + TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED); + if (element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile) { + return element; + } + + element = file.findElementAt(editor.getCaretModel().getOffset()); + } + } + return PsiTreeUtil.getNonStrictParentOfType(element, PyFunction.class, PyClass.class, PyFile.class); + } + + @NotNull + @Override + public HierarchyBrowser createHierarchyBrowser(PsiElement target) { + return new PyCallHierarchyBrowser(target); + } + + @Override + public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) { + ((PyCallHierarchyBrowser)hierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java new file mode 100644 index 000000000000..d3106dd7dbc3 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.google.common.collect.Lists; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author novokrest + */ +public class PyCalleeFunctionTreeStructure extends HierarchyTreeStructure { + private final String myScopeType; + + public PyCalleeFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) { + super(project, new PyCallHierarchyNodeDescriptor(project, null, element, true, false)); + myScopeType = currentScopeType; + } + + @NotNull + @Override + protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { + final PyElement element = ((PyCallHierarchyNodeDescriptor)descriptor).getEnclosingElement(); + final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile; + HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor(); + if (!isCallable || nodeDescriptor == null) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + final List callees = Lists.newArrayList(); + + PyCallDataManager[] functionManagers = { + // TODO: Add dynamic call data manager + PyStaticCallDataManager.getInstance(myProject), + }; + + for (PyCallDataManager functionManager : functionManagers) { + callees.addAll(functionManager.getCallees(element)); + } + + final Map calleeToDescriptorMap = new HashMap(); + final List descriptors = Lists.newArrayList(); + PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null; + + for (PsiElement callee : callees) { + if (baseClass != null && !isInScope(baseClass, callee, myScopeType)) continue; + + PyCallHierarchyNodeDescriptor calleeDescriptor = calleeToDescriptorMap.get(callee); + if (calleeDescriptor == null) { + calleeDescriptor = new PyCallHierarchyNodeDescriptor(myProject, null, callee, false, false); + calleeToDescriptorMap.put(callee, calleeDescriptor); + descriptors.add(calleeDescriptor); + } + } + + return ArrayUtil.toObjectArray(descriptors); + } + + @Override + public boolean isAlwaysShowPlus() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java new file mode 100644 index 000000000000..844f728381e8 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.google.common.collect.Lists; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.HashMap; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author novokrest + */ +public class PyCallerFunctionTreeStructure extends HierarchyTreeStructure { + private final String myScopeType; + + public PyCallerFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) { + super(project, new PyCallHierarchyNodeDescriptor(project, null, element, true, false)); + myScopeType = currentScopeType; + } + + @NotNull + @Override + protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { + final PyElement element = ((PyCallHierarchyNodeDescriptor)descriptor).getEnclosingElement(); + final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile; + HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor(); + if (!isCallable || nodeDescriptor == null) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + final List callers = Lists.newArrayList(); + + PyCallDataManager[] functionManagers = { + // TODO: Add dynamic call data manager + PyStaticCallDataManager.getInstance(myProject), + }; + + for (PyCallDataManager functionManager : functionManagers) { + callers.addAll(functionManager.getCallers(element)); + } + + final HashMap callerToDescriptorMap = new HashMap(); + final List descriptors = Lists.newArrayList(); + PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null; + + for (PsiElement caller : callers) { + if (baseClass != null && !isInScope(baseClass, caller, myScopeType)) continue; + + PyCallHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller); + if (callerDescriptor == null) { + callerDescriptor = new PyCallHierarchyNodeDescriptor(myProject, null, caller, false, false); + callerToDescriptorMap.put(caller, callerDescriptor); + descriptors.add(callerDescriptor); + } + } + + return ArrayUtil.toObjectArray(descriptors); + } + + @Override + public boolean isAlwaysShowPlus() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java new file mode 100644 index 000000000000..ed7c2f12721a --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; + +/** + * @author novokrest + */ +public abstract class PyStaticCallDataManager implements PyCallDataManager { + public static PyCallDataManager getInstance(Project project) { + return ServiceManager.getService(project, PyStaticCallDataManager.class); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java new file mode 100644 index 000000000000..f918638dc936 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java @@ -0,0 +1,165 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy.call; + +import com.google.common.collect.Lists; +import com.intellij.find.findUsages.FindUsagesHandler; +import com.intellij.find.findUsages.FindUsagesOptions; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.ArrayUtil; +import com.intellij.util.CommonProcessors; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.findUsages.PyClassFindUsagesHandler; +import com.jetbrains.python.findUsages.PyFunctionFindUsagesHandler; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.search.PySuperMethodsSearch; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * @author novokrest + */ +public class PyStaticCallDataManagerImpl extends PyStaticCallDataManager { + @Override + public Collection getCallees(@NotNull PyElement element) { + final List callees = Lists.newArrayList(); + + final PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() { + @Override + public void visitPyParameterList(PyParameterList node) { + } + + @Override + public void visitPyLambdaExpression(PyLambdaExpression node) { + } + + @Override + public void visitPyFunction(PyFunction innerFunction) { + for (PyParameter parameter : innerFunction.getParameterList().getParameters()) { + PsiElement defaultValue = parameter.getDefaultValue(); + if (defaultValue != null) { + defaultValue.accept(this); + } + } + } + + @Override + public void visitPyCallExpression(PyCallExpression callExpression) { + super.visitPyCallExpression(callExpression); + PsiElement calleeFunction = callExpression.resolveCalleeFunction(PyResolveContext.defaultContext()); + if (calleeFunction instanceof PyFunction) { + callees.add(calleeFunction); + } + } + }; + + visitor.visitElement(element); + + return callees; + } + + @Override + public Collection getCallers(@NotNull PyElement pyElement) { + final List callers = Lists.newArrayList(); + final Collection usages = findUsages(pyElement); + + for (UsageInfo usage : usages) { + PsiElement element = usage.getElement(); + if (element == null) { + continue; + } + + element = element.getParent(); + while (element instanceof PyParenthesizedExpression) { + element = element.getParent(); + } + + if (element instanceof PyCallExpression) { + PsiElement caller = PsiTreeUtil.getParentOfType(element, PyParameterList.class, PyFunction.class); + if (caller instanceof PyFunction) { + callers.add(caller); + } + else if (caller instanceof PyParameterList) { + PsiElement innerFunction = PsiTreeUtil.getParentOfType(caller, PyFunction.class); + PsiElement outerFunction = PsiTreeUtil.getParentOfType(innerFunction, PyFunction.class); + if (innerFunction != null && outerFunction != null) { + callers.add(outerFunction); + } + } + } + } + + return callers; + } + + private static Collection findUsages(@NotNull final PsiElement element) { + final FindUsagesHandler handler = createFindUsageHandler(element); + if (handler == null) { + return Lists.newArrayList(); + } + final CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(); + final PsiElement[] psiElements = ArrayUtil.mergeArrays(handler.getPrimaryElements(), handler.getSecondaryElements()); + final FindUsagesOptions options = handler.getFindUsagesOptions(null); + for (PsiElement psiElement : psiElements) { + handler.processElementUsages(psiElement, processor, options); + } + return processor.getResults(); + } + + /** + * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#createFindUsagesHandler(com.intellij.psi.PsiElement, boolean) createFindUsagesHandler} + */ + @Nullable + private static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElement element) { + if (element instanceof PyFunction) { + final Collection superMethods = PySuperMethodsSearch.search((PyFunction)element, true).findAll(); + if (superMethods.size() > 0) { + final PsiElement next = superMethods.iterator().next(); + if (next instanceof PyFunction && !isInObject((PyFunction)next)) { + List allMethods = Lists.newArrayList(); + allMethods.add(element); + allMethods.addAll(superMethods); + + return new PyFunctionFindUsagesHandler(element, allMethods); + } + } + return new PyFunctionFindUsagesHandler(element); + } + if (element instanceof PyClass) { + return new PyClassFindUsagesHandler((PyClass)element); + } + return null; + } + + /** + * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#isInObject(com.jetbrains.python.psi.PyFunction) isInObject} + */ + private static boolean isInObject(PyFunction fun) { + final PyClass containingClass = fun.getContainingClass(); + if (containingClass == null) { + return false; + } + return (PyNames.FAKE_OLD_BASE.equals(containingClass.getName()) || + (PyNames.OBJECT.equals(containingClass.getName()) && PyBuiltinCache.getInstance(fun).isBuiltin(containingClass))); + } +} diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml new file mode 100644 index 000000000000..2f7b31c8602e --- /dev/null +++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml new file mode 100644 index 000000000000..2f7b31c8602e --- /dev/null +++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/ArgumentList/file_1.py b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py new file mode 100644 index 000000000000..ea497204d2ca --- /dev/null +++ b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py @@ -0,0 +1,8 @@ +def target_func(): + pass + +def func1(f): + f() + +def func2(f): + return f \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/ArgumentList/main.py b/python/testData/hierarchy/call/Static/ArgumentList/main.py new file mode 100644 index 000000000000..b479ea6f9d3a --- /dev/null +++ b/python/testData/hierarchy/call/Static/ArgumentList/main.py @@ -0,0 +1,8 @@ +from file_1 import * + +target_func() +func1(target_func) +func1(target_func()) +func2(target_func) +func2(target_func()) + diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml new file mode 100644 index 000000000000..4e9a681100a1 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml new file mode 100644 index 000000000000..765e6f5f9e47 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Constructor/main.py b/python/testData/hierarchy/call/Static/Constructor/main.py new file mode 100644 index 000000000000..1c2e14776c25 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Constructor/main.py @@ -0,0 +1,32 @@ +class A(): + def __init__(self): + invoke1(self) + invoke2(self) + + def method1(self): + pass + + def method2(self): + pass + +def invoke1(p): + p.method1() + + +def invoke2(p): + p.method2() + + +def invokeA(): + a = A() + a.method1() + a.method2() + + def new_class_func(): + class C(): + def bar(self): + invokeA(A()) + return C() + +a = A() +A.__init__(a) \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml new file mode 100644 index 000000000000..98e62b66545f --- /dev/null +++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml new file mode 100644 index 000000000000..bb2ed79ce71e --- /dev/null +++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/DefaultValue/main.py b/python/testData/hierarchy/call/Static/DefaultValue/main.py new file mode 100644 index 000000000000..223642493ef4 --- /dev/null +++ b/python/testData/hierarchy/call/Static/DefaultValue/main.py @@ -0,0 +1,30 @@ +def target_func(): + pass + + +def func1(): + def inner_func1(x=target_func): + pass + + return inner_func1(target_func) + + +def func2(): + def inner_func2(x=target_func()): + pass + + return inner_func2(target_func) + + +def func3(x=target_func()): + pass + + +def func4(): + def inner_func4(x=target_func): + pass + + return inner_func4(target_func()) + + +target_func() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml new file mode 100644 index 000000000000..df150105b3a1 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml new file mode 100644 index 000000000000..678468dbc8cd --- /dev/null +++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Inheritance/main.py b/python/testData/hierarchy/call/Static/Inheritance/main.py new file mode 100644 index 000000000000..89c37de9bceb --- /dev/null +++ b/python/testData/hierarchy/call/Static/Inheritance/main.py @@ -0,0 +1,36 @@ + +class A(object): + def target_func(self): + pass + + +class B(A): + pass + + +class C(object): + def func(self, a): + a.target_func() + + +def foo1(b): + f = b.target_func + + +def foo2(b): + b.target_func() + + +def bar1(*args): + pass + + +def bar2(*args): + pass + + +b = B() +foo1(b) +foo2(b) +bar1(b.target_func) +bar2(b.target_func()) \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml new file mode 100644 index 000000000000..dac68ae1548e --- /dev/null +++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml new file mode 100644 index 000000000000..98e62b66545f --- /dev/null +++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/InnerFunction/main.py b/python/testData/hierarchy/call/Static/InnerFunction/main.py new file mode 100644 index 000000000000..7ffc14aaf745 --- /dev/null +++ b/python/testData/hierarchy/call/Static/InnerFunction/main.py @@ -0,0 +1,9 @@ +def bar(): + pass + +def target_func(): + def foo(): + return bar() + foo() + +target_func() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml new file mode 100644 index 000000000000..8e2ff7ea0ab0 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml new file mode 100644 index 000000000000..98e62b66545f --- /dev/null +++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Lambda/file_1.py b/python/testData/hierarchy/call/Static/Lambda/file_1.py new file mode 100644 index 000000000000..bf1185212963 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Lambda/file_1.py @@ -0,0 +1,18 @@ +def func1(): pass +def func2(): pass +def func3(): pass +def func4(): pass +def func5(): pass +def func6(): pass +def func7(): pass +def func8(): pass +def func9(): pass +def func10(): pass +def func11(): pass +def func12(): pass +def func13(): pass +def func14(): pass +def func15(): pass +def func16(): pass +def func17(): pass +def func18(): pass diff --git a/python/testData/hierarchy/call/Static/Lambda/main.py b/python/testData/hierarchy/call/Static/Lambda/main.py new file mode 100644 index 000000000000..7dab14e27566 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Lambda/main.py @@ -0,0 +1,18 @@ +from file_1 import * + + +def target_func(x=func1, y=func2(), z=lambda: func3, w=lambda: func4()): + p1 = lambda: func5() + p2 = lambda: func6 + p1(), p2() + def inner(ix=func7, iy=func8(), iz=lambda: func9, iw=lambda: func10()): + func11() + ip = lambda: func12() + ip() + func13() + inner(func14, func15(), lambda: func16, lambda: func17()) + + return func18 + + +target_func() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml new file mode 100644 index 000000000000..2f7335511a1a --- /dev/null +++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml new file mode 100644 index 000000000000..98e62b66545f --- /dev/null +++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/NestedCall/file_1.py b/python/testData/hierarchy/call/Static/NestedCall/file_1.py new file mode 100644 index 000000000000..3e89b6b290d0 --- /dev/null +++ b/python/testData/hierarchy/call/Static/NestedCall/file_1.py @@ -0,0 +1,10 @@ +def func1(*args): pass +def func2(*args): pass +def func3(*args): pass +def func4(*args): pass +def func5(*args): pass +def func6(*args): pass +def func7(*args): pass +def func8(*args): pass +def func9(*args): pass +def func10(*args): pass \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/NestedCall/main.py b/python/testData/hierarchy/call/Static/NestedCall/main.py new file mode 100644 index 000000000000..aca993a9d6a1 --- /dev/null +++ b/python/testData/hierarchy/call/Static/NestedCall/main.py @@ -0,0 +1,10 @@ +from file_1 import * + +def target_func(): + def inner(*args): + return func1() + + return inner(func1(func2(func3(func4, func5()), func6(), (((func7)))()), func8), func9, func10()) + + +target_func() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml new file mode 100644 index 000000000000..e68efab80f7b --- /dev/null +++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml new file mode 100644 index 000000000000..20fdb7938d8c --- /dev/null +++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py new file mode 100644 index 000000000000..e1e4fb90b949 --- /dev/null +++ b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py @@ -0,0 +1,8 @@ + +class A(object): + def target_func(self, p): + pass + + def another_func(self): + pass + diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/main.py b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py new file mode 100644 index 000000000000..288d638e0fea --- /dev/null +++ b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py @@ -0,0 +1,29 @@ +from file_1 import A + + +class B(A): + def target_func(self, p): + p.another_func() + + +class C(object): + def func1(self, a): + a.target_func(A()) + + def func2(self): + a = A() + b = B() + a.target_func(b) + + +def bar1(a): + a.target_func(a) + + +def bar2(a, b): + atf, btf = a.target_func, b.target_func + + +bar1(A()) +bar2(A(), B()) +B().target_func(A()) \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml new file mode 100644 index 000000000000..5c6e293e2220 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml new file mode 100644 index 000000000000..fca38e3b7359 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Parentheses/file_1.py b/python/testData/hierarchy/call/Static/Parentheses/file_1.py new file mode 100644 index 000000000000..ed3de6593a59 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Parentheses/file_1.py @@ -0,0 +1,20 @@ +import main + +def target_func(): + main.nothing(None) + + +def nothing(x): + pass + + +def foo(x=bar()): + ((target_func))() + + +def bar(): + main.nothing((target_func)) + + +def another(): + ((((target_func), 1))(), 2)() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Parentheses/main.py b/python/testData/hierarchy/call/Static/Parentheses/main.py new file mode 100644 index 000000000000..37fe5bf0819d --- /dev/null +++ b/python/testData/hierarchy/call/Static/Parentheses/main.py @@ -0,0 +1,7 @@ +from file_1 import target_func + +def nothing(x): + pass + + +target_func() \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml new file mode 100644 index 000000000000..54dd51a71979 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml new file mode 100644 index 000000000000..7237852a9a40 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/python/testData/hierarchy/call/Static/Simple/main.py b/python/testData/hierarchy/call/Static/Simple/main.py new file mode 100644 index 000000000000..9073f3df5d87 --- /dev/null +++ b/python/testData/hierarchy/call/Static/Simple/main.py @@ -0,0 +1,10 @@ +def func1(): + pass + +def func2(): + target_func() + +def target_func(): + func1() + +target_func() diff --git a/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java b/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java new file mode 100644 index 000000000000..08d09f061d2b --- /dev/null +++ b/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java @@ -0,0 +1,160 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy; + + +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import junit.framework.TestCase; +import org.jdom.Document; +import org.jdom.Element; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author novokrest + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ +public abstract class HierarchyTreeStructureViewer extends TestCase { + + private static final String NODE_ELEMENT_NAME = "node"; + private static final String ANY_NODES_ELEMENT_NAME = "any"; + private static final String TEXT_ATTR_NAME = "text"; + private static final String BASE_ATTR_NAME = "base"; + + /** + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ + public static void checkHierarchyTreeStructure(final HierarchyTreeStructure treeStructure, final Document document) { + final HierarchyNodeDescriptor rootNodeDescriptor = (HierarchyNodeDescriptor)treeStructure.getRootElement(); + rootNodeDescriptor.update(); + final Element rootElement = document.getRootElement(); + if (rootElement == null || !NODE_ELEMENT_NAME.equals(rootElement.getName())) { + throw new IllegalArgumentException("Incorrect root element in verification resource"); + } + checkNodeDescriptorRecursively(treeStructure, rootNodeDescriptor, rootElement); + } + + /** + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ + private static void checkNodeDescriptorRecursively(final HierarchyTreeStructure treeStructure, + final HierarchyNodeDescriptor descriptor, + final Element expectedElement) { + checkBaseNode(treeStructure, descriptor, expectedElement); + checkContent(descriptor, expectedElement); + checkChildren(treeStructure, descriptor, expectedElement); + } + + /** + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ + private static void checkBaseNode(final HierarchyTreeStructure treeStructure, + final HierarchyNodeDescriptor descriptor, + final Element expectedElement) { + final String baseAttrValue = expectedElement.getAttributeValue(BASE_ATTR_NAME); + final HierarchyNodeDescriptor baseDescriptor = treeStructure.getBaseDescriptor(); + final boolean mustBeBase = "true".equalsIgnoreCase(baseAttrValue); + assertTrue("Incorrect base node", mustBeBase ? baseDescriptor == descriptor : baseDescriptor != descriptor); + } + + /** + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ + private static void checkContent(final HierarchyNodeDescriptor descriptor, final Element expectedElement) { + assertEquals(expectedElement.getAttributeValue(TEXT_ATTR_NAME), descriptor.getHighlightedText().getText()); + } + + /** + * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase} + */ + private static void checkChildren(final HierarchyTreeStructure treeStructure, + final HierarchyNodeDescriptor descriptor, + final Element element) { + if (element.getChild(ANY_NODES_ELEMENT_NAME) != null) { + return; + } + + final Object[] children = treeStructure.getChildElements(descriptor); + //noinspection unchecked + final List expectedChildren = new ArrayList(element.getChildren(NODE_ELEMENT_NAME)); + + final StringBuilder messageBuilder = new StringBuilder("Actual children of [" + descriptor.getHighlightedText().getText() + "]:\n"); + for (Object child : children) { + final HierarchyNodeDescriptor nodeDescriptor = (HierarchyNodeDescriptor)child; + nodeDescriptor.update(); + messageBuilder.append(" [").append(nodeDescriptor.getHighlightedText().getText()).append("]\n"); + } + assertEquals(messageBuilder.toString(), expectedChildren.size(), children.length); + + Arrays.sort(children, new Comparator() { + @Override + public int compare(final Object first, final Object second) { + return ((HierarchyNodeDescriptor)first).getHighlightedText().getText() + .compareTo(((HierarchyNodeDescriptor)second).getHighlightedText().getText()); + } + }); + + Collections.sort(expectedChildren, new Comparator() { + @Override + public int compare(final Element first, final Element second) { + return first.getAttributeValue(TEXT_ATTR_NAME).compareTo(second.getAttributeValue(TEXT_ATTR_NAME)); + } + }); + + //noinspection unchecked + final Iterator iterator = expectedChildren.iterator(); + for (Object child : children) { + checkNodeDescriptorRecursively(treeStructure, ((HierarchyNodeDescriptor)child), iterator.next()); + } + } + + public static String dump(final HierarchyTreeStructure treeStructure, @Nullable HierarchyNodeDescriptor descriptor, int level) { + StringBuilder s = new StringBuilder(); + dump(treeStructure, descriptor, level, s); + return s.toString(); + } + + private static void dump(final HierarchyTreeStructure treeStructure, + @Nullable HierarchyNodeDescriptor descriptor, + int level, + StringBuilder b) { + if (level > 10) { + for(int i = 0; i\n"); + return; + } + if(descriptor==null) descriptor = (HierarchyNodeDescriptor)treeStructure.getRootElement(); + for(int i = 0; i0) { + b.append(">\n"); + for (Object o : children) { + HierarchyNodeDescriptor d = (HierarchyNodeDescriptor)o; + dump(treeStructure, d, level + 1, b); + } + for(int i = 0; i\n"); + } else { + b.append("/>\n"); + } + } +} diff --git a/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java b/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java new file mode 100644 index 000000000000..3fa13b1b7749 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.python.hierarchy; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.ide.hierarchy.HierarchyBrowserBaseEx; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.hierarchy.call.PyCalleeFunctionTreeStructure; +import com.jetbrains.python.hierarchy.call.PyCallerFunctionTreeStructure; +import com.jetbrains.python.psi.PyFunction; + +import java.io.File; + +/** + * @author novokrest + */ +public class PyCallHierarchyTest extends PyTestCase { + private static final String CALLER_VERIFICATION_SUFFIX = "_caller_verification.xml"; + private static final String CALLEE_VERIFICATION_SUFFIX = "_callee_verification.xml"; + + private String getBasePath() { + return "hierarchy/call/Static/" + getTestName(false); + } + + private void configureByFiles(String ... fileNames) { + String[] filePaths = new String[fileNames.length]; + int i = 0; + for (String fileName: fileNames) { + filePaths[i] = getBasePath() + "/" + fileName; + i++; + } + myFixture.configureByFiles(filePaths); + } + + private String getVerificationFilePath(final String suffix) { + return getTestDataPath() + "/" + getBasePath() + "/" + getTestName(false) + suffix; + } + + private String getVerificationCallerFilePath() { + return getVerificationFilePath(CALLER_VERIFICATION_SUFFIX); + } + + private String getVerificationCalleeFilePath() { + return getVerificationFilePath(CALLEE_VERIFICATION_SUFFIX); + } + + private void checkFunctionHierarchyTreeStructure(PyFunction function) throws Exception { + HierarchyTreeStructureViewer.checkHierarchyTreeStructure(new PyCallerFunctionTreeStructure(myFixture.getProject(), function, HierarchyBrowserBaseEx.SCOPE_PROJECT), + JDOMUtil.loadDocument(new File(getVerificationCallerFilePath()))); + HierarchyTreeStructureViewer.checkHierarchyTreeStructure(new PyCalleeFunctionTreeStructure(myFixture.getProject(), function, HierarchyBrowserBaseEx.SCOPE_PROJECT), + JDOMUtil.loadDocument(new File(getVerificationCalleeFilePath()))); + } + + private void doTestCallHierarchy(String ... fileNames) throws Exception { + configureByFiles(fileNames); + + final PsiElement targetElement = TargetElementUtilBase + .findTargetElement(myFixture.getEditor(), + TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED); + assert targetElement != null : "Cannot find referenced element"; + assert targetElement instanceof PyFunction : "Referenced element is not PyFunction"; + + PyFunction function = (PyFunction) targetElement; + checkFunctionHierarchyTreeStructure(function); + } + + public void testSimple() throws Exception { + doTestCallHierarchy("main.py"); + } + + public void testArgumentList() throws Exception { + doTestCallHierarchy("main.py", "file_1.py"); + } + + public void testDefaultValue() throws Exception { + doTestCallHierarchy("main.py"); + } + + public void testLambda() throws Exception { + doTestCallHierarchy("main.py", "file_1.py"); + } + + public void testNestedCall() throws Exception { + doTestCallHierarchy("main.py", "file_1.py"); + } + + public void testInheritance() throws Exception { + doTestCallHierarchy("main.py"); + } + + public void testOverriddenMethod() throws Exception { + doTestCallHierarchy("main.py", "file_1.py"); + } + + public void testInnerFunction() throws Exception { + doTestCallHierarchy("main.py"); + } + + public void testConstructor() throws Exception { + doTestCallHierarchy("main.py"); + } + + public void testParentheses() throws Exception { + doTestCallHierarchy("main.py", "file_1.py"); + } +} \ No newline at end of file From 3e277b819094fba9f6e086c8b68573b596e1afb1 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Tue, 2 Sep 2014 18:44:52 +0400 Subject: [PATCH 2/3] cleanup --- .../src/dom/impl/ExtensionDomExtender.java | 155 +++++++++--------- 1 file changed, 76 insertions(+), 79 deletions(-) diff --git a/plugins/devkit/src/dom/impl/ExtensionDomExtender.java b/plugins/devkit/src/dom/impl/ExtensionDomExtender.java index f5c83ccb66bc..538ee7e70243 100644 --- a/plugins/devkit/src/dom/impl/ExtensionDomExtender.java +++ b/plugins/devkit/src/dom/impl/ExtensionDomExtender.java @@ -15,7 +15,7 @@ */ package org.jetbrains.idea.devkit.dom.impl; -import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -50,57 +50,6 @@ import java.util.*; public class ExtensionDomExtender extends DomExtender { private static final PsiClassConverter CLASS_CONVERTER = new PluginPsiClassConverter(); private static final Converter LANGUAGE_CONVERTER = new LanguageResolvingConverter(); - - private static class MyRequired implements Required { - @Override - public boolean value() { - return true; - } - - @Override - public boolean nonEmpty() { - return true; - } - - @Override - public boolean identifier() { - return false; - } - - @Override - public Class annotationType() { - return Required.class; - } - } - - private static class MyExtendClass extends ExtendClassImpl { - private final String myInterfaceName; - - private MyExtendClass(String interfaceName) { - myInterfaceName = interfaceName; - } - - @Override - public boolean allowAbstract() { - return false; - } - - @Override - public boolean allowInterface() { - return false; - } - - @Override - public boolean allowEnum() { - return false; - } - - @Override - public String value() { - return myInterfaceName; - } - } - private static final DomExtender EXTENSION_EXTENDER = new DomExtender() { public void registerExtensions(@NotNull final DomElement domElement, @NotNull final DomExtensionsRegistrar registrar) { final ExtensionPoint extensionPoint = (ExtensionPoint)domElement.getChildDescription().getDomDeclaration(); @@ -127,31 +76,6 @@ public class ExtensionDomExtender extends DomExtender { } }; - - public void registerExtensions(@NotNull final Extensions extensions, @NotNull final DomExtensionsRegistrar registrar) { - final XmlElement xmlElement = extensions.getXmlElement(); - if (xmlElement == null) return; - - IdeaPlugin ideaPlugin = extensions.getParentOfType(IdeaPlugin.class, true); - - if (ideaPlugin == null) return; - - String epPrefix = extensions.getEpPrefix(); - for (IdeaPlugin plugin : getVisiblePlugins(ideaPlugin)) { - final String pluginId = StringUtil.notNullize(plugin.getPluginId(), "com.intellij"); - for (ExtensionPoints points : plugin.getExtensionPoints()) { - for (ExtensionPoint point : points.getExtensionPoints()) { - registerExtensionPoint(registrar, point, epPrefix, pluginId); - } - } - } - } - - @Override - public boolean supportsStubs() { - return false; - } - private static Set getVisiblePlugins(IdeaPlugin ideaPlugin) { Set result = ContainerUtil.newHashSet(); MultiMap byId = getPluginMap(ideaPlugin.getManager().getProject()); @@ -385,11 +309,10 @@ public class ExtensionDomExtender extends DomExtender { return PsiTypesUtil.getPsiClass(elementType); } - public static Collection getDependencies(IdeaPlugin ideaPlugin) { Set result = new HashSet(); - result.add(PluginManager.CORE_PLUGIN_ID); + result.add(PluginManagerCore.CORE_PLUGIN_ID); for (Dependency dependency : ideaPlugin.getDependencies()) { ContainerUtil.addIfNotNull(dependency.getStringValue(), result); @@ -422,10 +345,84 @@ public class ExtensionDomExtender extends DomExtender { return result; } + public void registerExtensions(@NotNull final Extensions extensions, @NotNull final DomExtensionsRegistrar registrar) { + final XmlElement xmlElement = extensions.getXmlElement(); + if (xmlElement == null) return; + + IdeaPlugin ideaPlugin = extensions.getParentOfType(IdeaPlugin.class, true); + + if (ideaPlugin == null) return; + + String epPrefix = extensions.getEpPrefix(); + for (IdeaPlugin plugin : getVisiblePlugins(ideaPlugin)) { + final String pluginId = StringUtil.notNullize(plugin.getPluginId(), "com.intellij"); + for (ExtensionPoints points : plugin.getExtensionPoints()) { + for (ExtensionPoint point : points.getExtensionPoints()) { + registerExtensionPoint(registrar, point, epPrefix, pluginId); + } + } + } + } + + @Override + public boolean supportsStubs() { + return false; + } + interface SimpleTagValue extends DomElement { @SuppressWarnings("UnusedDeclaration") @TagValue String getTagValue(); } + private static class MyRequired implements Required { + @Override + public boolean value() { + return true; + } + + @Override + public boolean nonEmpty() { + return true; + } + + @Override + public boolean identifier() { + return false; + } + + @Override + public Class annotationType() { + return Required.class; + } + } + + private static class MyExtendClass extends ExtendClassImpl { + private final String myInterfaceName; + + private MyExtendClass(String interfaceName) { + myInterfaceName = interfaceName; + } + + @Override + public boolean allowAbstract() { + return false; + } + + @Override + public boolean allowInterface() { + return false; + } + + @Override + public boolean allowEnum() { + return false; + } + + @Override + public String value() { + return myInterfaceName; + } + } + } From c614648f76da00153d04a4853fa02337a2c8ac3d Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Tue, 2 Sep 2014 19:19:36 +0400 Subject: [PATCH 3/3] IDEA-127191 "can't find tag for interface org.jetbrains.idea.devkit.dom.ExtensionPoint" thrown by 'Analyze Dependencies' action --- .../pluginWithXInclude-extensionPoints.xml | 5 ++++ .../pluginWithXInclude-main.xml | 10 +++++++ .../pluginXmlDomStubs/pluginWithXInclude.xml | 15 ++++++++++ .../codeInsight/PluginXmlDomStubsTest.java | 7 +++++ .../util/xml/stubs/StubParentStrategy.java | 21 +++++++++----- .../intellij/util/xml/stubs/DomStubTest.java | 28 +++++++++---------- 6 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-extensionPoints.xml create mode 100644 plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-main.xml create mode 100644 plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude.xml diff --git a/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-extensionPoints.xml b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-extensionPoints.xml new file mode 100644 index 000000000000..813122a13176 --- /dev/null +++ b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-extensionPoints.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-main.xml b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-main.xml new file mode 100644 index 000000000000..280b9b93c555 --- /dev/null +++ b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude-main.xml @@ -0,0 +1,10 @@ + + + com.intellij + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude.xml b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude.xml new file mode 100644 index 000000000000..849086d4989f --- /dev/null +++ b/plugins/devkit/testData/pluginXmlDomStubs/pluginWithXInclude.xml @@ -0,0 +1,15 @@ + + + foo + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/testSources/codeInsight/PluginXmlDomStubsTest.java b/plugins/devkit/testSources/codeInsight/PluginXmlDomStubsTest.java index 685d926b1b2a..100b956bdb4c 100644 --- a/plugins/devkit/testSources/codeInsight/PluginXmlDomStubsTest.java +++ b/plugins/devkit/testSources/codeInsight/PluginXmlDomStubsTest.java @@ -44,6 +44,13 @@ public class PluginXmlDomStubsTest extends DomStubTest { ); } + public void testXInclude() throws Exception { + prepareFile("pluginWithXInclude-extensionPoints.xml"); + prepareFile("pluginWithXInclude-main.xml"); + prepareFile("pluginWithXInclude.xml"); + myFixture.testHighlighting("pluginWithXInclude.xml"); + } + @Override protected String getBasePath() { return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/pluginXmlDomStubs"; diff --git a/xml/dom-impl/src/com/intellij/util/xml/stubs/StubParentStrategy.java b/xml/dom-impl/src/com/intellij/util/xml/stubs/StubParentStrategy.java index b827c6221746..951c6f576249 100644 --- a/xml/dom-impl/src/com/intellij/util/xml/stubs/StubParentStrategy.java +++ b/xml/dom-impl/src/com/intellij/util/xml/stubs/StubParentStrategy.java @@ -20,6 +20,7 @@ import com.intellij.psi.xml.XmlElement; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.xml.impl.*; +import com.intellij.xml.util.XmlUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,6 +33,11 @@ import java.util.List; public class StubParentStrategy implements DomParentStrategy { private final static Logger LOG = Logger.getInstance(StubParentStrategy.class); + protected final DomStub myStub; + + public StubParentStrategy(@NotNull DomStub stub) { + myStub = stub; + } public static StubParentStrategy createAttributeStrategy(@Nullable AttributeStub stub, @NotNull final DomStub parent) { if (stub == null) { @@ -59,12 +65,6 @@ public class StubParentStrategy implements DomParentStrategy { } } - protected final DomStub myStub; - - public StubParentStrategy(@NotNull DomStub stub) { - myStub = stub; - } - @Override public DomInvocationHandler getParentHandler() { DomStub parentStub = myStub.getParentStub(); @@ -82,7 +82,14 @@ public class StubParentStrategy implements DomParentStrategy { // for custom elements, namespace information is lost // todo: propagate ns info through DomChildDescriptions - XmlTag[] tags = parentTag.getSubTags(); + XmlTag[] tags; + try { + XmlUtil.BUILDING_DOM_STUBS.set(true); + tags = parentTag.getSubTags(); + } + finally { + XmlUtil.BUILDING_DOM_STUBS.set(false); + } int i = 0; String nameToFind = myStub.getName(); diff --git a/xml/dom-tests/tests/com/intellij/util/xml/stubs/DomStubTest.java b/xml/dom-tests/tests/com/intellij/util/xml/stubs/DomStubTest.java index d0c2174c01d0..a31bc6650aac 100644 --- a/xml/dom-tests/tests/com/intellij/util/xml/stubs/DomStubTest.java +++ b/xml/dom-tests/tests/com/intellij/util/xml/stubs/DomStubTest.java @@ -52,6 +52,19 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase { } }; + public static ElementStub getRootStub(@TestDataFile String filePath, JavaCodeInsightTestFixture fixture) { + PsiFile psiFile = fixture.configureByFile(filePath); + + StubTreeLoader loader = StubTreeLoader.getInstance(); + VirtualFile file = psiFile.getVirtualFile(); + assertTrue(loader.canHaveStub(file)); + ObjectStubTree stubTree = loader.readFromVFile(fixture.getProject(), file); + assertNotNull(stubTree); + ElementStub root = (ElementStub)stubTree.getRoot(); + assertNotNull(root); + return root; + } + @Override public void setUp() throws Exception { super.setUp(); @@ -67,19 +80,6 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase { return getRootStub(filePath, myFixture); } - public static ElementStub getRootStub(@TestDataFile String filePath, JavaCodeInsightTestFixture fixture) { - PsiFile psiFile = fixture.configureByFile(filePath); - - StubTreeLoader loader = StubTreeLoader.getInstance(); - VirtualFile file = psiFile.getVirtualFile(); - assertTrue(loader.canHaveStub(file)); - ObjectStubTree stubTree = loader.readFromVFile(fixture.getProject(), file); - assertNotNull(stubTree); - ElementStub root = (ElementStub)stubTree.getRoot(); - assertNotNull(root); - return root; - } - protected void doBuilderTest(@TestDataFile String file, String stubText) { ElementStub stub = getRootStub(file); assertEquals(stubText, DebugUtil.stubTreeToString(stub)); @@ -99,7 +99,7 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase { XmlFile file = (XmlFile)((PsiManagerEx)getPsiManager()).getFileManager().findFile(virtualFile); assertFalse(file.getNode().isParsed()); ObjectStubTree tree = StubTreeLoader.getInstance().readOrBuild(getProject(), virtualFile, file); - assertNotNull(tree); + assertNotNull("Can't build stubs for " + path, tree); ((PsiManagerImpl)getPsiManager()).cleanupForNextTest();