inspection highlights deprecated members

This commit is contained in:
Dmitry Jemerov
2011-06-24 10:26:01 +02:00
parent f3c219c42b
commit 5042838905
22 changed files with 265 additions and 50 deletions
+34 -29
View File
@@ -12,54 +12,59 @@ import java.util.regex.Pattern;
/**
* @author dcheryasov
*/
@NonNls
public class PyNames {
private PyNames() {
}
@NonNls public static final String INIT = "__init__";
@NonNls public static final String DOT_PY = ".py";
@NonNls public static final String INIT_DOT_PY = INIT + DOT_PY;
public static final String INIT = "__init__";
public static final String DOT_PY = ".py";
public static final String INIT_DOT_PY = INIT + DOT_PY;
@NonNls public static final String NEW = "__new__";
@NonNls public static final String GETATTR = "__getattr__";
@NonNls public static final String GETATTRIBUTE = "__getattribute__";
@NonNls public static final String CLASS = "__class__";
@NonNls public static final String METACLASS = "__metaclass__";
public static final String NEW = "__new__";
public static final String GETATTR = "__getattr__";
public static final String GETATTRIBUTE = "__getattribute__";
public static final String CLASS = "__class__";
public static final String METACLASS = "__metaclass__";
@NonNls public static final String SUPER = "super";
public static final String SUPER = "super";
@NonNls public static final String OBJECT = "object";
@NonNls public static final String NONE = "None";
@NonNls public static final String TRUE = "True";
@NonNls public static final String FALSE = "False";
@NonNls public static final String FAKE_OLD_BASE = "___Classobj";
public static final String OBJECT = "object";
public static final String NONE = "None";
public static final String TRUE = "True";
public static final String FALSE = "False";
public static final String FAKE_OLD_BASE = "___Classobj";
@NonNls public static final String FUTURE_MODULE = "__future__";
public static final String FUTURE_MODULE = "__future__";
@NonNls public static final String CLASSMETHOD = "classmethod";
@NonNls public static final String STATICMETHOD = "staticmethod";
public static final String CLASSMETHOD = "classmethod";
public static final String STATICMETHOD = "staticmethod";
@NonNls public static final String PROPERTY = "property";
public static final String PROPERTY = "property";
@NonNls public static final String ALL = "__all__";
@NonNls public static final String SLOTS = "__slots__";
@NonNls public static final String DEBUG = "__debug__";
public static final String ALL = "__all__";
public static final String SLOTS = "__slots__";
public static final String DEBUG = "__debug__";
@NonNls public static final String ISINSTANCE = "isinstance";
public static final String ISINSTANCE = "isinstance";
@NonNls public static final String DOCFORMAT = "__docformat__";
public static final String DOCFORMAT = "__docformat__";
@NonNls public static final String DIRNAME = "dirname";
@NonNls public static final String ABSPATH = "abspath";
@NonNls public static final String JOIN = "join";
@NonNls public static final String REPLACE = "replace";
@NonNls public static final String FILE = "__file__";
public static final String DIRNAME = "dirname";
public static final String ABSPATH = "abspath";
public static final String JOIN = "join";
public static final String REPLACE = "replace";
public static final String FILE = "__file__";
public static final String WARN = "warn";
public static final String DEPRECATION_WARNING = "DeprecationWarning";
public static final String PENDING_DEPRECATION_WARNING = "PendingDeprecationWarning";
/**
* Contains all known predefined names of "__foo__" form.
*/
public static ImmutableSet<String> UnderscoredAttributes = ImmutableSet.of(
"__all__",
"__all__",
"__author__",
"__bases__",
"__dict__",
@@ -0,0 +1,57 @@
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class PyDeprecationInspection extends PyInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return "Deprecated function, class or module";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder);
}
private static class Visitor extends PyInspectionVisitor {
public Visitor(@Nullable final ProblemsHolder holder) {
super(holder);
}
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
PsiElement resolveResult = node.getReference(PyResolveContext.noImplicits()).resolve();
String deprecationMessage = null;
if (resolveResult instanceof PyFunction) {
deprecationMessage = ((PyFunction) resolveResult).getDeprecationMessage();
}
else if (resolveResult instanceof PyFile) {
deprecationMessage = ((PyFile)resolveResult).getDeprecationMessage();
}
if (deprecationMessage != null) {
ASTNode nameElement = node.getNameElement();
registerProblem(nameElement == null ? node : nameElement.getPsi(), deprecationMessage, ProblemHighlightType.LIKE_DEPRECATED, null);
}
}
}
}
@@ -61,6 +61,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider {
PySetFunctionToLiteralInspection.class,
PyDecoratorInspection.class,
PyTypeCheckerInspection.class,
PyDeprecationInspection.class,
};
}
}
@@ -1,6 +1,5 @@
package com.jetbrains.python.psi;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
@@ -62,4 +61,11 @@ public interface PyFile extends PyElement, PsiFile, PyDocStringOwner, ScopeOwner
* Return true if the file contains a 'from __future__ import ...' statement with given feature.
*/
boolean hasImportFromFuture(FutureFeature feature);
/**
* If the function raises a DeprecationWarning or a PendingDeprecationWarning, returns the explanation text provided for the warning..
*
* @return the deprecation message or null if the function is not deprecated.
*/
String getDeprecationMessage();
}
@@ -14,6 +14,7 @@ import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.tree.IStubFileElementType;
import com.intellij.util.io.StringRef;
import com.jetbrains.python.lexer.PythonIndentingLexer;
import com.jetbrains.python.parsing.PyParser;
import com.jetbrains.python.parsing.StatementParsing;
@@ -42,7 +43,7 @@ public class PyFileElementType extends IStubFileElementType<PyFileStub> {
@Override
public int getStubVersion() {
return 33;
return 34;
}
@Override
@@ -84,13 +85,15 @@ public class PyFileElementType extends IStubFileElementType<PyFileStub> {
public void serialize(PyFileStub stub, StubOutputStream dataStream) throws IOException {
writeNullableList(dataStream, stub.getDunderAll());
writeBitSet(dataStream, stub.getFutureFeatures());
dataStream.writeName(stub.getDeprecationMessage());
}
@Override
public PyFileStub deserialize(StubInputStream dataStream, StubElement parentStub) throws IOException {
List<String> all = readNullableList(dataStream);
BitSet future_features = readBitSet(dataStream);
return new PyFileStubImpl(all, future_features);
StringRef deprecationMessage = dataStream.readName();
return new PyFileStubImpl(all, future_features, deprecationMessage);
}
private static BitSet readBitSet(StubInputStream dataStream) throws IOException {
@@ -53,6 +53,13 @@ extends
@Nullable
PyType getReturnTypeFromDocString();
/**
* If the function raises a DeprecationWarning or a PendingDeprecationWarning, returns the explanation text provided for the warning..
*
* @return the deprecation message or null if the function is not deprecated.
*/
String getDeprecationMessage();
/**
* Flags that mark common alterations of a function: decoration by and wrapping in classmethod() and staticmethod().
*/
@@ -51,7 +51,7 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress
@Override
public <T extends PsiElement> T getArgument(int index, Class<T> argClass) {
PyExpression[] args = getArguments();
return args.length >= index && argClass.isInstance(args[index]) ? argClass.cast(args[index]) : null;
return args.length > index && argClass.isInstance(args[index]) ? argClass.cast(args[index]) : null;
}
public void addArgument(PyExpression expression) {
@@ -547,6 +547,19 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return enabled;
}
@Override
public String getDeprecationMessage() {
final StubElement stub = getStub();
if (stub instanceof PyFileStub) {
return ((PyFileStub) stub).getDeprecationMessage();
}
return extractDeprecationMessage();
}
public String extractDeprecationMessage() {
return PyFunctionImpl.extractDeprecationMessage(getStatements());
}
public boolean calculateImportFromFuture(FutureFeature feature) {
final List<PyFromImportStatement> fromImports = getFromImports();
for (PyFromImportStatement fromImport : fromImports) {
@@ -220,6 +220,42 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
return PyTypeParser.getTypeByName(this, typeName);
}
@Override
public String getDeprecationMessage() {
PyFunctionStub stub = getStub();
if (stub != null) {
return stub.getDeprecationMessage();
}
return extractDeprecationMessage();
}
public String extractDeprecationMessage() {
PyStatementList statementList = getStatementList();
if (statementList == null) {
return null;
}
return extractDeprecationMessage(Arrays.asList(statementList.getStatements()));
}
public static String extractDeprecationMessage(List<PyStatement> statements) {
for (PyStatement statement : statements) {
if (statement instanceof PyExpressionStatement) {
PyExpressionStatement expressionStatement = (PyExpressionStatement)statement;
if (expressionStatement.getExpression() instanceof PyCallExpression) {
PyCallExpression callExpression = (PyCallExpression)expressionStatement.getExpression();
if (callExpression.isCalleeText(PyNames.WARN)) {
PyReferenceExpression warningClass = callExpression.getArgument(1, PyReferenceExpression.class);
if (warningClass != null && (PyNames.DEPRECATION_WARNING.equals(warningClass.getReferencedName()) ||
PyNames.PENDING_DEPRECATION_WARNING.equals(warningClass.getReferencedName()))) {
return PyUtil.strValue(callExpression.getArguments() [0]);
}
}
}
}
}
return null;
}
@Nullable
public String extractDocStringReturnType() {
final PyStringLiteralExpression docString = getDocStringExpression();
@@ -2,6 +2,7 @@ package com.jetbrains.python.psi.impl.stubs;
import com.intellij.psi.stubs.PsiFileStubImpl;
import com.intellij.psi.tree.IStubFileElementType;
import com.intellij.util.io.StringRef;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.FutureFeature;
import com.jetbrains.python.psi.PyFile;
@@ -17,6 +18,7 @@ import java.util.List;
public class PyFileStubImpl extends PsiFileStubImpl<PyFile> implements PyFileStub {
private final List<String> myDunderAll;
private final BitSet myFutureFeatures; // stores IDs of features
private final StringRef myDeprecationMessage;
private static final int FUTURE_FEATURE_SET_SIZE = 32; // 32 features is ought to be enough for everybody! all bits fit into an int.
@@ -28,12 +30,15 @@ public class PyFileStubImpl extends PsiFileStubImpl<PyFile> implements PyFileStu
for (FutureFeature fuf : FutureFeature.ALL) {
myFutureFeatures.set(fuf.ordinal(), fileImpl.calculateImportFromFuture(fuf));
}
String message = fileImpl.extractDeprecationMessage();
myDeprecationMessage = message == null ? null : StringRef.fromString(message);
}
public PyFileStubImpl(List<String> dunderAll, final BitSet future_features) {
public PyFileStubImpl(List<String> dunderAll, final BitSet future_features, final StringRef deprecationMessage) {
super(null);
myDunderAll = dunderAll;
myFutureFeatures = future_features;
myDeprecationMessage = deprecationMessage;
}
@Override
@@ -46,6 +51,11 @@ public class PyFileStubImpl extends PsiFileStubImpl<PyFile> implements PyFileStu
return myFutureFeatures;
}
@Override
public String getDeprecationMessage() {
return myDeprecationMessage == null ? null : myDeprecationMessage.getString();
}
@Override
public IStubFileElementType getType() {
return PythonLanguage.getInstance().getFileElementType();
@@ -32,19 +32,24 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
}
public PyFunctionStub createStub(final PyFunction psi, final StubElement parentStub) {
return new PyFunctionStubImpl(psi.getName(), ((PyFunctionImpl) psi).extractDocStringReturnType(), parentStub);
PyFunctionImpl function = (PyFunctionImpl)psi;
String message = function.extractDeprecationMessage();
return new PyFunctionStubImpl(psi.getName(), function.extractDocStringReturnType(),
message == null ? null : StringRef.fromString(message), parentStub);
}
public void serialize(final PyFunctionStub stub, final StubOutputStream dataStream)
throws IOException {
dataStream.writeName(stub.getName());
dataStream.writeName(stub.getReturnTypeFromDocString());
dataStream.writeName(stub.getDeprecationMessage());
}
public PyFunctionStub deserialize(final StubInputStream dataStream, final StubElement parentStub) throws IOException {
String name = StringRef.toString(dataStream.readName());
StringRef returnType = dataStream.readName();
return new PyFunctionStubImpl(name, returnType != null ? returnType.getString() : null, parentStub);
StringRef deprecationMessage = dataStream.readName();
return new PyFunctionStubImpl(name, returnType != null ? returnType.getString() : null, deprecationMessage, parentStub);
}
public void indexStub(final PyFunctionStub stub, final IndexSink sink) {
@@ -1,22 +1,24 @@
/*
* @author max
*/
package com.jetbrains.python.psi.impl.stubs;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.io.StringRef;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.stubs.PyFunctionStub;
import org.jetbrains.annotations.Nullable;
public class PyFunctionStubImpl extends StubBase<PyFunction> implements PyFunctionStub {
private final String myName;
private final String myReturnTypeFromDocString;
private final StringRef myDeprecationMessage;
public PyFunctionStubImpl(final String name, final String returnTypeFromDocString, final StubElement parent) {
public PyFunctionStubImpl(final String name, final String returnTypeFromDocString, @Nullable final StringRef deprecationMessage,
final StubElement parent) {
super(parent, PyElementTypes.FUNCTION_DECLARATION);
myName = name;
myReturnTypeFromDocString = returnTypeFromDocString;
myDeprecationMessage = deprecationMessage;
}
public String getName() {
@@ -27,6 +29,11 @@ public class PyFunctionStubImpl extends StubBase<PyFunction> implements PyFuncti
return myReturnTypeFromDocString;
}
@Override
public String getDeprecationMessage() {
return myDeprecationMessage == null ? null : myDeprecationMessage.getString();
}
@Override
public String toString() {
return "PyFunctionStub(" + myName + ")";
@@ -1,7 +1,6 @@
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.PsiFileStub;
import com.jetbrains.python.psi.FutureFeature;
import com.jetbrains.python.psi.PyFile;
import java.util.BitSet;
@@ -13,4 +12,5 @@ import java.util.List;
public interface PyFileStub extends PsiFileStub<PyFile> {
List<String> getDunderAll();
BitSet getFutureFeatures();
String getDeprecationMessage();
}
@@ -1,6 +1,3 @@
/*
* @author max
*/
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.NamedStub;
@@ -8,4 +5,5 @@ import com.jetbrains.python.psi.PyFunction;
public interface PyFunctionStub extends NamedStub<PyFunction> {
String getReturnTypeFromDocString();
String getDeprecationMessage();
}
@@ -0,0 +1 @@
import <warning descr="the deprecated module is deprecated; use a non-deprecated module instead">deprecatedModule</warning>
@@ -0,0 +1,3 @@
import warnings
warnings.warn("the deprecated module is deprecated; use a non-deprecated module instead",
DeprecationWarning, 2)
@@ -0,0 +1,8 @@
class Foo:
@property
def bar(self):
import warnings
warnings.warn("this is deprecated", DeprecationWarning, 2)
foo = Foo()
foo.<warning descr="this is deprecated">bar</warning>
@@ -0,0 +1,5 @@
def getstatus(file):
"""Return output of "ls -ld <file>" in a string."""
import warnings
warnings.warn("commands.getstatus() is deprecated", DeprecationWarning, 2)
return getoutput('ls -ld' + mkarg(file))
@@ -0,0 +1,47 @@
package com.jetbrains.python;
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
import com.jetbrains.python.inspections.PyDeprecationInspection;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
/**
* @author yole
*/
public class PyDeprecationTest extends PyLightFixtureTestCase {
public void testFunction() {
myFixture.configureByText(PythonFileType.INSTANCE,
"def getstatus(file):\n" +
" \"\"\"Return output of \"ls -ld <file>\" in a string.\"\"\"\n" +
" import warnings\n" +
" warnings.warn(\"commands.getstatus() is deprecated\", DeprecationWarning, 2)\n" +
" return getoutput('ls -ld' + mkarg(file))");
PyFunction getstatus = ((PyFile) myFixture.getFile()).findTopLevelFunction("getstatus");
assertEquals("commands.getstatus() is deprecated", getstatus.getDeprecationMessage());
}
public void testFunctionStub() {
myFixture.configureByFile("deprecation/functionStub.py");
PyFunction getstatus = ((PyFile) myFixture.getFile()).findTopLevelFunction("getstatus");
assertEquals("commands.getstatus() is deprecated", getstatus.getDeprecationMessage());
assertNotParsed((PyFile) myFixture.getFile());
}
public void testDeprecatedProperty() {
myFixture.enableInspections(PyDeprecationInspection.class);
myFixture.configureByFile("deprecation/deprecatedProperty.py");
myFixture.checkHighlighting(true, false, false);
}
public void testDeprecatedImport() {
myFixture.enableInspections(PyDeprecationInspection.class);
myFixture.configureByFiles("deprecation/deprecatedImport.py", "deprecation/deprecatedModule.py");
myFixture.checkHighlighting(true, false, false);
}
public void testFileStub() {
myFixture.configureByFile("deprecation/deprecatedModule.py");
assertEquals("the deprecated module is deprecated; use a non-deprecated module instead", ((PyFile) myFixture.getFile()).getDeprecationMessage());
assertNotParsed((PyFile) myFixture.getFile());
}
}
@@ -28,17 +28,12 @@ import java.util.List;
@TestDataPath("$CONTENT_ROOT/../testData/stubs/")
public class PyStubsTest extends PyLightFixtureTestCase {
private static final String PARSED_ERROR_MSG = "Operations should have been performed on stubs but caused file to be parsed";
@Override
protected String getTestDataPath() {
return PythonTestUtil.getTestDataPath() + "/stubs/";
}
private static void assertNotParsed(PyFile file) {
assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
}
public void testStubStructure() {
final PyFile file = getTestFile();
// vfile is problematic, but we need an SDK to check builtins
@@ -80,7 +80,8 @@ public class PythonAllTestsSuite {
PyCompletionPatternsTest.class,
PyCompatibilityInspectionTest.class,
PyUnresolvedReferencesInspectionTest.class,
PyUnusedImportTest.class
PyUnusedImportTest.class,
PyDeprecationTest.class
};
public static TestSuite suite() {
@@ -24,6 +24,8 @@ import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
import com.jetbrains.python.PythonMockSdk;
import com.jetbrains.python.PythonTestUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.impl.PyFileImpl;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import org.jetbrains.annotations.Nullable;
@@ -34,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
public abstract class PyLightFixtureTestCase extends UsefulTestCase {
private static final PyLightProjectDescriptor ourPyDescriptor = new PyLightProjectDescriptor("2.5");
protected static final PyLightProjectDescriptor ourPy3Descriptor = new PyLightProjectDescriptor("3.1");
private static final String PARSED_ERROR_MSG = "Operations should have been performed on stubs but caused file to be parsed";
protected CodeInsightTestFixture myFixture;
private static boolean ourPlatformPrefixInitialized;
@@ -86,6 +89,10 @@ public abstract class PyLightFixtureTestCase extends UsefulTestCase {
PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);
}
protected static void assertNotParsed(PyFile file) {
assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
}
protected static class PyLightProjectDescriptor implements LightProjectDescriptor {
private final String myPythonVersion;