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
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/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/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);
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/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
new file mode 100644
index 000000000000..f09a48f82a0e
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java
@@ -0,0 +1,198 @@
+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() {
+ }
+
+ // TODO provide more information about these decorators: attributes (e.g. lru_cache(f).cache_info), side-effects etc.
+ @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/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/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/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/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/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 fo[o
+ print(foo)
+ inner()
+
+outer()
\ No newline at end of file
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() {
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() {
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);
}
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();
}
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
]