From fe00c0f376dc43cd3f3fd0b41e04a46ff4aaef17 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Tue, 30 May 2017 17:05:33 +0300 Subject: [PATCH 01/84] inspection view: remove redundant inheritance on OccurenceNavigator --- .../ui/InspectionResultsView.java | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index ef815a16983b..5b2677bddcbf 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -93,10 +93,7 @@ import java.util.List; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; -/** - * @author max - */ -public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider { +public class InspectionResultsView extends JPanel implements Disposable, DataProvider { private static final Logger LOG = Logger.getInstance(InspectionResultsView.class); public static final DataKey DATA_KEY = DataKey.create("inspectionView"); @@ -865,36 +862,6 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren return myOccurenceNavigator; } - @Override - public boolean hasNextOccurence() { - return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence(); - } - - @Override - public boolean hasPreviousOccurence() { - return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence(); - } - - @Override - public OccurenceInfo goNextOccurence() { - return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null; - } - - @Override - public OccurenceInfo goPreviousOccurence() { - return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null; - } - - @Override - public String getNextOccurenceActionName() { - return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : ""; - } - - @Override - public String getPreviousOccurenceActionName() { - return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : ""; - } - @NotNull public Project getProject() { return myProject; From cca2aef6ec38849dc0af5ec5969b4b88c2cf5dd5 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Tue, 30 May 2017 17:09:58 +0300 Subject: [PATCH 02/84] inspection view: decompose InspectionResultView --- .../codeInspection/ui/CloseAction.java | 37 ++++++++++++++ .../ui/InspectionResultsView.java | 40 +-------------- .../codeInspection/ui/RerunAction.java | 49 +++++++++++++++++++ 3 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java create mode 100644 platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java new file mode 100644 index 000000000000..2e7280024136 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2017 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.intellij.codeInspection.ui; + +import com.intellij.CommonBundle; +import com.intellij.codeInspection.ex.GlobalInspectionContextImpl; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAware; + +class CloseAction extends AnAction implements DumbAware { + private GlobalInspectionContextImpl myContext; + + CloseAction(GlobalInspectionContextImpl context) { + super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); + myContext = context; + } + + @Override + public void actionPerformed(AnActionEvent e) { + myContext.close(true); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index 5b2677bddcbf..8b0a0c650c06 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -16,7 +16,6 @@ package com.intellij.codeInspection.ui; -import com.intellij.CommonBundle; import com.intellij.ReviseWhenPortedToJDK; import com.intellij.analysis.AnalysisScope; import com.intellij.analysis.AnalysisUIOptions; @@ -30,7 +29,6 @@ import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.ui.actions.ExportHTMLAction; import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction; import com.intellij.diff.util.DiffUtil; -import com.intellij.icons.AllIcons; import com.intellij.ide.*; import com.intellij.ide.actions.ContextHelpAction; import com.intellij.ide.actions.exclusion.ExclusionHandler; @@ -46,7 +44,6 @@ import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.ui.popup.JBPopup; @@ -386,8 +383,8 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro private JComponent createLeftActionsToolbar() { final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); DefaultActionGroup group = new DefaultActionGroup(); - group.add(new RerunAction(this)); - group.add(new CloseAction()); + group.add(new RerunAction(this, this)); + group.add(new CloseAction(myGlobalInspectionContext)); final TreeExpander treeExpander = new DefaultTreeExpander(myTree); group.add(actionsManager.createExpandAllAction(treeExpander, myTree)); group.add(actionsManager.createCollapseAllAction(treeExpander, myTree)); @@ -1074,44 +1071,11 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro return myDisposed; } - private class CloseAction extends AnAction implements DumbAware { - private CloseAction() { - super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myGlobalInspectionContext.close(true); - } - } - public void updateCurrentProfile() { final String name = myInspectionProfile.getName(); myInspectionProfile = myInspectionProfile.getProfileManager().getProfile(name); } - private class RerunAction extends AnAction { - RerunAction(JComponent comp) { - super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), - AllIcons.Actions.Rerun); - registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); - } - - @Override - public void update(AnActionEvent e) { - e.getPresentation().setEnabled(isRerunAvailable()); - } - - @Override - public void actionPerformed(AnActionEvent e) { - rerun(); - } - - private void rerun() { - InspectionResultsView.this.rerun(); - } - } - public boolean isRerunAvailable() { return !(myProvider instanceof OfflineInspectionRVContentProvider) && myScope.isValid(); } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java new file mode 100644 index 000000000000..2224c6a19cbd --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2017 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.intellij.codeInspection.ui; + +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonShortcuts; + +import javax.swing.*; + +class RerunAction extends AnAction { + private final InspectionResultsView myView; + + RerunAction(InspectionResultsView view, JComponent comp) { + super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), + AllIcons.Actions.Rerun); + myView = view; + registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(myView.isRerunAvailable()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + rerun(); + } + + private void rerun() { + myView.rerun(); + } +} From dfe1b5e47f89986c8bcc95c03797a411d1251827 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Tue, 30 May 2017 17:29:11 +0300 Subject: [PATCH 03/84] inspection view: inline project field in InspectionResultView --- .../ui/InspectionResultsView.java | 65 +++++++------------ .../codeInspection/ui/InspectionTree.java | 4 +- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index 8b0a0c650c06..66bc2ec0133a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -96,10 +96,8 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro public static final DataKey DATA_KEY = DataKey.create("inspectionView"); private static final Key PREVIEW_EDITOR_IS_REUSED_KEY = Key.create("inspection.tool.window.preview.editor.is.reused"); - private final Project myProject; private final InspectionTree myTree; - private final ConcurrentMap> myGroups = - ContainerUtil.newConcurrentMap(); + private final ConcurrentMap> myGroups = ContainerUtil.newConcurrentMap(); private final OccurenceNavigator myOccurenceNavigator; private volatile InspectionProfileImpl myInspectionProfile; private final boolean mySettingsEnabled; @@ -138,7 +136,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionRVContentProvider provider) { setLayout(new BorderLayout()); - myProject = globalInspectionContext.getProject(); myInspectionProfile = globalInspectionContext.getCurrentProfile(); myScope = globalInspectionContext.getCurrentScope(); myGlobalInspectionContext = globalInspectionContext; @@ -146,12 +143,12 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro myExcludedInspectionTreeNodesManager = new ExcludedInspectionTreeNodesManager(provider instanceof OfflineInspectionRVContentProvider, isSingleInspectionRun()); - myTree = new InspectionTree(myProject, globalInspectionContext, this); + myTree = new InspectionTree(globalInspectionContext, this); initTreeListeners(); myOccurenceNavigator = initOccurenceNavigator(); - mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION); + mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(globalInspectionContext.getProject()).SPLITTER_PROPORTION); mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT)); mySplitter.setHonorComponentsMinimumSize(false); @@ -245,9 +242,9 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } }; createActionsToolbar(); - PsiManager.getInstance(myProject).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this); + PsiManager.getInstance(getProject()).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this); - ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(myProject); + ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(getProject()); profileManager.addProfileChangeListener(new ProfileChangeAdapter() { @Override public void profileChanged(InspectionProfile profile) { @@ -358,12 +355,9 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } private void createActionsToolbar() { - final JComponent leftActionsToolbar = createLeftActionsToolbar(); - final JComponent rightActionsToolbar = createRightActionsToolbar(); - JPanel westPanel = new JPanel(new BorderLayout()); - westPanel.add(leftActionsToolbar, BorderLayout.WEST); - westPanel.add(rightActionsToolbar, BorderLayout.EAST); + westPanel.add(createLeftActionsToolbar(), BorderLayout.WEST); + westPanel.add(createRightActionsToolbar(), BorderLayout.EAST); add(westPanel, BorderLayout.WEST); } @@ -413,9 +407,8 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } } - private boolean isAutoScrollMode() { - String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId(); + String activeToolWindowId = ToolWindowManager.getInstance(getProject()).getActiveToolWindowId(); return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION)); } @@ -430,27 +423,13 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro @Nullable private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) { - final VirtualFile[] file = new VirtualFile[1]; - final int[] offset = new int[1]; - - ApplicationManager.getApplication().runReadAction(() -> { - PsiElement psiElement = refElement.getElement(); - if (psiElement != null) { - final PsiFile containingFile = psiElement.getContainingFile(); - if (containingFile != null) { - file[0] = containingFile.getVirtualFile(); - offset[0] = psiElement.getTextOffset(); - } - } - else { - file[0] = null; - } - }); - - if (file[0] != null && file[0].isValid()) { - return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]); - } - return null; + PsiElement psiElement = refElement.getElement(); + if (psiElement == null) return null; + final PsiFile containingFile = psiElement.getContainingFile(); + if (containingFile == null) return null; + VirtualFile file = containingFile.getVirtualFile(); + if (file == null) return null; + return new OpenFileDescriptor(refElement.getRefManager().getProject(), file, psiElement.getTextOffset()); } public void setApplyingFix(boolean applyingFix) { @@ -595,7 +574,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro myPreviewEditor.getMarkupModel().removeAllHighlighters(); } else { - myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, myProject, file.getVirtualFile(), true); + myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, getProject(), file.getVirtualFile(), true); DiffUtil.setFoldingModelSupport(myPreviewEditor); final EditorSettings settings = myPreviewEditor.getSettings(); settings.setLineNumbersShown(false); @@ -615,7 +594,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor); } if (selectedEntity == null) { - return Pair.create(new InspectionNodeInfo(myTree, myProject), null); + return Pair.create(new InspectionNodeInfo(myTree, getProject()), null); } if (selectedEntity.isValid()) { return Pair.create(InspectionResultsViewUtil.getPreviewIsNotAvailable(selectedEntity), null); @@ -784,7 +763,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro InspectionToolWrapper toolWrapper = state.getTool(); if (ReadAction.compute(() -> myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper))) { addTool(toolWrapper, - profile.getErrorLevel(key, state.getScope(myProject), myProject), + profile.getErrorLevel(key, state.getScope(getProject()), getProject()), isGroupedBySeverity, singleInspectionRun); } @@ -843,7 +822,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro if (isGroupedBySeverity) { InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level); if (severityGroupNode == null) { - InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level); + InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(getProject(), level); severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode); if (severityGroupNode == newNode) { InspectionTreeNode root = myTree.getRoot(); @@ -861,7 +840,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro @NotNull public Project getProject() { - return myProject; + return myGlobalInspectionContext.getProject(); } @Override @@ -972,7 +951,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro startOffset = textRange.getStartOffset(); } } - return new OpenFileDescriptor(myProject, virtualFile, startOffset); + return new OpenFileDescriptor(getProject(), virtualFile, startOffset); } return null; } @@ -1083,7 +1062,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro public void rerun() { myRerun = true; if (myScope.isValid()) { - AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions()); + AnalysisUIOptions.getInstance(getProject()).save(myGlobalInspectionContext.getUIOptions()); myGlobalInspectionContext.setTreeState(getTree().getTreeState()); myGlobalInspectionContext.doInspections(myScope); } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java index a08fffb23132..90dccde763d4 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java @@ -56,9 +56,9 @@ public class InspectionTree extends Tree { @NotNull private InspectionTreeState myState = new InspectionTreeState(); private boolean myQueueUpdate; - public InspectionTree(@NotNull Project project, - @NotNull GlobalInspectionContextImpl context, + public InspectionTree(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionResultsView view) { + Project project = context.getProject(); setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view)))); myContext = context; myExcludedManager = view.getExcludedManager(); From a270531ae09531faaf476577742ba1b782e22b03 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Tue, 30 May 2017 17:33:28 +0300 Subject: [PATCH 04/84] inspection view: fix typo --- .../codeInspection/ui/InspectionTreeState.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java index 70a850707b5e..bf420dfb96a2 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java @@ -63,14 +63,14 @@ public class InspectionTreeState { private static class InspectionTreeSelectionPath { private final Object[] myPath; - private final int[] myIndicies; + private final int[] myIndices; public InspectionTreeSelectionPath(TreePath path) { myPath = path.getPath(); - myIndicies = new int[myPath.length]; + myIndices = new int[myPath.length]; for (int i = 0; i < myPath.length - 1; i++) { InspectionTreeNode node = (InspectionTreeNode)myPath[i]; - myIndicies[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); + myIndices[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); } } @@ -116,8 +116,8 @@ public class InspectionTreeState { // Exactly same element not found. Trying to select somewhat near. int count = newRoot.getChildCount(); if (count > 0) { - if (myIndicies[idx] < count) { - newPath.add(newRoot.getChildAt(myIndicies[idx])); + if (myIndices[idx] < count) { + newPath.add(newRoot.getChildAt(myIndices[idx])); } else { newPath.add(newRoot.getChildAt(count - 1)); From 32e3b8b73a1db66b169eb9939f52478aff5d5b3f Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 13:42:30 +0300 Subject: [PATCH 05/84] convert to threadlocal: do not create initializer if variable was final (IDEA-173748) --- .../ConvertFieldToThreadLocalIntention.java | 4 +++- .../intentions/threadLocal/afterFinalField.java | 13 +++++++++++++ .../intentions/threadLocal/beforeFinalField.java | 8 ++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 java/typeMigration/testData/intentions/threadLocal/afterFinalField.java create mode 100644 java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java index 6161a42af635..0e03d91dd617 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java @@ -115,7 +115,9 @@ public class ConvertFieldToThreadLocalIntention extends PsiElementBaseIntentionA } PsiExpression initializer = psiField.getInitializer(); - if (initializer == null) { + + if (initializer == null && + !psiField.hasModifierProperty(PsiModifier.FINAL)) { final PsiType type = psiField.getType(); String initializerText = null; if (PsiType.BOOLEAN.equals(type)) { diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java new file mode 100644 index 000000000000..9575f8c84842 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java @@ -0,0 +1,13 @@ +// "Convert to ThreadLocal" "true" +class Foo { + private final ThreadLocal property; + + Foo(boolean property) { + this.property = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return property; + } + }; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java b/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java new file mode 100644 index 000000000000..1cffae690828 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java @@ -0,0 +1,8 @@ +// "Convert to ThreadLocal" "true" +class Foo { + private final boolean property; + + Foo(boolean property) { + this.property = property; + } +} \ No newline at end of file From 17b1b6ae0f07dbbb150a278eda4d89e14529c797 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Mon, 5 Jun 2017 15:48:49 +0300 Subject: [PATCH 06/84] Use soft wrapping for both editors (PY-14446) --- .../python/console/PydevConsoleRunnerImpl.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index 39c85123ceb5..353a5a72c265 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -48,7 +48,6 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.actionSystem.EditorAction; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; import com.intellij.openapi.editor.actions.SplitLineAction; @@ -784,7 +783,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { SoftWrapAction() { super(ActionsBundle.actionText("EditorToggleUseSoftWraps"), ActionsBundle.actionDescription("EditorToggleUseSoftWraps"), AllIcons.Actions.ToggleSoftWrap); - myConsoleView.getEditor().getSettings().setUseSoftWraps(isSelected); + updateEditors(); } @Override @@ -792,16 +791,15 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { return isSelected; } + private void updateEditors() { + myConsoleView.getEditor().getSettings().setUseSoftWraps(isSelected); + myConsoleView.getConsoleEditor().getSettings().setUseSoftWraps(isSelected); + } + @Override public void setSelected(AnActionEvent e, boolean state) { isSelected = state; - EditorSettings editorSettings = getConsoleView().getEditor().getSettings(); - if (isSelected) { - editorSettings.setUseSoftWraps(true); - } - else { - editorSettings.setUseSoftWraps(false); - } + updateEditors(); myConsoleSettings.setUseSoftWraps(isSelected); } } From 9be054ea9b69d74c355b4bc2c964fb1b643c504f Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 5 Jun 2017 16:10:49 +0300 Subject: [PATCH 07/84] EA-92305 - IOE: LocalFileSystemBase.createChildFile --- .../src/com/intellij/ide/scratch/ScratchFileServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java index c46db97f7546..a991bb85fafe 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java @@ -289,7 +289,7 @@ public class ScratchFileServiceImpl extends ScratchFileService implements Persis return VfsUtil.createChildSequent(LocalFileSystem.getInstance(), dir, fileName, StringUtil.notNullize(ext)); } else { - return dir.createChildData(LocalFileSystem.getInstance(), fileNameExt); + return dir.findOrCreateChildData(LocalFileSystem.getInstance(), fileNameExt); } }); } From 071634d77eff05eead4266c466d974e6a3ce05b6 Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Mon, 5 Jun 2017 15:04:40 +0300 Subject: [PATCH 08/84] Java: Navigate to proper overload of a method being accessed via reflection (IDEA-172319) --- ...vaLangInvokeHandleSignatureInspection.java | 2 +- .../JavaReflectionInvocationInspection.java | 35 ++++++--- .../JavaReflectionMemberAccessInspection.java | 5 ++ .../impl/JavaLangClassMemberReference.java | 47 ++++++++---- .../impl/JavaLangInvokeHandleReference.java | 25 ++++++- .../JavaLangInvokeHandleNavigationTest.kt | 64 ++++++++++++++++- .../JavaReflectionNavigationTest.java | 71 +++++++++++++++++-- 7 files changed, 217 insertions(+), 32 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java index 4db61720ca88..e71c07de7568 100644 --- a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java @@ -309,7 +309,7 @@ public class JavaLangInvokeHandleSignatureInspection extends BaseJavaBatchLocalI * from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) */ @Nullable - private static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { + public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { final PsiExpression typeDefinition = findDefinition(methodTypeExpression); if (typeDefinition instanceof PsiMethodCallExpression) { final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)typeDefinition; diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java index 04bd9a3dca84..6f3515fceafe 100644 --- a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java @@ -20,6 +20,7 @@ import com.intellij.codeInspection.InspectionsBundle; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -131,22 +132,34 @@ public class JavaReflectionInvocationInspection extends BaseJavaBatchLocalInspec if (definition instanceof PsiMethodCallExpression) { final PsiMethodCallExpression definitionCall = (PsiMethodCallExpression)definition; if (methodPredicate.test(definitionCall)) { - final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); - - if (arguments.length == argumentOffset + 1) { - final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]); - if (arrayElements != null) { - return Arrays.asList(arrayElements); - } - } - if (arguments.length >= argumentOffset) { - return Arrays.asList(arguments).subList(argumentOffset, arguments.length); - } + return getRequiredMethodArguments(definitionCall, argumentOffset); } } return null; } + private static List getRequiredMethodArguments(@NotNull PsiMethodCallExpression definitionCall, int argumentOffset) { + final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); + + if (arguments.length == argumentOffset + 1) { + final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]); + if (arrayElements != null) { + return Arrays.asList(arrayElements); + } + } + if (arguments.length >= argumentOffset) { + return Arrays.asList(arguments).subList(argumentOffset, arguments.length); + } + return null; + } + + @Nullable + public static List getReflectionMethodParameterTypes(@NotNull PsiMethodCallExpression definitionCall, + int argumentOffset) { + List arguments = getRequiredMethodArguments(definitionCall, argumentOffset); + return arguments != null ? ContainerUtil.map(arguments, type -> getReflectiveType(type)) : null; + } + @Nullable static Arguments getActualMethodArguments(PsiExpression[] arguments, int argumentOffset, boolean allowVarargAsArray) { if (allowVarargAsArray && arguments.length == argumentOffset + 1) { diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java index 951770eb6d56..90213c251cfa 100644 --- a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java @@ -279,6 +279,11 @@ public class JavaReflectionMemberAccessInspection extends BaseJavaBatchLocalInsp final List argumentTypes = ContainerUtil.map(methodArguments.expressions, JavaReflectionReferenceUtil::getReflectiveType); + return matchMethod(methods, argumentTypes); + } + + @Nullable + public static PsiMethod matchMethod(@NotNull PsiMethod[] methods, @NotNull List argumentTypes) { int mismatchCount = Integer.MAX_VALUE; PsiMethod bestGuess = null; for (PsiMethod method : methods) { diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java index 67629f036bce..00ba95c77149 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java @@ -18,9 +18,11 @@ package com.intellij.psi.impl.source.resolve.reference.impl; import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInspection.reflectiveAccess.JavaReflectionInvocationInspection; +import com.intellij.codeInspection.reflectiveAccess.JavaReflectionMemberAccessInspection; import com.intellij.psi.*; import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; @@ -28,10 +30,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Objects; -import java.util.Set; +import java.util.*; import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*; @@ -73,17 +72,29 @@ public class JavaLangClassMemberReference extends PsiReferenceBase isRegularMethod(method) && isPublic(method)); - if (publicMethod != null) { - return publicMethod; + PsiMethod[] methods = psiClass.findMethodsByName(name, true); + if (methods.length > 1) { + methods = + ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPublic(method)) + .toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + return findOverloadedMethod(methods); + } } - return ContainerUtil.find(methods, method -> isRegularMethod(method)); + return methods.length != 0 ? methods[0] : null; } case GET_DECLARED_METHOD: { - final PsiMethod[] methods = psiClass.findMethodsByName(name, false); - return ContainerUtil.find(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass)); + PsiMethod[] methods = psiClass.findMethodsByName(name, false); + if (methods.length > 1) { + methods = + ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass)) + .toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + return findOverloadedMethod(methods); + } + } + return methods.length != 0 ? methods[0] : null; } } } @@ -155,6 +166,18 @@ public class JavaLangClassMemberReference extends PsiReferenceBase parameterTypes = JavaReflectionInvocationInspection.getReflectionMethodParameterTypes(definitionCall, 1); + if (parameterTypes != null) { + return JavaReflectionMemberAccessInspection.matchMethod(methods, parameterTypes); + } + } + return null; + } + @Override public void handleInsert(InsertionContext context, LookupElement item) { final Object object = item.getObject(); diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java index 838ee3abe01e..2574d768a80e 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java @@ -19,13 +19,16 @@ import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.completion.JavaLookupElementBuilder; import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInspection.reflectiveAccess.JavaLangInvokeHandleSignatureInspection; import com.intellij.openapi.util.Condition; import com.intellij.psi.*; import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.ParenthesesUtils; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -97,9 +100,25 @@ public class JavaLangInvokeHandleReference extends PsiReferenceBase filter) { - final PsiMethod[] methods = psiClass.findMethodsByName(name, true); - return ContainerUtil.find(methods, filter); + private PsiElement resolveMethod(@NotNull String name, @NotNull PsiClass psiClass, Condition filter) { + PsiMethod[] methods = psiClass.findMethodsByName(name, true); + if (methods.length != 0) { + methods = ContainerUtil.filter(methods, filter).toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + final PsiMethodCallExpression definitionCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class); + if (definitionCall != null) { + final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); + if (arguments.length > 2) { + final PsiExpression typeExpression = ParenthesesUtils.stripParentheses(arguments[2]); + final ReflectiveSignature expectedSignature = JavaLangInvokeHandleSignatureInspection.composeMethodSignature(typeExpression); + if (expectedSignature != null) { + return ContainerUtil.find(methods, method -> expectedSignature.equals(getMethodSignature(method))); + } + } + } + } + } + return methods.length != 0 ? methods[0] : null; } @NotNull diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt index 73e7aa0e2f4c..b8f0b87796bb 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt @@ -16,10 +16,13 @@ package com.intellij.java.codeInsight.navigation import com.intellij.psi.PsiMember +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiReference import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import junit.framework.TestCase +import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.MagicConstant +import org.jetbrains.annotations.NonNls /** * @author Pavel.Dolgov @@ -74,6 +77,64 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { fun testStaticSetter6() = doNegativeTest("pf1", STATIC_SETTER) fun testStaticSetter7() = doNegativeTest("m1", STATIC_SETTER) + fun testOverloadedBothPublic() = doTestOverloaded( + """public class Overloaded { + public void foo(int n) {} + public void foo(String s) {} +}""", VIRTUAL, "java.lang.String") + + fun testOverloadedFirstPublic() = doTestOverloaded( + """public class Overloaded { + public void foo(int n) {} + void foo(String s) {} +}""", VIRTUAL, "int") + + fun testOverloadedSecondPublic() = doTestOverloaded( + """public class Overloaded { + void foo(int n) {} + public void foo(String s) {} +}""", VIRTUAL, "java.lang.String") + + fun testOverloadedInherited() { + myFixture.addClass("""public class OverloadedParent { + public static void foo(String s) {} +}""") + + doTestOverloaded( + """public class Overloaded extends OverloadedParent { + public static void foo(int n) {} +}""", STATIC, "java.lang.String") + } + + fun testOverloadedStatic() = doTestOverloaded( + """public class Overloaded { + public static void foo(int n) {} + public static void foo(String s) {} +}""", STATIC, "java.lang.String") + + + private fun doTestOverloaded(@NonNls @Language("JAVA") classText: String, function: String, vararg expectedParameterTypes: String) { + myFixture.addClass(classText) + + val methodType = arrayOf("void", *expectedParameterTypes).map { "$it.class" }.joinToString(", ") + val member = doTestImpl("foo", """ +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +class Main { + void foo() throws ReflectiveOperationException { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.$function(Overloaded.class, "foo", MethodType.methodType($methodType)); + } +}""") + + TestCase.assertTrue("Is method", member is PsiMethod) + val parameters = (member as PsiMethod).parameterList.parameters + TestCase.assertEquals("Parameter count", expectedParameterTypes.size, parameters.size) + for (i in 0 until expectedParameterTypes.size) { + TestCase.assertEquals("Parameter $i", expectedParameterTypes[i], parameters[i].type.canonicalText) + } + } + private fun doTest(name: String, @MagicConstant(stringValues = arrayOf(VIRTUAL, STATIC, SPECIAL, @@ -83,7 +144,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { doTestImpl(name, getMainClassText(name, function)) } - private fun doTestImpl(name: String, mainClassText: String) { + private fun doTestImpl(name: String, mainClassText: String): PsiMember { val reference = getReference(mainClassText) TestCase.assertEquals("Reference text", name, reference.canonicalText) val resolved = reference.resolve() @@ -91,6 +152,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { TestCase.assertTrue("Target is a member", resolved is PsiMember) val member = resolved as PsiMember? TestCase.assertEquals("Target name", name, member!!.name) + return member } private fun doNegativeTest(name: String, diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java index e904b9b9deb1..1102cb973937 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java @@ -15,9 +15,7 @@ */ package com.intellij.java.codeInsight.navigation; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiReference; +import com.intellij.psi.*; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.MagicConstant; @@ -93,6 +91,70 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas "}"); } + public void testOverloadedMethodBothPublic() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", false, "java.lang.String"); + } + + public void testOverloadedMethodPublicFirst() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " void foo(String s) {}\n" + + "}", false); + } + + public void testOverloadedMethodPublicSecond() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", false, "java.lang.String"); + } + + public void testOverloadedDeclaredMethod() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", true, "java.lang.String"); + } + + public void testOverloadedInheritedMethod() { + doTestOverloadedMethod("bar", + "class OverloadedParent {" + + " public void bar(String s) {}\n" + + "}" + + "" + + "class Overloaded extends OverloadedParent {\n" + + " public void bar() {}\n" + + "}", false, "java.lang.String"); + } + + private void doTestOverloadedMethod(String name, + @NotNull @NonNls @Language("JAVA") String classText, + boolean isDeclared, + String... expectedParameterTypes) { + myFixture.addClass(classText); + + PsiMember member = doTestImpl(name, + "class Main {" + + " void main() {" + + " Overloaded.class.get" + (isDeclared?"Declared":"") + "Method(\""+name+"\", String.class);" + + " }" + + "}"); + assertTrue("Target is a method", member instanceof PsiMethod); + PsiMethod method = (PsiMethod)member; + PsiParameter[] parameters = method.getParameterList().getParameters(); + assertEquals("Parameter count", expectedParameterTypes.length, parameters.length); + for (int i = 0; i < expectedParameterTypes.length; i++) { + assertEquals("Parameter type " + i, expectedParameterTypes[i], parameters[0].getType().getCanonicalText()); + } + } + private void doTest(String name, @MagicConstant(stringValues = {FIELD, METHOD, DF, DM}) String type) { @@ -104,7 +166,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas doTestImpl(name, mainClassText); } - private void doTestImpl(String name, String mainClassText) { + private PsiMember doTestImpl(String name, @NotNull @NonNls @Language("JAVA") String mainClassText) { PsiReference reference = getReference(mainClassText); assertEquals("Reference text", name, reference.getCanonicalText()); PsiElement resolved = reference.resolve(); @@ -112,6 +174,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas assertTrue("Target is a member", resolved instanceof PsiMember); PsiMember member = (PsiMember)resolved; assertEquals("Target name", name, member.getName()); + return member; } private void doNegativeTest(String name, From 6b7f8028db789cf2849fa7ca9dfe5ac8847dee42 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 15:44:59 +0300 Subject: [PATCH 09/84] AIOOBE (IDEA-173816) --- .../ipp/functional/ExtractToMethodReferenceIntention.java | 2 +- .../extractToMethodReference/EmptyCodeBlock.java | 5 +++++ .../extractToMethodReference/EmptyCodeBlock_after.java | 8 ++++++++ .../ipp/functional/ExtractToMethodReferenceTest.java | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java index 065bfd5899af..294f6f5074e9 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java @@ -130,7 +130,7 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti targetMethodName + parameters, targetClass); PsiCodeBlock targetMethodBody = emptyMethod.getBody(); LOG.assertTrue(targetMethodBody != null); - targetMethodBody.addRange(elements[0], elements[elements.length - 1]); + if (elements.length > 0) targetMethodBody.addRange(elements[0], elements[elements.length - 1]); PsiMethod method = (PsiMethod)CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(targetClass.add(emptyMethod))); PsiMethodReferenceExpression methodReference = diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java new file mode 100644 index 000000000000..bf1787f705eb --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java @@ -0,0 +1,5 @@ +class B { + { + Runnable r = () -> {} ; + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java new file mode 100644 index 000000000000..8ab95092e18a --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java @@ -0,0 +1,8 @@ +class B { + { + Runnable r = B::run; + } + + private static void run() { + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java index 5be4b04af3bf..c594a70339ac 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java @@ -54,6 +54,10 @@ public class ExtractToMethodReferenceTest extends IPPTestCase { assertIntentionNotAvailable(); } + public void testEmptyCodeBlock() throws Exception { + doTest(); + } + public void testUsedLocalVariables() throws Exception { assertIntentionNotAvailable(); } From 02bc8de1f4952ab4a6c3618a78ba4c26e953c32d Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 16:25:39 +0300 Subject: [PATCH 10/84] wrap with explicit array: disable for non-reifiable types (IDEA-173825) --- .../ipp/varargs/VarargArgumentsPredicate.java | 31 +++++++++---------- ...gumentsWithExplicitArrayIntentionTest.java | 12 +++++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java index bc9fc916519b..d2fb11276c48 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java @@ -15,7 +15,9 @@ */ package com.siyeh.ipp.varargs; +import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.siyeh.ipp.base.PsiElementPredicate; import org.jetbrains.annotations.NotNull; @@ -48,29 +50,22 @@ class VarargArgumentsPredicate implements PsiElementPredicate { // "Unnecessarily qualified static usage" inspection // the psi gets into a bad state, this guards against that. // http://www.jetbrains.net/jira/browse/IDEADEV-40124 - final PsiReferenceExpression methodExpression = - methodCallExpression.getMethodExpression(); - final PsiExpression qualifier = - methodExpression.getQualifierExpression(); + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final PsiExpression qualifier = methodExpression.getQualifierExpression(); if (qualifier == null) { - final PsiReferenceParameterList typeParameterList = - methodExpression.getParameterList(); + final PsiReferenceParameterList typeParameterList = methodExpression.getParameterList(); if (typeParameterList != null) { - final PsiTypeElement[] typeParameterElements = - typeParameterList.getTypeParameterElements(); + final PsiTypeElement[] typeParameterElements = typeParameterList.getTypeParameterElements(); if (typeParameterElements.length > 0) { return false; } } } - final PsiParameter[] parameters = parameterList.getParameters(); - final PsiParameter lastParameter = parameters[parameters.length - 1]; - final PsiEllipsisType lastParameterType = (PsiEllipsisType)lastParameter.getType(); - final PsiType lastType = lastParameterType.getComponentType(); final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics(); final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - final PsiType substitutedType = substitutor.substitute(lastType); + PsiType lastParameterType = PsiTypesUtil.getParameterType(parameterList.getParameters(), parametersCount - 1, true); + final PsiType substitutedType = substitutor.substitute(lastParameterType); if (substitutedType instanceof PsiCapturedWildcardType) { final PsiCapturedWildcardType capturedWildcardType = (PsiCapturedWildcardType)substitutedType; if (!capturedWildcardType.getWildcard().isSuper()) { @@ -78,13 +73,15 @@ class VarargArgumentsPredicate implements PsiElementPredicate { return false; } } + + if (!JavaGenericsUtil.isReifiableType(substitutedType)) { + return false; + } if (arguments.length != parametersCount) { return true; } - final PsiExpression lastExpression = - arguments[arguments.length - 1]; - final PsiExpression expression = PsiUtil.deparenthesizeExpression( - lastExpression); + final PsiExpression lastExpression = arguments[arguments.length - 1]; + final PsiExpression expression = PsiUtil.deparenthesizeExpression(lastExpression); if (expression instanceof PsiLiteralExpression) { final String text = expression.getText(); if ("null".equals(text)) { diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java index fc355c2f8579..4f811c316cc4 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java @@ -58,4 +58,16 @@ public class WrapVarargArgumentsWithExplicitArrayIntentionTest extends IPPTestCa " }\n" + "}"); } + + public void testNonReifiable() { + doTestIntentionNotAvailable( + "" + + "import java.util.*;\n" + + "class Y {\n" + + " void m(Set... t){}\n" + + " public static void run(Set s) {\n" + + " m(/*_Wrap vararg arguments with explicit array creation*/s);\n" + + " }\n" + + "}"); + } } From dc610f3287df866af06a90a39270bcc214951f49 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 16:42:15 +0300 Subject: [PATCH 11/84] split into declaration and assignment for fields (IDEA-173822) ensure field is not used before existing class initializer --- ...DeclarationAndInitializationIntention.java | 54 ++++++++++--------- .../FieldUsedBeforeInitializer.java | 8 +++ .../FieldUsedBeforeInitializer_after.java | 13 +++++ ...arationAndInitializationIntentionTest.java | 1 + 4 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java index aebb8f568814..c7f1a29a45aa 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java @@ -16,8 +16,11 @@ package com.siyeh.ipp.initialization; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.refactoring.util.RefactoringUtil; import com.siyeh.IntentionPowerPackBundle; import com.siyeh.ipp.base.Intention; @@ -26,6 +29,8 @@ import com.siyeh.ipp.psiutils.HighlightUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.function.Predicate; + public class SplitDeclarationAndInitializationIntention extends Intention { @Override @@ -46,58 +51,55 @@ public class SplitDeclarationAndInitializationIntention extends Intention { if (containingClass == null) { return; } - final boolean fieldIsStatic = - field.hasModifierProperty(PsiModifier.STATIC); - final PsiClassInitializer[] classInitializers = - containingClass.getInitializers(); + final boolean fieldIsStatic = field.hasModifierProperty(PsiModifier.STATIC); + final PsiClassInitializer[] classInitializers = containingClass.getInitializers(); PsiClassInitializer classInitializer = null; final int fieldOffset = field.getTextOffset(); for (PsiClassInitializer existingClassInitializer : classInitializers) { - final int initializerOffset = - existingClassInitializer.getTextOffset(); + final int initializerOffset = existingClassInitializer.getTextOffset(); if (initializerOffset <= fieldOffset) { continue; } - final boolean initializerIsStatic = - existingClassInitializer.hasModifierProperty( - PsiModifier.STATIC); + final boolean initializerIsStatic = existingClassInitializer.hasModifierProperty(PsiModifier.STATIC); if (initializerIsStatic == fieldIsStatic) { - classInitializer = existingClassInitializer; - break; + Predicate usedBeforeInitializer = ref -> { + PsiElement refElement = ref.getElement(); + if (refElement == null) { + return true; + } + TextRange textRange = refElement.getTextRange(); + return textRange == null || textRange.getStartOffset() < initializerOffset; + }; + if (ReferencesSearch.search(field, new LocalSearchScope(containingClass)).findAll().stream().noneMatch(usedBeforeInitializer)) { + classInitializer = existingClassInitializer; + break; + } } } final PsiManager manager = field.getManager(); final Project project = manager.getProject(); - final PsiElementFactory elementFactory = - JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); if (classInitializer == null) { classInitializer = elementFactory.createClassInitializer(); - classInitializer = (PsiClassInitializer) - containingClass.addAfter(classInitializer, field); + classInitializer = (PsiClassInitializer)containingClass.addAfter(classInitializer, field); // add some whitespace between the field and the class initializer - final PsiElement whitespace = - PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n"); + final PsiElement whitespace = PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n"); containingClass.addAfter(whitespace, field); } final PsiCodeBlock body = classInitializer.getBody(); - @NonNls final String initializationStatementText = - field.getName() + " = " + initializerText + ';'; - final PsiExpressionStatement statement = - (PsiExpressionStatement)elementFactory.createStatementFromText( - initializationStatementText, body); + @NonNls final String initializationStatementText = field.getName() + " = " + initializerText + ';'; + final PsiExpressionStatement statement = (PsiExpressionStatement)elementFactory.createStatementFromText(initializationStatementText, body); final PsiElement addedElement = body.add(statement); if (fieldIsStatic) { - final PsiModifierList modifierList = - classInitializer.getModifierList(); + final PsiModifierList modifierList = classInitializer.getModifierList(); if (modifierList != null) { modifierList.setModifierProperty(PsiModifier.STATIC, true); } } initializer.delete(); CodeStyleManager.getInstance(manager.getProject()).reformat(classInitializer); - HighlightUtil.highlightElement(addedElement, - IntentionPowerPackBundle.message( + HighlightUtil.highlightElement(addedElement, IntentionPowerPackBundle.message( "press.escape.to.remove.highlighting.message")); } } \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java new file mode 100644 index 000000000000..594414f839bb --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java @@ -0,0 +1,8 @@ +class Foo { + static final int f1 = 2; + static final int f2 = f1 + 1; + + static { + System.out.println(); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java new file mode 100644 index 000000000000..1697906d1cb0 --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java @@ -0,0 +1,13 @@ +class Foo { + static final int f1; + + static { + f1 = 2; + } + + static final int f2 = f1 + 1; + + static { + System.out.println(); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java index 933d5e2e7964..e8c5659abdd4 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java @@ -26,6 +26,7 @@ public class SplitDeclarationAndInitializationIntentionTest extends IPPTestCase public void testArrayInitializer() { doTest(); } public void testArray() { doTest(); } + public void testFieldUsedBeforeInitializer() { doTest(); } @Override protected String getRelativePath() { From 0d1b2368ed8eb5a2db6a9d7f2b8fc1680ad633ad Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 17:10:17 +0300 Subject: [PATCH 12/84] disable concatenation intentions in annotation methods (IDEA-173817) --- .../concatenation/SimpleStringConcatenationPredicate.java | 5 ++++- .../ConstantRequiredInsideAnnotationMethod.java | 5 +++++ .../ReplaceConcatenationWithStringBufferIntentionTest.java | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java index 1b5e41ed0fb7..8393cbd955bf 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java @@ -16,7 +16,9 @@ package com.siyeh.ipp.concatenation; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.psi.PsiAnnotationMethod; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ipp.base.PsiElementPredicate; @@ -33,6 +35,7 @@ class SimpleStringConcatenationPredicate implements PsiElementPredicate { if (!ExpressionUtils.isConcatenation(element)) { return false; } - return !(excludeConcatenationsInsideAnnotations && AnnotationUtil.isInsideAnnotation(element)); + return !(excludeConcatenationsInsideAnnotations && (AnnotationUtil.isInsideAnnotation(element) || + PsiTreeUtil.getParentOfType(element, PsiAnnotationMethod.class) != null)); } } diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java new file mode 100644 index 000000000000..c69e724452dd --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java @@ -0,0 +1,5 @@ +package com.siyeh.ipp.concatenation.string_builder; + +public @interface ConstantRequiredInsideAnnotationMethod { + String val() default "hey," + ""; +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java index 82ee9ca4b93e..3baa064bf8c8 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java @@ -10,6 +10,7 @@ public class ReplaceConcatenationWithStringBufferIntentionTest extends IPPTestCa public void testNonStringConcatenationStart() { doTest(); } public void testConcatenationInsideAppend() { doTest(); } + public void testConstantRequiredInsideAnnotationMethod() { assertIntentionNotAvailable(); } @Override protected String getIntentionName() { From 6423538939a7a5f4b4662c29dc1e7cc2b64c5ddf Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 17:20:27 +0300 Subject: [PATCH 13/84] surround with array fix: disable for non-denotable types (IDEA-173797) --- .../daemon/impl/quickfix/SurroundWithArrayFix.java | 3 ++- .../quickFix/surroundWithArray/beforeRejectNullType.java | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SurroundWithArrayFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SurroundWithArrayFix.java index 123d9771923e..16d5a01a2493 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SurroundWithArrayFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SurroundWithArrayFix.java @@ -23,6 +23,7 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ArrayUtilRt; @@ -92,7 +93,7 @@ public class SurroundWithArrayFix extends PsiElementBaseIntentionAction { final PsiType paramType = psiParameters[idx].getType(); if (paramType instanceof PsiArrayType) { final PsiType expressionType = TypeConversionUtil.erasure(expression.getType()); - if (expressionType != null) { + if (expressionType != null && PsiTypesUtil.isDenotableType(expressionType) && expressionType != PsiType.NULL) { final PsiType componentType = ((PsiArrayType)paramType).getComponentType(); if (TypeConversionUtil.isAssignable(componentType, expressionType)) { return expression; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java new file mode 100644 index 000000000000..6972290707e4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java @@ -0,0 +1,9 @@ +// "Surround with array initialization" "false" +class A { + void m1(String[] s, + String[] s2, + String[] s3) {} + { + m1( null, null); + } +} \ No newline at end of file From ad6d5ed5164532c9833568c34e60a9e15884b769 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 17:25:07 +0300 Subject: [PATCH 14/84] method has too many params should not warn on derivatives(IDEA-173754) --- .../siyeh/ig/methodmetrics/ParametersPerMethodInspection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java index b760b1ec3936..ea717056a154 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java @@ -19,7 +19,6 @@ import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameterList; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspectionVisitor; -import com.siyeh.ig.psiutils.LibraryUtil; import org.jetbrains.annotations.NotNull; public class ParametersPerMethodInspection extends MethodMetricInspection { @@ -76,7 +75,8 @@ public class ParametersPerMethodInspection extends MethodMetricInspection { if (parametersCount <= getLimit()) { return; } - if (LibraryUtil.isOverrideOfLibraryMethod(method)) { + //skip all derivatives + if (method.findDeepestSuperMethods().length > 0) { return; } registerMethodError(method, Integer.valueOf(parametersCount)); From 61f9e727d318483ce8a82c60f165246ba4b8c74d Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Mon, 5 Jun 2017 17:56:12 +0300 Subject: [PATCH 15/84] remove old key in WindowStateServiceImpl --- .../openapi/util/WindowStateServiceImpl.java | 170 +++++++++--------- 1 file changed, 80 insertions(+), 90 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java index 67d2c9eb27ad..622e5231de38 100644 --- a/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java @@ -26,11 +26,13 @@ import com.intellij.util.ui.UIUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Map; import java.util.TreeMap; -import java.util.function.Supplier; +import java.util.function.BiFunction; +import java.util.function.Function; /** * @author Sergey.Malenkov @@ -219,9 +221,32 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers private T getFor(Object object, @NotNull String key, @NotNull Class type) { GraphicsDevice screen = getScreen(object); - T state = get(getKey(screen, key), type); + float scale = getSysScale(screen); + + Function getState = (myKey) -> { + WindowState state = myStateMap.get(myKey); + if (state == null) return null; + state = state.copy().scaleDown(scale); + if (isVisible(state)) { + if (type == WindowState.class) { + return (T)state; + } + if (type == Point.class) { + return (T)state.getLocation(); + } + if (type == Dimension.class) { + return (T)state.getSize(); + } + if (type == Rectangle.class) { + return (T)state.getBounds(); + } + } + return null; + }; + + T state = getState.apply(getKey(screen, key)); if (state == null) { - state = get(getOldKey(screen, key), type); + state = getState.apply(getOldKey(screen, key)); } if (state != null) { return state; @@ -229,29 +254,7 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers if (object != null) { return getFor(null, key, type); } - return get(new KeyPair(key, 1f), type); - } - - @SuppressWarnings("unchecked") - private T get(@NotNull KeyPair keyPair, @NotNull Class type) { - WindowState state = myStateMap.get(keyPair.first); - if (state == null) return null; - state = state.copy().scaleDown(keyPair.second); - if (isVisible(state)) { - if (type == WindowState.class) { - return (T)state; - } - if (type == Point.class) { - return (T)state.getLocation(); - } - if (type == Dimension.class) { - return (T)state.getSize(); - } - if (type == Rectangle.class) { - return (T)state.getBounds(); - } - } - return null; + return getState.apply(key); } private void putFor(Object object, @NotNull String key, @@ -261,63 +264,64 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers boolean fullScreen, boolean fullScreenSet) { synchronized (myStateMap) { GraphicsDevice screen = getScreen(object); - KeyPair oldKeyPair = getOldKey(screen, key); + float scale = getSysScale(screen); - putImpl(getKey(screen, key), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + BiFunction putState = (myNewKey, myOldKey) -> { + // remove & migrate the old key state + WindowState oldState = myOldKey != null ? myStateMap.remove(myOldKey) : null; + if (oldState != null) { + oldState.scaleDown(scale); + WindowState newState = myStateMap.get(myNewKey); + if (newState != null) { + newState.merge(oldState); + } else { + myStateMap.put(myNewKey, oldState); + } + } + // put the new key state + WindowState state = myStateMap.get(myNewKey); + if (state != null) { + if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { + state.scaleUp(scale); + } else { + myStateMap.remove(myNewKey); + } + } + else { + state = new WindowState(); + if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { + state.scaleUp(scale); + myStateMap.put(myNewKey, state); + } + } + return null; + }; + + putState.apply(getKey(screen, key), getOldKey(screen, key)); if (screen != null) { - putImpl(getKey(null, key), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + putState.apply(getKey(null, key), getOldKey(null, key)); } - putImpl(new KeyPair(key, 1f), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + putState.apply(key, null); } } - private void putImpl(@NotNull KeyPair keyPair, - @NotNull KeyPair oldKeyPair, - Point location, boolean locationSet, - Dimension size, boolean sizeSet, - boolean maximized, boolean maximizedSet, - boolean fullScreen, boolean fullScreenSet) { - WindowState state = myStateMap.get(keyPair.first); - - // may be convert the old key state to the new key - WindowState oldState = myStateMap.remove(oldKeyPair.first); - if (oldState != null) { - oldState.scaleDown(oldKeyPair.second); - if (state != null) { - state.merge(oldState); - } else { - myStateMap.put(keyPair.first, state = oldState); - } - } - if (state != null) { - if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { - state.scaleUp(keyPair.second); - } else { - myStateMap.remove(keyPair.first); - } - } - else { - state = new WindowState(); - if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { - state.scaleUp(keyPair.second); - myStateMap.put(keyPair.first, state); - } - } + private static float getSysScale(GraphicsDevice screen) { + return UIUtil.isJreHiDPIEnabled() && screen != null ? JBUI.sysScale(screen.getDefaultConfiguration()) : 1f; } /* * todo: old hidpi-unaware key; to be removed */ @NotNull - private static KeyPair getOldKey(GraphicsDevice screen, String key) { + private static String getOldKey(@Nullable GraphicsDevice screen, String key) { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (environment.isHeadlessInstance()) { - return new KeyPair(key + ".headless", 1f); + return key + ".headless"; } StringBuilder sb = new StringBuilder(key); - float scale = 1f; for (GraphicsDevice device : environment.getScreenDevices()) { Rectangle bounds = device.getDefaultConfiguration().getBounds(); + normalizeSize(device, bounds); sb.append('/').append(bounds.x); sb.append('.').append(bounds.y); sb.append('.').append(bounds.width); @@ -325,25 +329,22 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } if (screen != null) { Rectangle bounds = screen.getDefaultConfiguration().getBounds(); + normalizeSize(screen, bounds); sb.append('@').append(bounds.x); sb.append('.').append(bounds.y); sb.append('.').append(bounds.width); sb.append('.').append(bounds.height); - if (UIUtil.isJreHiDPIEnabled()) { - scale = JBUI.sysScale(screen.getDefaultConfiguration()); - } } - return new KeyPair(sb.toString(), scale); + return sb.toString(); } @NotNull - private static KeyPair getKey(GraphicsDevice screen, String key) { + private static String getKey(@Nullable GraphicsDevice screen, String key) { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (environment.isHeadlessInstance()) { - return new KeyPair(key + ".headless", 1f); + return key + ".headless"; } StringBuilder sb = new StringBuilder(key); - float scale = 1f; // not storing screen x,y due to relying on isVisible(state) on key retrieval if (screen == null) { for (GraphicsDevice device : environment.getScreenDevices()) { @@ -360,13 +361,10 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers sb.append('.').append(bounds.height); if (JBUI.isPixHiDPI(screen.getDefaultConfiguration())) { int dpi = ((int)(96 * JBUI.pixScale(screen.getDefaultConfiguration()))); - sb.append("@" + dpi + "dpi"); - } - if (UIUtil.isJreHiDPIEnabled()) { - scale = JBUI.sysScale(screen.getDefaultConfiguration()); + sb.append("@").append(dpi).append("dpi"); } } - return new KeyPair(sb.toString(), scale); + return sb.toString(); } private static void normalizeSize(GraphicsDevice screen, Rectangle bounds) { @@ -390,15 +388,13 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } if (object instanceof Window) { Window window = (Window)object; - object = ScreenUtil.getScreenDevice(window.getBounds()); - if (object == null) { - LOG.warn("cannot find a screen for " + window); - return null; + GraphicsConfiguration gc = window.getGraphicsConfiguration(); + GraphicsDevice device = gc != null ? + window.getGraphicsConfiguration().getDevice() : ScreenUtil.getScreenDevice(window.getBounds()); + if (device != null) { + return device; } } - if (object instanceof GraphicsDevice) { - return (GraphicsDevice)object; - } LOG.warn("cannot find a screen for " + object); return null; } @@ -492,10 +488,4 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } return ScreenUtil.isVisible(new Rectangle(location, size)); } - - private static class KeyPair extends Pair { - public KeyPair(String key, Float scale) { - super(key, scale); - } - } } From 0c0ca5158de6e94a8c15fefeac1970dc4ec568f3 Mon Sep 17 00:00:00 2001 From: Julia Beliaeva Date: Mon, 5 Jun 2017 18:17:46 +0300 Subject: [PATCH 16/84] [vcs-log] fix older index files not being removed When a persistent structure is created for index, we provide it with a file that looks like "logId.kind.version". If this file does not exist, we assume that index version was incremented and attempt to delete files that start with "logId.kind". But this approach does not work with MapReduceIndex: it adds ".storage" suffix to the file given to it. In order to avoid deleting index files all the time, a brilliant solution was introduced in 91c7d97f0901bc97733a81c595c51ea51e0a0009: do not remove older versions at all. While the correct solution is to remove files that start with prefix corresponding to the older versions, which is done here. --- .../log/data/index/VcsLogFullDetailsIndex.java | 8 +------- .../vcs/log/data/index/VcsLogPathsIndex.java | 5 +++-- .../log/data/index/VcsLogPersistentIndex.java | 6 +++--- .../intellij/vcs/log/util/PersistentUtil.java | 16 +++++++++++----- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java index 61470653fda3..0cf752a66693 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java @@ -31,7 +31,6 @@ import com.intellij.vcs.log.util.PersistentUtil; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -130,11 +129,6 @@ public class VcsLogFullDetailsIndex implements Disposable { if (myDisposed) throw new ProcessCanceledException(); } - @NotNull - public static File getStorageFile(@NotNull String kind, @NotNull String id) { - return PersistentUtil.getStorageFile(INDEX, kind, id, getVersion(), false); - } - private class MyMapReduceIndex extends MapReduceIndex { public MyMapReduceIndex(@NotNull DataIndexer indexer, @NotNull DataExternalizer externalizer, @@ -157,7 +151,7 @@ public class VcsLogFullDetailsIndex implements Disposable { private static class MyMapIndexStorage extends MapIndexStorage { public MyMapIndexStorage(@NotNull String name, @NotNull String logId, @NotNull DataExternalizer externalizer) throws IOException { - super(VcsLogFullDetailsIndex.getStorageFile(name, logId), EnumeratorIntegerDescriptor.INSTANCE, externalizer, 5000, false); + super(PersistentUtil.getStorageFile(INDEX, name, logId, getVersion()), EnumeratorIntegerDescriptor.INSTANCE, externalizer, 5000, false); } @Override diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java index 899713a03e5b..9347f8fa478b 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java @@ -71,7 +71,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex createPathsEnumerator(@NotNull String logId) throws IOException { - File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion(), true); + File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion()); return new PersistentBTreeEnumerator<>(storageFile, SystemInfo.isFileSystemCaseSensitive ? EnumeratorStringDescriptor.INSTANCE : new ToLowerCaseStringDescriptor(), Page.PAGE_SIZE, null, getVersion()); @@ -309,7 +309,8 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex beforeData = fillDataWithNulls(result, parent, beforeId); beforeData.add(new ChangeData(ChangeKind.RENAMED_FROM, afterId)); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java index f9a4eeeb5874..783eaf34bf4c 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java @@ -415,11 +415,11 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { try { int version = getVersion(); - File commitsStorage = getStorageFile(INDEX, COMMITS, logId, version, true); + File commitsStorage = getStorageFile(INDEX, COMMITS, logId, version); commits = new PersistentSetImpl<>(commitsStorage, EnumeratorIntegerDescriptor.INSTANCE, Page.PAGE_SIZE, null, version); Disposer.register(disposable, () -> catchAndWarn(commits::close)); - File messagesStorage = getStorageFile(INDEX, MESSAGES, logId, VcsLogStorageImpl.VERSION + MESSAGES_VERSION, true); + File messagesStorage = getStorageFile(INDEX, MESSAGES, logId, VcsLogStorageImpl.VERSION + MESSAGES_VERSION); messages = new PersistentHashMap<>(messagesStorage, new IntInlineKeyDescriptor(), EnumeratorStringDescriptor.INSTANCE, Page.PAGE_SIZE); Disposer.register(disposable, () -> catchAndWarn(messages::close)); @@ -428,7 +428,7 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { users = new VcsLogUserIndex(logId, userRegistry, fatalErrorHandler, disposable); paths = new VcsLogPathsIndex(logId, roots, fatalErrorHandler, disposable); - File parentsStorage = getStorageFile(INDEX, PARENTS, logId, getVersion(), true); + File parentsStorage = getStorageFile(INDEX, PARENTS, logId, getVersion()); parents = new PersistentHashMap<>(parentsStorage, EnumeratorIntegerDescriptor.INSTANCE, new IntListDataExternalizer(), Page.PAGE_SIZE, getVersion()); Disposer.register(disposable, () -> catchAndWarn(parents::close)); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java index e81891dd7d9f..8f6da02b8bf7 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java @@ -110,17 +110,23 @@ public class PersistentUtil { public static File getStorageFile(@NotNull String subdirName, @NotNull String kind, @NotNull String id, - int version, - boolean cleanupOldVersions) { + int version) { File subdir = new File(LOG_CACHE, subdirName); String safeLogId = PathUtilRt.suggestFileName(id, true, true); - File file = new File(subdir, safeLogId + "." + kind + "." + version); - if (cleanupOldVersions && !file.exists()) { - IOUtil.deleteAllFilesStartingWith(new File(subdir, safeLogId + "." + kind)); + File file = getFileName(kind, subdir, safeLogId, version); + if (!file.exists()) { + for (int oldVersion = 0; oldVersion < version; oldVersion++) { + IOUtil.deleteAllFilesStartingWith(getFileName(kind, subdir, safeLogId, oldVersion)); + } } return file; } + @NotNull + private static File getFileName(@NotNull String kind, @NotNull File subdir, @NotNull String safeLogId, int version) { + return new File(subdir, safeLogId + "." + kind + "." + version); + } + @NotNull public static File getCorruptionMarkerFile() { return new File(LOG_CACHE, CORRUPTION_MARKER); From 056896852cd5ede202aa22ded764d37bbb0b2c42 Mon Sep 17 00:00:00 2001 From: Julia Beliaeva Date: Mon, 5 Jun 2017 18:23:00 +0300 Subject: [PATCH 17/84] [vcs-log] use version variable instead of method getVersion call --- .../intellij/vcs/log/data/index/VcsLogPersistentIndex.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java index 783eaf34bf4c..d74819c3b7e1 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java @@ -428,9 +428,9 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { users = new VcsLogUserIndex(logId, userRegistry, fatalErrorHandler, disposable); paths = new VcsLogPathsIndex(logId, roots, fatalErrorHandler, disposable); - File parentsStorage = getStorageFile(INDEX, PARENTS, logId, getVersion()); + File parentsStorage = getStorageFile(INDEX, PARENTS, logId, version); parents = new PersistentHashMap<>(parentsStorage, EnumeratorIntegerDescriptor.INSTANCE, - new IntListDataExternalizer(), Page.PAGE_SIZE, getVersion()); + new IntListDataExternalizer(), Page.PAGE_SIZE, version); Disposer.register(disposable, () -> catchAndWarn(parents::close)); } catch (Throwable t) { From 45bb74c79fdf0e5fe4713b56eceb8ca6cf1c5d4f Mon Sep 17 00:00:00 2001 From: "liana.bakradze" Date: Mon, 15 May 2017 14:31:39 +0300 Subject: [PATCH 18/84] initial support of pandas Series (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 56 +++++++++++++++++++ .../python/debugger/PyDebugValue.java | 20 +++---- .../containerview/DataViewStrategy.java | 10 +++- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index f3d9d030fd2d..46c27ddf404b 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -425,6 +425,9 @@ def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): xml += array_to_xml(array, roffset, coffset, rows, cols, format) elif type_name == 'DataFrame': xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format) + elif type_name == 'Series': + xml = series_to_xml(array, name, roffset, 0, rows, 1, format) + else: raise VariableError("Do not know how to convert type %s to table" % (type_name)) @@ -616,3 +619,56 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): value = col_formats[col] % value xml += var_to_xml(value, '') return xml + + +def series_to_xml(df, name, roffset, coffset, rows, cols, format): + """ + :type df: pandas.core.frame.DataFrame + :type name: str + :type coffset: int + :type roffset: int + :type rows: int + :type cols: int + :type format: str + + + """ + num_rows = df.shape[0] + xml = '\n' % \ + (name, num_rows, 1) + + if (rows, cols) == (-1, -1): + rows, cols = num_rows, 1 + + rows = min(rows, 100) + dtype = df.dtype.kind + col_bounds = [(df.min(), df.max()) if dtype in "biufc" else (0, 0)] + + df = df.iloc[roffset: roffset + rows] + rows, cols = df.shape[0], 1 + + + xml += "\n" % (rows, cols) + format = format.replace('%', '') + col_formats = [] + + + for col in range(cols): + fmt = format if (dtype == 'f' and format) else array_default_format(dtype) + col_formats.append('%' + fmt) + bounds = col_bounds[col] + + xml += '\n' % \ + (str(col), 1, dtype, fmt, bounds[1], bounds[0]) + for row, label in enumerate(iter(df.axes[0])): + xml += "\n" % \ + (str(row), 1) + xml += "\n" + xml += "\n" % (rows, cols) + for row in range(rows): + xml += "\n" % str(row) + for col in range(cols): + value = df.iat[row] + value = col_formats[col] % value + xml += var_to_xml(value, '') + return xml diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index 09444ad6d618..4d2d4a9ccdfb 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -4,12 +4,15 @@ import com.google.common.base.Strings; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Pair; +import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.frame.*; import com.jetbrains.python.debugger.pydev.PyVariableLocator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +20,10 @@ import java.util.regex.Pattern; // todo: null modifier for modify modules, class objects etc. public class PyDebugValue extends XNamedValue { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue"); + private static final String DATA_FRAME = "DataFrame"; + private static final String SERIES = "Series"; + private static final Map EVALUATOR_PREFIXES = + ContainerUtil.newHashMap(Pair.create("ndarray", "Array"), Pair.create(DATA_FRAME, DATA_FRAME), Pair.create(SERIES, SERIES)); public static final int MAX_VALUE = 256; public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict"; @@ -204,23 +211,16 @@ public class PyDebugValue extends XNamedValue { node.setPresentation(getValueIcon(), myType, value, myContainer); } - private boolean isDataFrame() { - return "DataFrame".equals(myType); - } - - private boolean isNdarray() { - return "ndarray".equals(myType); - } - private void setFullValueEvaluator(XValueNode node, String value) { String treeName = getFullTreeName(); - if (!isDataFrame() && !isNdarray()) { + String postfix = EVALUATOR_PREFIXES.get(myType); + if (postfix == null) { if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName)); } return; } - String linkText = "...View as " + (isDataFrame() ? "DataFrame" : "Array"); + String linkText = "...View as " + postfix; node.setFullValueEvaluator(new PyNumericContainerValueEvaluator(linkText, myFrameAccessor, treeName)); } diff --git a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java index 1eb2678b3715..842781afcbf6 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java +++ b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java @@ -29,7 +29,7 @@ import java.util.Set; public abstract class DataViewStrategy { private static class StrategyHolder { - private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy()); + private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); } public abstract AsyncArrayTableModel createTableModel(int rowCount, int columnCount, @NotNull PyDataViewerPanel panel, @NotNull PyDebugValue debugValue); @@ -53,4 +53,12 @@ public abstract class DataViewStrategy { } return null; } + + private static class SeriesViewStrategy extends DataFrameViewStrategy { + @NotNull + @Override + public String getTypeName() { + return "Series"; + } + } } \ No newline at end of file From 761ee433538af468a72816896114210b9d92cfec Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Tue, 16 May 2017 12:33:22 +0300 Subject: [PATCH 19/84] use constant (PY-21763) --- python/helpers/pydev/_pydevd_bundle/pydevd_vars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index 46c27ddf404b..effd51274548 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -640,7 +640,7 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): if (rows, cols) == (-1, -1): rows, cols = num_rows, 1 - rows = min(rows, 100) + rows = min(rows, MAXIMUM_ARRAY_SIZE) dtype = df.dtype.kind col_bounds = [(df.min(), df.max()) if dtype in "biufc" else (0, 0)] From 4fe9bb51abd89a77fb0841397341083aec0fc5e5 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Tue, 16 May 2017 17:19:13 +0300 Subject: [PATCH 20/84] use dict to map converter and type (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index effd51274548..420defeca9ab 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -413,25 +413,16 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SEN MAXIMUM_ARRAY_SIZE = 100 -def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): - _, type_name, _ = get_type(array) - if type_name == 'ndarray': - array, metaxml, r, c, f = array_to_meta_xml(array, name, format) - xml = metaxml - format = '%' + f - if rows == -1 and cols == -1: - rows = r - cols = c - xml += array_to_xml(array, roffset, coffset, rows, cols, format) - elif type_name == 'DataFrame': - xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format) - elif type_name == 'Series': - xml = series_to_xml(array, name, roffset, 0, rows, 1, format) - else: - raise VariableError("Do not know how to convert type %s to table" % (type_name)) - - return "%s" % xml +def array_to_xml_converter(array, name, roffset, coffset, rows, cols, format): + array, metaxml, r, c, f = array_to_meta_xml(array, name, format) + xml = metaxml + format = '%' + f + if rows == -1 and cols == -1: + rows = r + cols = c + xml += array_to_xml(array, roffset, coffset, rows, cols, format) + return xml def array_to_xml(array, roffset, coffset, rows, cols, format): @@ -672,3 +663,13 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): value = col_formats[col] % value xml += var_to_xml(value, '') return xml + +TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": series_to_xml} + + +def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): + _, type_name, _ = get_type(array) + if type_name in TYPE_TO_XML_CONVERTERS: + return "%s" % TYPE_TO_XML_CONVERTERS[type_name](array, name, roffset, coffset, rows, cols, format) + else: + raise VariableError("type %s not supported" % type_name) From 806c592b44ea57a1ba91d16acd75775008fe6751 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 14:17:05 +0300 Subject: [PATCH 21/84] extract writing row to xml (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index 420defeca9ab..dc9ba7e3da97 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -449,24 +449,20 @@ def array_to_xml(array, roffset, coffset, rows, cols, format): xml += "" % (rows, cols) for row in range(rows): - xml += "" % to_string(row) - for col in range(cols): + def get_value(col): value = array if rows == 1 or cols == 1: if rows == 1 and cols == 1: value = array[0] else: - if rows == 1: - dim = col - else: - dim = row - value = array[dim] + value = array[(col if rows == 1 else row)] if "ndarray" in str(type(value)): value = value[0] else: value = array[row][col] - value = format % value - xml += var_to_xml(value, '') + return value + xml += row_to_xml(row, (get_value(col) for col in range(cols))) + return xml @@ -604,11 +600,7 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): xml += "\n" xml += "\n" % (rows, cols) for row in range(rows): - xml += "\n" % str(row) - for col in range(cols): - value = df.iat[row, col] - value = col_formats[col] % value - xml += var_to_xml(value, '') + xml += row_to_xml(row, (col_formats[col] % df.iat[row, col] for col in range(cols))) return xml @@ -657,13 +649,17 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): xml += "\n" xml += "\n" % (rows, cols) for row in range(rows): - xml += "\n" % str(row) - for col in range(cols): - value = df.iat[row] - value = col_formats[col] % value - xml += var_to_xml(value, '') + xml += row_to_xml(row, (col_formats[col] % df.iat[row] for col in range(cols))) return xml + +def row_to_xml(index, formatted_values): + xml = "\n" % to_string(index) + for value in formatted_values: + xml += var_to_xml(value, '') + return xml + + TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": series_to_xml} From 80534296c4b9ef24dfb3b9d24fabbfb69afb4162 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 15:04:35 +0300 Subject: [PATCH 22/84] extract writing array data to xml (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index dc9ba7e3da97..c45a27b0c99f 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -447,22 +447,19 @@ def array_to_xml(array, roffset, coffset, rows, cols, format): array = array[roffset:] rows = min(rows, len(array)) - xml += "" % (rows, cols) - for row in range(rows): - def get_value(col): - value = array - if rows == 1 or cols == 1: - if rows == 1 and cols == 1: - value = array[0] - else: - value = array[(col if rows == 1 else row)] - if "ndarray" in str(type(value)): - value = value[0] + def get_value(row, col): + value = array + if rows == 1 or cols == 1: + if rows == 1 and cols == 1: + value = array[0] else: - value = array[row][col] - return value - xml += row_to_xml(row, (get_value(col) for col in range(cols))) - + value = array[(col if rows == 1 else row)] + if "ndarray" in str(type(value)): + value = value[0] + else: + value = array[row][col] + return value + xml += array_data_to_xml(rows, cols, lambda r: (get_value(r, c) for c in range(cols))) return xml @@ -598,9 +595,7 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): xml += "\n" % \ (str(row), get_label(label)) xml += "\n" - xml += "\n" % (rows, cols) - for row in range(rows): - xml += row_to_xml(row, (col_formats[col] % df.iat[row, col] for col in range(cols))) + xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[row, c] for c in range(cols))) return xml @@ -647,16 +642,16 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): xml += "\n" % \ (str(row), 1) xml += "\n" - xml += "\n" % (rows, cols) - for row in range(rows): - xml += row_to_xml(row, (col_formats[col] % df.iat[row] for col in range(cols))) + xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[r] for c in range(cols))) return xml -def row_to_xml(index, formatted_values): - xml = "\n" % to_string(index) - for value in formatted_values: - xml += var_to_xml(value, '') +def array_data_to_xml(rows, cols, get_row): + xml = "\n" % (rows, cols) + for row in range(rows): + xml += "\n" % to_string(row) + for value in get_row(row): + xml += var_to_xml(value, '') return xml From c11be637b680b718b2b5ac3cce184569842c6221 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 15:06:17 +0300 Subject: [PATCH 23/84] fix labels for series rows (PY-21763) --- python/helpers/pydev/_pydevd_bundle/pydevd_vars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index c45a27b0c99f..60818af333a6 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -640,7 +640,7 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): (str(col), 1, dtype, fmt, bounds[1], bounds[0]) for row, label in enumerate(iter(df.axes[0])): xml += "\n" % \ - (str(row), 1) + (str(row), str(label)) xml += "\n" xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[r] for c in range(cols))) return xml From dd983f8037a82890e46f3daf8d06c7c25c295abb Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 15:29:53 +0300 Subject: [PATCH 24/84] extract writing slice to xml (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index 60818af333a6..f50178e3ba19 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -520,9 +520,7 @@ def array_to_meta_xml(array, name, format): bounds = (0, 0) if type in "biufc": bounds = (array.min(), array.max()) - xml = '' % \ - (slice, rows, cols, format, type, bounds[1], bounds[0]) - return array, xml, rows, cols, format + return array, slice_to_xml(slice, rows, cols, format, type, bounds), rows, cols, format def array_default_format(type): @@ -554,8 +552,7 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): else: slice = '' slice = name + slice - xml = '\n' % \ - (slice, num_rows, num_cols) + xml = slice_to_xml(slice, num_rows, num_cols, "", "", (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, num_cols @@ -612,14 +609,14 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): """ num_rows = df.shape[0] - xml = '\n' % \ - (name, num_rows, 1) + dtype = df.dtype.kind + format = format.replace('%', '') + xml = slice_to_xml(name, num_rows, 1, dtype, format, (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, 1 rows = min(rows, MAXIMUM_ARRAY_SIZE) - dtype = df.dtype.kind col_bounds = [(df.min(), df.max()) if dtype in "biufc" else (0, 0)] df = df.iloc[roffset: roffset + rows] @@ -627,7 +624,6 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): xml += "\n" % (rows, cols) - format = format.replace('%', '') col_formats = [] @@ -655,6 +651,11 @@ def array_data_to_xml(rows, cols, get_row): return xml +def slice_to_xml(slice, rows, cols, format, type, bounds): + return '' % \ + (slice, rows, cols, format, type, bounds[1], bounds[0]) + + TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": series_to_xml} From eccd8751f542f5834e42051f617fa51af6ab90a4 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 15:35:37 +0300 Subject: [PATCH 25/84] remove redundant code (PY-21763) we don't have max slice constraint now so this condition is always false --- python/helpers/pydev/_pydevd_bundle/pydevd_vars.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index f50178e3ba19..6c302aae7a64 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -546,13 +546,7 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): """ num_rows = df.shape[0] num_cols = df.shape[1] - if (num_rows, num_cols) != df.shape: - df = df.iloc[0:num_rows, 0: num_cols] - slice = '.iloc[0:%s, 0:%s]' % (num_rows, num_cols) - else: - slice = '' - slice = name + slice - xml = slice_to_xml(slice, num_rows, num_cols, "", "", (0, 0)) + xml = slice_to_xml(name, num_rows, num_cols, "", "", (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, num_cols From 0ce7189a9272fc7539280039041d4539c29c43c0 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 17 May 2017 15:52:39 +0300 Subject: [PATCH 26/84] fix label for series with multi-indexes (PY-21763) --- python/helpers/pydev/_pydevd_bundle/pydevd_vars.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index 6c302aae7a64..b5ba01257e52 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -628,9 +628,10 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): xml += '\n' % \ (str(col), 1, dtype, fmt, bounds[1], bounds[0]) + get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) for row, label in enumerate(iter(df.axes[0])): xml += "\n" % \ - (str(row), str(label)) + (str(row), get_label(label)) xml += "\n" xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[r] for c in range(cols))) return xml From ac9e8e7459fe2869f689595cf5423687c91e5841 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Thu, 18 May 2017 19:14:26 +0300 Subject: [PATCH 27/84] extract writing header to xml (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index b5ba01257e52..6ce55448ba19 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -532,6 +532,10 @@ def array_default_format(type): return 's' +def get_label(label): + return str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) + + def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): """ :type df: pandas.core.frame.DataFrame @@ -566,27 +570,17 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] rows, cols = df.shape - - - xml += "\n" % (rows, cols) format = format.replace('%', '') - col_formats = [] - get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) + col_to_label = lambda col: get_label(df.axes[1].values[col]) + col_to_type = lambda col: df.dtypes.iloc[col].kind + col_to_bounds = lambda col: col_bounds[col] + col_to_format = lambda col: format if col_to_type(col) == 'f' and format else array_default_format(col_to_type(col)) + row_to_label = lambda row: get_label(df.axes[0].values[row]) - for col in range(cols): - dtype = df.dtypes.iloc[col].kind - fmt = format if (dtype == 'f' and format) else array_default_format(dtype) - col_formats.append('%' + fmt) - bounds = col_bounds[col] + xml += header_data_to_xml(rows, cols, col_to_label, col_to_type, col_to_bounds, col_to_format, row_to_label) - xml += '\n' % \ - (str(col), get_label(df.axes[1].values[col]), dtype, fmt, bounds[1], bounds[0]) - for row, label in enumerate(iter(df.axes[0])): - xml += "\n" % \ - (str(row), get_label(label)) - xml += "\n" - xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[row, c] for c in range(cols))) + xml += array_data_to_xml(rows, cols, lambda r: (("%" + col_to_format(c)) % df.iat[r, c] for c in range(cols))) return xml @@ -603,37 +597,24 @@ def series_to_xml(df, name, roffset, coffset, rows, cols, format): """ num_rows = df.shape[0] - dtype = df.dtype.kind - format = format.replace('%', '') - xml = slice_to_xml(name, num_rows, 1, dtype, format, (0, 0)) + + xml = slice_to_xml(name, num_rows, 1, "", "", (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, 1 rows = min(rows, MAXIMUM_ARRAY_SIZE) - col_bounds = [(df.min(), df.max()) if dtype in "biufc" else (0, 0)] + dtype = df.dtype.kind + col_bounds = (df.min(), df.max()) if dtype in "biufc" else (0, 0) df = df.iloc[roffset: roffset + rows] rows, cols = df.shape[0], 1 + format = format.replace('%', '') + format = format if (dtype == 'f' and format) else array_default_format(dtype) + xml += header_data_to_xml(rows, cols, lambda col: str(col), lambda col: dtype, lambda col: col_bounds, + lambda col: format, lambda row: get_label(df.axes[0].values[row])) - - xml += "\n" % (rows, cols) - col_formats = [] - - - for col in range(cols): - fmt = format if (dtype == 'f' and format) else array_default_format(dtype) - col_formats.append('%' + fmt) - bounds = col_bounds[col] - - xml += '\n' % \ - (str(col), 1, dtype, fmt, bounds[1], bounds[0]) - get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) - for row, label in enumerate(iter(df.axes[0])): - xml += "\n" % \ - (str(row), get_label(label)) - xml += "\n" - xml += array_data_to_xml(rows, cols, lambda r: (col_formats[c] % df.iat[r] for c in range(cols))) + xml += array_data_to_xml(rows, cols, lambda r: (('%' + format) % df.iat[r] for c in range(cols))) return xml @@ -651,6 +632,16 @@ def slice_to_xml(slice, rows, cols, format, type, bounds): (slice, rows, cols, format, type, bounds[1], bounds[0]) +def header_data_to_xml(rows, cols, col_to_labels, col_to_type, col_to_bounds, col_to_format, row_to_label): + xml = "\n" % (rows, cols) + for col in range(cols): + xml += '\n' % \ + (str(col), col_to_labels(col), col_to_type(col), col_to_format(col), col_to_bounds(col)[1], col_to_bounds(col)[0]) + for row in range(rows): + xml += "\n" % (str(row), row_to_label(row)) + xml += "\n" + return xml + TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": series_to_xml} From ced6560dcdb2e1baeaa638bb2a81b45d72aac454 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Thu, 18 May 2017 20:12:08 +0300 Subject: [PATCH 28/84] merge helpers (PY-21763) --- .../pydev/_pydevd_bundle/pydevd_vars.py | 90 +++++++------------ 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index 6ce55448ba19..ed693bc35674 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -548,73 +548,45 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): """ + dim = len(df.axes) num_rows = df.shape[0] - num_cols = df.shape[1] + num_cols = df.shape[1] if dim > 1 else 1 xml = slice_to_xml(name, num_rows, num_cols, "", "", (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, num_cols rows = min(rows, MAXIMUM_ARRAY_SIZE) - cols = min(min(cols, MAXIMUM_ARRAY_SIZE), num_cols) + cols = min(cols, MAXIMUM_ARRAY_SIZE, num_cols) # need to precompute column bounds here before slicing! col_bounds = [None] * cols - for col in range(cols): - dtype = df.dtypes.iloc[coffset + col].kind - if dtype in "biufc": - cvalues = df.iloc[:, coffset + col] - bounds = (cvalues.min(), cvalues.max()) - else: - bounds = (0, 0) - col_bounds[col] = bounds + dtypes = [None] * cols + if dim > 1: + for col in range(cols): + dtype = df.dtypes.iloc[coffset + col].kind + dtypes[col] = dtype + if dtype in "biufc": + cvalues = df.iloc[:, coffset + col] + bounds = (cvalues.min(), cvalues.max()) + else: + bounds = (0, 0) + col_bounds[col] = bounds + else: + dtype = df.dtype.kind + dtypes[0] = dtype + col_bounds[0] = (df.min(), df.max()) if dtype in "biufc" else (0, 0) - df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] - rows, cols = df.shape + df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] if dim > 1 else df.iloc[roffset: roffset + rows] + rows = df.shape[0] + cols = df.shape[1] if dim > 1 else 1 format = format.replace('%', '') - col_to_label = lambda col: get_label(df.axes[1].values[col]) - col_to_type = lambda col: df.dtypes.iloc[col].kind - col_to_bounds = lambda col: col_bounds[col] - col_to_format = lambda col: format if col_to_type(col) == 'f' and format else array_default_format(col_to_type(col)) - row_to_label = lambda row: get_label(df.axes[0].values[row]) + def col_to_format(c): + return format if dtypes[c] == 'f' and format else array_default_format(dtypes[c]) - xml += header_data_to_xml(rows, cols, col_to_label, col_to_type, col_to_bounds, col_to_format, row_to_label) - - xml += array_data_to_xml(rows, cols, lambda r: (("%" + col_to_format(c)) % df.iat[r, c] for c in range(cols))) - return xml - - -def series_to_xml(df, name, roffset, coffset, rows, cols, format): - """ - :type df: pandas.core.frame.DataFrame - :type name: str - :type coffset: int - :type roffset: int - :type rows: int - :type cols: int - :type format: str - - - """ - num_rows = df.shape[0] - - xml = slice_to_xml(name, num_rows, 1, "", "", (0, 0)) - - if (rows, cols) == (-1, -1): - rows, cols = num_rows, 1 - - rows = min(rows, MAXIMUM_ARRAY_SIZE) - dtype = df.dtype.kind - col_bounds = (df.min(), df.max()) if dtype in "biufc" else (0, 0) - - df = df.iloc[roffset: roffset + rows] - rows, cols = df.shape[0], 1 - format = format.replace('%', '') - format = format if (dtype == 'f' and format) else array_default_format(dtype) - xml += header_data_to_xml(rows, cols, lambda col: str(col), lambda col: dtype, lambda col: col_bounds, - lambda col: format, lambda row: get_label(df.axes[0].values[row])) - - xml += array_data_to_xml(rows, cols, lambda r: (('%' + format) % df.iat[r] for c in range(cols))) + xml += header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim) + xml += array_data_to_xml(rows, cols, lambda r: (("%" + col_to_format(c)) % (df.iat[r, c] if dim > 1 else df.iat[r]) + for c in range(cols))) return xml @@ -632,17 +604,19 @@ def slice_to_xml(slice, rows, cols, format, type, bounds): (slice, rows, cols, format, type, bounds[1], bounds[0]) -def header_data_to_xml(rows, cols, col_to_labels, col_to_type, col_to_bounds, col_to_format, row_to_label): +def header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim): xml = "\n" % (rows, cols) for col in range(cols): + col_label = get_label(df.axes[1].values[col]) if dim > 1 else str(col) + bounds = col_bounds[col] xml += '\n' % \ - (str(col), col_to_labels(col), col_to_type(col), col_to_format(col), col_to_bounds(col)[1], col_to_bounds(col)[0]) + (str(col), col_label, dtypes[col], col_to_format(col), bounds[1], bounds[0]) for row in range(rows): - xml += "\n" % (str(row), row_to_label(row)) + xml += "\n" % (str(row), get_label(df.axes[0].values[row])) xml += "\n" return xml -TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": series_to_xml} +TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": dataframe_to_xml} def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): From 5cc4d2d3fe3d15cdb1ef2dc1331c560e30b0c840 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Fri, 19 May 2017 13:46:51 +0300 Subject: [PATCH 29/84] do not show column header for series (PY-21763) Series are 1-dimensional array, so column header is useless for them --- .../python/debugger/containerview/DataViewStrategy.java | 9 +++++++++ .../python/debugger/containerview/PyDataViewerPanel.java | 1 + 2 files changed, 10 insertions(+) diff --git a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java index 842781afcbf6..18e53d8d8ad1 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java +++ b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java @@ -41,6 +41,10 @@ public abstract class DataViewStrategy { @NotNull public abstract String getTypeName(); + public boolean showColumnHeader() { + return true; + } + /** * @return null if no strategy for this type */ @@ -60,5 +64,10 @@ public abstract class DataViewStrategy { public String getTypeName() { return "Series"; } + + @Override + public boolean showColumnHeader() { + return false; + } } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java index a61f40f4141c..3d4c187e1961 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java +++ b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java @@ -205,6 +205,7 @@ public class PyDataViewerPanel extends JPanel { if (myTable.getColumnCount() > 0) { myTable.setDefaultRenderer(myTable.getColumnClass(0), cellRenderer); } + myTable.setShowColumns(strategy.showColumnHeader()); }); } From b2ea29fcfa739736014b699ecca382c117186beb Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Fri, 19 May 2017 13:50:42 +0300 Subject: [PATCH 30/84] incorrect name (PY-21763) --- .../pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index 4d2d4a9ccdfb..f6aaed10719b 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -22,7 +22,7 @@ public class PyDebugValue extends XNamedValue { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue"); private static final String DATA_FRAME = "DataFrame"; private static final String SERIES = "Series"; - private static final Map EVALUATOR_PREFIXES = + private static final Map EVALUATOR_POSTFIXES = ContainerUtil.newHashMap(Pair.create("ndarray", "Array"), Pair.create(DATA_FRAME, DATA_FRAME), Pair.create(SERIES, SERIES)); public static final int MAX_VALUE = 256; @@ -213,7 +213,7 @@ public class PyDebugValue extends XNamedValue { private void setFullValueEvaluator(XValueNode node, String value) { String treeName = getFullTreeName(); - String postfix = EVALUATOR_PREFIXES.get(myType); + String postfix = EVALUATOR_POSTFIXES.get(myType); if (postfix == null) { if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName)); From d954222265fc046c96aefa53b07782352940ce61 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Fri, 19 May 2017 13:56:51 +0300 Subject: [PATCH 31/84] code cleanup (PY-21763) --- .../containerview/DataViewStrategy.java | 16 ++-------- .../dataframe/SeriesViewStrategy.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java diff --git a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java index 18e53d8d8ad1..9f1538ccbe04 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java +++ b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java @@ -21,6 +21,7 @@ import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.array.ArrayViewStrategy; import com.jetbrains.python.debugger.array.AsyncArrayTableModel; import com.jetbrains.python.debugger.dataframe.DataFrameViewStrategy; +import com.jetbrains.python.debugger.dataframe.SeriesViewStrategy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,7 +30,7 @@ import java.util.Set; public abstract class DataViewStrategy { private static class StrategyHolder { - private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); + private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); } public abstract AsyncArrayTableModel createTableModel(int rowCount, int columnCount, @NotNull PyDataViewerPanel panel, @NotNull PyDebugValue debugValue); @@ -57,17 +58,4 @@ public abstract class DataViewStrategy { } return null; } - - private static class SeriesViewStrategy extends DataFrameViewStrategy { - @NotNull - @Override - public String getTypeName() { - return "Series"; - } - - @Override - public boolean showColumnHeader() { - return false; - } - } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java b/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java new file mode 100644 index 000000000000..1775ef163883 --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2017 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.debugger.dataframe; + +import org.jetbrains.annotations.NotNull; + +public class SeriesViewStrategy extends DataFrameViewStrategy { + @NotNull + @Override + public String getTypeName() { + return "Series"; + } + + @Override + public boolean showColumnHeader() { + return false; + } +} From b12fa91e404c4b97e67b1992f0e1c2b1ffa5f038 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Fri, 19 May 2017 14:23:46 +0300 Subject: [PATCH 32/84] add env test for series (PY-21763) --- python/testData/debug/test_series.py | 8 ++++++++ .../env/python/PythonDataViewerTest.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 python/testData/debug/test_series.py diff --git a/python/testData/debug/test_series.py b/python/testData/debug/test_series.py new file mode 100644 index 000000000000..151c1211927d --- /dev/null +++ b/python/testData/debug/test_series.py @@ -0,0 +1,8 @@ +import pandas as pd +import numpy as np + +frame = pd.DataFrame(data=np.random.randint(0, high=10, size=(4, 2)), columns=['a', 'b'], index=pd.MultiIndex([['s', 'd'], [2, 3]], [[0, 0, 1, 1], [0, 1, 0, 1]])) + +series = frame.a + +print(series) # line 7 diff --git a/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java index 7686c210bf46..81ffcd89ccad 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java @@ -20,6 +20,7 @@ import com.intellij.util.Consumer; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerTestUtil; import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.Staging; import com.jetbrains.env.python.debug.PyDebuggerTask; import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; @@ -75,6 +76,20 @@ public class PythonDataViewerTest extends PyEnvTestCase { }); } + @Test + @Staging + public void testSeries() throws Exception { + runPythonTest(new PyDataFrameDebuggerTask(getRelativeTestDataPath(), "test_series.py", ImmutableSet.of(7)) { + @Override + public void testing() throws Exception { + doTest("series", 4, 1, arrayChunk -> { + List labels = arrayChunk.getRowLabels(); + assertSameElements(labels, "s/2", "s/3", "d/2", "d/3"); + }); + } + }); + } + private static class PyDataFrameDebuggerTask extends PyDebuggerTask { private Set myLines; From d2848fbcfcbd9748305ed61387525bd3760fd412 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Tue, 30 May 2017 12:37:40 +0300 Subject: [PATCH 33/84] use immutable collections (PY-21763) --- .../com/jetbrains/python/debugger/PyDebugValue.java | 6 ++---- .../python/debugger/containerview/DataViewStrategy.java | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index f6aaed10719b..546da2bd248d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -1,11 +1,10 @@ package com.jetbrains.python.debugger; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Pair; -import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.frame.*; import com.jetbrains.python.debugger.pydev.PyVariableLocator; import org.jetbrains.annotations.NotNull; @@ -22,8 +21,7 @@ public class PyDebugValue extends XNamedValue { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue"); private static final String DATA_FRAME = "DataFrame"; private static final String SERIES = "Series"; - private static final Map EVALUATOR_POSTFIXES = - ContainerUtil.newHashMap(Pair.create("ndarray", "Array"), Pair.create(DATA_FRAME, DATA_FRAME), Pair.create(SERIES, SERIES)); + private static final Map EVALUATOR_POSTFIXES = ImmutableMap.of("ndarray", "Array", DATA_FRAME, DATA_FRAME, SERIES, SERIES); public static final int MAX_VALUE = 256; public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict"; diff --git a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java index 9f1538ccbe04..7f40c9caca3c 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java +++ b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java @@ -15,7 +15,7 @@ */ package com.jetbrains.python.debugger.containerview; -import com.intellij.util.containers.ContainerUtil; +import com.google.common.collect.ImmutableSet; import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.array.ArrayViewStrategy; @@ -30,7 +30,7 @@ import java.util.Set; public abstract class DataViewStrategy { private static class StrategyHolder { - private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); + private static final Set STRATEGIES = ImmutableSet.of(new ArrayViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); } public abstract AsyncArrayTableModel createTableModel(int rowCount, int columnCount, @NotNull PyDataViewerPanel panel, @NotNull PyDebugValue debugValue); From 32708887c48861b65873c8fd60e47f8bb3fd6417 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Tue, 30 May 2017 13:31:32 +0300 Subject: [PATCH 34/84] inline function (PY-21763) --- python/helpers/pydev/_pydevd_bundle/pydevd_vars.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index ed693bc35674..2e896252409b 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -414,19 +414,13 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SEN MAXIMUM_ARRAY_SIZE = 100 -def array_to_xml_converter(array, name, roffset, coffset, rows, cols, format): - array, metaxml, r, c, f = array_to_meta_xml(array, name, format) - xml = metaxml +def array_to_xml(array, name, roffset, coffset, rows, cols, format): + array, xml, r, c, f = array_to_meta_xml(array, name, format) format = '%' + f if rows == -1 and cols == -1: rows = r cols = c - xml += array_to_xml(array, roffset, coffset, rows, cols, format) - return xml - -def array_to_xml(array, roffset, coffset, rows, cols, format): - xml = "" rows = min(rows, MAXIMUM_ARRAY_SIZE) cols = min(cols, MAXIMUM_ARRAY_SIZE) @@ -616,7 +610,7 @@ def header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim): xml += "\n" return xml -TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml_converter, "DataFrame": dataframe_to_xml, "Series": dataframe_to_xml} +TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml, "DataFrame": dataframe_to_xml, "Series": dataframe_to_xml} def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): From 358409aa28370490b6381a8f9058daea34a9b6a2 Mon Sep 17 00:00:00 2001 From: Konstantin Ulitin Date: Mon, 5 Jun 2017 18:57:19 +0300 Subject: [PATCH 35/84] use base language BreadcrumbsProvider when exact is not available --- .../xml/breadcrumbs/BreadcrumbsXmlWrapper.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java index 372035d8794d..5f0d72f7af76 100644 --- a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java +++ b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java @@ -493,12 +493,16 @@ public class BreadcrumbsXmlWrapper extends JComponent implements Disposable { @Nullable private static BreadcrumbsProvider getInfoProvider(@NotNull Language language) { - for (BreadcrumbsProvider provider : BreadcrumbsProvider.EP_NAME.getExtensions()) { - for (Language supported : provider.getLanguages()) { - if (supported.isKindOf(language)) { - return provider; + BreadcrumbsProvider[] providers = BreadcrumbsProvider.EP_NAME.getExtensions(); + while (language != null) { + for (BreadcrumbsProvider provider : providers) { + for (Language supported : provider.getLanguages()) { + if (language.is(supported)) { + return provider; + } } } + language = language.getBaseLanguage(); } return null; } From fc44d1a3e4bb17dd0c48233013c42db6c1e8007a Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 19:24:36 +0300 Subject: [PATCH 36/84] convert to threadlocal: fix variable access inside thread local initializer (IDEA-173748) --- .../VariableAccessFromInnerClassFix.java | 50 ++++++++----- .../rules/ThreadLocalConversionRule.java | 35 ++++++++- .../rules/TypeConversionRuleUtil.java | 74 +++++++++++++++++++ .../ConvertToThreadLocalIntentionTest.java | 5 -- ...rFieldAssignmentFromNonFinalParameter.java | 17 +++++ ...eFieldAssignmentFromNonFinalParameter.java | 11 +++ 6 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java create mode 100644 java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java create mode 100644 java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java index 532199968579..06b50ce80d31 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java @@ -114,7 +114,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { makeArray(); break; case COPY_TO_FINAL: - copyToFinal(); + copyToFinal(myVariable, myContext); break; } } @@ -129,7 +129,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { private void makeArray() { for (PsiVariable var : getVariablesToFix()) { - makeArray(var); + makeArray(var, myContext); } } @@ -165,11 +165,11 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { } } - private void makeArray(PsiVariable variable) throws IncorrectOperationException { + private static void makeArray(PsiVariable variable, PsiElement context) throws IncorrectOperationException { variable.normalizeDeclaration(); PsiType type = variable.getType(); - PsiElementFactory factory = JavaPsiFacade.getInstance(myContext.getProject()).getElementFactory(); + PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); PsiType newType = type.createArrayType(); PsiDeclarationStatement variableDeclarationStatement; @@ -199,22 +199,22 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { variable.replace(newVariable); } - private void copyToFinal() throws IncorrectOperationException { - PsiManager psiManager = myContext.getManager(); + private static void copyToFinal(PsiVariable variable, PsiElement context) throws IncorrectOperationException { + PsiManager psiManager = context.getManager(); final Project project = psiManager.getProject(); PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - PsiExpression initializer = factory.createExpressionFromText(myVariable.getName(), myContext); - String newName = suggestNewName(project, myVariable); - PsiType type = myVariable.getType(); + PsiExpression initializer = factory.createExpressionFromText(variable.getName(), context); + String newName = suggestNewName(project, variable); + PsiType type = variable.getType(); PsiDeclarationStatement copyDecl = factory.createVariableDeclarationStatement(newName, type, initializer); PsiVariable newVariable = (PsiVariable)copyDecl.getDeclaredElements()[0]; final boolean mustBeFinal = - !PsiUtil.isLanguageLevel8OrHigher(myContext) || CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS; + !PsiUtil.isLanguageLevel8OrHigher(context) || CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS; PsiUtil.setModifierProperty(newVariable, PsiModifier.FINAL, mustBeFinal); - PsiElement statement = getStatementToInsertBefore(); + PsiElement statement = getStatementToInsertBefore(variable, context); if (statement == null) return; - PsiExpression newExpression = factory.createExpressionFromText(newName, myVariable); - replaceReferences(myContext, myVariable, newExpression); + PsiExpression newExpression = factory.createExpressionFromText(newName, variable); + replaceReferences(context, variable, newExpression); if (RefactoringUtil.isLoopOrIf(statement.getParent())) { RefactoringUtil.putStatementInLoopBody(copyDecl, statement.getParent(), statement); } else { @@ -222,12 +222,12 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { } } - private PsiElement getStatementToInsertBefore() { - PsiElement declarationScope = myVariable instanceof PsiParameter - ? ((PsiParameter)myVariable).getDeclarationScope() : PsiUtil.getVariableCodeBlock(myVariable, null); + private static PsiElement getStatementToInsertBefore(PsiVariable variable, PsiElement context) { + PsiElement declarationScope = variable instanceof PsiParameter + ? ((PsiParameter)variable).getDeclarationScope() : PsiUtil.getVariableCodeBlock(variable, null); if (declarationScope == null) return null; - PsiElement statement = myContext; + PsiElement statement = context; nextInnerClass: do { statement = RefactoringUtil.getParentStatement(statement, false); @@ -361,4 +361,20 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { public boolean startInWriteAction() { return false; } + + public static void fixAccess(@NotNull PsiVariable variable, @NotNull PsiElement context) { + int type = getQuickFixType(variable); + if (type == -1) return; + switch (type) { + case MAKE_FINAL: + PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true); + break; + case MAKE_ARRAY: + makeArray(variable, context); + break; + case COPY_TO_FINAL: + copyToFinal(variable, context); + break; + } + } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java index e403dfb45e99..db91ae18d825 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java @@ -1,5 +1,7 @@ package com.intellij.refactoring.typeMigration.rules; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassFix; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; @@ -8,10 +10,14 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + public class ThreadLocalConversionRule extends TypeConversionRule { private static final Logger LOG = Logger.getInstance(ThreadLocalConversionRule.class); @@ -150,7 +156,9 @@ public class ThreadLocalConversionRule extends TypeConversionRule { public static TypeConversionDescriptor wrapWithNewExpression(PsiType to, PsiType from, PsiExpression initializer) { final String boxedTypeName = from instanceof PsiPrimitiveType ? ((PsiPrimitiveType)from).getBoxedTypeName() : from.getCanonicalText(); - return new TypeConversionDescriptor("$qualifier$", "new " + + List toMakeFinal = TypeConversionRuleUtil.getVariablesToMakeFinal(initializer); + if (toMakeFinal == null) return null; + return new WrappingWithInnerClassOrLambdaDescriptor("$qualifier$", "new " + to.getCanonicalText() + "() {\n" + "@Override \n" + @@ -167,7 +175,8 @@ public class ThreadLocalConversionRule extends TypeConversionRule { ")" : initializer.getText())) + ";\n" + "}\n" + - "}", initializer); + "}", initializer, + toMakeFinal); } private static String toPrimitive(String replaceByArg, PsiType from, PsiElement context) { @@ -221,5 +230,27 @@ public class ThreadLocalConversionRule extends TypeConversionRule { return toBoxed(arg, from, context); } + private static class WrappingWithInnerClassOrLambdaDescriptor extends TypeConversionDescriptor { + private final List myVariablesToMakeFinal; + private WrappingWithInnerClassOrLambdaDescriptor(@NonNls final String stringToReplace, + @NonNls final String replaceByString, + final PsiExpression expression, + @NotNull List toMakeFinal) { + super(stringToReplace, replaceByString, expression); + myVariablesToMakeFinal = toMakeFinal; + } + + @Override + public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) { + PsiExpression replaced = super.replace(expression, evaluator); + boolean atLeastJava8 = PsiUtil.isLanguageLevel8OrHigher(replaced); + for (PsiVariable var : myVariablesToMakeFinal) { + if (!atLeastJava8 || !HighlightControlFlowUtil.isEffectivelyFinal(var, replaced, null)) { + VariableAccessFromInnerClassFix.fixAccess(var, replaced); + } + } + return replaced; + } + } } \ No newline at end of file diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java new file mode 100644 index 000000000000..6b4af6fdefa7 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2017 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.intellij.refactoring.typeMigration.rules; + +import com.intellij.psi.*; +import com.intellij.psi.controlFlow.*; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +class TypeConversionRuleUtil { + static List getVariablesToMakeFinal(@NotNull PsiExpression expression) { + final ControlFlow controlFlow; + try { + controlFlow = ControlFlowFactory.getInstance(expression.getProject()).getControlFlow(expression, new MyControlFlowPolicy(expression), false, false); + } + catch (AnalysisCanceledException e) { + return null; + } + + Collection writtenVariables = ControlFlowUtil.getWrittenVariables(controlFlow, 0, controlFlow.getSize(), false); + if (!writtenVariables.isEmpty()) return null; + + return ControlFlowUtil.getUsedVariables(controlFlow, 0, controlFlow.getSize()) + .stream() + .filter(v -> !v.hasModifierProperty(PsiModifier.FINAL)) + .collect(Collectors.toList()); + } + + private static class MyControlFlowPolicy implements ControlFlowPolicy { + private final PsiElement myElement; + + public MyControlFlowPolicy(PsiElement element) {myElement = element;} + + @Override + public PsiVariable getUsedVariable(@NotNull PsiReferenceExpression refExpr) { + if (refExpr.isQualified()) return null; + + PsiElement refElement = refExpr.resolve(); + if ((refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) && + !PsiTreeUtil.isAncestor(myElement, refElement, true)) { + return (PsiVariable) refElement; + } + + return null; + } + + @Override + public boolean isParameterAccepted(@NotNull PsiParameter psiParameter) { + return true; + } + + @Override + public boolean isLocalVariableAccepted(@NotNull PsiLocalVariable psiVariable) { + return true; + } + } +} diff --git a/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java b/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java index e45b26ba77c8..6a358bbbb2d1 100644 --- a/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java +++ b/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java @@ -8,11 +8,6 @@ import org.jetbrains.annotations.NotNull; * @author anna */ public class ConvertToThreadLocalIntentionTest extends LightQuickFixParameterizedTestCase { - @Override - protected boolean shouldBeAvailableAfterExecution() { - return true; - } - @Override protected String getBasePath() { return "/intentions/threadLocal"; diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java new file mode 100644 index 000000000000..f0531b222ae9 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java @@ -0,0 +1,17 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final ThreadLocal property; + + Main3(boolean property) {] + if (property) { + property = false; + } + boolean finalProperty = property; + this.property = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return finalProperty; + } + }; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java new file mode 100644 index 000000000000..4e1ffd387df8 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java @@ -0,0 +1,11 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final boolean property; + + Main3(boolean property) {] + if (property) { + property = false; + } + this.property = property; + } +} \ No newline at end of file From ca188f4863c129aed2b1b84393937c633f6239f3 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Mon, 5 Jun 2017 19:29:16 +0300 Subject: [PATCH 37/84] Return Git root, not Git repository folder Follow-up to e6c8b18, which works incorrectly e.g. for worktrees. The issue found in IDEA-CR-21166 --- plugins/git4idea/src/git4idea/GitUtil.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 038ac6e059b4..a6d53798ac1a 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -368,8 +368,9 @@ public class GitUtil { public static VirtualFile getGitRootOrNull(@NotNull final FilePath filePath) { File root = filePath.getIOFile(); while (root != null) { - File gitDir = findGitDir(root); - if (gitDir != null) return LocalFileSystem.getInstance().findFileByIoFile(root); + if (isGitRoot(root)) { + return LocalFileSystem.getInstance().findFileByIoFile(root); + } root = root.getParentFile(); } return null; From 970c6848cf7bb8e105e77a897e6ae8fe10eebca1 Mon Sep 17 00:00:00 2001 From: Alexander Zolotov Date: Mon, 5 Jun 2017 17:30:45 +0300 Subject: [PATCH 38/84] EA-102471 --- .../xml/breadcrumbs/BreadcrumbsInitializingActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java index 0058cbe3babe..418da0e5308f 100644 --- a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java +++ b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java @@ -41,7 +41,7 @@ import org.jetbrains.annotations.NotNull; public class BreadcrumbsInitializingActivity implements StartupActivity, DumbAware { @Override public void runActivity(@NotNull Project project) { - if (project.isDefault() || ApplicationManager.getApplication().isUnitTestMode()) { + if (project.isDefault() || ApplicationManager.getApplication().isUnitTestMode() || project.isDisposed()) { return; } From a43c830373b63e968986c2068d5018b389aa1e90 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 19:36:58 +0300 Subject: [PATCH 39/84] convert to threadlocal: convert initializer to modern ThreadLocal.withInitial if java version >= 8 --- .../rules/ThreadLocalConversionRule.java | 48 ++++++++++++------- .../intentions/threadLocal/after1.java | 7 +-- .../intentions/threadLocal/after2.java | 7 +-- .../intentions/threadLocal/after3.java | 7 +-- .../intentions/threadLocal/after4.java | 7 +-- .../intentions/threadLocal/after5.java | 7 +-- .../intentions/threadLocal/after6.java | 7 +-- .../intentions/threadLocal/after7.java | 7 +-- .../intentions/threadLocal/after8.java | 7 +-- .../threadLocal/afterArrayInitializer.java | 7 +-- ...rFieldAssignmentFromNonFinalParameter.java | 9 +--- .../threadLocal/afterFinalField.java | 7 +-- .../intentions/threadLocal/afterJava6.java | 13 +++++ .../threadLocal/afterNormalize.java | 7 +-- .../afterPrimitiveNoInitializer.java | 7 +-- .../intentions/threadLocal/afterTA1.java | 7 +-- ...eFieldAssignmentFromNonFinalParameter.java | 2 +- .../intentions/threadLocal/beforeJava6.java | 8 ++++ 18 files changed, 67 insertions(+), 104 deletions(-) create mode 100644 java/typeMigration/testData/intentions/threadLocal/afterJava6.java create mode 100644 java/typeMigration/testData/intentions/threadLocal/beforeJava6.java diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java index db91ae18d825..bf7e2cc746aa 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java @@ -158,27 +158,39 @@ public class ThreadLocalConversionRule extends TypeConversionRule { final String boxedTypeName = from instanceof PsiPrimitiveType ? ((PsiPrimitiveType)from).getBoxedTypeName() : from.getCanonicalText(); List toMakeFinal = TypeConversionRuleUtil.getVariablesToMakeFinal(initializer); if (toMakeFinal == null) return null; - return new WrappingWithInnerClassOrLambdaDescriptor("$qualifier$", "new " + - to.getCanonicalText() + - "() {\n" + - "@Override \n" + - "protected " + - boxedTypeName + - " initialValue() {\n" + - " return " + - (PsiUtil.isLanguageLevel5OrHigher(initializer) - ? initializer.getText() - : (from instanceof PsiPrimitiveType ? "new " + - ((PsiPrimitiveType)from).getBoxedTypeName() + - "(" + - initializer.getText() + - ")" : initializer.getText())) + - ";\n" + - "}\n" + - "}", initializer, + return new WrappingWithInnerClassOrLambdaDescriptor("$qualifier$", + createThreadLocalInitializerReplacement(to, from, initializer, boxedTypeName), + initializer, toMakeFinal); } + private static String createThreadLocalInitializerReplacement(PsiType to, + PsiType from, + PsiExpression initializer, + String boxedTypeName) { + if (PsiUtil.isLanguageLevel8OrHigher(initializer)) { + return "java.lang.ThreadLocal.withInitial(() -> " + initializer.getText() + ")"; + } + return "new " + + to.getCanonicalText() + + "() {\n" + + "@Override \n" + + "protected " + + boxedTypeName + + " initialValue() {\n" + + " return " + + (PsiUtil.isLanguageLevel5OrHigher(initializer) + ? initializer.getText() + : (from instanceof PsiPrimitiveType ? "new " + + ((PsiPrimitiveType)from).getBoxedTypeName() + + "(" + + initializer.getText() + + ")" : initializer.getText())) + + ";\n" + + "}\n" + + "}"; + } + private static String toPrimitive(String replaceByArg, PsiType from, PsiElement context) { return PsiUtil.isLanguageLevel5OrHigher(context) ? replaceByArg diff --git a/java/typeMigration/testData/intentions/threadLocal/after1.java b/java/typeMigration/testData/intentions/threadLocal/after1.java index b5df8fd35214..be391d4abec4 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after1.java +++ b/java/typeMigration/testData/intentions/threadLocal/after1.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> 0); void foo() { field.set(field.get() + 1); } diff --git a/java/typeMigration/testData/intentions/threadLocal/after2.java b/java/typeMigration/testData/intentions/threadLocal/after2.java index 3a546c5b0c9a..5f02c2f3925d 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after2.java +++ b/java/typeMigration/testData/intentions/threadLocal/after2.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> ""); void foo() { System.out.println(field.get()); } diff --git a/java/typeMigration/testData/intentions/threadLocal/after3.java b/java/typeMigration/testData/intentions/threadLocal/after3.java index 9fc0f825ac4b..2e2f950d899a 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after3.java +++ b/java/typeMigration/testData/intentions/threadLocal/after3.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new Integer(0)); void foo() { if (field.get() == null) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after4.java b/java/typeMigration/testData/intentions/threadLocal/after4.java index 8410a1c33c0e..4dbc67bb1b47 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after4.java +++ b/java/typeMigration/testData/intentions/threadLocal/after4.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> ""); void foo() { if (field.get().indexOf("a") == -1) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after5.java b/java/typeMigration/testData/intentions/threadLocal/after5.java index 435e1b6a50e6..31ca64fbaaef 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after5.java +++ b/java/typeMigration/testData/intentions/threadLocal/after5.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new Integer(0)); void foo(Test t) { if (t.field.get() == null) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after6.java b/java/typeMigration/testData/intentions/threadLocal/after6.java index 1879a46be16d..d153a6579061 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after6.java +++ b/java/typeMigration/testData/intentions/threadLocal/after6.java @@ -2,11 +2,6 @@ class Test { static final ThreadLocal field; static { - field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + field = ThreadLocal.withInitial(() -> new Integer(0)); } } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/after7.java b/java/typeMigration/testData/intentions/threadLocal/after7.java index 5398ff5e2991..14fa782d7060 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after7.java +++ b/java/typeMigration/testData/intentions/threadLocal/after7.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class X { - private final ThreadLocal bytes = new ThreadLocal() { - @Override - protected byte[] initialValue() { - return new byte[10]; - } - }; + private final ThreadLocal bytes = ThreadLocal.withInitial(() -> new byte[10]); byte foo(byte b) { bytes.get()[0] = 1; diff --git a/java/typeMigration/testData/intentions/threadLocal/after8.java b/java/typeMigration/testData/intentions/threadLocal/after8.java index d158894b6130..2d4aa4b61658 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after8.java +++ b/java/typeMigration/testData/intentions/threadLocal/after8.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class X { - final ThreadLocal i = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal i = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java b/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java index 59323f18e0e5..dde597cbc1da 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String[] initialValue() { - return new String[]{}; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new String[]{}); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java index f0531b222ae9..654691a7f2c9 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java @@ -2,16 +2,11 @@ class Main { private final ThreadLocal property; - Main3(boolean property) {] + Main3(boolean property) { if (property) { property = false; } boolean finalProperty = property; - this.property = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return finalProperty; - } - }; + this.property = ThreadLocal.withInitial(() -> finalProperty); } } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java index 9575f8c84842..da8f5235f329 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java @@ -3,11 +3,6 @@ class Foo { private final ThreadLocal property; Foo(boolean property) { - this.property = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return property; - } - }; + this.property = ThreadLocal.withInitial(() -> property); } } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterJava6.java b/java/typeMigration/testData/intentions/threadLocal/afterJava6.java new file mode 100644 index 000000000000..77bd711defff --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterJava6.java @@ -0,0 +1,13 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final ThreadLocal property; + + Main3(final boolean property) { + this.property = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return property; + } + }; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java b/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java index 1b2532d9d1de..9c7a8342ebe8 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class X { - private final ThreadLocal s = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + private final ThreadLocal s = ThreadLocal.withInitial(() -> ""); private String t; private String u; } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java b/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java index b922e385cde7..2560a2a708d6 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterTA1.java b/java/typeMigration/testData/intentions/threadLocal/afterTA1.java index 0e3687e146b6..d1da14e75739 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterTA1.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterTA1.java @@ -5,10 +5,5 @@ import java.lang.annotation.*; public @interface TA { int value(); } class Test { - final ThreadLocal<@TA(42) Integer> field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal<@TA(42) Integer> field = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java index 4e1ffd387df8..48a644f90053 100644 --- a/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java +++ b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java @@ -2,7 +2,7 @@ class Main { private final boolean property; - Main3(boolean property) {] + Main3(boolean property) { if (property) { property = false; } diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java b/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java new file mode 100644 index 000000000000..acc57b15ecde --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java @@ -0,0 +1,8 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final boolean property; + + Main3(boolean property) { + this.property = property; + } +} \ No newline at end of file From 60966c068f657936600b9aa34361ae9b8e135947 Mon Sep 17 00:00:00 2001 From: Alexander Zolotov Date: Mon, 5 Jun 2017 19:39:11 +0300 Subject: [PATCH 40/84] CSS: remove deprecated class --- .../options/editor/CssFoldingSettings.java | 62 ------------------- .../options/editor/XmlFoldingSettings.java | 8 --- 2 files changed, 70 deletions(-) delete mode 100644 xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java diff --git a/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java b/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java deleted file mode 100644 index 264eb995b2be..000000000000 --- a/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2000-2016 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.intellij.application.options.editor; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.util.xmlb.XmlSerializerUtil; -import org.jetbrains.annotations.Nullable; - -/** - * User: zolotov - * Date: 4/18/13 - * @deprecated use {@link XmlFoldingSettings} - * todo: delete after 2017.1 release - */ -@State( - name="CssFoldingSettings", - storages= { - @Storage(value = "editor.codeinsight.xml", deprecated = true)} -) -public class CssFoldingSettings implements PersistentStateComponent { - public static CssFoldingSettings getInstance() { - return ServiceManager.getService(CssFoldingSettings.class); - } - - private boolean myCollapseDataUri = true; - - public boolean isCollapseDataUri() { - return myCollapseDataUri; - } - - @SuppressWarnings("UnusedDeclaration") - public void setCollapseDataUri(boolean value) { - myCollapseDataUri = value; - } - - @Nullable - @Override - public CssFoldingSettings getState() { - return this; - } - - @Override - public void loadState(CssFoldingSettings state) { - XmlSerializerUtil.copyBean(state, this); - } -} diff --git a/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java b/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java index 54e8be90eef2..96873f18e36b 100644 --- a/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java +++ b/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java @@ -31,14 +31,6 @@ public class XmlFoldingSettings implements XmlCodeFoldingSettings, PersistentSta return ServiceManager.getService(XmlFoldingSettings.class); } - public XmlFoldingSettings() { - // todo: remove after 2017.1 release - CssFoldingSettings cssFoldingSettings = CssFoldingSettings.getInstance(); - if (cssFoldingSettings != null) { - myState.COLLAPSE_DATA_URI = cssFoldingSettings.isCollapseDataUri(); - } - } - @Override public boolean isCollapseXmlTags() { return myState.COLLAPSE_XML_TAGS; From d115095c6a378ee44b7c9deb15c7a2d0bd6e7183 Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Tue, 30 May 2017 14:44:10 +0300 Subject: [PATCH 41/84] [branchPopup]: add toolbar with Restore Default Size action * (IDEA-168078) Option to Reset Branches widget size to auto-size; * (IDEA-168355) Remote branches status bar popup: UI has gotten worse; --- .../dvcs/ui/BranchActionGroupPopup.java | 39 ++++++++++++++++-- platform/icons/src/vcs/restoreDefaultSize.png | Bin 0 -> 143 bytes .../icons/src/vcs/restoreDefaultSize@2x.png | Bin 0 -> 216 bytes .../src/vcs/restoreDefaultSize@2x_dark.png | Bin 0 -> 217 bytes .../icons/src/vcs/restoreDefaultSize_dark.png | Bin 0 -> 143 bytes .../util/src/com/intellij/icons/AllIcons.java | 1 + 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 platform/icons/src/vcs/restoreDefaultSize.png create mode 100644 platform/icons/src/vcs/restoreDefaultSize@2x.png create mode 100644 platform/icons/src/vcs/restoreDefaultSize@2x_dark.png create mode 100644 platform/icons/src/vcs/restoreDefaultSize_dark.png diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index 17445b9d0402..b81951c11cf2 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -15,6 +15,7 @@ */ package com.intellij.dvcs.ui; +import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.*; @@ -28,10 +29,7 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.WindowStateService; import com.intellij.openapi.vcs.ui.FlatSpeedSearchPopup; -import com.intellij.ui.ErrorLabel; -import com.intellij.ui.JBColor; -import com.intellij.ui.ScrollingUtil; -import com.intellij.ui.SeparatorWithText; +import com.intellij.ui.*; import com.intellij.ui.components.panels.OpaquePanel; import com.intellij.ui.popup.KeepingPopupOpenAction; import com.intellij.ui.popup.PopupFactoryImpl; @@ -84,6 +82,39 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } } trackDimensions(dimensionKey); + createTitlePanelToolbar(dimensionKey); + } + + void createTitlePanelToolbar(@Nullable String dimensionKey) { + if (dimensionKey == null) return; + AnAction restoreDefaultSizeAction = + new DumbAwareAction("Restore Size", "Restore default size for widget", AllIcons.Vcs.RestoreDefaultSize) { + @Override + public void actionPerformed(AnActionEvent e) { + WindowStateService.getInstance(myProject).putSizeFor(myProject, dimensionKey, null); + myUserSizeChanged = false; + pack(true, true); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabledAndVisible(myUserSizeChanged || + WindowStateService.getInstance(myProject).getSizeFor(myProject, dimensionKey) != null); + } + }; + final ActionToolbar popupTitleToolbar = ActionManager.getInstance() + .createActionToolbar("BranchWidget", new DefaultActionGroup(restoreDefaultSizeAction), true); + final JComponent toolbarComponent = popupTitleToolbar.getComponent(); + popupTitleToolbar.setReservePlaceAutoPopupIcon(false); + toolbarComponent.setBorder(JBUI.Borders.emptyRight(2)); + toolbarComponent.setOpaque(false); + + getTitle().setButtonComponent(new ActiveComponent.Adapter() { + @Override + public JComponent getComponent() { + return toolbarComponent; + } + }, null); } //for child popups only diff --git a/platform/icons/src/vcs/restoreDefaultSize.png b/platform/icons/src/vcs/restoreDefaultSize.png new file mode 100644 index 0000000000000000000000000000000000000000..c3dd16b7aa697724c7c5c3edbe104baa679fc56b GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`VV*9IAr-fh6C^k;T(LFe>6kxh zQKH?hGy&f(#{Z6O6P8MzWDxl0wJFVn`JZxidkpgl;}*R_Yx%n?MWmhbxPE(0V?5Ex r@=R!xNwW98BN88`tPyeJWneIUU3m4+Gp92^8yGxY{an^LB{Ts5)1okL literal 0 HcmV?d00001 diff --git a/platform/icons/src/vcs/restoreDefaultSize@2x.png b/platform/icons/src/vcs/restoreDefaultSize@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9dbc291778bd127287fa77abb9936748959964 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ`JOJ0Ar-gYPCLkXK!L~g?s47f zw=0D$rU{p@oDbt>-8zx2>wj>TLdWv&1{06WkUS7^?d-BIn&nzH^OYI=xX*tVWD1R3b;>SnWHk`tXL_tE!nC94%#S~3%4W4K^1cw? z6vy{ON;E=M<9Vfn`NQ0q8K)Ury=@uWw+o5vNMo&6ez4Gbj>3WF|5ki2Sr$bUewnxf P=u`$zS3j3^P6F}pvu^6&a|9GM33VMk3og|gGn1!V$=>);h>J1CGOeaA&Dz)bqUsBM zM}799UamDlQ$E`)nEhd!&z7eRhnCK3I2_H%6|=c%zu<=pvVH;&4%W-E?>c@v%tf#| Q66jV2Pgg&ebxsLQ04M@er~m)} literal 0 HcmV?d00001 diff --git a/platform/icons/src/vcs/restoreDefaultSize_dark.png b/platform/icons/src/vcs/restoreDefaultSize_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ed9d8114615c3299fccd6615ab58f0474af75612 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`VV*9IAr-fh6C^k`q}(-&SFm&M zYO5 Date: Tue, 30 May 2017 18:57:39 +0300 Subject: [PATCH 42/84] [branchPopup]: track window popup size instead of its component * track size changed event and mark it as internal if it comes from 'Restore Size' action; * move track dimensions subscriptions to afterShown method, because popup window is null before show method is called; --- .../dvcs/ui/BranchActionGroupPopup.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index b81951c11cf2..c9441acd93ce 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -58,11 +58,13 @@ import static com.intellij.util.ui.UIUtil.DEFAULT_VGAP; public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private static final DataKey POPUP_MODEL = DataKey.create("VcsPopupModel"); + private Project myProject; private MyPopupListElementRenderer myListElementRenderer; private boolean myShown; - @NotNull private Dimension myPrevSize = JBUI.emptySize(); private boolean myUserSizeChanged; - private Project myProject; + private boolean myInternalSizeChanged; + @Nullable private final String myKey; + @NotNull private Dimension myPrevSize = JBUI.emptySize(); public BranchActionGroupPopup(@NotNull String title, @NotNull Project project, @@ -74,25 +76,24 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { myProject = project; DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null); installOnHoverIconsSupport(getListElementRenderer()); - if (dimensionKey != null) { - Dimension storedSize = WindowStateService.getInstance(myProject).getSizeFor(myProject, dimensionKey); + myKey = dimensionKey; + if (myKey != null) { + Dimension storedSize = WindowStateService.getInstance(myProject).getSizeFor(myProject, myKey); if (storedSize != null) { //set forced size before component is shown setSize(storedSize); } + createTitlePanelToolbar(myKey); } - trackDimensions(dimensionKey); - createTitlePanelToolbar(dimensionKey); } - void createTitlePanelToolbar(@Nullable String dimensionKey) { - if (dimensionKey == null) return; + void createTitlePanelToolbar(@NotNull String dimensionKey) { AnAction restoreDefaultSizeAction = new DumbAwareAction("Restore Size", "Restore default size for widget", AllIcons.Vcs.RestoreDefaultSize) { @Override public void actionPerformed(AnActionEvent e) { WindowStateService.getInstance(myProject).putSizeFor(myProject, dimensionKey, null); - myUserSizeChanged = false; + myInternalSizeChanged = true; pack(true, true); } @@ -120,14 +121,16 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { //for child popups only private BranchActionGroupPopup(@Nullable WizardPopup aParent, @NotNull ListPopupStep aStep, @Nullable Object parentValue) { super(aParent, aStep, DataContext.EMPTY_CONTEXT, parentValue); + // don't store children popup userSize; + myKey = null; DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null); installOnHoverIconsSupport(getListElementRenderer()); - // don't store children popup userSize; - trackDimensions(null); } private void trackDimensions(@Nullable String dimensionKey) { - getComponent().addComponentListener(new ComponentAdapter() { + Window popupWindow = getPopupWindow(); + if (popupWindow == null) return; + popupWindow.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if (myShown) { @@ -149,7 +152,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private void processOnSizeChanged() { Dimension newSize = ObjectUtils.assertNotNull(getSize()); - if (myPrevSize.height < newSize.height) { + if (!myInternalSizeChanged && myPrevSize.height < newSize.height) { List mores = getMoreActions(); for (MoreAction more : mores) { if (!getList().getScrollableTracksViewportHeight()) break; @@ -160,7 +163,9 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } } myPrevSize = newSize; - myUserSizeChanged = true; + //ugly properties to distinguish user size changed from pack method call after Restore Size action performed + myUserSizeChanged = !myInternalSizeChanged; + myInternalSizeChanged = false; } @NotNull @@ -196,6 +201,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { if (size != null) { myPrevSize = size; } + trackDimensions(myKey); } private static void createSpeedSearchActions(@NotNull ActionGroup actionGroup, From f4fec35664d097ef49f0924d44a0f6c19ce3e68a Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Wed, 31 May 2017 19:44:21 +0300 Subject: [PATCH 43/84] [branchPopup]: show git current branch with other branches * (IDEA-171718) Not possible make current (checked out) branch favorite --- .../git4idea/ui/branch/GitBranchPopup.java | 18 +++++--- .../ui/branch/GitBranchPopupActions.java | 44 ++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java index 30f02e80538d..747ead0b1614 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java @@ -122,11 +122,19 @@ class GitBranchPopup extends DvcsBranchPopup { popupGroup.addAll(createRepositoriesActions()); popupGroup.addSeparator("Common Local Branches"); - List localBranchActions = - myMultiRootBranchConfig.getLocalBranchNames().stream().map(l -> createLocalBranchActions(allRepositories, l)).filter(Objects::nonNull) - .collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(localBranchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(localBranchActions), SHOW_ALL_LOCALS_KEY, true); + List localBranchActions = myMultiRootBranchConfig.getLocalBranchNames().stream() + .map(l -> createLocalBranchActions(allRepositories, l)) + .filter(Objects::nonNull) + .sorted(FAVORITE_BRANCH_COMPARATOR) + .collect(toList()); + int topShownBranches = getNumOfTopShownBranches(localBranchActions); + String currentBranch = myMultiRootBranchConfig.getCurrentBranch(); + if (currentBranch != null) { + localBranchActions + .add(0, new GitBranchPopupActions.CurrentBranchActions(myProject, allRepositories, currentBranch, myCurrentRepository)); + topShownBranches++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, localBranchActions, topShownBranches, SHOW_ALL_LOCALS_KEY, true); popupGroup.addSeparator("Common Remote Branches"); List remoteBranchActions = map(((GitMultiRootBranchConfig)myMultiRootBranchConfig).getRemoteBranches(), diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java index 3aff81024041..f7488605b70c 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java @@ -27,6 +27,7 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.util.containers.ContainerUtil; +import git4idea.GitLocalBranch; import git4idea.branch.GitBranchUtil; import git4idea.branch.GitBrancher; import git4idea.branch.GitNewBranchOptions; @@ -74,15 +75,21 @@ class GitBranchPopupActions { } popupGroup.addSeparator("Local Branches" + repoInfo); - List localBranchActions = - myRepository.getBranches().getLocalBranches().stream() - .sorted() - .filter(branch -> !branch.equals(myRepository.getCurrentBranch())) - .map(branch -> new LocalBranchActions(myProject, repositoryList, branch.getName(), myRepository)) - .collect(toList()); + GitLocalBranch currentBranch = myRepository.getCurrentBranch(); + List localBranchActions = myRepository.getBranches().getLocalBranches().stream() + .sorted() + .filter(branch -> !branch.equals(currentBranch)) + .map(branch -> new LocalBranchActions(myProject, repositoryList, branch.getName(), myRepository)) + .sorted(FAVORITE_BRANCH_COMPARATOR) + .collect(toList()); + int topShownBranches = getNumOfTopShownBranches(localBranchActions); + if (currentBranch != null) { + localBranchActions.add(0, new CurrentBranchActions(myProject, repositoryList, currentBranch.getName(), myRepository)); + topShownBranches++; + } // if there are only a few local favorites -> show all; for remotes it's better to show only favorites; - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(localBranchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(localBranchActions), firstLevelGroup ? GitBranchPopup.SHOW_ALL_LOCALS_KEY : null, + wrapWithMoreActionIfNeeded(myProject, popupGroup, localBranchActions, + topShownBranches, firstLevelGroup ? GitBranchPopup.SHOW_ALL_LOCALS_KEY : null, firstLevelGroup); popupGroup.addSeparator("Remote Branches" + repoInfo); @@ -160,9 +167,9 @@ class GitBranchPopupActions { */ static class LocalBranchActions extends BranchActionGroup implements PopupElementWithAdditionalInfo { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; + protected final Project myProject; + protected final List myRepositories; + protected final String myBranchName; @NotNull private final GitRepository mySelectedRepository; private final GitBranchManager myGitBranchManager; @@ -310,6 +317,21 @@ class GitBranchPopupActions { } } + static class CurrentBranchActions extends LocalBranchActions { + CurrentBranchActions(@NotNull Project project, + @NotNull List repositories, + @NotNull String branchName, + @NotNull GitRepository selectedRepository) { + super(project, repositories, branchName, selectedRepository); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new LocalBranchActions.RenameBranchAction(myProject, myRepositories, myBranchName)}; + } + } + /** * Actions available for remote branches */ From 66fd243815a01fbb584a130aed1b7a6dcc1d4a93 Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 1 Jun 2017 15:09:45 +0300 Subject: [PATCH 44/84] [branchPopup]: show hg current branch/bookmark with other branches --- .../branch/DvcsMultiRootBranchConfig.java | 8 +++- .../zmlx/hg4idea/branch/HgBranchPopup.java | 23 ++++++--- .../hg4idea/branch/HgBranchPopupActions.java | 47 ++++++++++++++++--- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java index ee24209167fa..d999bf5334c8 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.function.Function; public abstract class DvcsMultiRootBranchConfig { @NotNull protected final Collection myRepositories; @@ -34,9 +35,14 @@ public abstract class DvcsMultiRootBranchConfig { @Nullable public String getCurrentBranch() { + return getCommonName(Repository::getCurrentBranchName); + } + + @Nullable + public String getCommonName(@NotNull Function nameSupplier) { String commonBranch = null; for (Repo repository : myRepositories) { - String branchName = repository.getCurrentBranchName(); + String branchName = nameSupplier.apply(repository); if (branchName == null) { return null; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java index 67979119747b..cb9ef1f6a3e8 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java @@ -26,7 +26,6 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgProjectSettings; @@ -99,16 +98,26 @@ public class HgBranchPopup extends DvcsBranchPopup { List branchActions = myMultiRootBranchConfig.getLocalBranchNames().stream() .map(b -> createLocalBranchActions(allRepositories, b, false)) - .filter(Objects::nonNull).collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(branchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(branchActions), SHOW_ALL_BRANCHES_KEY, true); + .filter(Objects::nonNull).sorted(FAVORITE_BRANCH_COMPARATOR).collect(toList()); + int topShownBranches = getNumOfTopShownBranches(branchActions); + String commonBranch = myMultiRootBranchConfig.getCommonName(HgRepository::getCurrentBranch); + if (commonBranch != null) { + branchActions.add(0, new HgBranchPopupActions.CurrentBranch(myProject, allRepositories, commonBranch)); + topShownBranches++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, branchActions, topShownBranches, SHOW_ALL_BRANCHES_KEY, true); popupGroup.addSeparator("Common Bookmarks"); List bookmarkActions = ((HgMultiRootBranchConfig)myMultiRootBranchConfig).getBookmarkNames().stream() .map(bm -> createLocalBranchActions(allRepositories, bm, true)) - .filter(Objects::nonNull).collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(bookmarkActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(bookmarkActions), SHOW_ALL_BOOKMARKS_KEY, true); + .filter(Objects::nonNull).sorted(FAVORITE_BRANCH_COMPARATOR).collect(toList()); + int topShownBookmarks = getNumOfTopShownBranches(bookmarkActions); + String commonBookmark = myMultiRootBranchConfig.getCommonName(HgRepository::getCurrentBookmark); + if (commonBookmark != null) { + bookmarkActions.add(0, new HgBranchPopupActions.CurrentActiveBookmark(myProject, allRepositories, commonBookmark)); + topShownBookmarks++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, bookmarkActions, topShownBookmarks, SHOW_ALL_BOOKMARKS_KEY, true); } @Nullable diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java index b80579503082..22413177ab72 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java @@ -87,13 +87,19 @@ public class HgBranchPopupActions { } popupGroup.addSeparator("Bookmarks" + repoInfo); + String currentBookmark = myRepository.getCurrentBookmark(); List bookmarkActions = getSortedNamesWithoutHashes(myRepository.getBookmarks()).stream() + .filter(bm -> !bm.equals(currentBookmark)) .map(bm -> new BookmarkActions(myProject, Collections.singletonList(myRepository), bm)) + .sorted(FAVORITE_BRANCH_COMPARATOR) .collect(toList()); - // if there are only a few local favorites -> show all; for remotes it's better to show only favorites; - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(bookmarkActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(bookmarkActions), firstLevelGroup ? HgBranchPopup.SHOW_ALL_BOOKMARKS_KEY : null, - firstLevelGroup); + int topShownBookmarks = getNumOfTopShownBranches(bookmarkActions); + if (currentBookmark != null) { + bookmarkActions.add(0, new CurrentActiveBookmark(myProject, Collections.singletonList(myRepository), currentBookmark)); + topShownBookmarks++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, bookmarkActions, topShownBookmarks, + firstLevelGroup ? HgBranchPopup.SHOW_ALL_BOOKMARKS_KEY : null, firstLevelGroup); //only opened branches have to be shown popupGroup.addSeparator("Branches" + repoInfo); @@ -102,10 +108,11 @@ public class HgBranchPopupActions { .sorted() .filter(b -> !b.equals(myRepository.getCurrentBranch())) .map(b -> new BranchActions(myProject, Collections.singletonList(myRepository), b)) + .sorted(FAVORITE_BRANCH_COMPARATOR) .collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(branchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(branchActions), firstLevelGroup ? HgBranchPopup.SHOW_ALL_BRANCHES_KEY : null, - firstLevelGroup); + branchActions.add(0, new CurrentBranch(myProject, Collections.singletonList(myRepository), myRepository.getCurrentBranch())); + wrapWithMoreActionIfNeeded(myProject, popupGroup, branchActions, getNumOfTopShownBranches(branchActions) + 1, + firstLevelGroup ? HgBranchPopup.SHOW_ALL_BRANCHES_KEY : null, firstLevelGroup); return popupGroup; } @@ -279,6 +286,19 @@ public class HgBranchPopupActions { super(project, repositories, branchName, HgBranchType.BRANCH); } } + + public static class CurrentBranch extends BranchActions{ + + public CurrentBranch(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { + super(project, repositories, branchName); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return AnAction.EMPTY_ARRAY; + } + } /** * Actions available for bookmarks. @@ -314,4 +334,17 @@ public class HgBranchPopupActions { } } } + + public static class CurrentActiveBookmark extends BookmarkActions{ + + public CurrentActiveBookmark(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { + super(project, repositories, branchName); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new BookmarkActions.DeleteBookmarkAction(myProject, myRepositories, myBranchName)}; + } + } } From f36c6cfcb6a92637c13c47442b23304fd0083bcd Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 1 Jun 2017 15:29:40 +0300 Subject: [PATCH 45/84] [branchPopup]: show info label for current branch/bookmark --- .../dvcs/ui/BranchActionGroupPopup.java | 32 ++++++++++++++----- .../ui/PopupElementWithAdditionalInfo.java | 5 ++- .../ui/branch/GitBranchPopupActions.java | 6 ++++ .../hg4idea/branch/HgBranchPopupActions.java | 24 +++++++++----- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index c9441acd93ce..1ce059697920 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -38,6 +38,7 @@ import com.intellij.ui.popup.list.IconListPopupRenderer; import com.intellij.ui.popup.list.ListPopupImpl; import com.intellij.ui.popup.list.ListPopupModel; import com.intellij.ui.popup.list.PopupListElementRenderer; +import com.intellij.util.FontUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; @@ -327,6 +328,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private class MyPopupListElementRenderer extends PopupListElementRenderer implements IconListPopupRenderer { + private ErrorLabel myPrefixLabel; private ErrorLabel myInfoLabel; private IconComponent myIconLabel; @@ -365,26 +367,36 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } myIconLabel.setIcon(myDescriptor.getIconFor(value)); PopupElementWithAdditionalInfo additionalInfoAction = getSpecificAction(value, PopupElementWithAdditionalInfo.class); - String infoText = additionalInfoAction != null ? additionalInfoAction.getInfoText() : null; + updateInfoComponent(myPrefixLabel, additionalInfoAction != null ? additionalInfoAction.getPrefixInfo() : null, isSelected); + updateInfoComponent(myInfoLabel, additionalInfoAction != null ? additionalInfoAction.getInfoText() : null, isSelected); + } + + private void updateInfoComponent(@NotNull ErrorLabel infoLabel, @Nullable String infoText, boolean isSelected) { if (infoText != null) { - myInfoLabel.setVisible(true); - myInfoLabel.setText(infoText); + infoLabel.setVisible(true); + infoLabel.setText(infoText); if (isSelected) { - setSelected(myInfoLabel); + setSelected(infoLabel); } else { - myInfoLabel.setBackground(getBackground()); - myInfoLabel.setForeground(JBColor.GRAY); // different foreground than for other elements + infoLabel.setBackground(getBackground()); + infoLabel.setForeground(JBColor.GRAY); // different foreground than for other elements } } else { - myInfoLabel.setVisible(false); + infoLabel.setVisible(false); } } @Override protected JComponent createItemComponent() { + myPrefixLabel = new ErrorLabel(); + myPrefixLabel.setOpaque(true); + myPrefixLabel.setBorder(JBUI.Borders.empty(1, 1, 1, DEFAULT_HGAP)); + Font minusOneFont = FontUtil.minusOne(myPrefixLabel.getFont()); + myPrefixLabel.setFont(minusOneFont); + myTextLabel = new ErrorLabel(); myTextLabel.setOpaque(true); myTextLabel.setBorder(JBUI.Borders.empty(1)); @@ -392,15 +404,19 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { myInfoLabel = new ErrorLabel(); myInfoLabel.setOpaque(true); myInfoLabel.setBorder(JBUI.Borders.empty(1, DEFAULT_HGAP, 1, 1)); + myInfoLabel.setFont(minusOneFont); JPanel compoundPanel = new OpaquePanel(new BorderLayout(), JBColor.WHITE); myIconLabel = new IconComponent(); myInfoLabel.setHorizontalAlignment(SwingConstants.RIGHT); + JPanel compoundTextPanel = new OpaquePanel(new BorderLayout(), compoundPanel.getBackground()); JPanel textPanel = new OpaquePanel(new BorderLayout(), compoundPanel.getBackground()); compoundPanel.add(myIconLabel, BorderLayout.WEST); textPanel.add(myTextLabel, BorderLayout.WEST); textPanel.add(myInfoLabel, BorderLayout.CENTER); - compoundPanel.add(textPanel, BorderLayout.CENTER); + compoundTextPanel.add(myPrefixLabel, BorderLayout.WEST); + compoundTextPanel.add(textPanel, BorderLayout.CENTER); + compoundPanel.add(compoundTextPanel, BorderLayout.CENTER); return layoutComponent(compoundPanel); } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java index d59929095a9f..b7508b845dfb 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java @@ -19,5 +19,8 @@ import org.jetbrains.annotations.Nullable; public interface PopupElementWithAdditionalInfo { @Nullable - String getInfoText(); + default String getInfoText() {return null;} + + @Nullable + default String getPrefixInfo() {return null;} } diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java index f7488605b70c..8012860e3ac3 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java @@ -330,6 +330,12 @@ class GitBranchPopupActions { public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[]{new LocalBranchActions.RenameBranchAction(myProject, myRepositories, myBranchName)}; } + + @Nullable + @Override + public String getPrefixInfo() { + return "current"; + } } /** diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java index 22413177ab72..1adb7c3478e3 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java @@ -18,6 +18,7 @@ package org.zmlx.hg4idea.branch; import com.intellij.dvcs.DvcsUtil; import com.intellij.dvcs.repo.Repository; import com.intellij.dvcs.ui.NewBranchAction; +import com.intellij.dvcs.ui.PopupElementWithAdditionalInfo; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; @@ -34,7 +35,6 @@ import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog; import com.intellij.util.ArrayUtil; -import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.Hash; import com.intellij.vcs.log.impl.HashImpl; @@ -287,8 +287,7 @@ public class HgBranchPopupActions { } } - public static class CurrentBranch extends BranchActions{ - + public static class CurrentBranch extends BranchActions implements PopupElementWithAdditionalInfo { public CurrentBranch(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { super(project, repositories, branchName); } @@ -298,6 +297,12 @@ public class HgBranchPopupActions { public AnAction[] getChildren(@Nullable AnActionEvent e) { return AnAction.EMPTY_ARRAY; } + + @Nullable + @Override + public String getPrefixInfo() { + return "current"; + } } /** @@ -307,9 +312,6 @@ public class HgBranchPopupActions { BookmarkActions(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { super(project, repositories, branchName, HgBranchType.BOOKMARK); - if (myRepositories.size() == 1 && branchName.equals(myRepositories.get(0).getCurrentBookmark())) { - getTemplatePresentation().setIcon(PlatformIcons.CHECK_ICON); - } } @NotNull @@ -335,8 +337,8 @@ public class HgBranchPopupActions { } } - public static class CurrentActiveBookmark extends BookmarkActions{ - + public static class CurrentActiveBookmark extends BookmarkActions implements PopupElementWithAdditionalInfo { + public CurrentActiveBookmark(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { super(project, repositories, branchName); } @@ -346,5 +348,11 @@ public class HgBranchPopupActions { public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[]{new BookmarkActions.DeleteBookmarkAction(myProject, myRepositories, myBranchName)}; } + + @Nullable + @Override + public String getPrefixInfo() { + return "active"; + } } } From a2dd6063d452a80e8a2fdf72751dc72e7fca6be0 Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 1 Jun 2017 15:45:32 +0300 Subject: [PATCH 46/84] [branchPopup]: remove unnecessary current branch captions and rootInfo --- .../intellij/dvcs/branch/DvcsBranchPopup.java | 13 +++++------- .../dvcs/ui/BranchActionGroupPopup.java | 12 +---------- .../src/com/intellij/dvcs/ui/RootAction.java | 11 +++------- .../git4idea/ui/branch/GitBranchPopup.java | 21 +------------------ .../zmlx/hg4idea/branch/HgBranchPopup.java | 13 +++--------- 5 files changed, 13 insertions(+), 57 deletions(-) diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java index 1f3a87c7917f..6253c4b9be90 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java @@ -34,7 +34,6 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.util.List; @@ -65,7 +64,6 @@ public abstract class DvcsBranchPopup { ? " in " + DvcsUtil.getShortRepositoryName(currentRepository) : ""; myPopup = new BranchActionGroupPopup(title + myRepoTitleInfo, myProject, preselectActionCondition, createActions(), dimensionKey); initBranchSyncPolicyIfNotInitialized(); - setCurrentBranchInfo(); warnThatBranchesDivergedIfNeeded(); } @@ -86,11 +84,6 @@ public abstract class DvcsBranchPopup { } } - protected void setCurrentBranchInfo() { - String branchText = "Current branch : "; - myPopup.setAdText(branchText + myCurrentRepository.getCurrentBranchName(), SwingConstants.CENTER); - } - private void notifyAboutSyncedBranches() { String description = "You have several " + myVcs.getDisplayName() + " roots in the project and they all are checked out at the same branch. " + @@ -144,11 +137,15 @@ public abstract class DvcsBranchPopup { } private void warnThatBranchesDivergedIfNeeded() { - if (myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl()) { + if (isBranchesDiverged()) { myPopup.setWarning("Branches have diverged"); } } + protected boolean isBranchesDiverged() { + return myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl(); + } + @NotNull protected abstract DefaultActionGroup createRepositoriesActions(); diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index 1ce059697920..97bfdf5d0692 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -284,12 +284,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { @Override protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) { - WizardPopup popup = createListPopupStep(parent, step, parentValue); - RootAction rootAction = getRootAction(parentValue); - if (rootAction != null) { - popup.setAdText((rootAction).getCaption()); - } - return popup; + return createListPopupStep(parent, step, parentValue); } private WizardPopup createListPopupStep(WizardPopup parent, PopupStep step, Object parentValue) { @@ -299,11 +294,6 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { return super.createPopup(parent, step, parentValue); } - @Nullable - private static RootAction getRootAction(Object value) { - return getSpecificAction(value, RootAction.class); - } - private static T getSpecificAction(Object value, @NotNull Class clazz) { if (value instanceof PopupFactoryImpl.ActionItem) { AnAction action = ((PopupFactoryImpl.ActionItem)value).getAction(); diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java index 7e9f5a22778f..e407d1a5704e 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java @@ -31,9 +31,9 @@ public class RootAction extends ActionGroup implements Pop @NotNull protected final T myRepository; @NotNull private final ActionGroup myGroup; - @NotNull private final String myBranchText; + @Nullable private final String myBranchText; - public RootAction(@NotNull T repository, @NotNull ActionGroup actionsGroup, @NotNull String branchText) { + public RootAction(@NotNull T repository, @NotNull ActionGroup actionsGroup, @Nullable String branchText) { super("", true); myRepository = repository; myGroup = actionsGroup; @@ -41,18 +41,13 @@ public class RootAction extends ActionGroup implements Pop getTemplatePresentation().setText(DvcsUtil.getShortRepositoryName(repository), false); } - @NotNull - public String getCaption() { - return "Current branch in " + DvcsUtil.getShortRepositoryName(myRepository) + ": " + getInfoText(); - } - @NotNull @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return myGroup.getChildren(e); } - @NotNull + @Nullable @Override public String getInfoText() { return myBranchText; diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java index 747ead0b1614..21fd15497557 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java @@ -34,7 +34,6 @@ import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import java.util.List; import java.util.Objects; @@ -94,24 +93,6 @@ class GitBranchPopup extends DvcsBranchPopup { preselectActionCondition, DIMENSION_SERVICE_KEY); } - @Override - protected void setCurrentBranchInfo() { - String currentBranchText = "Current branch"; - if (myRepositoryManager.moreThanOneRoot()) { - if (myMultiRootBranchConfig.diverged()) { - currentBranchText += " in " + DvcsUtil.getShortRepositoryName(myCurrentRepository) + ": " + - GitBranchUtil.getDisplayableBranchText(myCurrentRepository); - } - else { - currentBranchText += ": " + myMultiRootBranchConfig.getCurrentBranch(); - } - } - else { - currentBranchText += ": " + GitBranchUtil.getDisplayableBranchText(myCurrentRepository); - } - myPopup.setAdText(currentBranchText, SwingConstants.CENTER); - } - @Override protected void fillWithCommonRepositoryActions(@NotNull DefaultActionGroup popupGroup, @NotNull AbstractRepositoryManager repositoryManager) { @@ -162,7 +143,7 @@ class GitBranchPopup extends DvcsBranchPopup { popupGroup.addSeparator("Repositories"); List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream() .map(repo -> new RootAction<>(repo, new GitBranchPopupActions(repo.getProject(), repo).createActions(), - GitBranchUtil.getDisplayableBranchText(repo))).collect(toList()); + isBranchesDiverged() ? GitBranchUtil.getDisplayableBranchText(repo) : null)).collect(toList()); wrapWithMoreActionIfNeeded(myProject, popupGroup, rootActions, rootActions.size() > MAX_NUM ? DEFAULT_NUM : MAX_NUM, SHOW_ALL_REPOSITORIES); return popupGroup; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java index cb9ef1f6a3e8..482cf5c2d1f6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java @@ -33,7 +33,6 @@ import org.zmlx.hg4idea.repo.HgRepository; import org.zmlx.hg4idea.repo.HgRepositoryManager; import org.zmlx.hg4idea.util.HgUtil; -import javax.swing.*; import java.util.List; import java.util.Objects; @@ -78,12 +77,6 @@ public class HgBranchPopup extends DvcsBranchPopup { super(currentRepository, repositoryManager, hgMultiRootBranchConfig, vcsSettings, preselectActionCondition, DIMENSION_SERVICE_KEY); } - protected void setCurrentBranchInfo() { - String branchText = "Current branch : "; - //always display heavy branch name for additional info // - myPopup.setAdText(branchText + myCurrentRepository.getCurrentBranch(), SwingConstants.CENTER); - } - @Override protected void fillWithCommonRepositoryActions(@NotNull DefaultActionGroup popupGroup, @NotNull AbstractRepositoryManager repositoryManager) { @@ -133,9 +126,9 @@ public class HgBranchPopup extends DvcsBranchPopup { protected DefaultActionGroup createRepositoriesActions() { DefaultActionGroup popupGroup = new DefaultActionGroup(null, false); popupGroup.addSeparator("Repositories"); - List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream() - .map(repo -> new RootAction<>(repo, new HgBranchPopupActions(repo.getProject(), repo).createActions(), - HgUtil.getDisplayableBranchOrBookmarkText(repo))).collect(toList()); + List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream().map( + repo -> new RootAction<>(repo, new HgBranchPopupActions(repo.getProject(), repo).createActions(), + isBranchesDiverged() ? HgUtil.getDisplayableBranchOrBookmarkText(repo) : null)).collect(toList()); wrapWithMoreActionIfNeeded(myProject, popupGroup, rootActions, rootActions.size() > MAX_NUM ? DEFAULT_NUM : MAX_NUM, SHOW_ALL_REPOSITORIES); return popupGroup; From 146e55c5ca1cf4bb635a158ec805f90e3a3db20f Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 1 Jun 2017 18:46:44 +0300 Subject: [PATCH 47/84] [branchPopup]: (IDEA-168834) Do not expand branches on resizing a bit --- .../src/com/intellij/dvcs/ui/BranchActionGroupPopup.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index 97bfdf5d0692..fdc42fc91cd3 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -64,6 +64,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private boolean myShown; private boolean myUserSizeChanged; private boolean myInternalSizeChanged; + private int myMeanRowHeight; @Nullable private final String myKey; @NotNull private Dimension myPrevSize = JBUI.emptySize(); @@ -86,6 +87,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } createTitlePanelToolbar(myKey); } + myMeanRowHeight = getList().getCellBounds(0, 0).height + UIUtil.getListCellVPadding() * 2; } void createTitlePanelToolbar(@NotNull String dimensionKey) { @@ -153,7 +155,11 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private void processOnSizeChanged() { Dimension newSize = ObjectUtils.assertNotNull(getSize()); - if (!myInternalSizeChanged && myPrevSize.height < newSize.height) { + int preferredHeight = getComponent().getPreferredSize().height; + int realHeight = getComponent().getHeight(); + boolean shouldExpand = preferredHeight + myMeanRowHeight < realHeight; + boolean sizeWasIncreased = myPrevSize.height < newSize.height; + if (!myInternalSizeChanged && sizeWasIncreased && shouldExpand) { List mores = getMoreActions(); for (MoreAction more : mores) { if (!getList().getScrollableTracksViewportHeight()) break; From db26e06168a5cbc9020df772cfc49e73b5cb7e1e Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Mon, 5 Jun 2017 16:48:44 +0300 Subject: [PATCH 48/84] [branchPopup]: (IDEA-168024) Pre-select previous branch in git popup --- .../src/git4idea/ui/branch/GitBranchPopup.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java index 21fd15497557..7cdc4ee0930e 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java @@ -23,6 +23,7 @@ import com.intellij.dvcs.ui.RootAction; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.EmptyAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.util.containers.ContainerUtil; @@ -42,6 +43,7 @@ import static com.intellij.dvcs.branch.DvcsBranchPopup.MyMoreIndex.MAX_NUM; import static com.intellij.dvcs.ui.BranchActionGroupPopup.wrapWithMoreActionIfNeeded; import static com.intellij.dvcs.ui.BranchActionUtil.FAVORITE_BRANCH_COMPARATOR; import static com.intellij.dvcs.ui.BranchActionUtil.getNumOfTopShownBranches; +import static com.intellij.util.ObjectUtils.tryCast; import static com.intellij.util.containers.ContainerUtil.map; import static java.util.stream.Collectors.toList; @@ -63,8 +65,8 @@ class GitBranchPopup extends DvcsBranchPopup { static GitBranchPopup getInstance(@NotNull final Project project, @NotNull GitRepository currentRepository) { final GitVcsSettings vcsSettings = GitVcsSettings.getInstance(project); Condition preselectActionCondition = action -> { - if (action instanceof GitBranchPopupActions.LocalBranchActions) { - GitBranchPopupActions.LocalBranchActions branchAction = (GitBranchPopupActions.LocalBranchActions)action; + GitBranchPopupActions.LocalBranchActions branchAction = getBranchAction(action); + if (branchAction != null) { String branchName = branchAction.getBranchName(); String recentBranch; @@ -85,6 +87,13 @@ class GitBranchPopup extends DvcsBranchPopup { return new GitBranchPopup(currentRepository, GitUtil.getRepositoryManager(project), vcsSettings, preselectActionCondition); } + @Nullable + private static GitBranchPopupActions.LocalBranchActions getBranchAction(@NotNull AnAction action) { + AnAction resultAction = + action instanceof EmptyAction.MyDelegatingActionGroup ? ((EmptyAction.MyDelegatingActionGroup)action).getDelegate() : action; + return tryCast(resultAction, GitBranchPopupActions.LocalBranchActions.class); + } + private GitBranchPopup(@NotNull GitRepository currentRepository, @NotNull GitRepositoryManager repositoryManager, @NotNull GitVcsSettings vcsSettings, From 4f2dafb7cef628417f93e4390b4de92a97c40901 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 19:42:53 +0300 Subject: [PATCH 49/84] create switch intention cleanup --- .../intention/impl/CreateSwitchIntention.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java index dfb800f41c1a..af7d2ac1aef2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java @@ -33,40 +33,40 @@ public class CreateSwitchIntention extends BaseElementAtCaretIntentionAction { public static final String TEXT = "Create switch statement"; @Override - public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException { - final PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); - final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); - PsiSwitchStatement switchStatement = (PsiSwitchStatement)elementFactory - .createStatementFromText(String.format("switch (%s) {}", expressionStatement.getExpression().getText()), null); + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { + PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); + PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + String valueToSwitch = expressionStatement.getExpression().getText(); + PsiSwitchStatement switchStatement = (PsiSwitchStatement)elementFactory.createStatementFromText("switch (" + valueToSwitch + ") {}", null); switchStatement = (PsiSwitchStatement)expressionStatement.replace(switchStatement); CodeStyleManager.getInstance(project).reformat(switchStatement); - final PsiJavaToken lBrace = switchStatement.getBody().getLBrace(); + PsiJavaToken lBrace = switchStatement.getBody().getLBrace(); editor.getCaretModel().moveToOffset(lBrace.getTextOffset() + lBrace.getTextLength()); } @Override - public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) { - final PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); return expressionStatement != null && isValidTypeForSwitch(expressionStatement.getExpression().getType(), expressionStatement); } - private static PsiExpressionStatement resolveExpressionStatement(final PsiElement element) { + private static PsiExpressionStatement resolveExpressionStatement(PsiElement element) { if (element instanceof PsiExpressionStatement) { return (PsiExpressionStatement)element; } else { - final PsiStatement psiStatement = PsiTreeUtil.getParentOfType(element, PsiStatement.class); + PsiStatement psiStatement = PsiTreeUtil.getParentOfType(element, PsiStatement.class); return psiStatement instanceof PsiExpressionStatement ? (PsiExpressionStatement)psiStatement : null; } } - private static boolean isValidTypeForSwitch(@Nullable final PsiType type, final PsiElement context) { + private static boolean isValidTypeForSwitch(@Nullable PsiType type, PsiElement context) { if (type == null) { return false; } if (type instanceof PsiClassType) { - final PsiClass resolvedClass = ((PsiClassType)type).resolve(); + PsiClass resolvedClass = ((PsiClassType)type).resolve(); if (resolvedClass == null) { return false; } From fbd082f0759fdfe9036fedc2644ed5d079c8b17a Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 20:06:00 +0300 Subject: [PATCH 50/84] create switch intention: disable in for update IDEA-173690 --- .../intention/impl/CreateSwitchIntention.java | 17 +++++------------ .../createSwitch/notAvailableInForUpdate.java | 5 +++++ .../codeInsight/intention/CreateSwitchTest.java | 4 ++++ 3 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java index af7d2ac1aef2..f536baaef798 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/CreateSwitchIntention.java @@ -34,7 +34,7 @@ public class CreateSwitchIntention extends BaseElementAtCaretIntentionAction { @Override public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { - PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); + PsiExpressionStatement expressionStatement = PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class, false); PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); String valueToSwitch = expressionStatement.getExpression().getText(); PsiSwitchStatement switchStatement = (PsiSwitchStatement)elementFactory.createStatementFromText("switch (" + valueToSwitch + ") {}", null); @@ -47,17 +47,10 @@ public class CreateSwitchIntention extends BaseElementAtCaretIntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { - PsiExpressionStatement expressionStatement = resolveExpressionStatement(element); - return expressionStatement != null && isValidTypeForSwitch(expressionStatement.getExpression().getType(), expressionStatement); - } - - private static PsiExpressionStatement resolveExpressionStatement(PsiElement element) { - if (element instanceof PsiExpressionStatement) { - return (PsiExpressionStatement)element; - } else { - PsiStatement psiStatement = PsiTreeUtil.getParentOfType(element, PsiStatement.class); - return psiStatement instanceof PsiExpressionStatement ? (PsiExpressionStatement)psiStatement : null; - } + PsiExpressionStatement expressionStatement = PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class, false); + return expressionStatement != null && + expressionStatement.getParent() instanceof PsiCodeBlock && + isValidTypeForSwitch(expressionStatement.getExpression().getType(), expressionStatement); } private static boolean isValidTypeForSwitch(@Nullable PsiType type, PsiElement context) { diff --git a/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java b/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java new file mode 100644 index 000000000000..f7d6fab86901 --- /dev/null +++ b/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java @@ -0,0 +1,5 @@ +class Test { + void m(int[] array) { + for (int i = 1; i < array.length; i++) { } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java index 286d1b717a53..e342f4c8692e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java @@ -55,6 +55,10 @@ public class CreateSwitchTest extends JavaCodeInsightFixtureTestCase { doTestNotAvailable(); } + public void testNotAvailableInForUpdate() { + doTestNotAvailable(); + } + private void doTestString() { final LanguageLevelProjectExtension languageLevelProjectExtension = LanguageLevelProjectExtension.getInstance(getProject()); final LanguageLevel oldLanguageLevel = languageLevelProjectExtension.getLanguageLevel(); From a5501a81c0c80baade96f0f3dfcae0ca80ccba6d Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 19:40:36 +0300 Subject: [PATCH 51/84] incompatible return types in hierarchy fixed (IDEA-173809) take parameter bounds into account --- .../daemon/impl/analysis/HighlightMethodUtil.java | 4 +++- .../IncompatibleReturnTypeBounds.java | 10 ++++++++++ .../codeInsight/daemon/GenericsHighlightingTest.java | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 5138d2cabf91..7299220a847a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -195,7 +195,9 @@ public class HighlightMethodUtil { if (returnType.equals(substitutedSuperReturnType)) return null; if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) { - if (isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) { + if (isJdk15 && LambdaUtil.performWithSubstitutedParameterBounds(methodSignature.getTypeParameters(), + methodSignature.getSubstitutor(), + () -> TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType))) { return null; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java new file mode 100644 index 000000000000..9e44426fec3b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java @@ -0,0 +1,10 @@ +class ListA { + public Ta foo() { throw new Error(); } +} + +class ListB extends ListA { + public Rb foo() { throw new Error(); } +} + +class ListC extends ListB { +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java index 2f0480f4bce6..d2276ff4bc6e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java @@ -180,6 +180,7 @@ public class GenericsHighlightingTest extends LightDaemonAnalyzerTestCase { public void testInaccessibleThroughWildcard() { doTest7Incompatibility(false);} public void testInconvertibleTypes() { doTest5(false); } public void testIncompatibleReturnType() { doTest5(false); } + public void testContinueInferenceAfterFirstRawResult() { doTest5(false); } public void testDoNotAcceptLowerBoundIfRaw() { doTest5(false); } public void testStaticOverride() { doTest5(false); } @@ -342,6 +343,7 @@ public class GenericsHighlightingTest extends LightDaemonAnalyzerTestCase { public void testIDEA71582() { doTest5(false); } public void testIDEA65377() { doTest5(false); } public void testIDEA113526() { doTest5(true); } + public void testIncompatibleReturnTypeBounds() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA116493() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA117827() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA118037() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } From c578a4ede7066716e2cc0993b5b6061e142f4bae Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 5 Jun 2017 19:44:50 +0300 Subject: [PATCH 52/84] disable assign param to field (IDEA-173747; IDEA-173689) inapplicable types or already assigned --- .../intention/impl/AssignFieldFromParameterAction.java | 2 +- .../intention/impl/FieldFromParameterUtils.java | 6 +++--- .../beforeAlreadyAssigned.java | 10 ++++++++++ .../beforeCheckAssignability.java | 10 ++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AssignFieldFromParameterAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AssignFieldFromParameterAction.java index 6cf28f28ccb1..fa578c172089 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AssignFieldFromParameterAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AssignFieldFromParameterAction.java @@ -46,7 +46,7 @@ public class AssignFieldFromParameterAction extends BaseIntentionAction { return false; } final PsiField field = findFieldToAssign(project, myParameter); - if (field == null) return false; + if (field == null || type == null || !field.getType().isAssignableFrom(type)) return false; if (!field.getLanguage().isKindOf(JavaLanguage.INSTANCE)) return false; setText(CodeInsightBundle.message("intention.assign.field.from.parameter.text", field.getName())); diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/FieldFromParameterUtils.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/FieldFromParameterUtils.java index aa644cd96a20..8168ae2fe60a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/FieldFromParameterUtils.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/FieldFromParameterUtils.java @@ -112,9 +112,9 @@ public final class FieldFromParameterUtils { for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), false)) { if (!(reference instanceof PsiReferenceExpression)) continue; final PsiReferenceExpression expression = (PsiReferenceExpression)reference; - if (!(expression.getParent() instanceof PsiAssignmentExpression)) continue; - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression.getParent(); - if (assignmentExpression.getRExpression() != expression) continue; + PsiAssignmentExpression assignmentExpression = PsiTreeUtil.getParentOfType(expression, PsiAssignmentExpression.class, true, PsiClass.class); + if (assignmentExpression == null) continue; + if (!PsiTreeUtil.isAncestor(assignmentExpression.getRExpression(), expression, false)) continue; final PsiExpression lExpression = assignmentExpression.getLExpression(); if (!(lExpression instanceof PsiReferenceExpression)) continue; final PsiElement element = ((PsiReferenceExpression)lExpression).resolve(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java new file mode 100644 index 000000000000..94684be58489 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java @@ -0,0 +1,10 @@ +// "Assign Parameter to Field 'myA'" "false" + +class Person { + int myA; + int myId; + void f(int a, String id) { + this.myA = foo(a); + } + int foo(int a) {return a;} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java new file mode 100644 index 000000000000..38db6f1a210d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java @@ -0,0 +1,10 @@ +// "Assign Parameter to Field 'myId'" "false" + +class Person { + int a; + int myId; + void f(int a, String id) { + this.a = foo(a); + } + int foo(int a) {return a;} +} \ No newline at end of file From 03a663c63e18f9e57796342c35f4d27949d4c559 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 5 Jun 2017 16:12:41 +0200 Subject: [PATCH 53/84] IDEA-165006 Support chained completion for method references in Java 8+ --- .../completion/JavaChainLookupElement.java | 17 ++++++----- .../completion/JavaNoVariantsDelegator.java | 9 +++--- ...erenceExpressionCompletionContributor.java | 11 ++++++-- .../normal/ChainedMethodReference.java | 5 ++++ .../normal/ChainedMethodReference_after.java | 5 ++++ .../completion/Normal8CompletionTest.groovy | 28 ++++++++++++------- 6 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java index 7f3f8a33b0af..40a9d3b70af7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java @@ -19,7 +19,6 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementDecorator; import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.codeInsight.lookup.TypedLookupItem; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.ClassConditionKey; import com.intellij.openapi.util.Key; @@ -39,19 +38,23 @@ import java.util.Set; */ public class JavaChainLookupElement extends LookupElementDecorator implements TypedLookupItem { public static final Key CHAIN_QUALIFIER = Key.create("CHAIN_QUALIFIER"); - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaChainLookupElement"); public static final ClassConditionKey CLASS_CONDITION_KEY = ClassConditionKey.create(JavaChainLookupElement.class); private final LookupElement myQualifier; + private final String mySeparator; public JavaChainLookupElement(LookupElement qualifier, LookupElement main) { + this(qualifier, main, "."); + } + public JavaChainLookupElement(LookupElement qualifier, LookupElement main, String separator) { super(main); myQualifier = qualifier; + mySeparator = separator; } @NotNull @Override public String getLookupString() { - return maybeAddParentheses(myQualifier.getLookupString()) + "." + getDelegate().getLookupString(); + return maybeAddParentheses(myQualifier.getLookupString()) + mySeparator + getDelegate().getLookupString(); } public LookupElement getQualifier() { @@ -70,7 +73,7 @@ public class JavaChainLookupElement extends LookupElementDecorator + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java new file mode 100644 index 000000000000..eb1f75bd4b66 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java @@ -0,0 +1,5 @@ +class A { + { + Runnable r = System::setOut; + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy index 30a1fbd37628..ae0af6127049 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy @@ -212,37 +212,37 @@ class Test88 { void testCollectorsToList() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('toList') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testStaticallyImportedCollectorsToList() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('collect(toList())') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testAllCollectors() { configureByTestName() myFixture.assertPreferredCompletionItems 0, 'collect', 'collect', 'collect(Collectors.toCollection())', 'collect(Collectors.toList())', 'collect(Collectors.toSet())' selectItem(myItems.find { it.lookupString.contains('toCollection') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testCollectorsToSet() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('toSet') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testNoExplicitTypeArgsInTernary() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('empty') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testCallBeforeLambda() { configureByTestName() - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testLambdaInAmbiguousCall() { @@ -264,13 +264,13 @@ class Test88 { void testNoSemicolonAfterVoidMethodInLambda() { configureByTestName() myFixture.type('l\t') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testFinishMethodReferenceWithColon() { configureByTestName() myFixture.type(':') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testPreferLocalsOverMethodRefs() { @@ -286,14 +286,22 @@ class Test88 { "}") configureByTestName() myFixture.type('\n') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testOverrideMethodAsDefault() { configureByTestName() assert LookupElementPresentation.renderElement(myFixture.lookupElements[0]).itemText == 'default void run' myFixture.type('\t') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } + void testChainedMethodReference() { + configureByTestName() + checkResultByFileName() + } + + private checkResultByFileName() { + checkResultByFile(getTestName(false) + "_after.java") + } } \ No newline at end of file From b61faedfd66cc41134d6bd922be0e56994840385 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 5 Jun 2017 19:16:16 +0200 Subject: [PATCH 54/84] support @TypeQualifierNickname (IDEA-173544) --- .../NullableNotNullManagerImpl.java | 77 ++++++++++++++++++- .../codeInsight/NullableNotNullManager.java | 27 +++++-- .../fixture/TypeQualifierNickname.java | 15 ++++ .../DataFlowInspectionTest.java | 30 +++++++- 4 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index db8722c06f12..6332fd8ed0af 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -16,22 +16,35 @@ package com.intellij.codeInsight; import com.intellij.codeInspection.dataFlow.HardcodedContracts; +import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.DefaultJDOMExternalizer; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; -import com.intellij.psi.PsiElement; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.StreamEx; import org.jdom.Element; +import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.serialization.java.compiler.JpsJavaCompilerNotNullableSerializer; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @State(name = "NullableNotNullManager") public class NullableNotNullManagerImpl extends NullableNotNullManager implements PersistentStateComponent { - public NullableNotNullManagerImpl() { + public static final String TYPE_QUALIFIER_NICKNAME = "javax.annotation.meta.TypeQualifierNickname"; + + public NullableNotNullManagerImpl(Project project) { + super(project); myNotNulls.addAll(getPredefinedNotNulls()); } @@ -80,4 +93,64 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement LOG.error(e); } } + + private List getAllNullabilityNickNames() { + if (!getNotNulls().contains(JAVAX_ANNOTATION_NONNULL)) { + return Collections.emptyList(); + } + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> { + List result = new ArrayList<>(); + GlobalSearchScope scope = GlobalSearchScope.allScope(myProject); + for (PsiClass tqNick : JavaPsiFacade.getInstance(myProject).findClasses(TYPE_QUALIFIER_NICKNAME, scope)) { + result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), candidate -> { + String qname = candidate.getQualifiedName(); + if (qname == null || qname.startsWith("javax.annotation.")) return false; + return getNickNamedNullability(candidate) != Nullness.UNKNOWN; + })); + } + return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT); + }); + } + + private static Nullness getNickNamedNullability(@NotNull PsiClass psiClass) { + if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullness.UNKNOWN; + + PsiAnnotation nonNull = AnnotationUtil.findAnnotation(psiClass, JAVAX_ANNOTATION_NONNULL); + return nonNull != null ? extractNullityFromWhenValue(nonNull) : Nullness.UNKNOWN; + } + + @NotNull + private static Nullness extractNullityFromWhenValue(PsiAnnotation nonNull) { + PsiAnnotationMemberValue when = nonNull.findAttributeValue("when"); + if (when instanceof PsiReferenceExpression) { + String refName = ((PsiReferenceExpression)when).getReferenceName(); + if ("ALWAYS".equals(refName)) { + return Nullness.NOT_NULL; + } + if ("MAYBE".equals(refName) || "NEVER".equals(refName)) { + return Nullness.NULLABLE; + } + } + return Nullness.UNKNOWN; + } + + private List filterNickNames(Nullness nullness) { + return StreamEx.of(getAllNullabilityNickNames()).filter(c -> getNickNamedNullability(c) == nullness).map(PsiClass::getQualifiedName).toList(); + } + + @NotNull + @Override + protected List getNullablesWithNickNames() { + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> + CachedValueProvider.Result.create(ContainerUtil.concat(getNullables(), filterNickNames(Nullness.NULLABLE)), + PsiModificationTracker.MODIFICATION_COUNT)); + } + + @NotNull + @Override + protected List getNotNullsWithNickNames() { + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> + CachedValueProvider.Result.create(ContainerUtil.concat(getNotNulls(), filterNickNames(Nullness.NOT_NULL)), + PsiModificationTracker.MODIFICATION_COUNT)); + } } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index b0ee0debbe61..a1ff985f7a1e 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -34,6 +34,7 @@ import java.util.*; */ public abstract class NullableNotNullManager { protected static final Logger LOG = Logger.getInstance(NullableNotNullManager.class); + protected final Project myProject; public String myDefaultNullable = AnnotationUtil.NULLABLE; public String myDefaultNotNull = AnnotationUtil.NOT_NULL; @@ -41,7 +42,7 @@ public abstract class NullableNotNullManager { public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(); private static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable"; - private static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; + protected static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, JAVAX_ANNOTATION_NULLABLE, "javax.annotation.CheckForNull", @@ -49,7 +50,8 @@ public abstract class NullableNotNullManager { "edu.umd.cs.findbugs.annotations.Nullable", "android.support.annotation.Nullable" }; - public NullableNotNullManager() { + public NullableNotNullManager(Project project) { + myProject = project; Collections.addAll(myNullables, DEFAULT_NULLABLES); } @@ -202,7 +204,7 @@ public abstract class NullableNotNullManager { String qName = annotation.getQualifiedName(); if (qName == null) return null; - List contradictory = nullable ? getNotNulls() : getNullables(); + List contradictory = nullable ? getNotNullsWithNickNames() : getNullablesWithNickNames(); if (contradictory.contains(qName)) return null; return annotation; @@ -241,13 +243,24 @@ public abstract class NullableNotNullManager { } private PsiAnnotation findPlainNullabilityAnnotation(@NotNull PsiModifierListOwner owner, boolean checkBases) { - Set qNames = ContainerUtil.newHashSet(getNullables()); - qNames.addAll(getNotNulls()); + Set qNames = ContainerUtil.newHashSet(getNullablesWithNickNames()); + qNames.addAll(getNotNullsWithNickNames()); return checkBases && owner instanceof PsiMethod ? AnnotationUtil.findAnnotationInHierarchy(owner, qNames) : AnnotationUtil.findAnnotation(owner, qNames); } + + @NotNull + protected List getNullablesWithNickNames() { + return getNullables(); + } + + @NotNull + protected List getNotNullsWithNickNames() { + return getNotNulls(); + } + protected boolean hasHardcodedContracts(PsiElement element) { return false; } @@ -360,10 +373,10 @@ public abstract class NullableNotNullManager { public abstract List getPredefinedNotNulls(); public static boolean isNullableAnnotation(@NotNull PsiAnnotation annotation) { - return getInstance(annotation.getProject()).getNullables().contains(annotation.getQualifiedName()); + return getInstance(annotation.getProject()).getNullablesWithNickNames().contains(annotation.getQualifiedName()); } public static boolean isNotNullAnnotation(@NotNull PsiAnnotation annotation) { - return getInstance(annotation.getProject()).getNotNulls().contains(annotation.getQualifiedName()); + return getInstance(annotation.getProject()).getNotNullsWithNickNames().contains(annotation.getQualifiedName()); } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java b/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java new file mode 100644 index 000000000000..c2f20a40f1a3 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java @@ -0,0 +1,15 @@ +import javax.annotation.meta.*; + +@TypeQualifierNickname() +@javax.annotation.Nonnull(when = When.MAYBE) +@interface NullableNick {} + +interface UnknownInterface { + void foo(String s); +} + +class ImplWithNotNull implements UnknownInterface { + public void foo(@NullableNick String s) { + System.out.println(s.hashCode()); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index ee1d16172c03..062f0c1a91aa 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -360,6 +360,17 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { myFixture.enableInspections(inspection); myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); } + + public void testTypeQualifierNickname() { + addJavaxNullabilityAnnotations(myFixture); + + myFixture.addClass("package bar;" + + "import javax.annotation.meta.*;" + + "@TypeQualifierNickname() @javax.annotation.NonNull(when = Maybe.MAYBE) " + + "public @interface NullableNick {}"); + + doTest(); + } public static void addJavaxDefaultNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) { fixture.addClass("package javax.annotation;" + @@ -371,12 +382,23 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { } public static void addJavaxNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) { - fixture.addClass("package javax.annotation;" + - "public @interface Nonnull {}"); - fixture.addClass("package javax.annotation;" + - "public @interface Nullable {}"); fixture.addClass("package javax.annotation.meta;" + "public @interface TypeQualifierDefault { java.lang.annotation.ElementType[] value() default {};}"); + fixture.addClass("package javax.annotation.meta;" + + "public enum When { ALWAYS, UNKNOWN, MAYBE, NEVER }"); + fixture.addClass("package javax.annotation.meta;" + + "public @interface TypeQualifierNickname {}"); + + fixture.addClass("package javax.annotation;" + + "import javax.annotation.meta.*;" + + "public @interface Nonnull {" + + " When when() default When.ALWAYS;" + + "}"); + fixture.addClass("package javax.annotation;" + + "import javax.annotation.meta.*;" + + "@TypeQualifierNickname " + + "@Nonnull(when = When.UNKNOWN) " + + "public @interface Nullable {}"); } public void testCustomTypeQualifierDefault() { From f1b5f0c2f14f1371ebafb34f2afb200e7755e120 Mon Sep 17 00:00:00 2001 From: Sergey Simonchik Date: Mon, 5 Jun 2017 20:26:19 +0300 Subject: [PATCH 55/84] simplify hasErrors --- .../src/com/intellij/util/PsiErrorElementUtil.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java b/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java index 5e9bc6559751..818c2fbfd728 100644 --- a/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java +++ b/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java @@ -20,11 +20,7 @@ import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.SyntaxTraverser; -import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.*; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; @@ -39,9 +35,7 @@ public class PsiErrorElementUtil { public static boolean hasErrors(@NotNull Project project, @NotNull VirtualFile virtualFile) { return ReadAction.compute(() -> { if (project.isDisposed() || !virtualFile.isValid()) return false; - - PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(project); - PsiFile psiFile = psiManager.getFileManager().findFile(virtualFile); + PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); return psiFile != null && hasErrors(psiFile); }); } From 8f4d31d4393c86dedd3db1f066b3c2220b26b196 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Wed, 31 May 2017 22:17:08 +0300 Subject: [PATCH 56/84] PY-23311 Fixed: Postgres Create Extension doesn't get syntax highlighting Add PostgreSQL extensions support. --- python/IntelliLang-python/src/resources/pyInjections.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/IntelliLang-python/src/resources/pyInjections.xml b/python/IntelliLang-python/src/resources/pyInjections.xml index 0c04cf7018d4..b9c1d06c4c7d 100644 --- a/python/IntelliLang-python/src/resources/pyInjections.xml +++ b/python/IntelliLang-python/src/resources/pyInjections.xml @@ -80,5 +80,11 @@ + + + + + + From de1a9451866978bc28accf5e8f05e0925aad9689 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 14 Apr 2017 15:35:27 +0300 Subject: [PATCH 57/84] Get rid of primitive definition level in PyNamedTupleType and use enum instead. --- .../codeInsight/stdlib/PyNamedTupleType.java | 50 ++++++++++++++----- .../stdlib/PyStdlibTypeProvider.java | 12 ++--- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index 2835e373a58c..818790416894 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -39,17 +39,25 @@ import java.util.Set; * @author yole */ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType { + + @NotNull + private final PsiElement myDeclaration; + + @NotNull private final String myName; - // 2 - namedtuple call itself - // 1 - return type of namedtuple call, aka namedtuple class - // 0 - namedtuple instance - private final int myDefinitionLevel; - private final PsiElement myDeclaration; + @NotNull private final List myFields; - public PyNamedTupleType(PyClass tupleClass, PsiElement declaration, String name, List fields, int definitionLevel) { - super(tupleClass, definitionLevel > 0); + @NotNull + private final DefinitionLevel myDefinitionLevel; + + public PyNamedTupleType(@NotNull PyClass tupleClass, + @NotNull PsiElement declaration, + @NotNull String name, + @NotNull List fields, + @NotNull DefinitionLevel definitionLevel) { + super(tupleClass, definitionLevel != DefinitionLevel.INSTANCE); myDeclaration = declaration; myFields = fields; myName = name; @@ -76,7 +84,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - List result = new ArrayList<>(); + final List result = new ArrayList<>(); Collections.addAll(result, super.getCompletionVariants(completionPrefix, location, context)); for (String field : myFields) { result.add(LookupElementBuilder.create(field)); @@ -84,6 +92,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return ArrayUtil.toObjectArray(result); } + @NotNull @Override public String getName() { return myName; @@ -97,22 +106,30 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Nullable @Override public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) { - if (myDefinitionLevel > 0) { - return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, myDefinitionLevel - 1); + if (myDefinitionLevel == DefinitionLevel.AS_SUPERCLASS) { + return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } + else if (myDefinitionLevel == DefinitionLevel.NEW_TYPE) { + return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.INSTANCE); + } + return null; } @NotNull @Override public PyClassType toInstance() { - return myDefinitionLevel == 1 ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 0) : this; + return myDefinitionLevel == DefinitionLevel.NEW_TYPE + ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.INSTANCE) + : this; } @NotNull @Override public PyClassLikeType toClass() { - return myDefinitionLevel == 0 ? this : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 1); + return myDefinitionLevel == DefinitionLevel.INSTANCE + ? this + : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } @Override @@ -137,4 +154,11 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType public List getElementNames() { return Collections.unmodifiableList(myFields); } + + public enum DefinitionLevel { + + AS_SUPERCLASS, + NEW_TYPE, + INSTANCE + } } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 14fb0f2f313d..be83fb0bb7ed 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -287,13 +287,13 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { final PyTargetExpressionStub stub = target.getStub(); if (stub != null) { - return getNamedTupleTypeFromStub(target, stub.getCustomStub(PyNamedTupleStub.class), 1); + return getNamedTupleTypeFromStub(target, stub.getCustomStub(PyNamedTupleStub.class), PyNamedTupleType.DefinitionLevel.NEW_TYPE); } else { - return getNamedTupleTypeFromAST(target, context, 1); + return getNamedTupleTypeFromAST(target, context, PyNamedTupleType.DefinitionLevel.NEW_TYPE); } } else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { - return getNamedTupleTypeFromAST((PyCallExpression)anchor, context, 2); + return getNamedTupleTypeFromAST((PyCallExpression)anchor, context, PyNamedTupleType.DefinitionLevel.AS_SUPERCLASS); } return null; } @@ -332,7 +332,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getNamedTupleTypeFromStub(@NotNull PsiElement referenceTarget, @Nullable PyNamedTupleStub stub, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (stub == null) { return null; } @@ -349,7 +349,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getNamedTupleTypeFromAST(@NotNull PyTargetExpression expression, @NotNull TypeEvalContext context, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (context.maySwitchToAST(expression)) { return getNamedTupleTypeFromStub(expression, PyNamedTupleStubImpl.create(expression), definitionLevel); } @@ -360,7 +360,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getNamedTupleTypeFromAST(@NotNull PyCallExpression expression, @NotNull TypeEvalContext context, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (context.maySwitchToAST(expression)) { return getNamedTupleTypeFromStub(expression, PyNamedTupleStubImpl.create(expression), definitionLevel); } From b8ad73fbb9b2587a7b269e12c4030fb93fb444c9 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 14 Apr 2017 18:08:59 +0300 Subject: [PATCH 58/84] PY-18246 Fixed: No completion for typing.NamedTuple Support typing.NamedTuple in namedtuple stubs. --- .../typing/PyTypingTypeProvider.java | 9 +- .../python/psi/PyFileElementType.java | 4 +- .../psi/impl/stubs/PyNamedTupleStubImpl.java | 201 +++++++++++++----- .../stubs/FullyQualifiedTypingNamedTuple.py | 3 + .../FullyQualifiedTypingNamedTupleKwargs.py | 3 + ...lyQualifiedTypingNamedTupleKwargsWithAs.py | 3 + .../FullyQualifiedTypingNamedTupleWithAs.py | 3 + .../stubs/ImportedTypingNamedTuple.py | 3 + .../stubs/ImportedTypingNamedTupleFields.py | 4 + .../stubs/ImportedTypingNamedTupleKwargs.py | 3 + .../ImportedTypingNamedTupleKwargsName.py | 4 + .../ImportedTypingNamedTupleKwargsWithAs.py | 3 + .../stubs/ImportedTypingNamedTupleName.py | 4 + .../stubs/ImportedTypingNamedTupleWithAs.py | 3 + .../stubs/TypingNamedTupleFieldsChain.py | 5 + .../stubs/TypingNamedTupleFieldsReference.py | 4 + .../stubs/TypingNamedTupleKwargsNameChain.py | 5 + .../TypingNamedTupleKwargsNameReference.py | 4 + .../stubs/TypingNamedTupleNameChain.py | 5 + .../stubs/TypingNamedTupleNameReference.py | 4 + .../com/jetbrains/python/PyStubsTest.java | 92 ++++++++ .../python/PythonCompletionTest.java | 58 ++++- 22 files changed, 368 insertions(+), 59 deletions(-) create mode 100644 python/testData/stubs/FullyQualifiedTypingNamedTuple.py create mode 100644 python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py create mode 100644 python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py create mode 100644 python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py create mode 100644 python/testData/stubs/ImportedTypingNamedTuple.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleFields.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleKwargs.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleKwargsName.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleName.py create mode 100644 python/testData/stubs/ImportedTypingNamedTupleWithAs.py create mode 100644 python/testData/stubs/TypingNamedTupleFieldsChain.py create mode 100644 python/testData/stubs/TypingNamedTupleFieldsReference.py create mode 100644 python/testData/stubs/TypingNamedTupleKwargsNameChain.py create mode 100644 python/testData/stubs/TypingNamedTupleKwargsNameReference.py create mode 100644 python/testData/stubs/TypingNamedTupleNameChain.py create mode 100644 python/testData/stubs/TypingNamedTupleNameReference.py diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index b27b06811b83..7bf709f60bd9 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -23,7 +23,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; -import com.intellij.psi.util.*; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.QualifiedName; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; @@ -55,6 +58,8 @@ import static com.jetbrains.python.psi.PyUtil.as; public class PyTypingTypeProvider extends PyTypeProviderBase { private static final Object RECURSION_KEY = new Object(); + public static final String TYPING = "typing"; + public static final String GENERATOR = "typing.Generator"; public static final String ASYNC_GENERATOR = "typing.AsyncGenerator"; public static final String COROUTINE = "typing.Coroutine"; @@ -63,6 +68,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { public static final String TYPE = "typing.Type"; public static final String ANY = "typing.Any"; + public static final String NAMEDTUPLE_SIMPLE = "NamedTuple"; + public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)"); private static final ImmutableMap COLLECTION_CLASSES = ImmutableMap.builder() diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index 11cd013273ee..e95590bbb9f3 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -62,7 +62,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 58; + return 59; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java index c4a3ec6b6e37..e68dd73e5da8 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,17 +15,21 @@ */ package com.jetbrains.python.psi.impl.stubs; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.stubs.StubInputStream; import com.intellij.psi.stubs.StubOutputStream; import com.intellij.psi.util.QualifiedName; +import com.intellij.util.ArrayUtil; import com.intellij.util.io.StringRef; import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.stubs.PyNamedTupleStub; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,22 +74,22 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { return null; } - final QualifiedName namedTupleQName = getNamedTupleQName(calleeReference); + final Pair calleeNameAndModule = getCalleeNameAndNTModule(calleeReference); - if (namedTupleQName != null) { + if (calleeNameAndModule != null) { final String name = resolveTupleName(expression); if (name == null) { return null; } - final List fields = resolveTupleFields(expression); + final List fields = resolveTupleFields(expression, calleeNameAndModule.getSecond()); if (fields == null) { return null; } - return new PyNamedTupleStubImpl(namedTupleQName, name, fields); + return new PyNamedTupleStubImpl(calleeNameAndModule.getFirst(), name, fields); } return null; @@ -144,14 +148,14 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { - final QualifiedName name = getFullyQualifiedNamedTupleQName(referenceExpression); + private static Pair getCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { + final Pair name = getFullyQCalleeNameAndNTModule(referenceExpression); if (name != null) { return name; } - return getImportedNamedTupleQName(referenceExpression); + return getImportedCalleeNameAndNTModule(referenceExpression); } @Nullable @@ -165,6 +169,13 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // Point = namedtuple(("Point"), ...) + // name = "Point" + // Point = NamedTuple(name, ...) + + // Point = NamedTuple("Point", ...) + + // Point = NamedTuple(("Point"), ...) + final PyExpression nameExpression = PyPsiUtils.flattenParens(callExpression.getArgument(0, PyExpression.class)); if (nameExpression instanceof PyReferenceExpression) { @@ -175,27 +186,15 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static List resolveTupleFields(@NotNull PyCallExpression callExpression) { - // SUPPORTED CASES: - - // fields = ["x", "y"] - // Point = namedtuple(..., fields) - - // Point = namedtuple(..., "x y") - - // Point = namedtuple(..., ("x y")) - - // Point = namedtuple(..., "x, y") - - // Point = namedtuple(..., ["x", "y"]) - - final PyExpression fieldsExpression = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); - - if (fieldsExpression instanceof PyReferenceExpression) { - return extractFields(fullResolveLocally((PyReferenceExpression)fieldsExpression)); + private static List resolveTupleFields(@NotNull PyCallExpression callExpression, @NotNull NamedTupleModule module) { + switch (module) { + case TYPING: + return resolveTypingNTFields(callExpression); + case COLLECTIONS: + return resolveCollectionsNTFields(callExpression); + default: + return null; } - - return extractFields(fieldsExpression); } @NotNull @@ -214,7 +213,7 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getFullyQualifiedNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { + private static Pair getFullyQCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { // SUPPORTED CASES: // import collections @@ -223,14 +222,27 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // import collections as c // Point = c.namedtuple(...) - if (PyNames.NAMEDTUPLE.equals(referenceExpression.getName())) { + // import typing + // ... = typing.NamedTuple(...) + + // import typing as t + // ... = t.NamedTuple(...) + + final String referenceName = referenceExpression.getName(); + final NamedTupleModule module = PyNames.NAMEDTUPLE.equals(referenceName) + ? NamedTupleModule.COLLECTIONS + : PyTypingTypeProvider.NAMEDTUPLE_SIMPLE.equals(referenceName) + ? NamedTupleModule.TYPING + : null; + + if (module != null) { final PyExpression qualifier = referenceExpression.getQualifier(); if (qualifier instanceof PyReferenceExpression) { final PyReferenceExpression qualifierReference = (PyReferenceExpression)qualifier; - if (!qualifierReference.isQualified() && resolvesToCollections(qualifierReference)) { - return QualifiedName.fromComponents(qualifierReference.getName(), referenceExpression.getName()); + if (!qualifierReference.isQualified() && resolvesToModule(qualifierReference, module)) { + return Pair.createNonNull(QualifiedName.fromComponents(qualifierReference.getName(), referenceName), module); } } } @@ -239,7 +251,7 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getImportedNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { + private static Pair getImportedCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { // SUPPORTED CASES: // from collections import namedtuple @@ -248,18 +260,31 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // from collections import namedtuple as NT // Point = NT(...) + // from typing import NamedTuple + // Point = NamedTuple(...) + + // from typing import NamedTuple as NT + // Point = NT(...) + for (PsiElement element : PyResolveUtil.resolveLocally(referenceExpression)) { if (element instanceof PyImportElement) { final PyImportElement importElement = (PyImportElement)element; + final QualifiedName importedQName = importElement.getImportedQName(); - if (equals(importElement.getImportedQName(), PyNames.NAMEDTUPLE)) { + final NamedTupleModule module = equals(importedQName, PyNames.NAMEDTUPLE) + ? NamedTupleModule.COLLECTIONS + : equals(importedQName, PyTypingTypeProvider.NAMEDTUPLE_SIMPLE) + ? NamedTupleModule.TYPING + : null; + + if (module != null) { final PyStatement importStatement = importElement.getContainingImportStatement(); if (importStatement instanceof PyFromImportStatement) { final PyFromImportStatement fromImportStatement = (PyFromImportStatement)importStatement; - if (equals(fromImportStatement.getImportSourceQName(), PyNames.COLLECTIONS)) { - return QualifiedName.fromComponents(referenceExpression.getName()); + if (equals(fromImportStatement.getImportSourceQName(), module.getModuleName())) { + return Pair.createNonNull(QualifiedName.fromComponents(referenceExpression.getName()), module); } } } @@ -269,12 +294,12 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { return null; } - private static boolean resolvesToCollections(@NotNull PyReferenceExpression referenceExpression) { + private static boolean resolvesToModule(@NotNull PyReferenceExpression referenceExpression, @NotNull NamedTupleModule module) { for (PsiElement element : PyResolveUtil.resolveLocally(referenceExpression)) { if (element instanceof PyImportElement) { final PyImportElement importElement = (PyImportElement)element; - if (equals(importElement.getImportedQName(), PyNames.COLLECTIONS)) { + if (equals(importElement.getImportedQName(), module.getModuleName())) { return true; } } @@ -305,32 +330,98 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static List extractFields(@Nullable PyExpression expression) { - if (expression == null) { - return null; - } + private static List resolveCollectionsNTFields(@NotNull PyCallExpression callExpression) { + // SUPPORTED CASES: - final List listValue = PyUtil.strListValue(expression); + // fields = ["x", "y"] + // Point = namedtuple(..., fields) - if (listValue != null) { - return listValue; - } + // Point = namedtuple(..., "x y") - return extractFields(PyPsiUtils.strValue(expression)); + // Point = namedtuple(..., ("x y")) + + // Point = namedtuple(..., "x, y") + + // Point = namedtuple(..., ["x", "y"]) + + final PyExpression fields = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); + + final PyExpression resolvedFields = fields instanceof PyReferenceExpression + ? fullResolveLocally((PyReferenceExpression)fields) + : fields; + + final List listValue = PyUtil.strListValue(resolvedFields); + if (listValue != null) return listValue; + + final String resolvedFieldsValue = PyPsiUtils.strValue(resolvedFields); + if (resolvedFieldsValue == null) return null; + + return StreamEx + .of(StringUtil.tokenize(resolvedFieldsValue, ", ").iterator()) + .toList(); } @Nullable - private static List extractFields(@Nullable String fieldsString) { - if (fieldsString == null) { - return null; + private static List resolveTypingNTFields(@NotNull PyCallExpression callExpression) { + // SUPPORTED CASES: + + // fields = [("x", str), ("y", int)] + // Point = NamedTuple(..., fields) + + // Point = NamedTuple(..., [("x", str), ("y", int)]) + + // Point = NamedTuple(..., x=str, y=int) + + final PyExpression secondArgument = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); + + if (secondArgument instanceof PyKeywordArgument) { + final PyExpression[] arguments = callExpression.getArguments(); + return StreamEx + .of(arguments, 1, arguments.length) + .select(PyKeywordArgument.class) + .map(PyKeywordArgument::getKeyword) + .toList(); + } else { + final PyExpression resolvedFields = secondArgument instanceof PyReferenceExpression + ? fullResolveLocally((PyReferenceExpression)secondArgument) + : secondArgument; + if (!(resolvedFields instanceof PySequenceExpression)) return null; + + final List result = new ArrayList<>(); + + for (PyExpression element : ((PySequenceExpression)resolvedFields).getElements()) { + if (!(element instanceof PyParenthesizedExpression)) return null; + + final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression(); + if (!(contained instanceof PyTupleExpression)) return null; + + final PyExpression[] nameAndType = ((PyTupleExpression)contained).getElements(); + final PyExpression name = ArrayUtil.getFirstElement(nameAndType); + if (nameAndType.length != 2 || !(name instanceof PyStringLiteralExpression)) return null; + + result.add(((PyStringLiteralExpression)name).getStringValue()); + } + + return result; } + } - final List result = new ArrayList<>(); + private enum NamedTupleModule { - for (String name : StringUtil.tokenize(fieldsString, ", ")) { - result.add(name); - } + COLLECTIONS { + @Override + public String getModuleName() { + return PyNames.COLLECTIONS; + } + }, - return result; + TYPING { + @Override + public String getModuleName() { + return PyTypingTypeProvider.TYPING; + } + }; + + public abstract String getModuleName(); } } diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTuple.py b/python/testData/stubs/FullyQualifiedTypingNamedTuple.py new file mode 100644 index 000000000000..0a0c72972354 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTuple.py @@ -0,0 +1,3 @@ +import typing + +nt = typing.NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py new file mode 100644 index 000000000000..b968fb3ec634 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py @@ -0,0 +1,3 @@ +import typing + +nt = typing.NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py new file mode 100644 index 000000000000..2d9e52c41d7b --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py @@ -0,0 +1,3 @@ +import typing as T + +nt = T.NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py new file mode 100644 index 000000000000..98b805441fb2 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py @@ -0,0 +1,3 @@ +import typing as T + +nt = T.NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTuple.py b/python/testData/stubs/ImportedTypingNamedTuple.py new file mode 100644 index 000000000000..f123cc049321 --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTuple.py @@ -0,0 +1,3 @@ +from typing import NamedTuple + +nt = NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleFields.py b/python/testData/stubs/ImportedTypingNamedTupleFields.py new file mode 100644 index 000000000000..649fa3cf576a --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleFields.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import fields + +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargs.py b/python/testData/stubs/ImportedTypingNamedTupleKwargs.py new file mode 100644 index 000000000000..7ee5acf92e5f --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple + +nt = NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py b/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py new file mode 100644 index 000000000000..de907d47e64b --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import name + +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py b/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py new file mode 100644 index 000000000000..43824d8ccfba --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple as NT + +nt = NT("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleName.py b/python/testData/stubs/ImportedTypingNamedTupleName.py new file mode 100644 index 000000000000..e0eb71d471bc --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleName.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import name + +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleWithAs.py b/python/testData/stubs/ImportedTypingNamedTupleWithAs.py new file mode 100644 index 000000000000..ef85b30618a3 --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleWithAs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple as NT + +nt = NT("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleFieldsChain.py b/python/testData/stubs/TypingNamedTupleFieldsChain.py new file mode 100644 index 000000000000..08f97f5c22e1 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleFieldsChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +fields0 = [("x", str), ("y", int)] +fields = fields0 +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleFieldsReference.py b/python/testData/stubs/TypingNamedTupleFieldsReference.py new file mode 100644 index 000000000000..de9e4629f175 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleFieldsReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +fields = [("x", str), ("y", int)] +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleKwargsNameChain.py b/python/testData/stubs/TypingNamedTupleKwargsNameChain.py new file mode 100644 index 000000000000..af123e5b88b2 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleKwargsNameChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +name0 = "name" +name = name0 +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleKwargsNameReference.py b/python/testData/stubs/TypingNamedTupleKwargsNameReference.py new file mode 100644 index 000000000000..781aedeb074c --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleKwargsNameReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +name = "name" +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleNameChain.py b/python/testData/stubs/TypingNamedTupleNameChain.py new file mode 100644 index 000000000000..338bc79f7077 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleNameChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +name0 = "name" +name = name0 +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleNameReference.py b/python/testData/stubs/TypingNamedTupleNameReference.py new file mode 100644 index 000000000000..3437d9e5db67 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleNameReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +name = "name" +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index d6b65743325c..5a055a1fe49c 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -490,6 +490,94 @@ public class PyStubsTest extends PyTestCase { doTestUnsupportedNamedTuple(); } + public void testFullyQualifiedTypingNamedTuple() { + doTestNamedTuple( + QualifiedName.fromDottedString("typing.NamedTuple") + ); + } + + public void testFullyQualifiedTypingNamedTupleWithAs() { + doTestNamedTuple( + QualifiedName.fromDottedString("T.NamedTuple") + ); + } + + public void testImportedTypingNamedTuple() { + doTestNamedTuple( + QualifiedName.fromComponents("NamedTuple") + ); + } + + public void testImportedTypingNamedTupleWithAs() { + doTestNamedTuple( + QualifiedName.fromComponents("NT") + ); + } + + public void testTypingNamedTupleNameReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleFieldsReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleNameChain() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleFieldsChain() { + doTestNamedTypingTupleArguments(); + } + + public void _testImportedTypingNamedTupleName() { + doTestUnsupportedNamedTuple(); + } + + public void _testImportedTypingNamedTupleFields() { + doTestUnsupportedNamedTuple(); + } + + public void testFullyQualifiedTypingNamedTupleKwargs() { + doTestNamedTuple( + QualifiedName.fromDottedString("typing.NamedTuple") + ); + } + + public void testFullyQualifiedTypingNamedTupleKwargsWithAs() { + doTestNamedTuple( + QualifiedName.fromDottedString("T.NamedTuple") + ); + } + + public void testImportedTypingNamedTupleKwargs() { + doTestNamedTuple( + QualifiedName.fromComponents("NamedTuple") + ); + } + + public void testImportedTypingNamedTupleKwargsWithAs() { + doTestNamedTuple( + QualifiedName.fromComponents("NT") + ); + } + + public void testTypingNamedTupleKwargsNameReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleKwargsNameChain() { + doTestNamedTypingTupleArguments(); + } + + public void _testImportedTypingNamedTupleKwargsName() { + doTestUnsupportedNamedTuple(); + } + + public void _testImportedTypingNamedTupleKwargsFields() { + doTestUnsupportedNamedTuple(); + } + private void doTestNamedTuple(@NotNull QualifiedName expectedCalleeName) { doTestNamedTuple("name", Collections.singletonList("field"), expectedCalleeName); } @@ -498,6 +586,10 @@ public class PyStubsTest extends PyTestCase { doTestNamedTuple("name", Arrays.asList("x", "y"), QualifiedName.fromComponents("namedtuple")); } + private void doTestNamedTypingTupleArguments() { + doTestNamedTuple("name", Arrays.asList("x", "y"), QualifiedName.fromComponents("NamedTuple")); + } + private void doTestNamedTuple(@NotNull String expectedName, @NotNull List expectedFields, @NotNull QualifiedName expectedCalleeName) { diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 480437f318ce..7496bec64430 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -1136,6 +1136,62 @@ public class PythonCompletionTest extends PyTestCase { assertDoesntContain(suggested, "_T", "_KT"); } + // PY-18246 + public void testTypingNamedTupleCreatedViaCallInstance() { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "EmployeeRecord = NamedTuple('EmployeeRecord', [\n" + + " ('name', str),\n" + + " ('age', int),\n" + + " ('title', str),\n" + + " ('department', str)\n" + + "])\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + + // PY-18246 + public void testTypingNamedTupleCreatedViaKwargsCallInstance() { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "EmployeeRecord = NamedTuple('EmployeeRecord', name=str, age=int, title=str, department=str)\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + + // PY-18246 + public void testTypingNamedTupleCreatedViaInheritanceInstance() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "class EmployeeRecord(NamedTuple):\n" + + " name: str\n" + + " age: int\n" + + " title: str\n" + + " department: str\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + ); + } + // PY-21519 public void testTypeComment() { myFixture.copyFileToProject("../typing/typing.py"); From e953f46acdd81a92cef0ea6512a59159ef430209 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 16 May 2017 15:09:03 +0300 Subject: [PATCH 59/84] Don't analyze calls to `typing.NamedTuple` while inferring call type in PyCallExpressionHelper. They will be processed in `PyStdlibTypeProvider.getReferenceType`. --- .../psi/impl/PyOverridingTypeProvider.java | 19 ++++++++++ python/src/META-INF/python-core-common.xml | 1 + .../stdlib/PyStdlibOverridingTypeProvider.kt | 38 +++++++++++++++++++ .../stdlib/PyStdlibTypeProvider.java | 6 +-- .../psi/impl/PyCallExpressionHelper.java | 5 +++ .../psi/impl/PyReferenceExpressionImpl.java | 13 +++++++ .../TypingNamedTupleAsParameter.py | 12 ++++++ .../Py3TypeCheckerInspectionTest.java | 4 ++ 8 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java create mode 100644 python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt create mode 100644 python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java new file mode 100644 index 000000000000..ca59095b042c --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java @@ -0,0 +1,19 @@ +/* + * Copyright 2000-2017 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.psi.impl; + +public interface PyOverridingTypeProvider extends PyTypeProvider { +} diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 1fcd94f60f08..eb4d7a1905f1 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -680,6 +680,7 @@ + diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt new file mode 100644 index 000000000000..90b7b71ad7b6 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2017 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.codeInsight.stdlib + +import com.intellij.psi.PsiElement +import com.jetbrains.python.PyNames +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider +import com.jetbrains.python.psi.PyFunction +import com.jetbrains.python.psi.impl.PyOverridingTypeProvider +import com.jetbrains.python.psi.types.PyType +import com.jetbrains.python.psi.types.PyTypeProviderBase +import com.jetbrains.python.psi.types.TypeEvalContext + +class PyStdlibOverridingTypeProvider : PyTypeProviderBase(), PyOverridingTypeProvider { + + override fun getReferenceType(referenceTarget: PsiElement, context: TypeEvalContext, anchor: PsiElement?): PyType? { + return if (isTypingNamedTupleInit(referenceTarget)) PyStdlibTypeProvider.getNamedTupleType(referenceTarget, context, anchor) else null + } + + private fun isTypingNamedTupleInit(referenceTarget: PsiElement): Boolean { + return referenceTarget is PyFunction && + PyNames.INIT == referenceTarget.name && + PyTypingTypeProvider.NAMEDTUPLE == referenceTarget.containingClass?.qualifiedName + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index be83fb0bb7ed..0f02f2196117 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -279,9 +279,9 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, - @NotNull TypeEvalContext context, - @Nullable PsiElement anchor) { + static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; final PyTargetExpressionStub stub = target.getStub(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 7e734531e9af..43ec7333f878 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -473,6 +473,11 @@ public class PyCallExpressionHelper { @Nullable private static Ref getCallTargetReturnType(@NotNull PyCallExpression call, @NotNull PsiElement target, @NotNull TypeEvalContext context) { + final PyType providedOverridingType = PyReferenceExpressionImpl.getReferenceTypeFromOverridingProviders(target, context, call); + if (providedOverridingType instanceof PyCallableType) { + return Ref.create(((PyCallableType)providedOverridingType).getCallType(context, call)); + } + PyClass cls = null; PyFunction init = null; if (target instanceof PyClass) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 6b363f4e636b..50964690da49 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -41,6 +41,7 @@ import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.refactoring.PyDefUseUtil; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -488,6 +489,18 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere return null; } + @Nullable + public static PyType getReferenceTypeFromOverridingProviders(@NotNull PsiElement target, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { + return StreamEx + .of(Extensions.getExtensions(PyTypeProvider.EP_NAME)) + .select(PyOverridingTypeProvider.class) + .map(provider -> provider.getReferenceType(target, context, anchor)) + .findFirst(Objects::nonNull) + .orElse(null); + } + @Nullable public static PyType getReferenceTypeFromProviders(@NotNull PsiElement target, @NotNull TypeEvalContext context, diff --git a/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py b/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py new file mode 100644 index 000000000000..c638e5985517 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py @@ -0,0 +1,12 @@ +from typing import NamedTuple + + +nt = NamedTuple("name", [("field", str)]) + + +def foo(x: nt): + pass + + +foo(5) +foo(nt(field = "f")) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index cbaef8617e4e..c118d3effe03 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -233,4 +233,8 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testGenericKwargs() { doTest(); } + + public void testTypingNamedTupleAsParameter() { + doTest(); + } } From 4c8d424207777fcf93f2982d212f3bd5028ddb7c Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 17 Apr 2017 19:09:17 +0300 Subject: [PATCH 60/84] PY-7322 Fixed: namedtuple types are not detected with reST :type : Pass `TypeEvalContext` to `PyTypeParser.getTypeByName` and `PyTypeParser.parse`. This allows to infer named tuple type from doc string when file was unstubbed. --- .../numpy/codeInsight/NumpyResolveRater.java | 4 +- .../controlflow/PyTypeAssertionEvaluator.java | 2 +- .../stdlib/PyStdlibTypeProvider.java | 14 ++-- .../typing/PyTypingTypeProvider.java | 6 +- .../debugger/PyCallSignatureTypeProvider.java | 6 +- .../python/debugger/PySignatureUtil.java | 75 ------------------- .../documentation/PyDocumentationBuilder.java | 11 +-- .../docstrings/PyDocStringTypeProvider.java | 10 +-- .../PyDocstringTypesInspection.java | 46 ++++++++++-- .../inspections/PyStringFormatInspection.java | 10 +-- .../python/psi/types/PyTypeParser.java | 47 ++++++++---- .../refactoring/PyReplaceExpressionUtil.java | 10 +-- .../com/jetbrains/python/PyTypeTest.java | 12 +++ 13 files changed, 121 insertions(+), 132 deletions(-) delete mode 100644 python/src/com/jetbrains/python/debugger/PySignatureUtil.java diff --git a/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java b/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java index 1ab734ab0dde..6bf899f43c9c 100644 --- a/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java +++ b/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -29,7 +29,7 @@ public class NumpyResolveRater extends PyResolveResultRaterBase { @Override public int getMemberRate(PsiElement member, PyType type, TypeEvalContext context) { if (member instanceof PsiNamedElement) { - final PyType ndArray = PyTypeParser.getTypeByName(member, NumpyDocStringTypeProvider.NDARRAY); + final PyType ndArray = PyTypeParser.getTypeByName(member, NumpyDocStringTypeProvider.NDARRAY, context); if (ndArray != null && PyTypeChecker.match(ndArray, type, context) && PyNames.isRightOperatorName(((PsiNamedElement)member).getName())) { return 100; diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index 2eea3ed647ff..44489ad8a501 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -76,7 +76,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { if (args.length == 1 && args[0] instanceof PyReferenceExpression) { final PyReferenceExpression target = (PyReferenceExpression)args[0]; - pushAssertion(target, myPositive, context -> PyTypeParser.getTypeByName(target, "collections." + PyNames.CALLABLE)); + pushAssertion(target, myPositive, context -> PyTypeParser.getTypeByName(target, "collections." + PyNames.CALLABLE, context)); } } } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 0f02f2196117..a11685feaa9d 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -151,7 +151,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } else if ("enum.EnumMeta.__members__".equals(name)) { - return PyTypeParser.getTypeByName(referenceTarget, "dict[str, unknown]"); + return PyTypeParser.getTypeByName(referenceTarget, "dict[str, unknown]", context); } } return null; @@ -163,7 +163,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { final String qname = function.getQualifiedName(); if (qname != null) { if (OPEN_FUNCTIONS.contains(qname) && callSite instanceof PyCallExpression) { - return getOpenFunctionType(qname, PyCallExpressionHelper.mapArguments(callSite, function, context).getMappedParameters(), callSite); + final PyCallExpressionHelper.ArgumentMappingResults mapping = PyCallExpressionHelper.mapArguments(callSite, function, context); + return getOpenFunctionType(qname, mapping.getMappedParameters(), callSite, context); } else if ("tuple.__init__".equals(qname) && callSite instanceof PyCallExpression) { return getTupleInitializationType((PyCallExpression)callSite, context); @@ -301,7 +302,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @NotNull private static Ref getOpenFunctionType(@NotNull String callQName, @NotNull Map arguments, - @NotNull PsiElement anchor) { + @NotNull PsiElement anchor, + @NotNull TypeEvalContext context) { String mode = "r"; for (Map.Entry entry : arguments.entrySet()) { final PyNamedParameter parameter = entry.getValue(); @@ -319,14 +321,14 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (LanguageLevel.forElement(anchor).isAtLeast(LanguageLevel.PYTHON30) || "io.open".equals(callQName) || "_io.open".equals(callQName)) { if (mode.contains("b")) { - return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_BINARY_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_BINARY_FILE_TYPE, context)); } else { - return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_TEXT_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_TEXT_FILE_TYPE, context)); } } - return Ref.create(PyTypeParser.getTypeByName(anchor, PY2K_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY2K_FILE_TYPE, context)); } @Nullable diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index 7bf709f60bd9..d08af5e4b638 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -486,7 +486,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (parameterizedType != null) { return Ref.create(parameterizedType); } - final PyType builtinCollection = getBuiltinCollection(resolved); + final PyType builtinCollection = getBuiltinCollection(resolved, context.getTypeContext()); if (builtinCollection != null) { return Ref.create(builtinCollection); } @@ -750,10 +750,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getBuiltinCollection(@NotNull PsiElement element) { + private static PyType getBuiltinCollection(@NotNull PsiElement element, @NotNull TypeEvalContext context) { final String collectionName = getQualifiedName(element); final String builtinName = COLLECTION_CLASSES.get(collectionName); - return builtinName != null ? PyTypeParser.getTypeByName(element, builtinName) : null; + return builtinName != null ? PyTypeParser.getTypeByName(element, builtinName, context) : null; } @NotNull diff --git a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java index 41734caedd8a..335c9dc4e503 100644 --- a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java +++ b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -34,7 +34,7 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (name != null) { final String typeName = PySignatureCacheManager.getInstance(param.getProject()).findParameterType(func, name); if (typeName != null) { - final PyType type = PyTypeParser.getTypeByName(param, typeName); + final PyType type = PyTypeParser.getTypeByName(param, typeName, context); if (type != null) { return Ref.create(PyDynamicallyEvaluatedType.create(type)); } @@ -51,7 +51,7 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (signature != null && signature.getReturnType() != null) { final String typeName = signature.getReturnType().getTypeQualifiedName(); if (typeName != null) { - final PyType type = PyTypeParser.getTypeByName(function, typeName); + final PyType type = PyTypeParser.getTypeByName(function, typeName, context); if (type != null) { return Ref.create(PyDynamicallyEvaluatedType.create(type)); } diff --git a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java deleted file mode 100644 index 21094b579750..000000000000 --- a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.debugger; - -import com.google.common.collect.Collections2; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.types.PyClassType; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyTypeParser; -import com.jetbrains.python.psi.types.PyUnionType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author traff - */ -public class PySignatureUtil { - private PySignatureUtil() { - } - - @Nullable - public static String getShortestImportableName(@Nullable PsiElement anchor, @NotNull String type) { - final PyType pyType = PyTypeParser.getTypeByName(anchor, type); - if (pyType instanceof PyClassType) { - PyClass c = ((PyClassType)pyType).getPyClass(); - return c.getQualifiedName(); - } - - if (pyType != null) { - return getPrintableName(pyType); - } - else { - return type; - } - } - - private static String getPrintableName(PyType type) { - if (type instanceof PyUnionType) { - return StringUtil.join(Collections2.transform(((PyUnionType)type).getMembers(), input -> getPrintableName(input)), " or "); - } - else if (type != null) { - return type.getName(); - } - else { - return PyNames.UNKNOWN_TYPE; - } - } - - @Nullable - public static String getArgumentType(@NotNull PyFunction function, @NotNull String name) { - PySignatureCacheManager cacheManager = PySignatureCacheManager.getInstance(function.getProject()); - PySignature signature = cacheManager.findSignature(function); - if (signature != null) { - return getShortestImportableName(function, signature.getArgTypeQualifiedName(name)); - } - return null; - } -} diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index e3c2fae4e871..952e80035be6 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -163,7 +163,7 @@ public class PyDocumentationBuilder { private void buildFromParameter(@NotNull final TypeEvalContext context, @Nullable final PsiElement outerElement, @NotNull final PsiElement elementDefinition) { myBody.addItem(combUp("Parameter " + PyUtil.getReadableRepr(elementDefinition, false))); - final boolean typeFromDocstringAdded = addTypeAndDescriptionFromDocstring((PyNamedParameter)elementDefinition); + final boolean typeFromDocstringAdded = addTypeAndDescriptionFromDocstring((PyNamedParameter)elementDefinition, context); if (outerElement instanceof PyExpression) { final PyType type = context.getType((PyExpression)outerElement); if (type != null) { @@ -409,11 +409,11 @@ public class PyDocumentationBuilder { } } - private void addPredefinedMethodDoc(PyFunction fun, String mothodName) { + private void addPredefinedMethodDoc(PyFunction fun, String methodName) { final PyClassType objectType = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff if (objectType != null) { final PyClass objectClass = objectType.getPyClass(); - final PyFunction predefinedMethod = objectClass.findMethodByName(mothodName, false, null); + final PyFunction predefinedMethod = objectClass.findMethodByName(methodName, false, null); if (predefinedMethod != null) { final PyStringLiteralExpression predefinedDocstring = getEffectiveDocStringExpression(predefinedMethod); final String predefinedDoc = predefinedDocstring != null ? predefinedDocstring.getStringValue() : null; @@ -468,9 +468,10 @@ public class PyDocumentationBuilder { * Adds type and description representation from function docstring * * @param parameter parameter of a function + * @param context type evaluation context * @return true if type from docstring was added */ - private boolean addTypeAndDescriptionFromDocstring(@NotNull final PyNamedParameter parameter) { + private boolean addTypeAndDescriptionFromDocstring(@NotNull PyNamedParameter parameter, @NotNull TypeEvalContext context) { final PyFunction function = PsiTreeUtil.getParentOfType(parameter, PyFunction.class); if (function != null) { final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression(function)); @@ -480,7 +481,7 @@ public class PyDocumentationBuilder { final String description = typeAndDescr.second; if (type != null) { - final PyType pyType = PyTypeParser.getTypeByName(parameter, type); + final PyType pyType = PyTypeParser.getTypeByName(parameter, type, context); if (pyType instanceof PyClassType) { myBody.addItem(": ").addWith(new LinkWrapper(PythonDocumentationProvider.LINK_TYPE_PARAM), $(pyType.getName())); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java index 2162d63865cb..a43bfa5936ae 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -39,7 +39,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { if (docString != null) { final String typeText = docString.getParamType(param.getName()); if (StringUtil.isNotEmpty(typeText)) { - final Ref typeRef = parseType(func, typeText); + final Ref typeRef = parseType(func, typeText, context); if (param.isPositionalContainer()) { return Ref.create(PyTypeUtil.toPositionalContainerType(param, typeRef.get())); @@ -63,7 +63,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { if (docString != null) { final String typeText = docString.getReturnType(); if (StringUtil.isNotEmpty(typeText)) { - return parseType(callable, typeText); + return parseType(callable, typeText, context); } } } @@ -71,8 +71,8 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { } @NotNull - private static Ref parseType(@NotNull PyCallable callable, String typeText) { - final PyType type = PyTypeParser.getTypeByName(callable, typeText); + private static Ref parseType(@NotNull PyCallable callable, @NotNull String typeText, @NotNull TypeEvalContext context) { + final PyType type = PyTypeParser.getTypeByName(callable, typeText, context); if (type != null) { type.assertValid("from docstring"); } diff --git a/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java b/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java index 13fab23dce28..58b69113132f 100644 --- a/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,19 +20,18 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.debugger.PySignature; import com.jetbrains.python.debugger.PySignatureCacheManager; -import com.jetbrains.python.debugger.PySignatureUtil; import com.jetbrains.python.documentation.docstrings.DocStringUtil; import com.jetbrains.python.documentation.docstrings.PlainDocString; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStringLiteralExpression; import com.jetbrains.python.psi.StructuredDocString; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyTypeChecker; -import com.jetbrains.python.psi.types.PyTypeParser; +import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.Substring; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -94,7 +93,7 @@ public class PyDocstringTypesInspection extends PyInspection { if (type != null) { String dynamicType = signature.getArgTypeQualifiedName(param); if (dynamicType != null) { - String dynamicTypeShortName = PySignatureUtil.getShortestImportableName(function, dynamicType); + String dynamicTypeShortName = getShortestImportableName(function, dynamicType); if (!match(function, dynamicType, type.getValue())) { registerProblem(node, "Dynamically inferred type '" + dynamicTypeShortName + @@ -109,9 +108,40 @@ public class PyDocstringTypesInspection extends PyInspection { } } + @Nullable + private String getShortestImportableName(@Nullable PsiElement anchor, @NotNull String type) { + final PyType pyType = PyTypeParser.getTypeByName(anchor, type, myTypeEvalContext); + if (pyType instanceof PyClassType) { + return ((PyClassType)pyType).getPyClass().getQualifiedName(); + } + + if (pyType != null) { + return getPrintableName(pyType); + } + else { + return type; + } + } + + @Nullable + private static String getPrintableName(@Nullable PyType type) { + if (type instanceof PyUnionType) { + return StreamEx + .of(((PyUnionType)type).getMembers()) + .map(Visitor::getPrintableName) + .joining(" or "); + } + else if (type != null) { + return type.getName(); + } + else { + return PyNames.UNKNOWN_TYPE; + } + } + private boolean match(PsiElement anchor, String dynamicTypeName, String specifiedTypeName) { - final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName); - final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName); + final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName, myTypeEvalContext); + final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName, myTypeEvalContext); return PyTypeChecker.match(specifiedType, dynamicType, myTypeEvalContext); } } diff --git a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java index e942a9755639..4977c1495d19 100644 --- a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -118,7 +118,7 @@ public class PyStringFormatInspection extends PyInspection { final PyType elementType = tupleType.getElementType(i); if (elementType != null) { final String typeName = myFormatSpec.get(String.valueOf(i + 1)); - final PyType type = typeName != null ? PyTypeParser.getTypeByName(problemTarget, typeName) : null; + final PyType type = typeName != null ? PyTypeParser.getTypeByName(problemTarget, typeName, myTypeEvalContext) : null; checkTypeCompatible(problemTarget, elementType, type); } } @@ -171,7 +171,7 @@ public class PyStringFormatInspection extends PyInspection { else if (PyUtil.instanceOf(rightExpression, PySequenceExpression.class, PyComprehensionElement.class)) { if (s != null) { checkTypeCompatible(problemTarget, builtinCache.getStrType(), - PyTypeParser.getTypeByName(problemTarget, s)); + PyTypeParser.getTypeByName(problemTarget, s, myTypeEvalContext)); return 1; } } @@ -184,7 +184,7 @@ public class PyStringFormatInspection extends PyInspection { if (PyTypeChecker.match(listType, type, myTypeEvalContext) || PyTypeChecker.match(stringType, type, myTypeEvalContext)) { checkTypeCompatible(problemTarget, builtinCache.getStrType(), - PyTypeParser.getTypeByName(problemTarget, s)); + PyTypeParser.getTypeByName(problemTarget, s, myTypeEvalContext)); return 1; } PySliceItem sliceItem = ((PySliceExpression)rightExpression).getSliceItem(); @@ -328,7 +328,7 @@ public class PyStringFormatInspection extends PyInspection { @NotNull final String expectedTypeName, @NotNull PsiElement problemTarget) { final PyType actual = myTypeEvalContext.getType(expression); - final PyType expected = PyTypeParser.getTypeByName(problemTarget, expectedTypeName); + final PyType expected = PyTypeParser.getTypeByName(problemTarget, expectedTypeName, myTypeEvalContext); if (actual != null) { checkTypeCompatible(problemTarget, actual, expected); } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index f82c0310924b..1499e771c542 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -116,25 +116,45 @@ public class PyTypeParser { * @return null either if there was an error during parsing or if extracted type is equivalent to Any or undefined */ @Nullable - public static PyType getTypeByName(@Nullable final PsiElement anchor, @NotNull String type) { + public static PyType getTypeByName(@Nullable PsiElement anchor, @NotNull String type) { + if (anchor == null) return EMPTY_RESULT.getType(); return parse(anchor, type).getType(); } /** - * @param anchor should never be null or null will be returned + * @param anchor should never be null or null will be returned + * @param context type evaluation context + * @return null either if there was an error during parsing or if extracted type is equivalent to Any or undefined + */ + @Nullable + public static PyType getTypeByName(@Nullable PsiElement anchor, @NotNull String type, @NotNull TypeEvalContext context) { + if (anchor == null) return EMPTY_RESULT.getType(); + return parse(anchor, type, context).getType(); + } + + /** + * @param anchor should never be null or {@link PyTypeParser#EMPTY_RESULT} will be returned + * @param type representation of the type to parse */ @NotNull - public static ParseResult parse(@Nullable final PsiElement anchor, @NotNull String type) { + public static ParseResult parse(@NotNull PsiElement anchor, @NotNull String type) { + return parse(anchor, type, TypeEvalContext.codeInsightFallback(anchor.getProject())); + } + + /** + * @param anchor should never be null or {@link PyTypeParser#EMPTY_RESULT} will be returned + * @param type representation of the type to parse + * @param context type evaluation context + */ + @NotNull + public static ParseResult parse(@NotNull PsiElement anchor, @NotNull String type, @NotNull TypeEvalContext context) { PyPsiUtils.assertValid(anchor); - if (anchor == null) { - return EMPTY_RESULT; - } final ForwardDeclaration typeExpr = ForwardDeclaration.create(); final FunctionalParser classType = token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER)))) - .map(new MakeSimpleType(anchor)) + .map(new MakeSimpleType(anchor, context)) .cached() .named("class-type"); @@ -304,9 +324,11 @@ public class PyTypeParser { private static class MakeSimpleType implements Function, List>>, ParseResult> { @NotNull private final PsiElement myAnchor; + @NotNull private final TypeEvalContext myContext; - public MakeSimpleType(@NotNull PsiElement anchor) { + public MakeSimpleType(@NotNull PsiElement anchor, @NotNull TypeEvalContext context) { myAnchor = anchor; + myContext = context; } @Nullable @@ -331,15 +353,14 @@ public class PyTypeParser { if (file instanceof PyFile) { final PyFile pyFile = (PyFile)file; - final TypeEvalContext context = TypeEvalContext.codeInsightFallback(file.getProject()); final Map types = new HashMap<>(); final Map fullRanges = new HashMap<>(); final Map imports = new HashMap<>(); - PyType type = resolveQualifierType(tokens, pyFile, context, types, fullRanges, imports); + PyType type = resolveQualifierType(tokens, pyFile, myContext, types, fullRanges, imports); if (type != null) { - final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context); + final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(myContext); final PyExpression expression = myAnchor instanceof PyExpression ? (PyExpression)myAnchor : null; for (Token token : tokens) { @@ -350,7 +371,7 @@ public class PyTypeParser { if (results != null && !results.isEmpty()) { final PsiElement resolved = results.get(0).getElement(); if (resolved instanceof PyTypedElement) { - type = context.getType((PyTypedElement)resolved); + type = myContext.getType((PyTypedElement)resolved); if (type != null && !allowResolveToType(type)) { type = null; break; diff --git a/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java b/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java index 31017cda1cc6..13365921cf80 100644 --- a/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -22,8 +22,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PythonStringUtil; -import com.jetbrains.python.inspections.PyStringFormatParser; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; @@ -154,7 +152,7 @@ public class PyReplaceExpressionUtil implements PyElementTypes { final PyType valueType = context.getType(formatValue); final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(oldExpression); final PyType tupleType = builtinCache.getTupleType(); - final PyType mappingType = PyTypeParser.getTypeByName(null, "collections.Mapping"); + final PyType mappingType = PyTypeParser.getTypeByName(null, "collections.Mapping", context); if (!PyTypeChecker.match(tupleType, valueType, context) || (mappingType != null && !PyTypeChecker.match(mappingType, valueType, context))) { return replaceSubstringWithSingleValueFormatting(oldExpression, textRange, prefix, suffix, formatValue, newText, substitutions); @@ -322,12 +320,12 @@ public class PyReplaceExpressionUtil implements PyElementTypes { builder.append("("); } if (!leftQuote.endsWith(prefix)) { - builder.append(prefix + rightQuote + " + "); + builder.append(prefix).append(rightQuote).append(" + "); } final int pos = builder.toString().length(); builder.append(newText); if (!rightQuote.startsWith(suffix)) { - builder.append(" + " + leftQuote + suffix); + builder.append(" + ").append(leftQuote).append(suffix); } if (hasSubstitutions) { builder.append(")"); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 85fa183469b9..ddfea706620b 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1677,6 +1677,18 @@ public class PyTypeTest extends PyTestCase { " return get_class()"); } + // PY-7322 + public void testNamedTupleParameterInDocString() { + doTest("Point", + "from collections import namedtuple\n" + + "Point = namedtuple('Point', ('x', 'y'))\n" + + "def takes_a_point(point):\n" + + " \"\"\"\n" + + " :type point: Point\n" + + " \"\"\"\n" + + " expr = point"); + } + // PY-22919 public void testMaxListKnownElements() { doTest("int", From dd8cd1a094d1869b5002d398dcf7455afeb6c03c Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 5 Jun 2017 21:10:38 +0300 Subject: [PATCH 61/84] EA-98972 - IOE: VirtualDirectoryImpl.getInputStream --- .../org/intellij/plugins/intelliLang/InjectionsSettingsUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java index 40649feaf573..9c2e48b07843 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java @@ -709,7 +709,7 @@ public class InjectionsSettingsUI extends SearchableConfigurable.Parent.Abstract } private void doImportAction(final DataContext dataContext) { - final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, true, false, true, false) { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, true, false) { @Override public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { return super.isFileVisible(file, showHiddenFiles) && From 0ad3340f78f03fd79ed417d5fb07236513c33e84 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 4 May 2017 15:16:02 +0300 Subject: [PATCH 62/84] PY-8174 Cleanup in PyInitNewSignatureInspection: reformat + TypeEvalContext --- .../python/inspections/PyInitNewSignatureInspection.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java index ed5a883e9e7c..fdec3cde1bc5 100644 --- a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java @@ -61,15 +61,15 @@ public class PyInitNewSignatureInspection extends PyInspection { if (!PyNames.NEW.equals(functionName) && !PyNames.INIT.equals(functionName)) return; final PyClass cls = node.getContainingClass(); if (cls == null) return; - if (!cls.isNewStyleClass(null)) return; + if (!cls.isNewStyleClass(myTypeEvalContext)) return; final String complementaryName = PyNames.NEW.equals(functionName) ? PyNames.INIT : PyNames.NEW; - final PyFunction complementaryMethod = cls.findMethodByName(complementaryName, true, null); + final PyFunction complementaryMethod = cls.findMethodByName(complementaryName, true, myTypeEvalContext); if (complementaryMethod == null || PyUtil.isObjectClass(assertNotNull(complementaryMethod.getContainingClass()))) return; if (!PyUtil.isSignatureCompatibleTo(complementaryMethod, node, myTypeEvalContext) && !PyUtil.isSignatureCompatibleTo(node, complementaryMethod, myTypeEvalContext) && node.getContainingFile() == cls.getContainingFile()) { - registerProblem(node.getParameterList(), PyNames.NEW.equals(node.getName()) ? PyBundle.message("INSP.new.incompatible.to.init") : - PyBundle.message("INSP.init.incompatible.to.new"), + registerProblem(node.getParameterList(), PyBundle.message(PyNames.NEW.equals(node.getName()) ? "INSP.new.incompatible.to.init" + : "INSP.init.incompatible.to.new"), new PyChangeSignatureQuickFix(false)); } } From 48a0d643e2b21bec8a5c9bead40de57cc7ba8166 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 4 May 2017 17:39:33 +0300 Subject: [PATCH 63/84] PY-8174 Refactor PyChangeSignatureQuickfix to allow passing extra parameters --- .../PyInitNewSignatureInspection.java | 2 +- .../PyMethodOverridingInspection.java | 2 +- .../quickfix/PyChangeSignatureQuickFix.java | 93 ++++++++++++------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java index fdec3cde1bc5..ace34764820b 100644 --- a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java @@ -70,7 +70,7 @@ public class PyInitNewSignatureInspection extends PyInspection { node.getContainingFile() == cls.getContainingFile()) { registerProblem(node.getParameterList(), PyBundle.message(PyNames.NEW.equals(node.getName()) ? "INSP.new.incompatible.to.init" : "INSP.init.incompatible.to.new"), - new PyChangeSignatureQuickFix(false)); + PyChangeSignatureQuickFix.forMismatchingMethods(node, complementaryMethod)); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java index 9540013c42e0..cf335472c09f 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java @@ -69,7 +69,7 @@ public class PyMethodOverridingInspection extends PyInspection { final String msg = PyBundle.message("INSP.signature.mismatch", cls.getName() + "." + name + "()", baseClass != null ? baseClass.getName() : ""); - registerProblem(function.getParameterList(), msg, new PyChangeSignatureQuickFix(true)); + registerProblem(function.getParameterList(), msg, PyChangeSignatureQuickFix.forMismatchingMethods(function, baseMethod)); } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 1acbd0d2bcd0..adeb3ff355a5 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -15,30 +15,46 @@ */ package com.jetbrains.python.inspections.quickfix; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; +import com.google.common.collect.Iterators; +import com.google.common.collect.PeekingIterator; +import com.intellij.codeInspection.LocalQuickFixOnPsiElement; import com.intellij.openapi.project.Project; -import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.search.PySuperMethodsSearch; -import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog; import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor; import com.jetbrains.python.refactoring.changeSignature.PyParameterInfo; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; -public class PyChangeSignatureQuickFix implements LocalQuickFix { +public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { - private final boolean myOverridenMethod; + @NotNull private final List> myExtraParameters; - public PyChangeSignatureQuickFix(boolean overriddenMethod) { - myOverridenMethod = overriddenMethod; + @NotNull + public static PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) { + final int paramLength = function.getParameterList().getParameters().length; + final int complementaryParamLength = complementary.getParameterList().getParameters().length; + if (complementaryParamLength > paramLength) { + return new PyChangeSignatureQuickFix(function, + Collections.singletonList(Pair.create(paramLength - 1, + new PyParameterInfo(-1, "**kwargs", "", false)))); + } + return new PyChangeSignatureQuickFix(function, Collections.emptyList()); + } + + + public PyChangeSignatureQuickFix(@NotNull PyFunction function, @NotNull List> extraParameters) { + super(function); + myExtraParameters = ContainerUtil.sorted(extraParameters, Comparator.comparingInt(p -> p.getFirst())); } @NotNull @@ -46,33 +62,40 @@ public class PyChangeSignatureQuickFix implements LocalQuickFix { return PyBundle.message("QFIX.NAME.change.signature"); } - public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { - final PyFunction function = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); - if (function == null) return; - final PyClass cls = function.getContainingClass(); - assert cls != null; - final String functionName = function.getName(); - final String complementaryName = PyNames.NEW.equals(functionName) ? PyNames.INIT : PyNames.NEW; - final TypeEvalContext context = TypeEvalContext.userInitiated(project, descriptor.getEndElement().getContainingFile()); - final PyFunction complementaryMethod = myOverridenMethod ? (PyFunction)PySuperMethodsSearch.search(function, context).findFirst() - : cls.findMethodByName(complementaryName, true, null); + @NotNull + @Override + public String getText() { + return getFamilyName(); + } - assert complementaryMethod != null; - final PyMethodDescriptor methodDescriptor = new PyMethodDescriptor(function) { - @Override - public List getParameters() { - final List parameterInfos = super.getParameters(); - final int paramLength = function.getParameterList().getParameters().length; - final int complementaryParamLength = complementaryMethod.getParameterList().getParameters().length; - if (complementaryParamLength > paramLength) - parameterInfos.add(new PyParameterInfo(-1, "**kwargs", "", false)); - return parameterInfos; - } - }; - final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, methodDescriptor); + @Override + public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { + final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor((PyFunction)startElement)); dialog.show(); } + @NotNull + private PyMethodDescriptor createMethodDescriptor(final PyFunction function) { + return new PyMethodDescriptor(function) { + @Override + public List getParameters() { + final List result = new ArrayList<>(); + final List originalParams = super.getParameters(); + final PeekingIterator> extra = Iterators.peekingIterator(myExtraParameters.iterator()); + while (extra.hasNext() && extra.peek().getFirst() < 0) { + result.add(extra.next().getSecond()); + } + for (int i = 0; i < originalParams.size(); i++) { + result.add(originalParams.get(i)); + while (extra.hasNext() && extra.peek().getFirst() == i) { + result.add(extra.next().getSecond()); + } + } + return result; + } + }; + } + @Override public boolean startInWriteAction() { return false; From fcd8262c10d3248efc30cbd21f06264a9114752a Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 4 May 2017 19:07:26 +0300 Subject: [PATCH 64/84] PY-8174 Initial version of the quickfix --- .../com/jetbrains/python/PyBundle.properties | 1 + .../inspections/PyArgumentListInspection.java | 16 ++++- .../quickfix/PyChangeSignatureQuickFix.java | 64 +++++++++++++++++-- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index eea21e952154..f8f65fa4930b 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -150,6 +150,7 @@ QFIX.NAME.add.specifier=Add format specifier character QFIX.NAME.add.exception.base=Add Exception base class QFIX.NAME.change.signature=Change signature +QFIX.change.signature.of=Change signature of {0} QFIX.NAME.remove.argument=Remove argument diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index d3f80dc20f00..9a1e815cf0e5 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -28,6 +28,7 @@ import com.intellij.xml.util.XmlStringUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix; import com.jetbrains.python.inspections.quickfix.PyRemoveArgumentQuickFix; import com.jetbrains.python.inspections.quickfix.PyRenameArgumentQuickFix; import com.jetbrains.python.psi.*; @@ -207,7 +208,18 @@ public class PyArgumentListInspection extends PyInspection { // if there is only one mapping, we could suggest quick fixes final Set duplicateKeywords = getDuplicateKeywordArguments(node); - for (PyExpression argument : mappings.get(0).getUnmappedArguments()) { + final PyCallExpression.PyArgumentsMapping mapping = mappings.get(0); + if (!mapping.getUnmappedArguments().isEmpty() && mapping.getUnmappedParameters().isEmpty()) { + final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee(); + if (markedCallee != null && markedCallee.getCallable() instanceof PyFunction) { + holder.registerProblem(node, + PyBundle.message("INSP.unexpected.arg(s)"), + PyChangeSignatureQuickFix.forMismatchedCall(mapping)); + } + } + + + for (PyExpression argument : mapping.getUnmappedArguments()) { final List quickFixes = Lists.newArrayList(new PyRemoveArgumentQuickFix()); if (argument instanceof PyKeywordArgument) { if (duplicateKeywords.contains(((PyKeywordArgument)argument).getKeyword())) { @@ -225,7 +237,7 @@ public class PyArgumentListInspection extends PyInspection { holder.registerProblem(node, addPossibleCalleesRepresentationAndWrapInHtml(PyBundle.message("INSP.unexpected.arg(s)"), mappings, context)); } } - + private static void highlightUnfilledParameters(@NotNull PyArgumentList node, @NotNull ProblemsHolder holder, @NotNull List mappings, diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index adeb3ff355a5..d92fdece4a43 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -20,24 +20,66 @@ import com.google.common.collect.PeekingIterator; import com.intellij.codeInspection.LocalQuickFixOnPsiElement; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.PyCallExpression.PyArgumentsMapping; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog; import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor; import com.jetbrains.python.refactoring.changeSignature.PyParameterInfo; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import static com.jetbrains.python.psi.PyUtil.as; + public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { - @NotNull private final List> myExtraParameters; + private final List> myExtraParameters; + + @NotNull + public static PyChangeSignatureQuickFix forMismatchedCall(@NotNull PyArgumentsMapping mapping) { + assert mapping.getMarkedCallee() != null; + final PyFunction function = as(mapping.getMarkedCallee().getCallable(), PyFunction.class); + final PyCallExpression callExpression = mapping.getCallExpression(); + assert function != null; + int positionalParamAnchor = -1; + final PyParameter[] parameters = function.getParameterList().getParameters(); + for (PyParameter parameter : parameters) { + final PyNamedParameter namedParam = parameter.getAsNamed(); + final boolean isVararg = namedParam != null && (namedParam.isPositionalContainer() || namedParam.isKeywordContainer()); + if (parameter instanceof PySingleStarParameter || parameter.hasDefaultValue() || isVararg) { + break; + } + positionalParamAnchor++; + } + final List> newParameters = new ArrayList<>(); + for (PyExpression arg : mapping.getUnmappedArguments()) { + if (arg instanceof PyKeywordArgument) { + newParameters.add(Pair.create(parameters.length - 1, new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), "", false))); + } + else { + final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callExpression.getContainingFile()); + final PyType type = context.getType(arg); + final String typeName = type != null && type.getName() != null ? type.getName() : PyNames.OBJECT; + final String paramName = PyRefactoringUtil.selectUniqueNameFromType(typeName, function.getStatementList()); + newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, "", false))); + } + } + return new PyChangeSignatureQuickFix(function, newParameters); + } + @NotNull public static PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) { @@ -65,12 +107,26 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { @NotNull @Override public String getText() { - return getFamilyName(); + final PyFunction function = getFunction(); + if (function == null) { + return getFamilyName(); + } + final String params = StringUtil.join(createMethodDescriptor(function).getParameters(), info -> { + return info.getOldIndex() == -1 ? "" + info.getName() + "" : info.getName(); + }, ", "); + return "" + + PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")") + + ""; + } + + @Nullable + private PyFunction getFunction() { + return (PyFunction)getStartElement(); } @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor((PyFunction)startElement)); + final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor(getFunction())); dialog.show(); } From 7837c5a19d30320239260d048a78dba4ab6a62d4 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 5 May 2017 14:54:53 +0300 Subject: [PATCH 65/84] PY-8174 Check that the file containing the function to be changed is writable --- .../inspections/quickfix/PyChangeSignatureQuickFix.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index d92fdece4a43..244226f43322 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -52,8 +52,8 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { public static PyChangeSignatureQuickFix forMismatchedCall(@NotNull PyArgumentsMapping mapping) { assert mapping.getMarkedCallee() != null; final PyFunction function = as(mapping.getMarkedCallee().getCallable(), PyFunction.class); - final PyCallExpression callExpression = mapping.getCallExpression(); assert function != null; + final PyCallExpression callExpression = mapping.getCallExpression(); int positionalParamAnchor = -1; final PyParameter[] parameters = function.getParameterList().getParameters(); for (PyParameter parameter : parameters) { @@ -152,6 +152,12 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { }; } + @Nullable + @Override + public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) { + return getFunction(); + } + @Override public boolean startInWriteAction() { return false; From b05653ccc85f218cb7e072c2c96da3d0bcdfcab0 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 5 May 2017 17:12:36 +0300 Subject: [PATCH 66/84] PY-8174 Don't show the quickfix where Change Signature is not possible Mostly, because we strictly prohibit changing signatures of functions in excluded directories and in library roots outside of the project root even if they can be made writable as in case with a common virtual env directory in user's home. --- .../python/inspections/PyArgumentListInspection.java | 12 ++++++++---- .../changeSignature/PyChangeSignatureHandler.java | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index 9a1e815cf0e5..1d5c432431a3 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -20,6 +20,7 @@ import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.ResolveResult; @@ -37,6 +38,7 @@ import com.jetbrains.python.psi.types.PyABCUtil; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.PyTypeChecker; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureHandler; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -211,10 +213,12 @@ public class PyArgumentListInspection extends PyInspection { final PyCallExpression.PyArgumentsMapping mapping = mappings.get(0); if (!mapping.getUnmappedArguments().isEmpty() && mapping.getUnmappedParameters().isEmpty()) { final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee(); - if (markedCallee != null && markedCallee.getCallable() instanceof PyFunction) { - holder.registerProblem(node, - PyBundle.message("INSP.unexpected.arg(s)"), - PyChangeSignatureQuickFix.forMismatchedCall(mapping)); + if (markedCallee != null) { + final PyCallable callable = markedCallee.getCallable(); + final Project project = node.getProject(); + if (callable instanceof PyFunction && !PyChangeSignatureHandler.isNotUnderSourceRoot(project, callable.getContainingFile())) { + holder.registerProblem(node, PyBundle.message("INSP.unexpected.arg(s)"), PyChangeSignatureQuickFix.forMismatchedCall(mapping)); + } } } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java index 6b67be8045df..3fac20e8f0b4 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java @@ -139,7 +139,7 @@ public class PyChangeSignatureHandler implements ChangeSignatureHandler { CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, "refactoring.renameRefactorings"); } - private static boolean isNotUnderSourceRoot(@NotNull final Project project, @Nullable final PsiFile psiFile) { + public static boolean isNotUnderSourceRoot(@NotNull final Project project, @Nullable final PsiFile psiFile) { if (psiFile == null) { return true; } From 58d6406e07bcc7825bbae02f48e3a18ade67bfcf Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 5 May 2017 17:55:01 +0300 Subject: [PATCH 67/84] PY-24149 Focus on the first function parameter by default in Change Signature dialog --- .../refactoring/changeSignature/PyChangeSignatureDialog.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index 0d3857a0f85d..0bd46f68787a 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -229,11 +229,6 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase Date: Fri, 5 May 2017 18:02:28 +0300 Subject: [PATCH 68/84] PY-8174 Focus on the first new parameter in the dialog --- .../inspections/quickfix/PyChangeSignatureQuickFix.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 244226f43322..117de83ec401 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -34,6 +34,7 @@ import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog; import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor; import com.jetbrains.python.refactoring.changeSignature.PyParameterInfo; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -126,7 +127,13 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor(getFunction())); + final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor(getFunction())) { + // Similar to JavaChangeSignatureDialog.createAndPreselectNew() + @Override + protected int getSelectedIdx() { + return (int)StreamEx.of(getParameters()).indexOf(info -> info.getOldIndex() < 0).orElse(super.getSelectedIdx()); + } + }; dialog.show(); } From 61797297a0c28f64bf51d010a8efef3793d5a98e Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 11 May 2017 16:52:07 +0300 Subject: [PATCH 69/84] PY-24150 Fix scaling by using EditorTextFieldJBTableRowRendered in the dialog Also provided straightforward implementation ParametersListTable and thus got rid of deprecated methods. --- .../PyChangeSignatureDialog.java | 274 +++++++++--------- 1 file changed, 145 insertions(+), 129 deletions(-) diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index 0bd46f68787a..99613726848f 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -42,9 +42,10 @@ import com.intellij.util.Consumer; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.HashSet; import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.table.JBListTable; +import com.intellij.util.ui.table.EditorTextFieldJBTableRowRenderer; import com.intellij.util.ui.table.JBTableRow; import com.intellij.util.ui.table.JBTableRowEditor; +import com.intellij.util.ui.table.JBTableRowRenderer; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; @@ -54,6 +55,7 @@ import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyParameterList; import com.jetbrains.python.refactoring.introduce.IntroduceValidator; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -255,146 +257,160 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase item, boolean selected, final boolean focused) { - String text = item.parameter.getName(); - final String defaultCallValue = item.defaultValueCodeFragment.getText(); - PyParameterTableModelItem pyItem = (PyParameterTableModelItem)item; - final String defaultValue = pyItem.isDefaultInSignature()? pyItem.defaultValueCodeFragment.getText() : ""; - - if (StringUtil.isNotEmpty(defaultValue)) { - text += " = " + defaultValue; - } - - String tail = ""; - if (StringUtil.isNotEmpty(defaultCallValue)) { - tail += " default value = " + defaultCallValue; - } - if (!StringUtil.isEmpty(tail)) { - text += " //" + tail; - } - return JBListTable.createEditorTextFieldPresentation(getProject(), getFileType(), " " + text, selected, focused); - } - - @Override - protected boolean isListTableViewSupported() { - return true; - } - - @Override - protected JBTableRowEditor getTableEditor(final JTable t, final ParameterTableModelItemBase item) { - return new JBTableRowEditor() { - private EditorTextField myNameEditor; - private EditorTextField myDefaultValueEditor; - private JCheckBox myDefaultInSignature; - + protected ParametersListTable createParametersListTable() { + return new ParametersListTable() { @Override - public void prepareEditor(JTable table, int row) { - setLayout(new GridLayout(1, 3)); - final JPanel parameterPanel = createParameterPanel(); - add(parameterPanel); - final JPanel defaultValuePanel = createDefaultValuePanel(); - add(defaultValuePanel); - final JPanel defaultValueCheckBox = createDefaultValueCheckBox(); - add(defaultValueCheckBox); - - final String nameText = myNameEditor.getText(); - myDefaultValueEditor.setEnabled(!nameText.startsWith("*") - && !PyNames.CANONICAL_SELF.equals(nameText)); - myDefaultInSignature.setEnabled(!nameText.startsWith("*") - && !PyNames.CANONICAL_SELF.equals(nameText)); - } - - private JPanel createDefaultValueCheckBox() { - final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - - final JBLabel inSignatureLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.checkbox"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(inSignatureLabel, - myDefaultInSignature); - defaultValuePanel.add(inSignatureLabel, BorderLayout.WEST); - myDefaultInSignature = new JCheckBox(); - myDefaultInSignature.setSelected( - ((PyParameterTableModelItem)item).isDefaultInSignature()); - myDefaultInSignature.addItemListener(new ItemListener() { + protected JBTableRowRenderer getRowRenderer(int row) { + return new EditorTextFieldJBTableRowRenderer(getProject(), getFileType(), getDisposable()) { @Override - public void itemStateChanged(ItemEvent event) { - ((PyParameterTableModelItem)item) - .setDefaultInSignature(myDefaultInSignature.isSelected()); - } - }); - myDefaultInSignature.addChangeListener(mySignatureUpdater); - myDefaultInSignature.setEnabled(item.parameter.getOldIndex() == -1); - defaultValuePanel.add(myDefaultInSignature, BorderLayout.EAST); - return defaultValuePanel; - } + protected String getText(JTable table, int row) { + final PyParameterTableModelItem pyItem = getRowItem(row); + String text = pyItem.parameter.getName(); + final String defaultCallValue = pyItem.defaultValueCodeFragment.getText(); + final String defaultValue = pyItem.isDefaultInSignature() ? pyItem.defaultValueCodeFragment.getText() : ""; - private JPanel createDefaultValuePanel() { - final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment); - myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType()); - final JBLabel defaultValueLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.label"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(defaultValueLabel, myDefaultValueEditor); - defaultValuePanel.add(defaultValueLabel); - defaultValuePanel.add(myDefaultValueEditor); - myDefaultValueEditor.setPreferredWidth(t.getWidth() / 2); - myDefaultValueEditor.addDocumentListener(mySignatureUpdater); - return defaultValuePanel; - } - - private JPanel createParameterPanel() { - final JPanel namePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType()); - final JBLabel nameLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.name.label"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(nameLabel, myNameEditor); - namePanel.add(nameLabel); - namePanel.add(myNameEditor); - myNameEditor.setPreferredWidth(t.getWidth() / 2); - myNameEditor.addDocumentListener(new DocumentListener() { - @Override - public void documentChanged(DocumentEvent event) { - fireDocumentChanged(event, 0); - myDefaultValueEditor.setEnabled(!myNameEditor.getText().startsWith("*")); - myDefaultInSignature.setEnabled(!myNameEditor.getText().startsWith("*")); - } - }); - - myNameEditor.addDocumentListener(mySignatureUpdater); - return namePanel; - } - - @Override - public JBTableRow getValue() { - return new JBTableRow() { - @Override - public Object getValueAt(int column) { - switch (column) { - case 0: return myNameEditor.getText().trim(); - case 1: return new Pair<>(item.defaultValueCodeFragment, - ((PyParameterTableModelItem)item).isDefaultInSignature()); + if (StringUtil.isNotEmpty(defaultValue)) { + text += " = " + defaultValue; } - return null; + + String tail = ""; + if (StringUtil.isNotEmpty(defaultCallValue)) { + tail += " default value = " + defaultCallValue; + } + if (!StringUtil.isEmpty(tail)) { + text += " //" + tail; + } + return text; + } + }; + } + + @NotNull + @Override + protected JBTableRowEditor getRowEditor(ParameterTableModelItemBase item) { + return new JBTableRowEditor() { + private EditorTextField myNameEditor; + private EditorTextField myDefaultValueEditor; + private JCheckBox myDefaultInSignature; + + @Override + public void prepareEditor(JTable table, int row) { + setLayout(new GridLayout(1, 3)); + final JPanel parameterPanel = createParameterPanel(); + add(parameterPanel); + final JPanel defaultValuePanel = createDefaultValuePanel(); + add(defaultValuePanel); + final JPanel defaultValueCheckBox = createDefaultValueCheckBox(); + add(defaultValueCheckBox); + + final String nameText = myNameEditor.getText(); + myDefaultValueEditor.setEnabled(!nameText.startsWith("*") && !PyNames.CANONICAL_SELF.equals(nameText)); + myDefaultInSignature.setEnabled(!nameText.startsWith("*") && !PyNames.CANONICAL_SELF.equals(nameText)); + } + + private JPanel createDefaultValueCheckBox() { + final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + + final JBLabel inSignatureLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.checkbox"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(inSignatureLabel, + myDefaultInSignature); + defaultValuePanel.add(inSignatureLabel, BorderLayout.WEST); + myDefaultInSignature = new JCheckBox(); + myDefaultInSignature.setSelected(((PyParameterTableModelItem)item).isDefaultInSignature()); + myDefaultInSignature.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + ((PyParameterTableModelItem)item).setDefaultInSignature(myDefaultInSignature.isSelected()); + } + }); + myDefaultInSignature.addChangeListener(mySignatureUpdater); + myDefaultInSignature.setEnabled(item.parameter.getOldIndex() == -1); + defaultValuePanel.add(myDefaultInSignature, BorderLayout.EAST); + return defaultValuePanel; + } + + private JPanel createDefaultValuePanel() { + final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment); + myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType()); + final JBLabel defaultValueLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.label"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(defaultValueLabel, myDefaultValueEditor); + defaultValuePanel.add(defaultValueLabel); + defaultValuePanel.add(myDefaultValueEditor); + myDefaultValueEditor.setPreferredWidth(getTable().getWidth() / 2); + myDefaultValueEditor.addDocumentListener(mySignatureUpdater); + return defaultValuePanel; + } + + private JPanel createParameterPanel() { + final JPanel namePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType()); + final JBLabel nameLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.name.label"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(nameLabel, myNameEditor); + namePanel.add(nameLabel); + namePanel.add(myNameEditor); + myNameEditor.setPreferredWidth(getTable().getWidth() / 2); + myNameEditor.addDocumentListener(new DocumentListener() { + @Override + public void documentChanged(DocumentEvent event) { + fireDocumentChanged(event, 0); + myDefaultValueEditor.setEnabled(!myNameEditor.getText().startsWith("*")); + myDefaultInSignature.setEnabled(!myNameEditor.getText().startsWith("*")); + } + }); + + myNameEditor.addDocumentListener(mySignatureUpdater); + return namePanel; + } + + @Override + public JBTableRow getValue() { + return new JBTableRow() { + @Override + public Object getValueAt(int column) { + switch (column) { + case 0: + return myNameEditor.getText().trim(); + case 1: + return new Pair<>(item.defaultValueCodeFragment, + ((PyParameterTableModelItem)item).isDefaultInSignature()); + } + return null; + } + }; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myNameEditor.getFocusTarget(); + } + + @Override + public JComponent[] getFocusableComponents() { + final List focusable = new ArrayList<>(); + focusable.add(myNameEditor.getFocusTarget()); + if (myDefaultValueEditor != null) { + focusable.add(myDefaultValueEditor.getFocusTarget()); + } + return focusable.toArray(new JComponent[focusable.size()]); } }; } @Override - public JComponent getPreferredFocusedComponent() { - return myNameEditor.getFocusTarget(); - } - - @Override - public JComponent[] getFocusableComponents() { - final List focusable = new ArrayList<>(); - focusable.add(myNameEditor.getFocusTarget()); - if (myDefaultValueEditor != null) { - focusable.add(myDefaultValueEditor.getFocusTarget()); - } - return focusable.toArray(new JComponent[focusable.size()]); + protected boolean isRowEmpty(int row) { + return false; } }; } + + @Override + protected boolean isListTableViewSupported() { + return true; + } @Override protected boolean mayPropagateParameters() { From 9af15aabf161740114a7eab5ac588fb3592e2274 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 11 May 2017 17:19:42 +0300 Subject: [PATCH 70/84] PY-19010 Properly focus on the checkbox "Use default value in signature" on Tab It did work for Tab already, yet not for Shift+Tab when the focus was switching between "Name" and "Default value" fields. --- .../refactoring/changeSignature/PyChangeSignatureDialog.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index 99613726848f..623d5e7f74d4 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -395,6 +395,9 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase Date: Thu, 11 May 2017 17:37:24 +0300 Subject: [PATCH 71/84] PY-8174 Fix several code style issues and warnings in the dialog Method doValidate() is already indented to be called of EDT, so additional invokeLater() is not necessary. --- .../PyChangeSignatureDialog.java | 59 ++++++++----------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index 623d5e7f74d4..aa4984030914 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -38,6 +38,7 @@ import com.intellij.refactoring.ui.VisibilityPanelBase; import com.intellij.ui.EditorTextField; import com.intellij.ui.components.JBLabel; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.HashSet; @@ -54,7 +55,6 @@ import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyParameterList; import com.jetbrains.python.refactoring.introduce.IntroduceValidator; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,10 +70,10 @@ import java.util.Set; * User : ktisha */ -public class PyChangeSignatureDialog extends ChangeSignatureDialogBase { +public class PyChangeSignatureDialog extends + ChangeSignatureDialogBase { - public PyChangeSignatureDialog(Project project, - PyMethodDescriptor method) { + public PyChangeSignatureDialog(Project project, PyMethodDescriptor method) { super(project, method, false, method.getMethod().getContext()); } @@ -109,9 +109,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase parameters = myParametersTableModel.getItems(); - Set parameterNames = new HashSet<>(); + final Set parameterNames = new HashSet<>(); boolean hadPositionalContainer = false; boolean hadKeywordContainer = false; boolean hadDefaultValue = false; boolean hadSingleStar = false; boolean hadParamsAfterSingleStar = false; - LanguageLevel languageLevel = LanguageLevel.forElement(myMethod.getMethod()); + final LanguageLevel languageLevel = LanguageLevel.forElement(myMethod.getMethod()); - int parametersLength = parameters.size(); + final int parametersLength = parameters.size(); - for (int index = 0; index != parametersLength; ++index) { - PyParameterTableModelItem info = parameters.get(index); + for (int index = 0; index < parametersLength; index++) { + final PyParameterTableModelItem info = parameters.get(index); final PyParameterInfo parameter = info.parameter; final String name = parameter.getName(); final String nameWithoutStars = StringUtil.trimLeading(name, '*').trim(); @@ -150,7 +148,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase { - getRefactorAction().setEnabled(message == null); - getPreviewAction().setEnabled(message == null); - }); + getRefactorAction().setEnabled(message == null); + getPreviewAction().setEnabled(message == null); if (message != null) return new ValidationInfo(message); return super.doValidate(); } @Override protected String calculateSignature() { - @NonNls StringBuilder builder = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); builder.append(getMethodName()); builder.append("("); final List parameters = myParametersTableModel.getItems(); - for (int i = 0; i != parameters.size(); ++i) { - PyParameterTableModelItem parameterInfo = parameters.get(i); + for (int i = 0; i < parameters.size(); i++) { + final PyParameterTableModelItem parameterInfo = parameters.get(i); builder.append(parameterInfo.parameter.getName()); final String defaultValue = parameterInfo.defaultValueCodeFragment.getText(); if (!defaultValue.isEmpty() && parameterInfo.isDefaultInSignature()) { - builder.append(" = " + defaultValue); + builder.append(" = ").append(defaultValue); } - if (i != parameters.size()-1) + if (i != parameters.size() - 1) { builder.append(", "); + } } builder.append(")"); return builder.toString(); @@ -253,7 +250,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase createVisibilityControl() { - return new ComboBoxVisibilityPanel<>(new String[0]); + return new ComboBoxVisibilityPanel<>(ArrayUtil.EMPTY_STRING_ARRAY); } @Override @@ -265,22 +262,18 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase Date: Tue, 16 May 2017 15:09:34 +0300 Subject: [PATCH 72/84] PY-8174 Use provided arguments as default values for new parameters --- .../com/jetbrains/python/PyTokenTypes.java | 4 ++ .../quickfix/PyChangeSignatureQuickFix.java | 59 ++++++++++++------- .../python/refactoring/PyRefactoringUtil.java | 14 +++++ .../PyChangeSignatureDialog.java | 2 +- .../PyChangeSignatureProcessor.java | 8 +-- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java index 70f636351f3a..f35ce48bee2d 100644 --- a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java +++ b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java @@ -165,6 +165,10 @@ public class PyTokenTypes { public static final TokenSet WHITESPACE_OR_LINEBREAK = TokenSet.create(SPACE, TAB, FORMFEED, LINE_BREAK); public static final TokenSet OPEN_BRACES = TokenSet.create(LBRACKET, LBRACE, LPAR); public static final TokenSet CLOSE_BRACES = TokenSet.create(RBRACKET, RBRACE, RPAR); + + public static final TokenSet NUMERIC_LITERALS = TokenSet.create(FLOAT_LITERAL, INTEGER_LITERAL, IMAGINARY_LITERAL); + public static final TokenSet BOOL_LITERALS = TokenSet.create(TRUE_KEYWORD, FALSE_KEYWORD); + public static final TokenSet SCALAR_LITERALS = TokenSet.orSet(STRING_NODES, BOOL_LITERALS, NUMERIC_LITERALS, TokenSet.create(NONE_KEYWORD)); public static final TokenSet AUG_ASSIGN_OPERATIONS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, ATEQ, DIVEQ, PERCEQ, EXPEQ, GTGTEQ, LTLTEQ, ANDEQ, OREQ, XOREQ, FLOORDIVEQ); diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 117de83ec401..c4ffdf79bd25 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -18,6 +18,7 @@ package com.jetbrains.python.inspections.quickfix; import com.google.common.collect.Iterators; import com.google.common.collect.PeekingIterator; import com.intellij.codeInspection.LocalQuickFixOnPsiElement; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; @@ -68,7 +69,16 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { final List> newParameters = new ArrayList<>(); for (PyExpression arg : mapping.getUnmappedArguments()) { if (arg instanceof PyKeywordArgument) { - newParameters.add(Pair.create(parameters.length - 1, new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), "", false))); + final String defaultValueText; + final PyExpression value = ((PyKeywordArgument)arg).getValueExpression(); + if (value != null && PyRefactoringUtil.isSimpleExpression(value) && !value.textContains('\n')) { + defaultValueText = value.getText(); + } + else { + defaultValueText = ApplicationManager.getApplication().isUnitTestMode() ? "None" : ""; + } + newParameters.add(Pair.create(parameters.length - 1, + new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), defaultValueText, true))); } else { final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callExpression.getContainingFile()); @@ -77,10 +87,10 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { final String paramName = PyRefactoringUtil.selectUniqueNameFromType(typeName, function.getStatementList()); newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, "", false))); } - } + } return new PyChangeSignatureQuickFix(function, newParameters); } - + @NotNull public static PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) { @@ -117,7 +127,7 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { }, ", "); return "" + PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")") + - ""; + ""; } @Nullable @@ -127,36 +137,45 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, createMethodDescriptor(getFunction())) { + final PyFunction function = getFunction(); + final PyMethodDescriptor descriptor = createMethodDescriptor(function); + + final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, descriptor) { // Similar to JavaChangeSignatureDialog.createAndPreselectNew() @Override protected int getSelectedIdx() { return (int)StreamEx.of(getParameters()).indexOf(info -> info.getOldIndex() < 0).orElse(super.getSelectedIdx()); } }; - dialog.show(); + + if (ApplicationManager.getApplication().isUnitTestMode()) { + dialog.createRefactoringProcessor().run(); + } + else { + dialog.show(); + } } @NotNull private PyMethodDescriptor createMethodDescriptor(final PyFunction function) { return new PyMethodDescriptor(function) { - @Override - public List getParameters() { - final List result = new ArrayList<>(); - final List originalParams = super.getParameters(); - final PeekingIterator> extra = Iterators.peekingIterator(myExtraParameters.iterator()); - while (extra.hasNext() && extra.peek().getFirst() < 0) { + @Override + public List getParameters() { + final List result = new ArrayList<>(); + final List originalParams = super.getParameters(); + final PeekingIterator> extra = Iterators.peekingIterator(myExtraParameters.iterator()); + while (extra.hasNext() && extra.peek().getFirst() < 0) { + result.add(extra.next().getSecond()); + } + for (int i = 0; i < originalParams.size(); i++) { + result.add(originalParams.get(i)); + while (extra.hasNext() && extra.peek().getFirst() == i) { result.add(extra.next().getSecond()); } - for (int i = 0; i < originalParams.size(); i++) { - result.add(originalParams.get(i)); - while (extra.hasNext() && extra.peek().getFirst() == i) { - result.add(extra.next().getSecond()); - } - } - return result; } - }; + return result; + } + }; } @Nullable diff --git a/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java index 4448804a780c..14592a8abbe1 100644 --- a/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java @@ -17,6 +17,7 @@ package com.jetbrains.python.refactoring; import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.find.findUsages.FindUsagesHandler; +import com.intellij.lang.ASTNode; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; @@ -28,6 +29,7 @@ import com.intellij.usageView.UsageInfo; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.jetbrains.python.PyNames; +import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory; import com.jetbrains.python.psi.*; import com.jetbrains.python.refactoring.introduce.IntroduceValidator; @@ -397,4 +399,16 @@ public class PyRefactoringUtil { public static boolean isValidNewName(@NotNull String name, @NotNull PsiElement scopeAnchor) { return !(IntroduceValidator.isDefinedInScope(name, scopeAnchor) || PyNames.isReserved(name)); } + + public static boolean isSimpleExpression(@NotNull PyExpression value) { + if (value instanceof PyLiteralExpression) { + final ASTNode node = value.getNode(); + // Check that string literal doesn't contain multiple glued nodes + return node.getChildren(null).length == 1 && PyTokenTypes.SCALAR_LITERALS.contains(node.getFirstChildNode().getElementType()); + } + else if (value instanceof PyReferenceExpression) { + return PyUtil.isPy2ReservedWord((PyReferenceExpression)value); + } + return false; + } } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index aa4984030914..09ba82f0c219 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -89,7 +89,7 @@ public class PyChangeSignatureDialog extends } @Override - protected BaseRefactoringProcessor createRefactoringProcessor() { + public BaseRefactoringProcessor createRefactoringProcessor() { final List parameters = getParameters(); return new PyChangeSignatureProcessor(myProject, myMethod.getMethod(), getMethodName(), parameters.toArray(new PyParameterInfo[parameters.size()])); diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java index 7dc010b07233..9b19d26d9577 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java @@ -28,10 +28,10 @@ import org.jetbrains.annotations.NotNull; public class PyChangeSignatureProcessor extends ChangeSignatureProcessorBase { - protected PyChangeSignatureProcessor(Project project, - PyFunction method, - String newName, - PyParameterInfo[] parameterInfo) { + public PyChangeSignatureProcessor(Project project, + PyFunction method, + String newName, + PyParameterInfo[] parameterInfo) { super(project, generateChangeInfo(method, newName, parameterInfo)); } From 05e9418a47eab0c45415c32b82afe0aff2e69a75 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 30 May 2017 15:19:41 +0300 Subject: [PATCH 73/84] PY-8174 Include the new warning, the quickfix is bound to, in the test data --- .../PyArgumentListInspection/badarglist.py | 12 ++++++------ .../PyArgumentListInspection/decorators.py | 4 ++-- .../PyArgumentListInspection/kwargsMapToNothing.py | 4 ++-- .../inspections/PyArgumentListInspection/py1268.py | 14 +++++++------- .../inspections/PyArgumentListInspection/py3k.py | 2 +- .../PyArgumentListInspection/tupleVsLiteralList.py | 2 +- .../PyRemoveArgumentQuickFixTest/duplicate.py | 2 +- .../PyRemoveArgumentQuickFixTest/unexpected.py | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/python/testData/inspections/PyArgumentListInspection/badarglist.py b/python/testData/inspections/PyArgumentListInspection/badarglist.py index 8ac2755a27aa..08bf20f42b00 100644 --- a/python/testData/inspections/PyArgumentListInspection/badarglist.py +++ b/python/testData/inspections/PyArgumentListInspection/badarglist.py @@ -18,8 +18,8 @@ def f1(): pass f1() -f1(1) -f1(a = 1) +f1(1) +f1(a = 1) def f2(a): @@ -28,19 +28,19 @@ def f2(a): f2() # ok, fail f2(1) # ok, pass -f2(1, 2) # ok, fail +f2(1, 2) # ok, fail f2(a = 1) # ok, pass f2(b = 1) # ok, fail -f2(a = 1, b = 2) # ok, fail +f2(a = 1, b = 2) # ok, fail def f3(a, b): pass f3(1, 2) -f3(1, 2, 3) +f3(1, 2, 3) f3(b=2, a=1) -f3(b=1, b=2, a=1) +f3(b=1, b=2, a=1) f3(1, b=2) f3(a=1, 2) diff --git a/python/testData/inspections/PyArgumentListInspection/decorators.py b/python/testData/inspections/PyArgumentListInspection/decorators.py index 9377b5eb8148..6cb33c5d4842 100644 --- a/python/testData/inspections/PyArgumentListInspection/decorators.py +++ b/python/testData/inspections/PyArgumentListInspection/decorators.py @@ -13,7 +13,7 @@ def f7(): def f8(): pass -@deco(1, 2) # fail: extra param +@deco(1, 2) # fail: extra param def f9(): pass @@ -57,7 +57,7 @@ class Dec2: def __init__(self, p1, p2): pass -@Dec2() # fail: no p1, p2 +@Dec2() # fail: no p1, p2 def f17(): pass diff --git a/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py b/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py index 5029c3b56bd8..c285a60cf423 100644 --- a/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py +++ b/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py @@ -5,5 +5,5 @@ arg = [1, 2, 3] kwarg = {'c':3} f5(*arg, **kwarg) # ok f5(1,2, **kwarg) # ok -f5(1, 2, 3, **kwarg) # fail -f5(1, 2, 3, *arg) # fail +f5(1, 2, 3, **kwarg) # fail +f5(1, 2, 3, *arg) # fail diff --git a/python/testData/inspections/PyArgumentListInspection/py1268.py b/python/testData/inspections/PyArgumentListInspection/py1268.py index baaa9fd9e496..951df27428ee 100644 --- a/python/testData/inspections/PyArgumentListInspection/py1268.py +++ b/python/testData/inspections/PyArgumentListInspection/py1268.py @@ -3,8 +3,8 @@ def f(a, b, c): f(c=1, *(10, 20)) f(*(10, 20), c=1) -f(*(10, 20, 30), c=1) # fail: duplicate c -f(1, *(10, 20, 30)) # fail: tuple too long +f(*(10, 20, 30), c=1) # fail: duplicate c +f(1, *(10, 20, 30)) # fail: tuple too long f(1, *(10)) # fail: wrong type f(1, *(10,)) # fail: tuple too short, c not mapped @@ -24,14 +24,14 @@ f2(*(1,2), a=1, b=2, *(1,2)) # fail: a and b twice -f3(1, 2, *(3,), c=4) # fail: c twice +f3(c=3, a=1, b=2, *(1,2)) # fail: a and b twice +f3(1, 2, *(3,), c=4) # fail: c twice f3(1,2,3, *(1,2)) f3(c=3, *(1,2)) # -f3(1, c=3, *(1,2)) # fail: c twice -f3(c=3, a=1, b=2, d=(1,2)) # fail: unexpected d +f3(1, c=3, *(1,2)) # fail: c twice +f3(c=3, a=1, b=2, d=(1,2)) # fail: unexpected d f3(1, c=3, *(10,)) # ZZZ f3(1, *(10,)) f3(1, *(10,), c=20) f3(*(1,2), c=20) -f3(*(1,2), a=20) # fail: a twice +f3(*(1,2), a=20) # fail: a twice diff --git a/python/testData/inspections/PyArgumentListInspection/py3k.py b/python/testData/inspections/PyArgumentListInspection/py3k.py index 70e7820379f0..4ac87031c611 100644 --- a/python/testData/inspections/PyArgumentListInspection/py3k.py +++ b/python/testData/inspections/PyArgumentListInspection/py3k.py @@ -19,7 +19,7 @@ def a23(a, *b, c=1): pass a23(1,2,3, c=10) # pass -a23(1,2,3, c=10, a=1) # fail +a23(1,2,3, c=10, a=1) # fail a23(c=10, a=1) # pass a23(c=10, 1) # fail a23(*args, c=1) # pass diff --git a/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py b/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py index d3c41a99bd97..3936d0df1780 100644 --- a/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py +++ b/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py @@ -2,4 +2,4 @@ def f20(a, (b, c)): pass f20(1, [2, 3]) # ok -f20(1, (2, 3, 4)) # fail +f20(1, (2, 3, 4)) # fail diff --git a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py index 4716f42c8564..47b5067cc548 100644 --- a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py +++ b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py @@ -2,4 +2,4 @@ def foo(a, p): pass -foo(1, p=2, p=33) +foo(1, p=2, p=33) diff --git a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py index 82f42d31e15a..2ae09ef56bce 100644 --- a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py +++ b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py @@ -2,6 +2,6 @@ def foo(a): pass -foo(1, 23) +foo(1, 23) From 9eca5367bda1a89473b8beb3b121848822895f7b Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 19:06:17 +0300 Subject: [PATCH 74/84] PY-8174 Don't change the original call in change signature It doesn't make much sense to modifying the call expression as the result of refactoring which sole purpose was to match the definition and the rest of the usages with this very expression. --- .../quickfix/PyChangeSignatureQuickFix.java | 51 ++++++++++++++----- .../PyChangeSignatureUsageProcessor.java | 6 +++ .../AddKeywordAndPositionalParameters.py | 5 ++ ...AddKeywordAndPositionalParameters_after.py | 5 ++ .../com/jetbrains/python/PyQuickFixTest.java | 5 ++ 5 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 python/testData/inspections/AddKeywordAndPositionalParameters.py create mode 100644 python/testData/inspections/AddKeywordAndPositionalParameters_after.py diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index c4ffdf79bd25..1900f4ff5eed 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -20,10 +20,13 @@ import com.google.common.collect.PeekingIterator; import com.intellij.codeInspection.LocalQuickFixOnPsiElement; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; @@ -48,7 +51,7 @@ import static com.jetbrains.python.psi.PyUtil.as; public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { - private final List> myExtraParameters; + public static final Key CHANGE_SIGNATURE_ORIGINAL_CALL = Key.create("CHANGE_SIGNATURE_ORIGINAL_CALL"); @NotNull public static PyChangeSignatureQuickFix forMismatchedCall(@NotNull PyArgumentsMapping mapping) { @@ -88,26 +91,39 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, "", false))); } } - return new PyChangeSignatureQuickFix(function, newParameters); + return new PyChangeSignatureQuickFix(function, newParameters, mapping.getCallExpression()); } - @NotNull public static PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) { final int paramLength = function.getParameterList().getParameters().length; final int complementaryParamLength = complementary.getParameterList().getParameters().length; + final List> extraParams; if (complementaryParamLength > paramLength) { - return new PyChangeSignatureQuickFix(function, - Collections.singletonList(Pair.create(paramLength - 1, - new PyParameterInfo(-1, "**kwargs", "", false)))); + extraParams = Collections.singletonList(Pair.create(paramLength - 1, new PyParameterInfo(-1, "**kwargs", "", false))); } - return new PyChangeSignatureQuickFix(function, Collections.emptyList()); + else { + extraParams = Collections.emptyList(); + } + return new PyChangeSignatureQuickFix(function, extraParams, null); } - public PyChangeSignatureQuickFix(@NotNull PyFunction function, @NotNull List> extraParameters) { + private final List> myExtraParameters; + private final SmartPsiElementPointer myOriginalCallExpression; + + + public PyChangeSignatureQuickFix(@NotNull PyFunction function, + @NotNull List> extraParameters, + @Nullable PyCallExpression expression) { super(function); myExtraParameters = ContainerUtil.sorted(extraParameters, Comparator.comparingInt(p -> p.getFirst())); + if (expression != null) { + myOriginalCallExpression = SmartPointerManager.getInstance(function.getProject()).createSmartPsiElementPointer(expression); + } + else { + myOriginalCallExpression = null; + } } @NotNull @@ -148,11 +164,22 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { } }; - if (ApplicationManager.getApplication().isUnitTestMode()) { - dialog.createRefactoringProcessor().run(); + final PyCallExpression originalCall = myOriginalCallExpression.getElement(); + try { + if (originalCall != null) { + originalCall.putUserData(CHANGE_SIGNATURE_ORIGINAL_CALL, true); + } + if (ApplicationManager.getApplication().isUnitTestMode()) { + dialog.createRefactoringProcessor().run(); + } + else { + dialog.show(); + } } - else { - dialog.show(); + finally { + if (originalCall != null) { + originalCall.putUserData(CHANGE_SIGNATURE_ORIGINAL_CALL, null); + } } } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java index 2e7a96175f84..e84de500a694 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java @@ -34,6 +34,7 @@ import com.intellij.util.containers.MultiMap; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.documentation.docstrings.PyDocstringGenerator; +import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.refactoring.PyRefactoringUtil; @@ -101,8 +102,13 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc RenameUtil.doRenameGenericNamedElement(method, changeInfo.getNewName(), usages, null); } if (element == null) return false; + if (element.getParent() instanceof PyCallExpression) { final PyCallExpression call = (PyCallExpression)element.getParent(); + // Don't modify the call that was the cause of Change Signature invocation + if (call.getUserData(PyChangeSignatureQuickFix.CHANGE_SIGNATURE_ORIGINAL_CALL) != null) { + return true; + } final PyArgumentList argumentList = call.getArgumentList(); if (argumentList != null) { final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject()); diff --git a/python/testData/inspections/AddKeywordAndPositionalParameters.py b/python/testData/inspections/AddKeywordAndPositionalParameters.py new file mode 100644 index 000000000000..d557fb3a02f5 --- /dev/null +++ b/python/testData/inspections/AddKeywordAndPositionalParameters.py @@ -0,0 +1,5 @@ +def f(x, foo=1): + pass + + +f(x, 42, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/AddKeywordAndPositionalParameters_after.py b/python/testData/inspections/AddKeywordAndPositionalParameters_after.py new file mode 100644 index 000000000000..1d9c05e5cf52 --- /dev/null +++ b/python/testData/inspections/AddKeywordAndPositionalParameters_after.py @@ -0,0 +1,5 @@ +def f(x, foo=1, bar='spam'): + pass + + +f(x, 42, bar='spam') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 05f1b21b15f9..50dbf9511cd4 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -647,6 +647,11 @@ public class PyQuickFixTest extends PyTestCase { " \"((/(?P.+))?))\")"); } + // PY-8174 + public void testAddKeywordAndPositionalParameters() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + } + @Override @NonNls protected String getTestDataPath() { From fc59d948bf180fa04f8554bfdd73ae4a556ecae4 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 01:12:17 +0300 Subject: [PATCH 75/84] PY-8174 New parameters names are unique if arguments have the same type Incrementally increasing numbers are appended to the names derived from the types of the corresponding arguments to make them sufficiently different. --- .../quickfix/PyChangeSignatureQuickFix.java | 50 ++++++++++++++----- ...ositionalParametersWithSameArgumentType.py | 6 +++ ...nalParametersWithSameArgumentType_after.py | 6 +++ .../com/jetbrains/python/PyQuickFixTest.java | 5 ++ 4 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py create mode 100644 python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 1900f4ff5eed..f5ceff9d9893 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -20,6 +20,7 @@ import com.google.common.collect.PeekingIterator; import com.intellij.codeInspection.LocalQuickFixOnPsiElement; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; @@ -28,12 +29,16 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; +import com.intellij.xml.util.XmlStringUtil; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.PyCallExpression.PyArgumentsMapping; +import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.PyUnionType; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.NameSuggesterUtil; import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog; import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor; @@ -42,10 +47,7 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import static com.jetbrains.python.psi.PyUtil.as; @@ -70,6 +72,8 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { positionalParamAnchor++; } final List> newParameters = new ArrayList<>(); + final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callExpression.getContainingFile()); + final Set usedParamNames = new HashSet<>(); for (PyExpression arg : mapping.getUnmappedArguments()) { if (arg instanceof PyKeywordArgument) { final String defaultValueText; @@ -84,11 +88,9 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), defaultValueText, true))); } else { - final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callExpression.getContainingFile()); - final PyType type = context.getType(arg); - final String typeName = type != null && type.getName() != null ? type.getName() : PyNames.OBJECT; - final String paramName = PyRefactoringUtil.selectUniqueNameFromType(typeName, function.getStatementList()); + final String paramName = generateParameterName(arg, function, usedParamNames, context); newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, "", false))); + usedParamNames.add(paramName); } } return new PyChangeSignatureQuickFix(function, newParameters, mapping.getCallExpression()); @@ -141,9 +143,9 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { final String params = StringUtil.join(createMethodDescriptor(function).getParameters(), info -> { return info.getOldIndex() == -1 ? "" + info.getName() + "" : info.getName(); }, ", "); - return "" + - PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")") + - ""; + + final String message = PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")"); + return XmlStringUtil.wrapInHtml(message); } @Nullable @@ -183,6 +185,30 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { } } + @NotNull + private static String generateParameterName(@NotNull PyExpression argumentValue, + @NotNull PyFunction function, + @NotNull Set usedParameterNames, + @NotNull TypeEvalContext context) { + PyType type = context.getType(argumentValue); + if (type instanceof PyUnionType) { + type = ContainerUtil.find(((PyUnionType)type).getMembers(), Conditions.instanceOf(PyClassType.class)); + } + final String typeName = type != null && type.getName() != null ? type.getName() : "object"; + + final Collection suggestions = NameSuggesterUtil.generateNamesByType(typeName); + final String shortestName = ContainerUtil.getFirstItem(suggestions); + assert shortestName != null; + + String result = shortestName; + int counter = 1; + while (!PyRefactoringUtil.isValidNewName(result, function.getStatementList()) || usedParameterNames.contains(result)) { + result = shortestName + counter; + counter++; + } + return result; + } + @NotNull private PyMethodDescriptor createMethodDescriptor(final PyFunction function) { return new PyMethodDescriptor(function) { diff --git a/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py b/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py new file mode 100644 index 000000000000..fc6951765467 --- /dev/null +++ b/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py @@ -0,0 +1,6 @@ +def func(i1): + i2 = 'Spam' + + + +func(1, 2, 3) \ No newline at end of file diff --git a/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py b/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py new file mode 100644 index 000000000000..6fc392ed7552 --- /dev/null +++ b/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py @@ -0,0 +1,6 @@ +def func(i1, i, i3): + i2 = 'Spam' + + + +func(1, 2, 3) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 50dbf9511cd4..2c18d8f19bce 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -652,6 +652,11 @@ public class PyQuickFixTest extends PyTestCase { doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); } + // PY-8174 + public void testAddSeveralPositionalParametersWithSameArgumentType() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + } + @Override @NonNls protected String getTestDataPath() { From ef60722aead81e020d4effddba8ea6db97b33502 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 14:14:31 +0300 Subject: [PATCH 76/84] PY-8174 Always use actual arguments as default values for new parameters even if names referenced in them are not available at the places of some usages and the definition. It's user responsibility to review them and ensure that refactoring won't cause any errors. This approach follows the one used for Java. --- .../quickfix/PyChangeSignatureQuickFix.java | 19 ++++++++----------- .../python/refactoring/PyRefactoringUtil.java | 14 -------------- .../inspections/AddParameterDefaultValues.py | 6 ++++++ .../AddParameterDefaultValues_after.py | 6 ++++++ .../com/jetbrains/python/PyQuickFixTest.java | 5 +++++ 5 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 python/testData/inspections/AddParameterDefaultValues.py create mode 100644 python/testData/inspections/AddParameterDefaultValues_after.py diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index f5ceff9d9893..ec2f531832ba 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -76,20 +76,14 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { final Set usedParamNames = new HashSet<>(); for (PyExpression arg : mapping.getUnmappedArguments()) { if (arg instanceof PyKeywordArgument) { - final String defaultValueText; final PyExpression value = ((PyKeywordArgument)arg).getValueExpression(); - if (value != null && PyRefactoringUtil.isSimpleExpression(value) && !value.textContains('\n')) { - defaultValueText = value.getText(); - } - else { - defaultValueText = ApplicationManager.getApplication().isUnitTestMode() ? "None" : ""; - } + final String valueText = value != null ? value.getText() : ""; newParameters.add(Pair.create(parameters.length - 1, - new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), defaultValueText, true))); + new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), valueText, true))); } else { final String paramName = generateParameterName(arg, function, usedParamNames, context); - newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, "", false))); + newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, arg.getText(), false))); usedParamNames.add(paramName); } } @@ -109,12 +103,15 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { } return new PyChangeSignatureQuickFix(function, extraParams, null); } - - + private final List> myExtraParameters; private final SmartPsiElementPointer myOriginalCallExpression; + /** + * @param extraParameters new parameters anchored by indexes of the existing parameters they should be inserted after + * (-1 in case they should precede the first parameter) + */ public PyChangeSignatureQuickFix(@NotNull PyFunction function, @NotNull List> extraParameters, @Nullable PyCallExpression expression) { diff --git a/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java index 14592a8abbe1..4448804a780c 100644 --- a/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java @@ -17,7 +17,6 @@ package com.jetbrains.python.refactoring; import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.find.findUsages.FindUsagesHandler; -import com.intellij.lang.ASTNode; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; @@ -29,7 +28,6 @@ import com.intellij.usageView.UsageInfo; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.jetbrains.python.PyNames; -import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory; import com.jetbrains.python.psi.*; import com.jetbrains.python.refactoring.introduce.IntroduceValidator; @@ -399,16 +397,4 @@ public class PyRefactoringUtil { public static boolean isValidNewName(@NotNull String name, @NotNull PsiElement scopeAnchor) { return !(IntroduceValidator.isDefinedInScope(name, scopeAnchor) || PyNames.isReserved(name)); } - - public static boolean isSimpleExpression(@NotNull PyExpression value) { - if (value instanceof PyLiteralExpression) { - final ASTNode node = value.getNode(); - // Check that string literal doesn't contain multiple glued nodes - return node.getChildren(null).length == 1 && PyTokenTypes.SCALAR_LITERALS.contains(node.getFirstChildNode().getElementType()); - } - else if (value instanceof PyReferenceExpression) { - return PyUtil.isPy2ReservedWord((PyReferenceExpression)value); - } - return false; - } } diff --git a/python/testData/inspections/AddParameterDefaultValues.py b/python/testData/inspections/AddParameterDefaultValues.py new file mode 100644 index 000000000000..eceee62d0ccc --- /dev/null +++ b/python/testData/inspections/AddParameterDefaultValues.py @@ -0,0 +1,6 @@ +def func(): + pass + + +func(42, foo='spam') +func() diff --git a/python/testData/inspections/AddParameterDefaultValues_after.py b/python/testData/inspections/AddParameterDefaultValues_after.py new file mode 100644 index 000000000000..046a292bc48c --- /dev/null +++ b/python/testData/inspections/AddParameterDefaultValues_after.py @@ -0,0 +1,6 @@ +def func(i, foo='spam'): + pass + + +func(42, foo='spam') +func(42) diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 2c18d8f19bce..2f07cca791a7 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -657,6 +657,11 @@ public class PyQuickFixTest extends PyTestCase { doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); } + // PY-8174 + public void testAddParameterDefaultValues() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + } + @Override @NonNls protected String getTestDataPath() { From 0a575d9531bfe6648a6af0c5eb54a1aaf0d33682 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 14:37:21 +0300 Subject: [PATCH 77/84] PY-8174 Consistent naming of test cases and full quickfix names in them --- ...ChangeSignatureKeywordAndPositionalParameters.py} | 0 ...SignatureKeywordAndPositionalParameters_after.py} | 0 ...tType.py => ChangeSignatureNewParametersNames.py} | 0 ...py => ChangeSignatureNewParametersNames_after.py} | 0 ....py => ChangeSignatureParametersDefaultValues.py} | 0 ... ChangeSignatureParametersDefaultValues_after.py} | 0 .../testSrc/com/jetbrains/python/PyQuickFixTest.java | 12 ++++++------ 7 files changed, 6 insertions(+), 6 deletions(-) rename python/testData/inspections/{AddKeywordAndPositionalParameters.py => ChangeSignatureKeywordAndPositionalParameters.py} (100%) rename python/testData/inspections/{AddKeywordAndPositionalParameters_after.py => ChangeSignatureKeywordAndPositionalParameters_after.py} (100%) rename python/testData/inspections/{AddSeveralPositionalParametersWithSameArgumentType.py => ChangeSignatureNewParametersNames.py} (100%) rename python/testData/inspections/{AddSeveralPositionalParametersWithSameArgumentType_after.py => ChangeSignatureNewParametersNames_after.py} (100%) rename python/testData/inspections/{AddParameterDefaultValues.py => ChangeSignatureParametersDefaultValues.py} (100%) rename python/testData/inspections/{AddParameterDefaultValues_after.py => ChangeSignatureParametersDefaultValues_after.py} (100%) diff --git a/python/testData/inspections/AddKeywordAndPositionalParameters.py b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters.py similarity index 100% rename from python/testData/inspections/AddKeywordAndPositionalParameters.py rename to python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters.py diff --git a/python/testData/inspections/AddKeywordAndPositionalParameters_after.py b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters_after.py similarity index 100% rename from python/testData/inspections/AddKeywordAndPositionalParameters_after.py rename to python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters_after.py diff --git a/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py b/python/testData/inspections/ChangeSignatureNewParametersNames.py similarity index 100% rename from python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType.py rename to python/testData/inspections/ChangeSignatureNewParametersNames.py diff --git a/python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py similarity index 100% rename from python/testData/inspections/AddSeveralPositionalParametersWithSameArgumentType_after.py rename to python/testData/inspections/ChangeSignatureNewParametersNames_after.py diff --git a/python/testData/inspections/AddParameterDefaultValues.py b/python/testData/inspections/ChangeSignatureParametersDefaultValues.py similarity index 100% rename from python/testData/inspections/AddParameterDefaultValues.py rename to python/testData/inspections/ChangeSignatureParametersDefaultValues.py diff --git a/python/testData/inspections/AddParameterDefaultValues_after.py b/python/testData/inspections/ChangeSignatureParametersDefaultValues_after.py similarity index 100% rename from python/testData/inspections/AddParameterDefaultValues_after.py rename to python/testData/inspections/ChangeSignatureParametersDefaultValues_after.py diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 2f07cca791a7..17244296a5b6 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -648,18 +648,18 @@ public class PyQuickFixTest extends PyTestCase { } // PY-8174 - public void testAddKeywordAndPositionalParameters() { - doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + public void testChangeSignatureKeywordAndPositionalParameters() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of f(x, foo, bar)", true, true); } // PY-8174 - public void testAddSeveralPositionalParametersWithSameArgumentType() { - doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + public void testChangeSignatureNewParametersNames() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i1, i, i3)", true, true); } // PY-8174 - public void testAddParameterDefaultValues() { - doInspectionTest(PyArgumentListInspection.class, "Change signature of", true, true); + public void testChangeSignatureParametersDefaultValues() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i, foo)", true, true); } @Override From f303faa96733f0412da7145625ec001c5ea7a409 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 14:55:56 +0300 Subject: [PATCH 78/84] PY-8174 Add a test on keyword only parameters in Python 3 The test on parameter names also checks the case when union type was inferred for an argument. --- .../inspections/ChangeSignatureAddKeywordOnlyParameter.py | 4 ++++ .../ChangeSignatureAddKeywordOnlyParameter_after.py | 4 ++++ .../inspections/ChangeSignatureNewParametersNames.py | 4 ++-- .../inspections/ChangeSignatureNewParametersNames_after.py | 4 ++-- python/testSrc/com/jetbrains/python/PyQuickFixTest.java | 7 +++++++ 5 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py create mode 100644 python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py diff --git a/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py new file mode 100644 index 000000000000..86ecc3f8fd07 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py @@ -0,0 +1,4 @@ +def func(x, *args, foo=None): + pass + +func(1, 2, 3, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py new file mode 100644 index 000000000000..2196ab7ac340 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py @@ -0,0 +1,4 @@ +def func(x, *args, foo=None, bar='spam'): + pass + +func(1, 2, 3, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames.py b/python/testData/inspections/ChangeSignatureNewParametersNames.py index fc6951765467..2d7040d78247 100644 --- a/python/testData/inspections/ChangeSignatureNewParametersNames.py +++ b/python/testData/inspections/ChangeSignatureNewParametersNames.py @@ -2,5 +2,5 @@ def func(i1): i2 = 'Spam' - -func(1, 2, 3) \ No newline at end of file +x = 42 or 'str' +func(1, 2, x) \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames_after.py b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py index 6fc392ed7552..24912b03b2c8 100644 --- a/python/testData/inspections/ChangeSignatureNewParametersNames_after.py +++ b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py @@ -2,5 +2,5 @@ def func(i1, i, i3): i2 = 'Spam' - -func(1, 2, 3) \ No newline at end of file +x = 42 or 'str' +func(1, 2, x) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 17244296a5b6..44d34f955a4b 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -652,6 +652,13 @@ public class PyQuickFixTest extends PyTestCase { doInspectionTest(PyArgumentListInspection.class, "Change signature of f(x, foo, bar)", true, true); } + // PY-8174 + public void testChangeSignatureAddKeywordOnlyParameter() { + runWithLanguageLevel(LanguageLevel.PYTHON30, () -> { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(x, *args, foo, bar)", true, true); + }); + } + // PY-8174 public void testChangeSignatureNewParametersNames() { doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i1, i, i3)", true, true); From cf0c247416168198d7e4b176a7ca8058f35a1411 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 19:16:34 +0300 Subject: [PATCH 79/84] PY-8174 Make PyChangeSignatureQuickFix constructor private --- .../inspections/quickfix/PyChangeSignatureQuickFix.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index ec2f531832ba..24886a73f171 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -112,9 +112,9 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { * @param extraParameters new parameters anchored by indexes of the existing parameters they should be inserted after * (-1 in case they should precede the first parameter) */ - public PyChangeSignatureQuickFix(@NotNull PyFunction function, - @NotNull List> extraParameters, - @Nullable PyCallExpression expression) { + private PyChangeSignatureQuickFix(@NotNull PyFunction function, + @NotNull List> extraParameters, + @Nullable PyCallExpression expression) { super(function); myExtraParameters = ContainerUtil.sorted(extraParameters, Comparator.comparingInt(p -> p.getFirst())); if (expression != null) { From 33f424edc76b927a32731025e797e0fc1ef7299c Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 5 Jun 2017 20:55:25 +0300 Subject: [PATCH 80/84] PY-8174 Suggests better names for new parameters in the quickfix using NameSuggestionUtil#generateNames() and text of the main reference expression in argument as a template. Only if no meaningful names were found this way, it falls back to names generated from expression types. --- .../quickfix/PyChangeSignatureQuickFix.java | 22 ++++++++++++------- .../ChangeSignatureNewParametersNames.py | 3 +-- ...ChangeSignatureNewParametersNames_after.py | 5 ++--- .../com/jetbrains/python/PyQuickFixTest.java | 8 +++++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 24886a73f171..0f61583d7b46 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -187,15 +187,21 @@ public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { @NotNull PyFunction function, @NotNull Set usedParameterNames, @NotNull TypeEvalContext context) { - PyType type = context.getType(argumentValue); - if (type instanceof PyUnionType) { - type = ContainerUtil.find(((PyUnionType)type).getMembers(), Conditions.instanceOf(PyClassType.class)); + final Collection suggestions = new LinkedHashSet<>(); + final PyCallExpression callExpr = as(argumentValue, PyCallExpression.class); + final PyElement referenceElem = as(callExpr != null ? callExpr.getCallee() : argumentValue, PyReferenceExpression.class); + if (referenceElem != null) { + suggestions.addAll(NameSuggesterUtil.generateNames(referenceElem.getText())); } - final String typeName = type != null && type.getName() != null ? type.getName() : "object"; - - final Collection suggestions = NameSuggesterUtil.generateNamesByType(typeName); - final String shortestName = ContainerUtil.getFirstItem(suggestions); - assert shortestName != null; + if (suggestions.isEmpty()) { + PyType type = context.getType(argumentValue); + if (type instanceof PyUnionType) { + type = ContainerUtil.find(((PyUnionType)type).getMembers(), Conditions.instanceOf(PyClassType.class)); + } + final String typeName = type != null && type.getName() != null ? type.getName() : "object"; + suggestions.addAll(NameSuggesterUtil.generateNamesByType(typeName)); + } + final String shortestName = Collections.min(suggestions, Comparator.comparingInt(String::length)); String result = shortestName; int counter = 1; diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames.py b/python/testData/inspections/ChangeSignatureNewParametersNames.py index 2d7040d78247..1477278aa0db 100644 --- a/python/testData/inspections/ChangeSignatureNewParametersNames.py +++ b/python/testData/inspections/ChangeSignatureNewParametersNames.py @@ -2,5 +2,4 @@ def func(i1): i2 = 'Spam' -x = 42 or 'str' -func(1, 2, x) \ No newline at end of file +func(1, 2, 42 or 'str', get_num()) \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames_after.py b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py index 24912b03b2c8..8281e37a5ed5 100644 --- a/python/testData/inspections/ChangeSignatureNewParametersNames_after.py +++ b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py @@ -1,6 +1,5 @@ -def func(i1, i, i3): +def func(i1, i, i3, num): i2 = 'Spam' -x = 42 or 'str' -func(1, 2, x) \ No newline at end of file +func(1, 2, 42 or 'str', get_num()) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 44d34f955a4b..bf03e83e183d 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -17,8 +17,10 @@ package com.jetbrains.python; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.TestDataFile; import com.intellij.testFramework.TestDataPath; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.documentation.docstrings.DocStringFormat; import com.jetbrains.python.fixtures.PyTestCase; @@ -661,7 +663,7 @@ public class PyQuickFixTest extends PyTestCase { // PY-8174 public void testChangeSignatureNewParametersNames() { - doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i1, i, i3)", true, true); + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i1, i, i3, num)", true, true); } // PY-8174 @@ -712,7 +714,9 @@ public class PyQuickFixTest extends PyTestCase { final List intentionActions = myFixture.filterAvailableIntentions(quickFixName); if (available) { if (intentionActions.isEmpty()) { - throw new AssertionError("Quickfix \"" + quickFixName + "\" is not available"); + final List intentionNames = ContainerUtil.map(myFixture.getAvailableIntentions(), IntentionAction::getText); + throw new AssertionError("Quickfix starting with \"" + quickFixName + "\" is not available. " + + "Available intentions:\n" + StringUtil.join(intentionNames, "\n")); } if (intentionActions.size() > 1) { throw new AssertionError("There are more than one quickfix with the name \"" + quickFixName + "\""); From 57b939eef42bd9172d08e29e53114a28ef561ec3 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Mon, 5 Jun 2017 22:29:21 +0300 Subject: [PATCH 81/84] convert to atomic: add rule for array.length --- .../typeMigration/rules/AtomicConversionRule.java | 14 ++++++++++++++ .../intentions/atomic/afterAtomicArrayLength.java | 10 ++++++++++ .../intentions/atomic/beforeAtomicArrayLength.java | 8 ++++++++ 3 files changed, 32 insertions(+) create mode 100644 java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java create mode 100644 java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java index 49047421571e..86be70f05ee9 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java @@ -12,6 +12,8 @@ import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import com.intellij.util.ObjectUtils; +import com.siyeh.HardcodedMethodConstants; import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -342,6 +344,9 @@ public class AtomicConversionRule extends TypeConversionRule { if (context instanceof PsiArrayAccessExpression) { return new TypeConversionDescriptor("$qualifier$[$idx$]", "$qualifier$.get($idx$)", (PsiExpression)context); } + if (parent instanceof PsiReferenceExpression && isReferenceToLengthField((PsiReferenceExpression)parent)) { + return new TypeConversionDescriptor("$qualifier$.length", "$qualifier$.length()", (PsiExpression)parent); + } return null; } @@ -438,4 +443,13 @@ public class AtomicConversionRule extends TypeConversionRule { return null; } + private static boolean isReferenceToLengthField(@NotNull PsiReferenceExpression refExpr) { + if (!"length".equals(refExpr.getReferenceName())) { + return false; + } + PsiClass aClass = JavaPsiFacade.getElementFactory(refExpr.getProject()).getArrayClass(PsiUtil.getLanguageLevel(refExpr)); + PsiField lengthField = ObjectUtils.notNull(aClass.findFieldByName(HardcodedMethodConstants.LENGTH, false)); + return refExpr.isReferenceTo(lengthField); + } + } diff --git a/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java b/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java new file mode 100644 index 000000000000..76bd5940cb3a --- /dev/null +++ b/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java @@ -0,0 +1,10 @@ +import java.util.concurrent.atomic.AtomicIntegerArray; + +// "Convert to atomic" "true" +class Test { + final AtomicIntegerArray ii = new AtomicIntegerArray(new int[12]); + + void m() { + int k = ii.length(); + } +} diff --git a/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java b/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java new file mode 100644 index 000000000000..01fa0f5b0866 --- /dev/null +++ b/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java @@ -0,0 +1,8 @@ +// "Convert to atomic" "true" +class Test { + int[] ii = new int[12]; + + void m() { + int k = ii.length; + } +} From 706aad11ff195b37766097950b308571ed275188 Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Mon, 5 Jun 2017 23:22:39 +0300 Subject: [PATCH 82/84] IOExceptionDialog it too wide --- .../src/com/intellij/util/net/IOExceptionDialog.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java b/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java index 571ab8edc263..35dc36354d77 100644 --- a/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java @@ -41,7 +41,6 @@ public class IOExceptionDialog extends DialogWrapper { myErrorLabel = new JTextArea(); myErrorLabel.setEditable(false); myErrorLabel.setText(errorText); - myErrorLabel.setColumns(120); myErrorLabel.setLineWrap(true); myErrorLabel.setWrapStyleWord(true); myErrorLabel.setFont(UIManager.getFont("Label.font")); From a893d47f6b3c4eb0c4f69c832eca624aa7f172d1 Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Mon, 5 Jun 2017 23:30:43 +0300 Subject: [PATCH 83/84] "" font item when no secondary font (for IDEA-173831) --- .../src/com/intellij/ui/FontComboBox.java | 66 ++++++++++++++----- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java index 93c4d48fd0ba..9d82923d1f55 100644 --- a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java +++ b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java @@ -16,9 +16,11 @@ package com.intellij.ui; import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.util.ui.FontInfo; +import org.jetbrains.annotations.Nullable; import java.awt.Dimension; import java.util.ArrayList; @@ -40,11 +42,11 @@ public final class FontComboBox extends ComboBox { } public FontComboBox(boolean withAllStyles) { - this(withAllStyles, true); + this(withAllStyles, true, false); } - public FontComboBox(boolean withAllStyles, boolean filterNonLatin) { - super(new Model(withAllStyles, filterNonLatin)); + public FontComboBox(boolean withAllStyles, boolean filterNonLatin, boolean noFontItem) { + super(new Model(withAllStyles, filterNonLatin, noFontItem)); Dimension size = getPreferredSize(); size.width = size.height * 8; setPreferredSize(size); @@ -68,10 +70,14 @@ public final class FontComboBox extends ComboBox { return item == null ? null : item.toString(); } - public void setFontName(String item) { + public void setFontName(@Nullable String item) { myModel.setSelectedItem(item); } + public boolean isNoFontSelected() { + return myModel.isNoFontSelected(); + } + @Override public void setModel(ComboBoxModel model) { if (model instanceof Model) { @@ -84,12 +90,14 @@ public final class FontComboBox extends ComboBox { } private static final class Model extends AbstractListModel implements ComboBoxModel { + private final NoFontItem NO_FONT_ITEM; private volatile List myAllFonts = Collections.emptyList(); private volatile List myMonoFonts = Collections.emptyList(); private boolean myMonospacedOnly; private Object mySelectedItem; - private Model(boolean withAllStyles, boolean filterNonLatin) { + private Model(boolean withAllStyles, boolean filterNonLatin, boolean noFontItem) { + NO_FONT_ITEM = noFontItem ? new NoFontItem() : null; Application application = ApplicationManager.getApplication(); if (application == null || application.isUnitTestMode()) { setFonts(FontInfo.getAll(withAllStyles), filterNonLatin); @@ -132,17 +140,22 @@ public final class FontComboBox extends ComboBox { } @Override - public void setSelectedItem(Object item) { - if (item instanceof FontInfo) { - FontInfo info = getInfo(item); - if (info == null) { - List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - item = list.isEmpty() ? null : list.get(0); - } + public void setSelectedItem(@Nullable Object item) { + if (item == null && NO_FONT_ITEM != null) { + item = NO_FONT_ITEM; } - if (item instanceof String) { - FontInfo info = getInfo(item); - if (info != null) item = info; + else { + if (item instanceof FontInfo) { + FontInfo info = getInfo(item); + if (info == null) { + List list = myMonospacedOnly ? myMonoFonts : myAllFonts; + item = list.isEmpty() ? null : list.get(0); + } + } + if (item instanceof String) { + FontInfo info = getInfo(item); + if (info != null) item = info; + } } if (!(mySelectedItem == null ? item == null : mySelectedItem.equals(item))) { mySelectedItem = item; @@ -150,16 +163,28 @@ public final class FontComboBox extends ComboBox { } } + public boolean isNoFontSelected() { + return getSelectedItem() == NO_FONT_ITEM; + } + @Override public int getSize() { List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - return mySelectedItem instanceof String ? 1 + list.size() : list.size(); + int size = list.size(); + if (mySelectedItem instanceof String) size ++; + if (NO_FONT_ITEM != null) size++; + return size; } @Override public Object getElementAt(int index) { + int i = index; + if (NO_FONT_ITEM != null) { + if (index == 0) return NO_FONT_ITEM; + i --; + } List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - return 0 <= index && index < list.size() ? list.get(index) : mySelectedItem; + return 0 <= i && i < list.size() ? list.get(i) : mySelectedItem; } private FontInfo getInfo(Object item) { @@ -170,5 +195,12 @@ public final class FontComboBox extends ComboBox { } return null; } + + private final static class NoFontItem { + @Override + public String toString() { + return ApplicationBundle.message("settings.editor.font.none"); + } + } } } From c26bb5297cda8bf0d433c61ba50e9d00b13b7f07 Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Mon, 5 Jun 2017 23:34:37 +0300 Subject: [PATCH 84/84] IDEA-173831 Change editor/scheme Font settings UI --- .../colors/AbstractFontOptionsPanel.java | 91 +++++++++++++------ .../options/colors/ConsoleFontOptions.java | 14 ++- .../options/colors/FontOptions.java | 85 ++++++++++++++--- .../src/messages/ApplicationBundle.properties | 7 +- 4 files changed, 150 insertions(+), 47 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java b/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java index 893da0cf4387..509478b12a95 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java @@ -23,7 +23,6 @@ import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.FontPreferences; import com.intellij.openapi.editor.colors.ModifiableFontPreferences; -import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.SystemInfo; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.FontComboBox; @@ -32,7 +31,6 @@ import com.intellij.ui.TooltipWithClickableLinks; import com.intellij.ui.components.JBCheckBox; import com.intellij.util.EventDispatcher; import com.intellij.util.ui.JBUI; -import net.miginfocom.swing.MigLayout; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,9 +59,8 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options @NotNull private final JTextField myEditorFontSizeField = new JTextField(4); @NotNull private final JTextField myLineSpacingField = new JTextField(4); private final FontComboBox myPrimaryCombo = new FontComboBox(); - private final JCheckBox myUseSecondaryFontCheckbox = new JCheckBox(ApplicationBundle.message("secondary.font")); private final JCheckBox myEnableLigaturesCheckbox = new JCheckBox(ApplicationBundle.message("use.ligatures")); - private final FontComboBox mySecondaryCombo = new FontComboBox(false, false); + private final FontComboBox mySecondaryCombo = new FontComboBox(false, false, true); @NotNull private final JBCheckBox myOnlyMonospacedCheckBox = new JBCheckBox(ApplicationBundle.message("checkbox.show.only.monospaced.fonts")); @@ -72,30 +69,67 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options private JLabel myPrimaryLabel; private JLabel mySizeLabel; + protected final static int ADDITIONAL_VERTICAL_GAP = 12; + protected final static int BASE_INSET = 5; + private JLabel mySecondaryFontLabel; + private JLabel myLineSpacingLabel; protected AbstractFontOptionsPanel() { - setLayout(new MigLayout("ins 0, gap 5, flowx")); - initControls(); + setLayout(new FlowLayout(FlowLayout.LEFT)); + add(createControls()); + } + + protected JComponent createControls() { + return createFontSettingsPanel(); } @SuppressWarnings("unchecked") - protected void initControls() { - add(myOnlyMonospacedCheckBox, "newline 10, sgx b, sx 2"); + protected final JPanel createFontSettingsPanel() { + JPanel fontPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.WEST; + c.insets = JBUI.insets(BASE_INSET, BASE_INSET, 0, 0); + c.gridx = 0; + c.gridy = 0; myPrimaryLabel = new JLabel(ApplicationBundle.message("primary.font")); - add(myPrimaryLabel, "newline, ax right"); - add(myPrimaryCombo, "sgx b"); + fontPanel.add(myPrimaryLabel, c); + + c.gridx = 1; + fontPanel.add(myPrimaryCombo, c); + + c.gridx = 2; + fontPanel.add(myOnlyMonospacedCheckBox, c); + + c.gridx = 0; + c.gridy = 1; mySizeLabel = new JLabel(ApplicationBundle.message("editbox.font.size")); - add(mySizeLabel, "gapleft 20"); - add(myEditorFontSizeField); - add(new JLabel(ApplicationBundle.message("editbox.line.spacing")), "gapleft 20"); - add(myLineSpacingField); + fontPanel.add(mySizeLabel, c); + + c.gridx = 1; + fontPanel.add(myEditorFontSizeField, c); + + c.gridx = 0; + c.gridy = 2; + myLineSpacingLabel = new JLabel(ApplicationBundle.message("editbox.line.spacing")); + myLineSpacingLabel.setLabelFor(myLineSpacingField); + fontPanel.add(myLineSpacingLabel, c); + c.gridx = 1; + fontPanel.add(myLineSpacingField,c); + + c.gridy = 3; + c.gridx = 0; + c.insets = JBUI.insets(BASE_INSET + ADDITIONAL_VERTICAL_GAP, BASE_INSET, 0, 0); + mySecondaryFontLabel = new JLabel(ApplicationBundle.message("secondary.font")); + mySecondaryFontLabel.setLabelFor(mySecondaryCombo); + fontPanel.add(mySecondaryFontLabel, c); + c.gridx = 1; + fontPanel.add(mySecondaryCombo, c); + c.gridx = 2; + JLabel fallbackLabel = new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description")); + fallbackLabel.setEnabled(false); + fontPanel.add(fallbackLabel, c); - add(new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description"), - MessageType.INFO.getDefaultIcon(), - SwingConstants.LEFT), "newline, sx 5"); - add(myUseSecondaryFontCheckbox, "newline, ax right"); - add(mySecondaryCombo, "sgx b"); JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); myEnableLigaturesCheckbox.setBorder(null); panel.add(myEnableLigaturesCheckbox); @@ -108,10 +142,12 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options warningIcon.setBorder(JBUI.Borders.emptyLeft(5)); warningIcon.setVisible(!SystemInfo.isJetBrainsJvm); panel.add(warningIcon); - add(panel, "newline, sx 2"); + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 2; + fontPanel.add(panel, c); myOnlyMonospacedCheckBox.setBorder(null); - myUseSecondaryFontCheckbox.setBorder(null); mySecondaryCombo.setEnabled(false); myOnlyMonospacedCheckBox.setSelected(EditorColorsManager.getInstance().isUseOnlyMonospacedFonts()); @@ -126,10 +162,6 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected()); mySecondaryCombo.setRenderer(RENDERER); - myUseSecondaryFontCheckbox.addActionListener(e -> { - mySecondaryCombo.setEnabled(myUseSecondaryFontCheckbox.isSelected()); - syncFontFamilies(); - }); ItemListener itemListener = this::syncFontFamilies; myPrimaryCombo.addItemListener(itemListener); mySecondaryCombo.addItemListener(itemListener); @@ -198,6 +230,7 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options updateDescription(true); } }); + return fontPanel; } protected void setDelegatingPreferences(boolean isDelegating) { @@ -249,7 +282,7 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options modifiableFontPreferences.clearFonts(); modifiableFontPreferences.setUseLigatures(myEnableLigaturesCheckbox.isSelected()); String primaryFontFamily = myPrimaryCombo.getFontName(); - String secondaryFontFamily = mySecondaryCombo.isEnabled() ? mySecondaryCombo.getFontName() : null; + String secondaryFontFamily = mySecondaryCombo.isNoFontSelected() ? null : mySecondaryCombo.getFontName(); int fontSize = getFontSizeFromField(); if (primaryFontFamily != null) { if (!FontPreferences.DEFAULT_FONT_NAME.equals(primaryFontFamily)) { @@ -276,7 +309,6 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options List fontFamilies = fontPreferences.getEffectiveFontFamilies(); myPrimaryCombo.setFontName(fontPreferences.getFontFamily()); boolean isThereSecondaryFont = fontFamilies.size() > 1; - myUseSecondaryFontCheckbox.setSelected(isThereSecondaryFont); mySecondaryCombo.setFontName(isThereSecondaryFont ? fontFamilies.get(1) : null); myEditorFontSizeField.setText(String.valueOf(fontPreferences.getSize(fontPreferences.getFontFamily()))); @@ -285,12 +317,13 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options boolean readOnly = isReadOnlyColorScheme || !(getFontPreferences() instanceof ModifiableFontPreferences); myPrimaryCombo.setEnabled(!readOnly); myPrimaryLabel.setEnabled(!readOnly); - mySecondaryCombo.setEnabled(isThereSecondaryFont && !readOnly); + mySecondaryCombo.setEnabled(!readOnly); + mySecondaryFontLabel.setEnabled(!readOnly); myOnlyMonospacedCheckBox.setEnabled(!readOnly); myLineSpacingField.setEnabled(!readOnly); + myLineSpacingLabel.setEnabled(!readOnly); myEditorFontSizeField.setEnabled(!readOnly); mySizeLabel.setEnabled(!readOnly); - myUseSecondaryFontCheckbox.setEnabled(!readOnly); myEnableLigaturesCheckbox.setEnabled(!readOnly && SystemInfo.isJetBrainsJvm); myEnableLigaturesCheckbox.setSelected(fontPreferences.useLigatures()); diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java b/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java index 6a1f8fcd3c51..35c0886248c6 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java @@ -31,8 +31,13 @@ public class ConsoleFontOptions extends FontOptions { @Nullable @Override - protected String getInheritFontTitle() { - return "editor font"; + protected String getInheritedFontTitle() { + return "Scheme font"; + } + + @Override + protected String getOverwriteFontTitle() { + return "Set console font for color scheme"; } @Override @@ -55,6 +60,11 @@ public class ConsoleFontOptions extends FontOptions { return getCurrentScheme().getConsoleFontPreferences(); } + @Override + protected FontPreferences getBaseFontPreferences() { + return getCurrentScheme().getFontPreferences(); + } + @Override protected void setDelegatingPreferences(boolean isDelegating) { FontPreferences currPrefs = getCurrentScheme().getConsoleFontPreferences(); diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java b/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java index 0f790fad6150..be16c024c59e 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java @@ -21,9 +21,13 @@ import com.intellij.ide.DataManager; import com.intellij.openapi.editor.colors.DelegatingFontPreferences; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.colors.FontPreferences; +import com.intellij.openapi.editor.colors.impl.AppEditorFontOptions; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ex.Settings; import com.intellij.ui.HoverHyperlinkLabel; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.JBDimension; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,24 +43,53 @@ public class FontOptions extends AbstractFontOptionsPanel { @NotNull private final ColorAndFontOptions myOptions; private @Nullable JCheckBox myInheritFontCheckbox; + private @Nullable JLabel myBaseFontInfoLabel; + + private final static int FONT_PANEL_LEFT_OFFSET = 15; public FontOptions(@NotNull ColorAndFontOptions options) { myOptions = options; } @Nullable - protected String getInheritFontTitle() { - return "default font"; + protected String getInheritedFontTitle() { + return "Default font"; + } + + protected String getOverwriteFontTitle() { + return "Set font for color scheme"; } @Override - protected void initControls() { - createInheritCheckBox(); - super.initControls(); + protected JComponent createControls() { + Component inheritBox = createInheritCheckBox(); + if (inheritBox != null) { + JPanel topPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.insets = JBUI.insets(BASE_INSET, BASE_INSET, ADDITIONAL_VERTICAL_GAP, 0); + c.anchor = GridBagConstraints.LINE_START; + topPanel.add(inheritBox, c); + c.gridy = 1; + c.gridx = 0; + c.gridwidth = 1; + c.insets = JBUI.emptyInsets(); + topPanel.add(Box.createRigidArea(JBDimension.create(new Dimension(FONT_PANEL_LEFT_OFFSET, 0))), c); + c.gridx = 1; + c.anchor = GridBagConstraints.NORTHWEST; + topPanel.add(createFontSettingsPanel(), c); + return topPanel; + } + else { + return super.createControls(); + } } - private void createInheritCheckBox() { - if (getInheritFontTitle() != null) { + @Nullable + private Component createInheritCheckBox() { + if (getInheritedFontTitle() != null) { JPanel inheritPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0,0 )); inheritPanel.setBorder(BorderFactory.createEmptyBorder()); myInheritFontCheckbox = new JCheckBox(); @@ -68,17 +101,40 @@ public class FontOptions extends AbstractFontOptionsPanel { } }); inheritPanel.add(myInheritFontCheckbox); - inheritPanel.add(new JLabel("Use ")); - inheritPanel.add(createHyperlinkLabel()); - - add(inheritPanel, "newline, span"); - add(new JSeparator(), "newline, growx, span"); + inheritPanel.add(new JLabel(getOverwriteFontTitle())); + inheritPanel.add(Box.createRigidArea(JBDimension.create(new Dimension(5,0)))); + inheritPanel.add(grayed(new JLabel("("))); + inheritPanel.add(grayed(createHyperlinkLabel())); + inheritPanel.add(grayed(new JLabel(": "))); + myBaseFontInfoLabel = grayed(new JLabel("?")); + inheritPanel.add(myBaseFontInfoLabel); + inheritPanel.add(grayed(new JLabel(")"))); + return inheritPanel; } + return null; + } + + private static JLabel grayed(JLabel label) { + label.setForeground(JBColor.GRAY); + return label; + } + + private String getBaseFontInfo() { + StringBuilder sb = new StringBuilder(); + FontPreferences basePrefs = getBaseFontPreferences(); + sb.append(basePrefs.getFontFamily()); + sb.append(','); + sb.append(basePrefs.getSize(basePrefs.getFontFamily())); + return sb.toString(); + } + + protected FontPreferences getBaseFontPreferences() { + return AppEditorFontOptions.getInstance().getFontPreferences(); } @NotNull private JLabel createHyperlinkLabel() { - HoverHyperlinkLabel label = new HoverHyperlinkLabel(getInheritFontTitle()); + HoverHyperlinkLabel label = new HoverHyperlinkLabel(getInheritedFontTitle()); label.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { @@ -154,6 +210,9 @@ public class FontOptions extends AbstractFontOptionsPanel { myInheritFontCheckbox.setEnabled(!isReadOnly()); myInheritFontCheckbox.setSelected(isDelegating()); } + if (myBaseFontInfoLabel != null) { + myBaseFontInfoLabel.setText(getBaseFontInfo()); + } } } diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 9668a52b2261..3b9f5d210601 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -542,10 +542,11 @@ error.a.scheme.with.this.name.already.exists.or.was.deleted.without.applying.the title.select.font=Select Font action.apply.editor.font.settings=Apply editor font settings checkbox.show.only.monospaced.fonts=Show only monospaced fonts -primary.font=Primary font: -secondary.font=Secondary font: +primary.font=Font: +secondary.font=Fallback font: use.ligatures=Enable font ligatures ligatures.jre.warning=The JRE you are running {0} with
is known to have performance issues
related to ligatures support.
Use JetBrains Runtime instead to enable ligatures. +settings.editor.font.none= editbox.enter.tag.name=Enter tag name: title.tag.name=Tag Name title.xml=XML @@ -574,7 +575,7 @@ title.scope.based=By Scope title.colors.and.fonts=Colors \\& Fonts progress.analysing.font=Analysing font: {0} group.editor.font=Editor Font -label.fallback.fonts.list.description=If primary font fails, IDE tries to use the secondary one +label.fallback.fonts.list.description=For symbols not supported by the main font quickdoc.tooltip.font.size.by.wheel=Slider or Ctrl+Wheel change font size label.font.size=Font size: label.font.name=Name: