From 1355b9513e319933f44636ed6c1dd481931d926d Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 24 Nov 2014 17:50:42 +0300 Subject: [PATCH 1/7] fixed PY-13685 Replace function call: invalid parenthesis for calls with inline comments --- .../StatementEffectFunctionCallQuickFix.java | 71 +++++----- .../inspections/ReplaceExecComment.py | 1 + .../inspections/ReplaceExecComment_after.py | 1 + .../com/jetbrains/python/Py3QuickFixTest.java | 126 ++++++++++++++++++ .../com/jetbrains/python/PyQuickFixTest.java | 54 +++----- 5 files changed, 181 insertions(+), 72 deletions(-) create mode 100644 python/testData/inspections/ReplaceExecComment.py create mode 100644 python/testData/inspections/ReplaceExecComment_after.py create mode 100644 python/testSrc/com/jetbrains/python/Py3QuickFixTest.java diff --git a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java index 1ed246cb1b77..ac65f6e01879 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java @@ -27,6 +27,7 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * User: catherine @@ -63,28 +64,14 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { final String expressionText = expression.getText(); final StringBuilder stringBuilder = new StringBuilder(expressionText + " ("); - final PsiElement whiteSpace = expression.getContainingFile().findElementAt(expression.getTextOffset() + expression.getTextLength()); - PsiElement next = null; - if (whiteSpace instanceof PsiWhiteSpace) { - final String whiteSpaceText = whiteSpace.getText(); - if (!whiteSpaceText.contains("\n")) { - next = whiteSpace.getNextSibling(); - while (next instanceof PsiWhiteSpace && whiteSpaceText.contains("\\")) { - next = next.getNextSibling(); - } - } - } - else - next = whiteSpace; - - RemoveUnnecessaryBackslashQuickFix.removeBackSlash(next); - if (whiteSpace != null) whiteSpace.delete(); + final PsiElement next = getNextElement(expression); if (next == null) { stringBuilder.append(")"); expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, stringBuilder.toString())); return; } + final String commentText = getComment(next); if (next instanceof PyExpressionStatement) { final PyExpression expr = ((PyExpressionStatement)next).getExpression(); if (expr instanceof PyBinaryExpression) { @@ -107,7 +94,7 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { } } else { - stringBuilder.append(next.getText()); + stringBuilder.append(((PyExpressionStatement)next).getExpression().getText()); } } else { @@ -115,10 +102,24 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { } next.delete(); stringBuilder.append(")"); + if (commentText != null) { + stringBuilder.append(commentText); + } expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, stringBuilder.toString())); } + private static String getComment(@Nullable final PsiElement next) { + String commentText = null; + if (next != null) { + final PsiElement lastChild = next.getLastChild(); + if (lastChild instanceof PsiComment) { + commentText = lastChild.getText(); + } + } + return commentText; + } + private static void addInArguments(@NotNull final StringBuilder stringBuilder, @NotNull final PyBinaryExpression binaryExpression) { stringBuilder.append(binaryExpression.getLeftExpression().getText()); stringBuilder.append(", "); @@ -132,6 +133,23 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { final String expressionText = expression.getText(); final StringBuilder stringBuilder = new StringBuilder(expressionText + " ("); + final PsiElement next = getNextElement(expression); + String commentText = getComment(next); + if (next != null) { + final String text = next instanceof PyExpressionStatement ? ((PyExpressionStatement)next).getExpression().getText() : next.getText(); + stringBuilder.append(text); + if (text.endsWith(",")) stringBuilder.append(" end=' '"); + next.delete(); + } + stringBuilder.append(")"); + if (commentText != null) { + stringBuilder.append(commentText); + } + expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, + stringBuilder.toString())); + } + + private static PsiElement getNextElement(@NotNull final PsiElement expression) { final PsiElement whiteSpace = expression.getContainingFile().findElementAt(expression.getTextOffset() + expression.getTextLength()); PsiElement next = null; if (whiteSpace instanceof PsiWhiteSpace) { @@ -148,23 +166,6 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { RemoveUnnecessaryBackslashQuickFix.removeBackSlash(next); if (whiteSpace != null) whiteSpace.delete(); - String commentText = null; - if (next != null) { - final PsiElement lastChild = next.getLastChild(); - if (lastChild instanceof PsiComment) { - commentText = lastChild.getText(); - } - final String text = next instanceof PyExpressionStatement ? ((PyExpressionStatement)next).getExpression().getText() : next.getText(); - - stringBuilder.append(text); - if (text.endsWith(",")) stringBuilder.append(" end=' '"); - next.delete(); - } - stringBuilder.append(")"); - if (commentText != null) { - stringBuilder.append(commentText); - } - expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, - stringBuilder.toString())); + return next; } } diff --git a/python/testData/inspections/ReplaceExecComment.py b/python/testData/inspections/ReplaceExecComment.py new file mode 100644 index 000000000000..6477f3109bda --- /dev/null +++ b/python/testData/inspections/ReplaceExecComment.py @@ -0,0 +1 @@ +exec 1 # <- doesn't work either \ No newline at end of file diff --git a/python/testData/inspections/ReplaceExecComment_after.py b/python/testData/inspections/ReplaceExecComment_after.py new file mode 100644 index 000000000000..31861724a9c1 --- /dev/null +++ b/python/testData/inspections/ReplaceExecComment_after.py @@ -0,0 +1 @@ +exec(1) # <- doesn't work either diff --git a/python/testSrc/com/jetbrains/python/Py3QuickFixTest.java b/python/testSrc/com/jetbrains/python/Py3QuickFixTest.java new file mode 100644 index 000000000000..f757da9f3fac --- /dev/null +++ b/python/testSrc/com/jetbrains/python/Py3QuickFixTest.java @@ -0,0 +1,126 @@ +/* + * 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; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.TestDataFile; +import com.intellij.testFramework.TestDataPath; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.inspections.PyMissingConstructorInspection; +import com.jetbrains.python.inspections.PyStatementEffectInspection; +import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NonNls; + +@TestDataPath("$CONTENT_ROOT/../testData/inspections/") +public class Py3QuickFixTest extends PyTestCase { + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return PyTestCase.ourPy3Descriptor; + } + + public void testReplacePrintEnd() { + runWithLanguageLevel(LanguageLevel.PYTHON34, new Runnable() { + @Override + public void run() { + doInspectionTest("ReplacePrintEnd.py", PyStatementEffectInspection.class, PyBundle.message("QFIX.statement.effect"), true, true); + }}); + } + + public void testReplacePrintComment() { + runWithLanguageLevel(LanguageLevel.PYTHON34, new Runnable() { + @Override + public void run() { + doInspectionTest("ReplacePrintComment.py", PyStatementEffectInspection.class, PyBundle.message("QFIX.statement.effect"), true, + true); + }}); + } + + public void testReplaceExecComment() { + runWithLanguageLevel(LanguageLevel.PYTHON34, new Runnable() { + @Override + public void run() { + doInspectionTest("ReplaceExecComment.py", PyStatementEffectInspection.class, PyBundle.message("QFIX.statement.effect"), true, + true); + }}); + } + + public void testAddCallSuperAnnotations() { + runWithLanguageLevel(LanguageLevel.PYTHON33, new Runnable() { + @Override + public void run() { + doInspectionTest("AddCallSuperAnnotations.py", + PyMissingConstructorInspection.class, + PyBundle.message("QFIX.add.super"), true, true); + } + }); + } + + @Override + @NonNls + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/inspections/"; + } + + protected void doInspectionTest(@TestDataFile @NonNls String testFileName, + final Class inspectionClass, + @NonNls String quickFixName, + boolean applyFix, + boolean available) { + doInspectionTest(new String[]{testFileName}, inspectionClass, quickFixName, applyFix, available); + } + + /** + * Runs daemon passes and looks for given fix within infos. + * + * @param testFiles names of files to participate; first is used for inspection and then for check by "_after". + * @param inspectionClass what inspection to run + * @param quickFixName how the resulting fix should be named (the human-readable name users see) + * @param applyFix true if the fix needs to be applied + * @param available true if the fix should be available, false if it should be explicitly not available. + * @throws Exception + */ + protected void doInspectionTest(@NonNls String[] testFiles, + final Class inspectionClass, + @NonNls String quickFixName, + boolean applyFix, + boolean available) { + myFixture.enableInspections(inspectionClass); + myFixture.configureByFiles(testFiles); + myFixture.checkHighlighting(true, false, false); + final IntentionAction intentionAction = myFixture.findSingleIntention(quickFixName); + if (available) { + assertNotNull(intentionAction); + if (applyFix) { + myFixture.launchAction(intentionAction); + + myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after")); + } + } + else { + assertNull(intentionAction); + } + } + + // Turns "name.ext" to "name_insertion.ext" + + @NonNls + private static String graftBeforeExt(String name, String insertion) { + int dotpos = name.indexOf('.'); + if (dotpos < 0) dotpos = name.length(); + return name.substring(0, dotpos) + insertion + name.substring(dotpos, name.length()); + } +} diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 4b9b5ca42e0c..811eb0f679a3 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -253,40 +253,27 @@ public class PyQuickFixTest extends PyTestCase { public void testStatementEffect() { // PY-1362, PY-2585 doInspectionTest("StatementEffect.py", PyStatementEffectInspection.class, - PyBundle.message("QFIX.statement.effect"), true, true); + PyBundle.message("QFIX.statement.effect"), true, true); } public void testStatementEffectIntroduceVariable() { // PY-1265 doInspectionTest("StatementEffectIntroduceVariable.py", PyStatementEffectInspection.class, - PyBundle.message("QFIX.statement.effect.introduce.variable"), true, true); - } - - public void testReplacePrintEnd() { - runWithLanguageLevel(LanguageLevel.PYTHON34, new Runnable() { - @Override - public void run() { - doInspectionTest("ReplacePrintEnd.py", PyStatementEffectInspection.class, PyBundle.message("QFIX.statement.effect"), true, true); - }}); - } - - public void testReplacePrintComment() { - runWithLanguageLevel(LanguageLevel.PYTHON34, new Runnable() { - @Override - public void run() { - doInspectionTest("ReplacePrintComment.py", PyStatementEffectInspection.class, PyBundle.message("QFIX.statement.effect"), true, - true); - }}); + PyBundle.message("QFIX.statement.effect.introduce.variable"), true, true); } public void testUnresolvedWith() { // PY-2083 - setLanguageLevel(LanguageLevel.PYTHON25); - doInspectionTest("UnresolvedWith.py", PyUnresolvedReferencesInspection.class, - PyBundle.message("QFIX.unresolved.reference.add.future"), true, true); + runWithLanguageLevel(LanguageLevel.PYTHON25, new Runnable() { + @Override + public void run() { + doInspectionTest("UnresolvedWith.py", PyUnresolvedReferencesInspection.class, + PyBundle.message("QFIX.unresolved.reference.add.future"), true, true); + } + }); } public void testUnresolvedRefCreateFunction() { // PY-2092 doInspectionTest("UnresolvedRefCreateFunction.py", PyUnresolvedReferencesInspection.class, - PyBundle.message("QFIX.unresolved.reference.create.function.$0", "ref"), true, true); + PyBundle.message("QFIX.unresolved.reference.create.function.$0", "ref"), true, true); } public void testUnresolvedRefNoCreateFunction() { @@ -343,17 +330,6 @@ public class PyQuickFixTest extends PyTestCase { PyBundle.message("QFIX.add.super"), true, true); } - public void testAddCallSuperAnnotations() { - runWithLanguageLevel(LanguageLevel.PYTHON33, new Runnable() { - @Override - public void run() { - doInspectionTest("AddCallSuperAnnotations.py", - PyMissingConstructorInspection.class, - PyBundle.message("QFIX.add.super"), true, true); - } - }); - } - public void testAddCallSuperPass() { //PY-8654 doInspectionTest("AddCallSuperPass.py", PyMissingConstructorInspection.class, PyBundle.message("QFIX.add.super"), true, true); @@ -387,9 +363,13 @@ public class PyQuickFixTest extends PyTestCase { } public void testSetFunctionToLiteral() { //PY-3120 - setLanguageLevel(LanguageLevel.PYTHON27); - doInspectionTest("SetFunctionToLiteral.py", PySetFunctionToLiteralInspection.class, - PyBundle.message("QFIX.replace.function.set.with.literal"), true, true); + runWithLanguageLevel(LanguageLevel.PYTHON27, new Runnable() { + @Override + public void run() { + doInspectionTest("SetFunctionToLiteral.py", PySetFunctionToLiteralInspection.class, + PyBundle.message("QFIX.replace.function.set.with.literal"), true, true); + } + }); } public void testDictComprehensionToCall() { From 28c885a5724b2d49aab3c554e959f52a5ab29470 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 24 Nov 2014 18:57:32 +0300 Subject: [PATCH 2/7] fixed PY-13965 Global declarations in nested functions are marked as undefined at module level --- .../python/codeInsight/dataflow/scope/Scope.java | 2 +- .../codeInsight/dataflow/scope/impl/ScopeImpl.java | 8 ++++---- .../jetbrains/python/psi/resolve/PyResolveUtil.java | 3 ++- python/testData/resolve/GlobalInNestedFunction.py | 10 ++++++++++ .../testSrc/com/jetbrains/python/PyResolveTest.java | 12 +++++++++--- 5 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 python/testData/resolve/GlobalInNestedFunction.py diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java index 04d4d2e04922..2467f1235098 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java @@ -46,7 +46,7 @@ public interface Scope { List getImportedNameDefiners(); @Nullable - PsiNamedElement getNamedElement(String name); + PsiNamedElement getNamedElement(String name, boolean includeNestedGlobals); @NotNull Collection getNamedElements(); diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java index 99507727bde2..92b99eb8c502 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java @@ -118,7 +118,7 @@ public class ScopeImpl implements Scope { if (isNonlocal(name)) { return false; } - if (getNamedElement(name) != null) { + if (getNamedElement(name, true) != null) { return true; } if (isAugAssignment(name)) { @@ -143,7 +143,7 @@ public class ScopeImpl implements Scope { @Nullable @Override - public PsiNamedElement getNamedElement(String name) { + public PsiNamedElement getNamedElement(String name, boolean includeNestedGlobals) { if (myNamedElements == null) { collectDeclarations(); } @@ -151,9 +151,9 @@ public class ScopeImpl implements Scope { if (element != null) { return element; } - if (isGlobal(name)) { + if (includeNestedGlobals && isGlobal(name)) { for (Scope scope : myNestedScopes) { - final PsiNamedElement global = scope.getNamedElement(name); + final PsiNamedElement global = scope.getNamedElement(name, true); if (global != null) { return global; } diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 8774c71a6a42..5c6f93811661 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -133,7 +133,8 @@ public class PyResolveUtil { final Scope scope = ControlFlowCache.getScope(scopeOwner); boolean found = false; if (name != null) { - final PsiElement resolved = scope.getNamedElement(name); + final boolean includeNestedGlobals = scopeOwner instanceof PyFile; + final PsiElement resolved = scope.getNamedElement(name, includeNestedGlobals); if (resolved != null) { if (!processor.execute(resolved, ResolveState.initial())) { found = true; diff --git a/python/testData/resolve/GlobalInNestedFunction.py b/python/testData/resolve/GlobalInNestedFunction.py new file mode 100644 index 000000000000..643de248b7b6 --- /dev/null +++ b/python/testData/resolve/GlobalInNestedFunction.py @@ -0,0 +1,10 @@ + +foo = 0 + +def outer(): + def inner(): + global foo + print(foo) + inner() + +outer() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 6b629537ba04..91a8955938cf 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -160,13 +160,19 @@ public class PyResolveTest extends PyResolveTestCase { public void testTryExceptElse() { PsiElement targetElement = resolve(); - Assert.assertTrue(targetElement instanceof PyTargetExpression); + assertTrue(targetElement instanceof PyTargetExpression); } public void testGlobal() { PsiElement targetElement = resolve(); - Assert.assertTrue(targetElement instanceof PyTargetExpression); - Assert.assertTrue(targetElement.getParent() instanceof PyAssignmentStatement); + assertTrue(targetElement instanceof PyTargetExpression); + assertTrue(targetElement.getParent() instanceof PyAssignmentStatement); + } + + public void testGlobalInNestedFunction() { + PsiElement targetElement = resolve(); + assertInstanceOf(targetElement, PyTargetExpression.class); + assertInstanceOf(ScopeUtil.getScopeOwner(targetElement), PyFile.class); } public void testGlobalDefinedLocally() { From 613f745cd710e68d3a16878f0fa28aebae9c974b Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Mon, 24 Nov 2014 19:15:13 +0300 Subject: [PATCH 3/7] Fixed WEB-2405 Code style / HTML / "Do not indent children ... or if tag size more than" actually works like "more or equals" --- xml/impl/src/com/intellij/psi/formatter/xml/HtmlPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xml/impl/src/com/intellij/psi/formatter/xml/HtmlPolicy.java b/xml/impl/src/com/intellij/psi/formatter/xml/HtmlPolicy.java index 57e653828954..f89ceff8a601 100644 --- a/xml/impl/src/com/intellij/psi/formatter/xml/HtmlPolicy.java +++ b/xml/impl/src/com/intellij/psi/formatter/xml/HtmlPolicy.java @@ -74,7 +74,7 @@ public class HtmlPolicy extends XmlFormattingPolicy { private int getLines(final XmlTag parentTag) { final TextRange textRange = parentTag.getTextRange(); - return myDocumentModel.getLineNumber(textRange.getEndOffset()) - myDocumentModel.getLineNumber(textRange.getStartOffset()); + return myDocumentModel.getLineNumber(textRange.getEndOffset()) - myDocumentModel.getLineNumber(textRange.getStartOffset()) + 1; } @Override From 7791018a4f7227fa7592af7945e09e88a20879b1 Mon Sep 17 00:00:00 2001 From: Liana Bakradze Date: Mon, 24 Nov 2014 19:20:47 +0300 Subject: [PATCH 4/7] EDU-285 Do not allow to run current file if no sdk selected --- .../com/jetbrains/python/edu/StudyUtils.java | 8 ++++ .../python/edu/actions/StudyCheckAction.java | 44 +++++++++++++++++-- .../python/edu/actions/StudyRunAction.java | 23 ++++++---- .../python/edu/editor/StudyEditor.java | 39 +++++++++------- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java index 21881c629c7c..c84fbf81b7d8 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java @@ -9,7 +9,9 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -19,6 +21,7 @@ import com.intellij.util.ui.UIUtil; import com.jetbrains.python.edu.course.*; import com.jetbrains.python.edu.editor.StudyEditor; import com.jetbrains.python.edu.ui.StudyToolWindowFactory; +import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -185,4 +188,9 @@ public class StudyUtils { FileUtil.copy(new File(pathToResource, sourceName), resourceFile); return resourceFile; } + + @Nullable + public static Sdk findPythonSdk(@NotNull final Project project) { + return PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]); + } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java index b5f4c003ffe8..733514de75a9 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java @@ -12,8 +12,10 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.BalloonBuilder; @@ -33,6 +35,8 @@ import com.jetbrains.python.edu.editor.StudyEditor; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; import java.awt.*; import java.io.IOException; import java.util.Map; @@ -41,7 +45,7 @@ public class StudyCheckAction extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName()); private static final String ANSWERS_POSTFIX = "_answers.py"; - public static final String ACTION_ID = "CheckAction"; + public static final String ACTION_ID = "CheckAction"; public static final String SHORTCUT = "ctrl alt pressed ENTER"; @@ -108,9 +112,15 @@ public class StudyCheckAction extends DumbAwareAction { VirtualFile taskDir = studyState.getTaskDir(); flushWindows(task, taskDir); StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID); - if (runAction != null && taskFiles.size() == 1) { - runAction.run(project); + if (runAction == null) { + return; } + Sdk sdk = StudyUtils.findPythonSdk(project); + if (sdk == null) { + createNoPythonInterpreterPopUp(project); + return; + } + runAction.run(project, sdk); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { @@ -157,6 +167,27 @@ public class StudyCheckAction extends DumbAwareAction { }); } + private static void createNoPythonInterpreterPopUp(@NotNull final Project project) { + String text = "No Python interpreter configured for the project
Configure interpreter"; + BalloonBuilder balloonBuilder = + JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, MessageType.WARNING.getPopupBackground(), new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent event) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); + } + }); + } + } + }); + balloonBuilder.setHideOnLinkClick(true); + final Balloon balloon = balloonBuilder.createBalloon(); + showCheckPopUp(project, balloon); + } + private static void navigateToFailedTaskWindow(@NotNull final StudyState studyState, @NotNull final Task task, @NotNull final VirtualFile taskDir, @@ -261,6 +292,13 @@ public class StudyCheckAction extends DumbAwareAction { BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null); final Balloon balloon = balloonBuilder.createBalloon(); + showCheckPopUp(project, balloon); + } + + /** + * shows pop up in the center of "check task" button in study editor + */ + private static void showCheckPopUp(@NotNull final Project project, @NotNull final Balloon balloon) { StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project); assert studyEditor != null; JButton checkButton = studyEditor.getCheckButton(); diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java index 71e95defdedc..44bd31b5ef56 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java @@ -9,18 +9,18 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.edu.StudyResourceManger; import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.course.Task; import com.jetbrains.python.edu.course.TaskFile; import com.jetbrains.python.edu.editor.StudyEditor; +import org.jetbrains.annotations.NotNull; import java.io.File; @@ -28,7 +28,7 @@ public class StudyRunAction extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(StudyRunAction.class.getName()); public static final String ACTION_ID = "StudyRunAction"; - public void run(Project project) { + public void run(@NotNull final Project project, @NotNull final Sdk sdk) { Editor selectedEditor = StudyEditor.getSelectedEditor(project); FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); assert selectedEditor != null; @@ -37,9 +37,7 @@ public class StudyRunAction extends DumbAwareAction { if (openedFile != null && openedFile.getCanonicalPath() != null) { String filePath = openedFile.getCanonicalPath(); GeneralCommandLine cmd = new GeneralCommandLine(); - cmd.setWorkDirectory(openedFile.getParent().getCanonicalPath()); - Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]); - if (sdk != null) { + cmd.withWorkDirectory(openedFile.getParent().getCanonicalPath()); String pythonPath = sdk.getHomePath(); if (pythonPath != null) { cmd.setExePath(pythonPath); @@ -79,11 +77,18 @@ public class StudyRunAction extends DumbAwareAction { LOG.error(e); } } - } } } - public void actionPerformed(AnActionEvent e) { - run(e.getProject()); + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null) { + return; + } + Sdk sdk = StudyUtils.findPythonSdk(project); + if (sdk == null) { + return; + } + run(project, sdk); } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java index b497b5216c26..14f932ede2f0 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java @@ -20,6 +20,7 @@ import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; @@ -35,6 +36,7 @@ import com.intellij.util.ui.EmptyClipboardOwner; import com.intellij.util.ui.UIUtil; import com.jetbrains.python.edu.StudyDocumentListener; import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.actions.*; import com.jetbrains.python.edu.course.Task; import com.jetbrains.python.edu.course.TaskFile; @@ -209,23 +211,26 @@ public class StudyEditor implements TextEditor { myRefreshButton = addButton(taskActionsPanel, StudyRefreshTaskFileAction.ACTION_ID, AllIcons.Actions.Refresh, StudyRefreshTaskFileAction.SHORTCUT); JButton myShowHintButton = addButton(taskActionsPanel, StudyShowHintAction.ACTION_ID, StudyIcons.ShowHint, StudyShowHintAction.SHORTCUT); if (!taskFile.getTask().getUserTests().isEmpty()) { - JButton runButton = addButton(taskActionsPanel, StudyRunAction.ACTION_ID, AllIcons.General.Run, null); - runButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - StudyRunAction studyRunAction = (StudyRunAction)ActionManager.getInstance().getAction("StudyRunAction"); - studyRunAction.run(myProject); - } - }); - JButton watchInputButton = addButton(taskActionsPanel, "WatchInputAction", StudyIcons.WatchInput, null); - watchInputButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - StudyEditInputAction studyEditInputAction = - (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction"); - studyEditInputAction.showInput(myProject); - } - }); + final Sdk sdk = StudyUtils.findPythonSdk(myProject); + if (sdk != null) { + JButton runButton = addButton(taskActionsPanel, StudyRunAction.ACTION_ID, AllIcons.General.Run, null); + runButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + StudyRunAction studyRunAction = (StudyRunAction)ActionManager.getInstance().getAction("StudyRunAction"); + studyRunAction.run(myProject, sdk); + } + }); + JButton watchInputButton = addButton(taskActionsPanel, "WatchInputAction", StudyIcons.WatchInput, null); + watchInputButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + StudyEditInputAction studyEditInputAction = + (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction"); + studyEditInputAction.showInput(myProject); + } + }); + } } myCheckButton.addActionListener(new ActionListener() { @Override From d31c1977b80c6b23183eef456f4aafe46e52083d Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 24 Nov 2014 19:23:03 +0300 Subject: [PATCH 5/7] fixed PY-14149 Generate stub for binary module: not available on from imports --- .../inspections/quickfix/GenerateBinaryStubsFix.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java index 03ab4f240884..99075ba9a023 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java @@ -76,6 +76,11 @@ public class GenerateBinaryStubsFix implements LocalQuickFix { public static Collection generateFixes(@NotNull final PyImportStatementBase importStatementBase) { final List names = importStatementBase.getFullyQualifiedObjectNames(); final List result = new ArrayList(names.size()); + if (importStatementBase instanceof PyFromImportStatement && names.isEmpty()) { + final QualifiedName qName = ((PyFromImportStatement)importStatementBase).getImportSourceQName(); + if (qName != null) + result.add(new GenerateBinaryStubsFix(importStatementBase, qName.toString())); + } for (final String qualifiedName : names) { result.add(new GenerateBinaryStubsFix(importStatementBase, qualifiedName)); } @@ -225,7 +230,8 @@ public class GenerateBinaryStubsFix implements LocalQuickFix { * @return true if this fix could work */ public static boolean isApplicable(@NotNull final PyImportStatementBase importStatementBase) { - if (importStatementBase.getFullyQualifiedObjectNames().isEmpty()) { + if (importStatementBase.getFullyQualifiedObjectNames().isEmpty() && + !(importStatementBase instanceof PyFromImportStatement && ((PyFromImportStatement)importStatementBase).isStarImport())) { return false; } final Sdk sdk = getPythonSdk(importStatementBase); From 031ab39ef52a81ac849ee8bf8622c61ade83c09e Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 24 Nov 2014 14:16:39 +0300 Subject: [PATCH 6/7] PY-9778 Do not warn about unused inner functions with unknown decorators I added helper class PyKnownDecoratorUtil that contains list of well-known decorators from Python's standard library and redefined several existing methods from PyUtil in terms of this class. --- .../PyUnusedLocalInspectionVisitor.java | 3 + .../python/psi/PyKnownDecoratorUtil.java | 197 ++++++++++++++++++ .../src/com/jetbrains/python/psi/PyUtil.java | 27 +-- .../asyncio/__init__.py | 1 + .../PyUnusedLocalCoroutine/asyncio/tasks.py | 2 + .../PyUnusedLocalCoroutine/test.py | 7 + .../expected.xml | 8 +- .../src/test.py | 22 ++ .../python/PythonInspectionsTest.java | 6 + 9 files changed, 247 insertions(+), 26 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java create mode 100644 python/testData/inspections/PyUnusedLocalCoroutine/asyncio/__init__.py create mode 100644 python/testData/inspections/PyUnusedLocalCoroutine/asyncio/tasks.py create mode 100644 python/testData/inspections/PyUnusedLocalCoroutine/test.py diff --git a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java index 8b0e635d4ca4..ee23f5adccc6 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java +++ b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java @@ -101,6 +101,9 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor { for (Instruction instruction : instructions) { final PsiElement element = instruction.getElement(); if (element instanceof PyFunction && owner instanceof PyFunction) { + if (PyKnownDecoratorUtil.hasUnknownDecorator((PyFunction)element, myTypeEvalContext)) { + continue; + } if (!myUsedElements.contains(element)) { myUnusedElements.add(element); } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java new file mode 100644 index 000000000000..bfa676e9dfef --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -0,0 +1,197 @@ +package com.jetbrains.python.psi; + +import com.google.common.collect.Iterators; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.QualifiedName; +import com.intellij.util.containers.Convertor; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.types.TypeEvalContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static com.intellij.util.containers.ContainerUtil.newMapFromValues; +import static com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.*; +import static com.jetbrains.python.psi.PyUtil.as; + +/** + * Contains list of well-behaved decorators from Pythons standard library, that don't change + * signature of underlying function/class or use it implicitly somewhere (e.g. register as a callback). + * + * @author Mikhail Golubev + */ +public class PyKnownDecoratorUtil { + + private PyKnownDecoratorUtil() { + } + + @SuppressWarnings("SpellCheckingInspection") + public enum KnownDecorator { + + STATICMETHOD(PyNames.STATICMETHOD), + CLASSMETHOD(PyNames.CLASSMETHOD), + PROPERTY(PyNames.PROPERTY), + + CONTEXTLIB_CONTEXTMANAGER("contextlib.contextmanager"), + + FUNCTOOLS_LRU_CACHE("functools.lru_cache"), + FUNCTOOLS_WRAPS("functools.wraps"), + FUNCTOOLS_TOTAL_ORDERING("functools.total_ordering"), + FUNCTOOLS_SINGLEDISPATCH("functools.singledispatch"), + + ABC_ABSTRACTMETHOD("abc.abstractmethod"), + ABC_ABSTRACTCLASSMETHOD("abc.abstractclassmethod"), + ABC_ABSTRACTSTATICMETHOD("abc.abstractstaticmethod"), + ABC_ABSTRACTPROPERTY("abc.abstractproperty"), + + //ATEXIT_REGISTER("atexit.register", true), + //ATEXIT_UNREGISTER("atexit.unregister", false), + + ASYNCIO_COROUTINE("asyncio.tasks.coroutine"), + + UNITTEST_SKIP("unittest.case.skip"), + UNITTEST_SKIP_IF("unittest.case.skipIf"), + UNITTEST_SKIP_UNLESS("unittest.case.skipUnless"), + UNITTEST_EXPECTED_FAILURE("unittest.case.expectedFailure"), + UNITTEST_MOCK_PATCH("unittest.mock.patch"), + + REPRLIB_RECURSIVE_REPR("reprlib.recursive_repr"); + + private final QualifiedName myQualifiedName; + + KnownDecorator(@NotNull String qualifiedName) { + myQualifiedName = QualifiedName.fromDottedString(qualifiedName); + } + + @NotNull + public QualifiedName getQualifiedName() { + return myQualifiedName; + } + + @NotNull + public String getShortName() { + //noinspection ConstantConditions + return myQualifiedName.getLastComponent(); + } + } + + private static final Set BUILTIN_DECORATORS = EnumSet.of(PROPERTY, CLASSMETHOD, STATICMETHOD); + private static final Set ABSTRACT_DECORATORS = EnumSet.of(ABC_ABSTRACTMETHOD, + ABC_ABSTRACTPROPERTY, + ABC_ABSTRACTSTATICMETHOD, + ABC_ABSTRACTCLASSMETHOD); + + private static final Map ourByShortName = newMapFromValues(Iterators.forArray(values()), + new Convertor() { + @Override + public String convert(KnownDecorator o) { + return o.getShortName(); + } + }); + + /** + * Map decorators of element to {@link com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator}. + * + * @param element decoratable element to check + * @param context type evaluation context. If it's {@code null} or doesn't allow switch to AST only textual comparison will be performed. + * @return list of known decorators in declaration order with duplicates (with any) + */ + @NotNull + public static List getKnownDecorators(@NotNull PyDecoratable element, @Nullable TypeEvalContext context) { + final PyDecoratorList decoratorList = element.getDecoratorList(); + if (decoratorList == null) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + final boolean allowResolve = context != null && context.maySwitchToAST((PsiElement)element); + for (PyDecorator decorator : decoratorList.getDecorators()) { + final QualifiedName qualifiedName = decorator.getQualifiedName(); + if (qualifiedName == null) { + continue; + } + + final KnownDecorator knownDecorator = ourByShortName.get(qualifiedName.getLastComponent()); + if (knownDecorator != null) { + if (allowResolve) { + PyQualifiedNameOwner resolved = as(resolveDecorator(decorator), PyQualifiedNameOwner.class); + if (resolved instanceof PyFunction && PyNames.INIT.equals(resolved.getName())) { + resolved = ((PyFunction)resolved).getContainingClass(); + } + if (resolved != null && resolved.getQualifiedName() != null) { + final QualifiedName resolvedName = QualifiedName.fromDottedString(resolved.getQualifiedName()); + if (resolvedName.equals(knownDecorator.getQualifiedName())) { + result.add(knownDecorator); + } + } + } + else { + result.add(knownDecorator); + } + } + } + return result; + } + + @Nullable + private static PsiElement resolveDecorator(@NotNull PyDecorator decorator) { + final PyExpression callee = decorator.getCallee(); + if (callee == null) { + return null; + } + final PsiReference reference = callee.getReference(); + if (reference == null) { + return null; + } + return reference.resolve(); + } + + /** + * Check that given element has any non-standard (read "unreliable") decorators. + * + * @param element decoratable element to check + * @param context type evaluation context. If it's {@code null} or doesn't allow switch to AST only textual comparison will be performed. + * @see PyKnownDecoratorUtil.KnownDecorator + */ + public static boolean hasUnknownDecorator(@NotNull PyDecoratable element, @Nullable TypeEvalContext context) { + return !allDecoratorsAreKnown(element, getKnownDecorators(element, context)); + } + + /** + * Checks that given element has any non-builtin decorators. + * + * @param element decoratable element to check + * @param context type evaluation context. If it's {@code null} or doesn't allow switch to AST only textual comparison will be performed. + * @see PyKnownDecoratorUtil.KnownDecorator + */ + public static boolean hasNonBuiltinDecorator(@NotNull PyDecoratable element, @Nullable TypeEvalContext context) { + final List knownDecorators = getKnownDecorators(element, context); + if (!allDecoratorsAreKnown(element, knownDecorators)) { + return true; + } + knownDecorators.removeAll(BUILTIN_DECORATORS); + return !knownDecorators.isEmpty(); + } + + /** + * Checks that given function has any decorators from {@code abc} module. + * + * @param element Python function to check + * @param context type evaluation context. If it's {@code null} or doesn't allow switch to AST only textual comparison will be performed. + * @see PyKnownDecoratorUtil.KnownDecorator + */ + public static boolean hasAbstractDecorator(@NotNull PyDecoratable element, @Nullable TypeEvalContext context) { + final List knownDecorators = getKnownDecorators(element, context); + if (knownDecorators.isEmpty()) { + return false; + } + knownDecorators.retainAll(ABSTRACT_DECORATORS); + return !knownDecorators.isEmpty(); + } + + private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List decorators) { + final PyDecoratorList decoratorList = element.getDecoratorList(); + return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size(); + } +} diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index b7aa91031d5e..5946be8705da 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -695,34 +695,11 @@ public class PyUtil { } public static boolean hasCustomDecorators(@NotNull PyDecoratable decoratable) { - PyDecoratorList decoratorList = decoratable.getDecoratorList(); - if (decoratorList == null) { - return false; - } - for (PyDecorator decorator : decoratorList.getDecorators()) { - QualifiedName name = decorator.getQualifiedName(); - if (name == null || (!PyNames.CLASSMETHOD.equals(name.toString()) && !PyNames.STATICMETHOD.equals(name.toString()))) { - return true; - } - } - return false; + return PyKnownDecoratorUtil.hasNonBuiltinDecorator(decoratable, null); } public static boolean isDecoratedAsAbstract(@NotNull final PyDecoratable decoratable) { - final PyDecoratorList decoratorList = decoratable.getDecoratorList(); - if (decoratorList == null) { - return false; - } - for (PyDecorator decorator : decoratorList.getDecorators()) { - final QualifiedName qualifiedName = decorator.getQualifiedName(); - if (qualifiedName != null) { - final String name = qualifiedName.toString(); - if (name.endsWith(PyNames.ABSTRACTMETHOD) || name.endsWith(PyNames.ABSTRACTPROPERTY)) { - return true; - } - } - } - return false; + return PyKnownDecoratorUtil.hasAbstractDecorator(decoratable, null); } public static ASTNode createNewName(PyElement element, String name) { diff --git a/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/__init__.py b/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/__init__.py new file mode 100644 index 000000000000..f5456c32b2d2 --- /dev/null +++ b/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/__init__.py @@ -0,0 +1 @@ +from .tasks import * diff --git a/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/tasks.py b/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/tasks.py new file mode 100644 index 000000000000..b8c03e414d45 --- /dev/null +++ b/python/testData/inspections/PyUnusedLocalCoroutine/asyncio/tasks.py @@ -0,0 +1,2 @@ +def coroutine(func): + return func diff --git a/python/testData/inspections/PyUnusedLocalCoroutine/test.py b/python/testData/inspections/PyUnusedLocalCoroutine/test.py new file mode 100644 index 000000000000..ff497d32ed67 --- /dev/null +++ b/python/testData/inspections/PyUnusedLocalCoroutine/test.py @@ -0,0 +1,7 @@ +import asyncio + +# PY-9778 +def process(): + @asyncio.coroutine + def func(): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyUnusedLocalFunctionInspection/expected.xml b/python/testData/inspections/PyUnusedLocalFunctionInspection/expected.xml index 10d4a4dc49bb..4f5aee828eb6 100644 --- a/python/testData/inspections/PyUnusedLocalFunctionInspection/expected.xml +++ b/python/testData/inspections/PyUnusedLocalFunctionInspection/expected.xml @@ -3,11 +3,17 @@ test.py 2 - Local function 'bar' is not used + Local function 'bar' is not used test.py 13 Local function 'bar1' is not used + + test.py + 29 + light_idea_test_case + Local function 'func' is not used + \ No newline at end of file diff --git a/python/testData/inspections/PyUnusedLocalFunctionInspection/src/test.py b/python/testData/inspections/PyUnusedLocalFunctionInspection/src/test.py index 5c358bcec316..cffb893a1baa 100644 --- a/python/testData/inspections/PyUnusedLocalFunctionInspection/src/test.py +++ b/python/testData/inspections/PyUnusedLocalFunctionInspection/src/test.py @@ -21,3 +21,25 @@ def foo(): def baz(): #pass bar() baz() + + +# PY-9778 +def unused_inner_function_with_known_decorator(): + @staticmethod + def func(): # fail + yield + + +def unused_inner_function_with_unknown_decorator(): + def register(f): + return f + + @register + @contextmanager + def func(): # pass + pass + +def unused_inner_function_with_incomplete_decorator(): + @ + def func(): # pass + pass diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 7413026fdaf4..089f0f8b368e 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -101,6 +101,12 @@ public class PythonInspectionsTest extends PyTestCase { doTest(getTestName(false), inspection); } + // PY-9778 + public void testPyUnusedLocalCoroutine() { + myFixture.copyDirectoryToProject("inspections/" + getTestName(false), ""); + doHighlightingTest(PyUnusedLocalInspection.class, LanguageLevel.PYTHON34); + } + public void testPyDictCreationInspection() { doHighlightingTest(PyDictCreationInspection.class, LanguageLevel.PYTHON26); } From 80203d3a6403c639acfaf5d3947aee647a5e69a7 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 24 Nov 2014 19:08:06 +0300 Subject: [PATCH 7/7] PY-13791 Do not ignore unresolved attributes of functions annotated only with built-in decorators --- .../PyUnresolvedReferencesInspection.java | 2 +- .../com/jetbrains/python/psi/PyKnownDecoratorUtil.java | 1 + .../methodSpecialAttributes.py | 10 ++++------ .../PyUnresolvedReferencesInspectionTest.java | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index 5f6961bad551..43bc2a925c1c 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -736,7 +736,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } if (type instanceof PyFunctionType) { final Callable callable = ((PyFunctionType)type).getCallable(); - if (callable instanceof PyFunction && ((PyFunction)callable).getDecoratorList() != null) { + if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasNonBuiltinDecorator((PyFunction)callable, myTypeEvalContext)) { return true; } } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index bfa676e9dfef..f09a48f82a0e 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -27,6 +27,7 @@ public class PyKnownDecoratorUtil { private PyKnownDecoratorUtil() { } + // TODO provide more information about these decorators: attributes (e.g. lru_cache(f).cache_info), side-effects etc. @SuppressWarnings("SpellCheckingInspection") public enum KnownDecorator { diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py b/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py index 7a60e533a15d..f8706ab8199d 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py @@ -7,7 +7,7 @@ class MyClass(object): pass -# Unbound method still treated as __method in Python 2 +# Unbound methods are still treated as __method in Python 2 MyClass.method.__func__ MyClass.method.__defaults__ @@ -16,10 +16,8 @@ inst = MyClass() inst.method.__func__ inst.method.__defaults__ -# Reassigned bound method without qualifier -m = inst.method - # Static method -# This reference should be marked as unresolved, but such warnings are suppressed for methods with decorators -inst.static_method.__func__ +inst.static_method.__func__ inst.static_method.__defaults__ +MyClass.static_method.__func__ +MyClass.static_method.__defaults__ diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 5367412c133e..b367fcb8089f 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -374,7 +374,7 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doMultiFileTest(); } - // PY-9342 + // PY-9342, PY-13791 public void testMethodSpecialAttributes() { doTest(); }