Merge remote-tracking branch 'origin/master'

This commit is contained in:
Dmitry Trofimov
2014-11-24 19:37:27 +03:00
28 changed files with 554 additions and 146 deletions
@@ -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]);
}
}
@@ -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 = "<html>No Python interpreter configured for the project<br><a href=\"\">Configure interpreter</a></html>";
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();
@@ -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);
}
}
@@ -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
@@ -46,7 +46,7 @@ public interface Scope {
List<PyImportedNameDefiner> getImportedNameDefiners();
@Nullable
PsiNamedElement getNamedElement(String name);
PsiNamedElement getNamedElement(String name, boolean includeNestedGlobals);
@NotNull
Collection<PsiNamedElement> getNamedElements();
@@ -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;
}
@@ -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);
}
@@ -76,6 +76,11 @@ public class GenerateBinaryStubsFix implements LocalQuickFix {
public static Collection<GenerateBinaryStubsFix> generateFixes(@NotNull final PyImportStatementBase importStatementBase) {
final List<String> names = importStatementBase.getFullyQualifiedObjectNames();
final List<GenerateBinaryStubsFix> result = new ArrayList<GenerateBinaryStubsFix>(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);
@@ -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;
}
}
@@ -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;
}
}
@@ -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<KnownDecorator> BUILTIN_DECORATORS = EnumSet.of(PROPERTY, CLASSMETHOD, STATICMETHOD);
private static final Set<KnownDecorator> ABSTRACT_DECORATORS = EnumSet.of(ABC_ABSTRACTMETHOD,
ABC_ABSTRACTPROPERTY,
ABC_ABSTRACTSTATICMETHOD,
ABC_ABSTRACTCLASSMETHOD);
private static final Map<String, KnownDecorator> ourByShortName = newMapFromValues(Iterators.forArray(values()),
new Convertor<KnownDecorator, String>() {
@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<KnownDecorator> getKnownDecorators(@NotNull PyDecoratable element, @Nullable TypeEvalContext context) {
final PyDecoratorList decoratorList = element.getDecoratorList();
if (decoratorList == null) {
return Collections.emptyList();
}
final List<KnownDecorator> result = new ArrayList<KnownDecorator>();
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<KnownDecorator> 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<KnownDecorator> 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<KnownDecorator> decorators) {
final PyDecoratorList decoratorList = element.getDecoratorList();
return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size();
}
}
@@ -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) {
@@ -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;
@@ -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.<warning descr="Cannot find reference '__defaults__' in 'function'">__defaults__</warning>
@@ -16,10 +16,8 @@ inst = MyClass()
inst.method.__func__
inst.method.<warning descr="Cannot find reference '__defaults__' in 'function'">__defaults__</warning>
# 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.<warning descr="Cannot find reference '__func__' in 'function'">__func__</warning>
inst.static_method.__defaults__
MyClass.static_method.<warning descr="Cannot find reference '__func__' in 'function'">__func__</warning>
MyClass.static_method.__defaults__
@@ -0,0 +1 @@
from .tasks import *
@@ -0,0 +1,2 @@
def coroutine(func):
return func
@@ -0,0 +1,7 @@
import asyncio
# PY-9778
def process():
@asyncio.coroutine
def <weak_warning descr="Local function 'func' is not used">func</weak_warning>():
pass
@@ -3,11 +3,17 @@
<problem>
<file>test.py</file>
<line>2</line>
<description>Local function 'bar' is not used</description>
<description>Local function 'bar' is not used</description>
</problem>
<problem>
<file>test.py</file>
<line>13</line>
<description>Local function 'bar1' is not used</description>
</problem>
<problem>
<file>test.py</file>
<line>29</line>
<module>light_idea_test_case</module>
<description>Local function 'func' is not used</description>
</problem>
</problems>
@@ -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
@@ -0,0 +1 @@
<warning descr="Statement seems to have no effect and can be replaced with function call to have effect">exec</warning><error descr="End of statement expected"> </error><warning descr="Statement seems to have no effect">1</warning> # <- doesn't work either
@@ -0,0 +1 @@
exec(1) # <- doesn't work either
@@ -0,0 +1,10 @@
foo = 0
def outer():
def inner():
global fo<ref>o
print(foo)
inner()
outer()
@@ -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());
}
}
@@ -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() {
@@ -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() {
@@ -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);
}
@@ -374,7 +374,7 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doMultiFileTest();
}
// PY-9342
// PY-9342, PY-13791
public void testMethodSpecialAttributes() {
doTest();
}
@@ -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