From faa5af1211017733ddf82d86554edfcdf6b0597d Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 29 Aug 2013 20:12:38 +0400 Subject: [PATCH 01/11] fixed PY-8823 Enforce PEP 8 naming conventions --- python/src/META-INF/python-plugin-common.xml | 2 + .../inspections/PyPep8NamingInspection.java | 110 ++++++++++++++++++ .../inspections/PyRenameElementQuickFix.java | 86 ++++++++++++++ .../PyShadowingBuiltinsInspection.java | 61 ---------- .../refactoring/PyNameSuggestionProvider.java | 57 +++++++++ 5 files changed, 255 insertions(+), 61 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java create mode 100644 python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java create mode 100644 python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index b7a25ca8218f..2a4ce74d6c09 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -4,6 +4,7 @@ com.jetbrains.python.PyBundle + @@ -338,6 +339,7 @@ + diff --git a/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java b/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java new file mode 100644 index 000000000000..f1a26b0c954d --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java @@ -0,0 +1,110 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import org.jetbrains.annotations.NotNull; + +import java.util.regex.Pattern; + +/** + * + * User : ktisha + */ +public class PyPep8NamingInspection extends PyInspection { + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + return new Visitor(holder, session); + } + + public static class Visitor extends PyInspectionVisitor { + Pattern LOWERCASE_REGEX = Pattern.compile("[_a-z][_a-z0-9]*"); + Pattern UPPERCASE_REGEX = Pattern.compile("[_A-Z][_A-Z0-9]*"); + Pattern MIXEDCASE_REGEX = Pattern.compile("_?[A-Z][a-zA-Z0-9]*"); + + public Visitor(@NotNull final ProblemsHolder holder, LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyAssignmentStatement(PyAssignmentStatement node) { + final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class); + if (function == null) return; + for (PyExpression expression : node.getTargets()) { + final String name = expression.getName(); + if (name == null) continue; + if (!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_")) { + registerProblem(expression, "Variable in function should be lowercase", new PyRenameElementQuickFix()); + } + } + } + + @Override + public void visitPyParameter(PyParameter node) { + final String name = node.getName(); + if (name == null) return; + if (!LOWERCASE_REGEX.matcher(name).matches()) { + registerProblem(node, "Argument name should be lowercase", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyFunction(PyFunction node) { + final PyClass containingClass = node.getContainingClass(); + final String name = node.getName(); + if (name == null) return; + if (containingClass != null && name.startsWith("__") && name.endsWith("__")) { + return; + } + if (!LOWERCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = node.getNameNode(); + if (nameNode != null) + registerProblem(nameNode.getPsi(), "Function name should be lowercase", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyClass(PyClass node) { + final String name = node.getName(); + if (name == null) return; + if (!MIXEDCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = node.getNameNode(); + if (nameNode != null) + registerProblem(nameNode.getPsi(), "Class names should use CamelCase convention", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyImportElement(PyImportElement node) { + final String asName = node.getAsName(); + final PyQualifiedName importedQName = node.getImportedQName(); + if (importedQName == null) return; + final String name = importedQName.toString(); + + if (asName == null || name == null) return; + if (UPPERCASE_REGEX.matcher(name).matches()) { + if (!UPPERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "Constant variable imported as non constant", new PyRenameElementQuickFix()); + } + } + else if (LOWERCASE_REGEX.matcher(name).matches()) { + if (!LOWERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "Lowercase variable imported as non lowercase", new PyRenameElementQuickFix()); + } + } + else if (LOWERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "CamelCase variable imported as lowercase", new PyRenameElementQuickFix()); + } + else if (UPPERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "CamelCase variable imported as constant", new PyRenameElementQuickFix()); + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java b/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java new file mode 100644 index 000000000000..55941fbcd1c9 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java @@ -0,0 +1,86 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiNameIdentifierOwner; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.rename.PsiElementRenameHandler; +import com.intellij.refactoring.rename.RenameProcessor; +import com.intellij.refactoring.rename.RenamePsiElementProcessor; +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.PyNamedParameter; +import com.jetbrains.python.psi.PyTargetExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + */ +public class PyRenameElementQuickFix implements LocalQuickFix { + @NotNull + @Override + public String getName() { + return "Rename element"; + } + + @NotNull + @Override + public String getFamilyName() { + return "Rename element"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? + (PsiNameIdentifierOwner)element : + PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); + if (nameOwner != null) { + final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + final Editor editor = FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true); + if (ApplicationManager.getApplication().isUnitTestMode()) { + renameInUnitTestMode(project, nameOwner, editor); + } + else { + if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { + new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); + } + else { + PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); + } + } + } + } + } + + @Nullable + protected PsiElement checkLocalScope(PsiElement element) { + final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element); + if (searchScope instanceof LocalSearchScope) { + final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); + return PsiTreeUtil.findCommonParent(elements); + } + + return null; + } + + private static void renameInUnitTestMode(@NotNull Project project, @NotNull PsiNameIdentifierOwner nameOwner, + @Nullable Editor editor) { + final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); + if (substitution != null) { + new RenameProcessor(project, substitution, "a", false, false).run(); + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java index 64c3ff48a5c3..3c917efe9c07 100644 --- a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java @@ -1,24 +1,10 @@ package com.jetbrains.python.inspections; import com.intellij.codeInspection.LocalInspectionToolSession; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiNameIdentifierOwner; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.refactoring.rename.PsiElementRenameHandler; -import com.intellij.refactoring.rename.RenameProcessor; -import com.intellij.refactoring.rename.RenamePsiElementProcessor; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; -import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import org.jetbrains.annotations.NotNull; @@ -81,52 +67,5 @@ public class PyShadowingBuiltinsInspection extends PyInspection { } } } - - private static class PyRenameElementQuickFix implements LocalQuickFix { - @NotNull - @Override - public String getName() { - return "Rename element"; - } - - @NotNull - @Override - public String getFamilyName() { - return "Rename element"; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? - (PsiNameIdentifierOwner)element : - PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); - if (nameOwner != null) { - final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); - if (virtualFile != null) { - final Editor editor = FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true); - if (ApplicationManager.getApplication().isUnitTestMode()) { - renameInUnitTestMode(project, nameOwner, editor); - } - else { - if (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression) { - new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); - } - else { - PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); - } - } - } - } - } - - private static void renameInUnitTestMode(@NotNull Project project, @NotNull PsiNameIdentifierOwner nameOwner, - @Nullable Editor editor) { - final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); - if (substitution != null) { - new RenameProcessor(project, substitution, "a", false, false).run(); - } - } - } } } diff --git a/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java b/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java new file mode 100644 index 000000000000..875ecf1c9a77 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java @@ -0,0 +1,57 @@ +/* + * @author max + */ +package com.jetbrains.python.refactoring; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.rename.NameSuggestionProvider; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; + +/** + * User : ktisha + */ +public class PyNameSuggestionProvider implements NameSuggestionProvider { + public SuggestedNameInfo getSuggestedNames(PsiElement element, PsiElement nameSuggestionContext, Set result) { + if (!(element instanceof PyElement)) return null; + final String name = ((PyElement)element).getName(); + if (name == null) return null; + + if (element instanceof PyClass) { + result.add(toCamelCase(name, true)); + } + else if (element instanceof PyFunction || element instanceof PyParameter) { + result.add(name.toLowerCase()); + } + else { + result.add(name.toLowerCase()); + final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(element, PyAssignmentStatement.class); + if (assignmentStatement != null) return null; + result.add(name.toUpperCase()); + result.add(toCamelCase(name, false)); + } + return SuggestedNameInfo.NULL_INFO; + } + + @NotNull + protected String toCamelCase(@NotNull final String name, boolean uppercaseFirstLetter) { + final List strings = StringUtil.split(name, "_"); + if (strings.size() > 0) { + final StringBuilder buf = new StringBuilder(); + String str = strings.get(0).toLowerCase(); + if (uppercaseFirstLetter) str = StringUtil.capitalize(str); + buf.append(str); + for (int i = 1; i < strings.size(); i++) { + buf.append(StringUtil.capitalize(strings.get(i).toLowerCase())); + } + return buf.toString(); + } + return name; + } +} From 5dc8c31010ee57b3279a65ecfa5e47c516948954 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 30 Aug 2013 15:04:57 +0400 Subject: [PATCH 02/11] fixed references in from import in doctests --- .../doctest/PyDocReferenceExpression.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java index 60b25d3b579b..4464ec2f03f8 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java @@ -1,10 +1,12 @@ package com.jetbrains.python.documentation.doctest; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyImportStatement; +import com.jetbrains.python.psi.PyFromImportStatement; +import com.jetbrains.python.psi.PyImportElement; import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; import com.jetbrains.python.psi.impl.references.PyImportReference; import com.jetbrains.python.psi.impl.references.PyQualifiedReference; @@ -27,8 +29,10 @@ public class PyDocReferenceExpression extends PyReferenceExpressionImpl { if (qualifier != null) { return new PyQualifiedReference(this, context); } - else if (PsiTreeUtil.getParentOfType(this, PyImportStatement.class) != null) - return new PyImportReference(this, context); + final PsiElement importParent = PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class); + if (importParent != null) { + return PyImportReference.forElement(this, importParent, context); + } return new PyDocReference(this, context); } } From ed02fb5ec4d60c3cdeba87071935c654b222befb Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 30 Aug 2013 15:41:39 +0400 Subject: [PATCH 03/11] fixed PY-8485 Move module: breaks imports in doctests --- .../codeInsight/imports/AddImportHelper.java | 24 ++++++++++++------- .../PyDocstringTokenSetContributor.java | 5 ++++ .../classes/PyClassRefactoringUtil.java | 14 ++++++++++- .../refactoring/move/PyMoveFileHandler.java | 6 ++--- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java index 3857991591d1..db8df782ac60 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java @@ -1,5 +1,6 @@ package com.jetbrains.python.codeInsight.imports; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; @@ -47,8 +48,9 @@ public class AddImportHelper { boolean skippedOverImports = false; boolean skippedOverDoc = false; PsiElement seeker = feeler; + final boolean isInjected = InjectedLanguageManager.getInstance(feeler.getProject()).isInjectedFragment(feeler.getContainingFile()); do { - if (feeler instanceof PyImportStatementBase) { + if (feeler instanceof PyImportStatementBase && !isInjected) { if (nameToImport != null && priority != null && shouldInsertBefore(file, (PyImportStatementBase)feeler, nameToImport, priority)) { break; } @@ -172,17 +174,21 @@ public class AddImportHelper { * @param asName optional name for 'as' clause */ public static void addImportFromStatement(PsiFile file, String from, String name, @Nullable String asName, ImportPriority priority) { - String asClause; - if (asName == null) { - asClause = ""; - } - else { - asClause = " as " + asName; - } + String asClause = asName == null ? "" : " as " + asName; + final PyFromImportStatement importNodeToInsert = PyElementGenerator.getInstance(file.getProject()).createFromText( LanguageLevel.forElement(file), PyFromImportStatement.class, "from " + from + " import " + name + asClause); try { - file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + if (InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { + final PsiElement element = file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + PsiElement whitespace = element.getNextSibling(); + if (!(whitespace instanceof PsiWhiteSpace)) + whitespace = PsiParserFacade.SERVICE.getInstance(file.getProject()).createWhiteSpaceFromText(" >>> "); + file.addBefore(whitespace, element); + } + else { + file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + } } catch (IncorrectOperationException e) { LOG.error(e); diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java index 86f284b70a30..326e246ae822 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java @@ -11,4 +11,9 @@ public class PyDocstringTokenSetContributor extends PythonTokenSetContributor { public TokenSet getExpressionTokens() { return TokenSet.orSet(super.getExpressionTokens(), TokenSet.create(PyDocstringTokenTypes.DOC_REFERENCE)); } + + @Override + public TokenSet getReferenceExpressionTokens() { + return TokenSet.orSet(super.getExpressionTokens(), TokenSet.create(PyDocstringTokenTypes.DOC_REFERENCE)); + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index b22c41ddf1f7..2b0e5bb63d65 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -1,5 +1,6 @@ package com.jetbrains.python.refactoring.classes; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; @@ -348,7 +349,11 @@ public class PyClassRefactoringUtil { } if (deleteImportElement) { if (importStatement.getImportElements().length == 1) { - importStatement.delete(); + final boolean isInjected = InjectedLanguageManager.getInstance(importElement.getProject()).isInjectedFragment(importElement.getContainingFile()); + if (!isInjected) + importStatement.delete(); + else + deleteImportStatementFromInjected(importStatement); } else { importElement.delete(); @@ -358,6 +363,13 @@ public class PyClassRefactoringUtil { } } + private static void deleteImportStatementFromInjected(@NotNull final PyImportStatementBase importStatement) { + final PsiElement sibling = importStatement.getPrevSibling(); + importStatement.delete(); + if (sibling instanceof PsiWhiteSpace) + sibling.delete(); + } + @Nullable public static String getOriginalName(@NotNull PsiNamedElement element) { if (element instanceof PyFile) { diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java index 85b24086454b..900e83129e7a 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java @@ -77,8 +77,6 @@ public class PyMoveFileHandler extends MoveFileHandler { for (UsageInfo usage : usages) { final PsiElement element = usage.getElement(); if (element != null) { - if (InjectedLanguageManager.getInstance(element.getProject()).isInjectedFragment(element.getContainingFile())) - continue; final PsiNamedElement newElement = element.getCopyableUserData(REFERENCED_ELEMENT); element.putCopyableUserData(REFERENCED_ELEMENT, null); if (newElement != null) { @@ -111,7 +109,9 @@ public class PyMoveFileHandler extends MoveFileHandler { if (!updatedFiles.isEmpty()) { final PyImportOptimizer optimizer = new PyImportOptimizer(); for (PsiFile file : updatedFiles) { - optimizer.processFile(file).run(); + final boolean injectedFragment = InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file); + if (!injectedFragment) + optimizer.processFile(file).run(); } } } From 9d36b467a5e81cf4a78c9ee755a7a77d1b878e7a Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 2 Sep 2013 18:00:59 +0400 Subject: [PATCH 04/11] parameter info generics: check resulted html --- .../testSrc/com/jetbrains/python/PyParameterInfoTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 6dcca4ca6720..d4f40cf21e94 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -422,16 +422,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } @Override - public void setupUIComponentPresentation(String[] texts, EnumSet[] flags, Color background) { + public String setupUIComponentPresentation(String[] texts, EnumSet[] flags, Color background) { assert texts.length == flags.length; myTexts = texts; myFlags = flags; + return StringUtil.join(texts, ""); } @Override - public void setupUIComponentPresentation(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled, - boolean strikeout, boolean isDisabledBeforeHighlight, Color background) { + public String setupUIComponentPresentation(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled, + boolean strikeout, boolean isDisabledBeforeHighlight, Color background) { // nothing, we don't use it + return text; } @Override From 4386ab9a8d127debd2da8a39d29f162b5e8581b8 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 2 Sep 2013 18:46:53 +0400 Subject: [PATCH 05/11] Search for user skeletons in PyCharm config --- .../userSkeletons/PyUserSkeletonsUtil.java | 26 ++++++++++++------- .../python/sdk/PythonSdkUpdater.java | 5 +++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java index 676b86f83ae6..e9ef7ebeee19 100644 --- a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,21 +35,28 @@ import java.util.List; * @author vlan */ public class PyUserSkeletonsUtil { + public static final String USER_SKELETONS_DIR = "user-skeletons"; @Nullable private static VirtualFile ourUserSkeletonsDirectory; @NotNull - public static String getUserSkeletonsPath() { - if (ApplicationManager.getApplication().isInternal()) { - return StringUtil.join(new String[] {PathManager.getHomePath(), "python", "helpers", "user-skeletons"}, File.separator); - } - // TODO: Add the possibility to put skeletons into PathManager.getSystemPath() + "/user-skeletons" - return PythonHelpersLocator.getHelperPath("user-skeletons"); + private static List getPossibleUserSkeletonsPaths() { + final List result = new ArrayList(); + result.add(PathManager.getConfigPath() + File.separator + USER_SKELETONS_DIR); + result.add(ApplicationManager.getApplication().isInternal() + ? StringUtil.join(new String[]{PathManager.getHomePath(), "python", "helpers", USER_SKELETONS_DIR}, File.separator) + : PythonHelpersLocator.getHelperPath(USER_SKELETONS_DIR)); + return result; } @Nullable public static VirtualFile getUserSkeletonsDirectory() { if (ourUserSkeletonsDirectory == null) { - ourUserSkeletonsDirectory = LocalFileSystem.getInstance().findFileByPath(getUserSkeletonsPath()); + for (String path : getPossibleUserSkeletonsPaths()) { + ourUserSkeletonsDirectory = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + if (ourUserSkeletonsDirectory != null) { + break; + } + } } return ourUserSkeletonsDirectory; } @@ -75,7 +83,7 @@ public class PyUserSkeletonsUtil { if (sdk != null) { final Project project = foothold.getProject(); final PythonSdkPathCache cache = PythonSdkPathCache.getInstance(project, sdk); - final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString("user-skeletons." + qName); + final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString(USER_SKELETONS_DIR + "." + qName); final List results = cache.get(cacheQName); if (results != null) { final PsiElement element = results.isEmpty() ? null : results.get(0); @@ -101,7 +109,7 @@ public class PyUserSkeletonsUtil { } public static void addUserSkeletonsRoot(@NotNull SdkModificator sdkModificator) { - final VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByPath(getUserSkeletonsPath()); + final VirtualFile root = getUserSkeletonsDirectory(); if (root != null) { sdkModificator.addRoot(root, OrderRootType.CLASSES); } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index 69cfc3f42229..15dae82bf84f 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -126,7 +126,10 @@ public class PythonSdkUpdater implements StartupActivity { private static void updateSysPath(final Sdk sdk) throws InvalidSdkException { long start_time = System.currentTimeMillis(); final List sysPath = PythonSdkType.getSysPath(sdk.getHomePath()); - sysPath.add(PyUserSkeletonsUtil.getUserSkeletonsPath()); + final VirtualFile file = PyUserSkeletonsUtil.getUserSkeletonsDirectory(); + if (file != null) { + sysPath.add(file.getPath()); + } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { From cbed76ea3359df6764ae2e46f4ef6b3d437ca97e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 2 Sep 2013 18:52:37 +0400 Subject: [PATCH 06/11] Renamed user-skeletons to python-skeletons --- python/helpers/python-skeletons/README.md | 139 +++++++ .../helpers/python-skeletons/__builtin__.py | 377 ++++++++++++++++++ .../helpers/python-skeletons/nose/__init__.py | 0 .../python-skeletons/nose/tools/__init__.py | 177 ++++++++ python/helpers/python-skeletons/re.py | 277 +++++++++++++ .../userSkeletons/PyUserSkeletonsUtil.java | 2 +- 6 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 python/helpers/python-skeletons/README.md create mode 100644 python/helpers/python-skeletons/__builtin__.py create mode 100644 python/helpers/python-skeletons/nose/__init__.py create mode 100644 python/helpers/python-skeletons/nose/tools/__init__.py create mode 100644 python/helpers/python-skeletons/re.py diff --git a/python/helpers/python-skeletons/README.md b/python/helpers/python-skeletons/README.md new file mode 100644 index 000000000000..953a0d0a11d7 --- /dev/null +++ b/python/helpers/python-skeletons/README.md @@ -0,0 +1,139 @@ +Python Skeletons +================ + +_This proposal is a draft._ + +Python skeletons are Python files that contain API definitions of existing +libraries extended for static analysis tools. + +Rationale +--------- + +Python is a dynamic language less suitable for static code analysis than static +languages like C or Java. Although Python static analysis tools can extract +some information from Python source code without executing it, this information +is often very shallow and incomplete. + +Dynamic features of Python are very useful for user code. But using these +features in APIs of third-party libraries and the standard library is not +always a good idea. Tools (and users, in fact) need clear definitions of APIs. +Often library API definitions are quite static and easy to grasp (defined +using `class`, `def`), but types of function parameters and return values +usually are not specified. Sometimes API definitions involve metaprogramming. + +As there is not enough information in API defintion code of libraries, +developers of static analysis tools collect extended API data themselves and +store it in their own formats. For example, PyLint uses imperative AST +transformations of API modules in order to extend them with hard-coded data. +PyCharm extends APIs via its proprietary database of declarative type +annotations. The absence of a common extended API information format makes it +hard for developers and users of tools to collect and share data. + + +Proposal +-------- + +The proposal is to create a common database of extended API definitions as a +collection of Python files called skeletons. Static analysis tools already +understand Python code, so it should be easy to start extracting API +definitions from these Python skeleton files. Regular function and class +definitions can be extended with additional docstrings and decorators, e.g. for +providing types of function parameters and return values. Static analysis tools +may use a subset of information contained in skeleton files needed for their +operation. Using Python files instead of a custom API definition format will +also make it easier for users to populate the skeletons database. + +Declarative Python API definitions for static analysis tools cannot cover all +dynamic tricks used in real APIs of libraries: some of them still require +library-specific code analysis. Nevertheless the skeletons database is enough +for many libraries. + +The proposed [python-skeletons](https://github.com/JetBrains/python-skeletons) +repository is hosted on GitHub. + + +Conventions +----------- + +Skeletons should respect PEP-8 and PEP-257 style guides. + +The most simple way of specifying types in skeletons is Sphinx docstrings. +Function annoatations could be used for specifying types, but they are +available only for Python 3. + +There is no standard notation for specifying types in Python code. We propose +the following notation: + + Foo # Class Foo visible in the current scope + x.y.Bar # Class Bar from x.y module + Foo | Bar # Foo or Bar + (Foo, Bar) # Tuple of Foo and Bar + list[Foo] # List of Foo elements + dict[Foo, Bar] # Dict from Foo to Bar + T # Generic type (T-Z are reserved for generics) + T <= Foo # Generic type with upper bound Foo + Foo[T] # Foo parameterized with T + (Foo, Bar) -> Baz # Function of Foo and Bar that returns Baz + +The formal syntax is defined in `pytypes` library (work in progress). + +There are several shortcuts available: + + unknown # Unknown type + None # type(None) + string # Py2: str | unicode, Py3: str + bytestring # Py2: str | unicode, Py3: bytes + bytes # Py2: str, Py3: bytes + unicode # Py2: unicode, Py3: str + +The syntax is a subject to change. It is almost compatible to Python (except +function types), but its semantics differs from Python (no `|`, no implicitly +visible names, no generic types). So you cannot use these expressions in +Python 3 function annotations. See also `python-righarrow`, `typeannotations`. + +The recommended way of checking the version of Python is: + + import sys + + if sys.version_info >= (2, 7) and sys.version_info < (3,): + def from_27_until_30(): + pass + + +PyCharm +------- + +PyCharm 3 can extract the following information from the skeletons: + +* Parameters of functions and methods +* Return types and parameter types of functions and methods +* Types of assignment targets +* Extra module members +* TODO + +PyCharm 3 comes with a snapshot of the Python skeletons repository. You +should not modify it, because it will be updated with the PyCharm +installation. If you want to change the skeletons, clone the skeletons GitHub +repository into your PyCharm config directory: + + cd + git clone https://github.com/JetBrains/python-skeletons.git + +where `` is: + +* Mac OS X: `~/Library/Prefernces/PyCharmXX/config` +* Linux: `~/.PyCharmXX/config` +* Windows: `\.PyCharmXX\config` + +Please send your PyCharm-related bug reports and feature requests to +[PyCharm issue tracker](http://youtrack.jetbrains.com/issues/PY). + + +Feedback +-------- + +If you want to contribute, send your pull requests to the Python skeletons +repository on GitHub. Please make sure, that you follow the conventions above. + +Use [code-quality](http://mail.python.org/mailman/listinfo/code-quality) +mailing list to discuss Python skeletons. diff --git a/python/helpers/python-skeletons/__builtin__.py b/python/helpers/python-skeletons/__builtin__.py new file mode 100644 index 000000000000..682c491c7367 --- /dev/null +++ b/python/helpers/python-skeletons/__builtin__.py @@ -0,0 +1,377 @@ +"""Skeletons for built-in symbols.""" + +import sys as __sys + + +def abs(number): + """Return the absolute value of the argument. + + :type number: T + :rtype: T | unknown + """ + pass + + +def all(iterable): + """Return True if bool(x) is True for all values x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def any(iterable): + """Return True if bool(x) is True for any x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def bin(number): + """Return the binary representation of an integer or long integer. + + :type number: numbers.Number + :rtype: bytes + """ + pass + + +def callable(object): + """Return whether the object is callable (i.e., some kind of function). + Note that classes are callable, as are instances with a __call__() method. + + :rtype: bool + """ + pass + + +def chr(i): + """Return a string of one character with ordinal i; 0 <= i < 256. + + :type i: int + :rtype: string + """ + pass + + +def cmp(x, y): + """Return negative if xy. + + :rtype: int + """ + pass + + +def dir(object=None): + """If called without an argument, return the names in the current scope. + Else, return an alphabetized list of names comprising (some of) the attributes + of the given object, and of attributes reachable from it. + + :rtype: list[string] + """ + pass + + +def divmod(x, y): + """Return the tuple ((x-x%y)/y, x%y). + + :type x: numbers.Number + :type y: numbers.Number + :rtype: (int | long | float | unknown, int | long | float | unknown) + """ + pass + + +def filter(function_or_none, sequence): + """Return those items of sequence for which function(item) is true. If + function is None, return the items that are true. If sequence is a tuple + or string, return the same type, else return a list. + + :type function_or_none: collections.Callable | None + :type sequence: T <= list | collections.Iterable | bytes | unicode + :rtype: T + """ + pass + + +def getattr(object, name, default=None): + """Get a named attribute from an object; getattr(x, 'y') is equivalent to + x.y. When a default argument is given, it is returned when the attribute + doesn't exist; without it, an exception is raised in that case. + + :type name: string + :rtype: object | unknown + """ + pass + + +def globals(): + """Return the dictionary containing the current scope's global variables. + + :rtype: dict[string, unknown] + """ + pass + + +def hasattr(object, name): + """Return whether the object has an attribute with the given name. + + :type name: string + :rtype: bool + """ + pass + + +def hash(object): + """Return a hash value for the object. + + :rtype: int + """ + pass + + +def hex(number): + """Return the hexadecimal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def id(object): + """Return the identity of an object. + + :rtype: int + """ + pass + + +def isinstance(object, class_or_type_or_tuple): + """Return whether an object is an instance of a class or of a subclass + thereof. + + :rtype: bool + """ + pass + + +def issubclass(C, B): + """Return whether class C is a subclass (i.e., a derived class) of class B. + + :rtype: bool + """ + pass + + +def iter(source, sentinel=None): + """Get an iterator from an object. In the first form, the argument must + supply its own iterator, or be a sequence. In the second form, the callable + is called until it returns the sentinel. + + :type source: collections.Iterable[T] + :rtype: collections.Iterator[T] + """ + pass + + +def len(object): + """Return the number of items of a sequence or mapping. + + :type object: collections.Sized + :rtype: int + """ + pass + + +def locals(): + """Update and return a dictionary containing the current scope's local + variables. + + :rtype: dict[string, unknown] + """ + pass + + +def map(function, sequence, *sequence_1): + """Return a list of the results of applying the function to the items of + the argument sequence(s). + + :type function: ((T) -> V) | None + :type sequence: collections.Iterable[T] + :rtype: list[V] | bytes | unicode + """ + pass + + +def next(iterator, default=None): + """Return the next item from the iterator. + + :type iterator: collections.Iterator[T] + :rtype: T + """ + pass + + +def oct(number): + """Return the octal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=None, opener=None): + """Open a file, returns a file object. + + :type name: string + :type mode: string + :type buffering: int + :type encoding: string | None + :type errors: string | None + :rtype: file + """ + pass + + +def ord(c): + """Return the integer ordinal of a one-character string. + + :type c: string + :rtype: int + """ + pass + + +def pow(x, y, z=None): + """With two arguments, equivalent to x**y. With three arguments, + equivalent to (x**y) % z, but may be more efficient (e.g. for longs). + + :type x: numbers.Number + :type y: numbers.Number + :type z: numbers.Number | None + :rtype: int | long | float | complex + """ + pass + + +if __sys.version_info < (3,): + def range(start, stop=None, step=None): + """Return a list containing an arithmetic progression of integers. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: list[int] + """ + pass + + +def reduce(function, sequence, initial=None): + """Apply a function of two arguments cumulatively to the items of a sequence, + from left to right, so as to reduce the sequence to a single value. + + :type function: collections.Callable + :type sequence: collections.Iterable + :type initial: T + :rtype: T | unknown + """ + pass + + +def repr(object): + """ + Return the canonical string representation of the object. + + :rtype: string + """ + pass + + +def round(number, ndigits=None): + """Round a number to a given precision in decimal digits (default 0 digits). + + :type number: numbers.Real + :type ndigits: numbers.Real | None + :rtype: float + """ + pass + + +class slice(object): + def __init__(self, start, stop=None, step=None): + """Create a slice object. This is used for extended slicing (e.g. a[0:10:2]). + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + """ + return + + +def vars(object=None): + """Without arguments, equivalent to locals(). With an argument, equivalent + to object.__dict__. + + :rtype: dict[string, unknown] + """ + pass + + +class object: + """ The most base type.""" + + @staticmethod + def __new__(cls, *more): + """Create a new object. + + :type cls: T + :rtype: T + """ + pass + + +class enumerate(object): + """enumerate object.""" + + def __init__(self, iterable, start=0): + """Create an enumerate object. + + :type iterable: collections.Iterable[T] + :type start: int | long + :rtype: enumerate[int, T] + """ + pass + + def next(self): + """Return the next value, or raise StopIteration. + + :rtype: (int, T) + """ + pass + + def __iter__(self): + """x.__iter__() <==> iter(x). + + :rtype: enumerate[int, T] + """ + pass + + +if __sys.version_info < (3,): + class xrange(object): + """xrange object.""" + + def __init__(self, start, stop=None, step=None): + """Create an xrange object. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: xrange[int] + """ + pass diff --git a/python/helpers/python-skeletons/nose/__init__.py b/python/helpers/python-skeletons/nose/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/helpers/python-skeletons/nose/tools/__init__.py b/python/helpers/python-skeletons/nose/tools/__init__.py new file mode 100644 index 000000000000..143355fe3dbb --- /dev/null +++ b/python/helpers/python-skeletons/nose/tools/__init__.py @@ -0,0 +1,177 @@ +"""Skeleton for 'nose.tools' module.""" + + +import sys + + +def assert_equal(first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' operator. + """ + pass + + +def assert_not_equal(first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' operator. + """ + pass + + +def assert_true(expr, msg=None): + """Check that the expression is true.""" + pass + + +def assert_false(expr, msg=None): + """Check that the expression is false.""" + pass + + +if sys.version_info >= (2, 7): + def assert_is(expr1, expr2, msg=None): + """Just like assert_true(a is b), but with a nicer default message.""" + pass + + def assert_is_not(expr1, expr2, msg=None): + """Just like assert_true(a is not b), but with a nicer default message. + """ + pass + + def assert_is_none(obj, msg=None): + """Same as assert_true(obj is None), with a nicer default message. + """ + pass + + def assert_is_not_none(obj, msg=None): + """Included for symmetry with assert_is_none.""" + pass + + def assert_in(member, container, msg=None): + """Just like assert_true(a in b), but with a nicer default message.""" + pass + + def assert_not_in(member, container, msg=None): + """Just like assert_true(a not in b), but with a nicer default message. + """ + pass + + def assert_is_instance(obj, cls, msg=None): + """Same as assert_true(isinstance(obj, cls)), with a nicer default + message. + """ + pass + + def assert_not_is_instance(obj, cls, msg=None): + """Included for symmetry with assert_is_instance.""" + pass + + +def assert_raises(excClass, callableObj=None, *args, **kwargs): + """Fail unless an exception of class excClass is thrown by callableObj when + invoked with arguments args and keyword arguments kwargs. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with assert_raises(SomeException): + do_something() + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +if sys.version_info >= (2, 7): + def assert_raises_regexp(expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +def assert_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are unequal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is more than the + given delta. + """ + pass + + +def assert_not_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are equal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is less than the + given delta. + """ + pass + + +if sys.version_info >= (2, 7): + def assert_greater(a, b, msg=None): + """Just like assert_true(a > b), but with a nicer default message.""" + pass + + def assert_greater_equal(a, b, msg=None): + """Just like assert_true(a >= b), but with a nicer default message.""" + pass + + def assert_less(a, b, msg=None): + """Just like assert_true(a < b), but with a nicer default message.""" + pass + + def assert_less_equal(a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default + message. + """ + pass + + def assert_regexp_matches(text, expected_regexp, msg=None): + """Fail the test unless the text matches the regular expression.""" + pass + + def assert_not_regexp_matches(text, unexpected_regexp, msg=None): + """Fail the test if the text matches the regular expression.""" + pass + + def assert_items_equal(expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + actual_seq and expected_seq have the same element counts. + """ + pass + + def assert_dict_contains_subset(expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + pass + + def assert_multi_line_equal(first, second, msg=None): + """Assert that two multi-line strings are equal.""" + pass + + def assert_sequence_equal(seq1, seq2, msg=None, seq_type=None): + """An equality assertion for ordered sequences (like lists and tuples). + """ + pass + + def assert_list_equal(list1, list2, msg=None): + """A list-specific equality assertion.""" + pass + + def assert_tuple_equal(tuple1, tuple2, msg=None): + """A tuple-specific equality assertion.""" + pass + + def assert_set_equal(set1, set2, msg=None): + """A set-specific equality assertion.""" + pass + + def assert_dict_equal(d1, d2, msg=None): + """A dict-specific equality assertion.""" + pass + + +assert_equals = assert_equal +assert_not_equals = assert_not_equal +assert_almost_equals = assert_almost_equal +assert_not_almost_equals = assert_not_almost_equal diff --git a/python/helpers/python-skeletons/re.py b/python/helpers/python-skeletons/re.py new file mode 100644 index 000000000000..151ddc9b7257 --- /dev/null +++ b/python/helpers/python-skeletons/re.py @@ -0,0 +1,277 @@ +"""Skeleton for 're' stdlib module.""" + + +def compile(pattern, flags=0): + """Compile a regular expression pattern, returning a pattern object. + + :type pattern: bytes | unicode + :type flags: int + :rtype: __Regex + """ + pass + + +def search(pattern, string, flags=0): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def match(pattern, string, flags=0): + """Matches zero or more characters at the beginning of the string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def split(pattern, string, maxsplit=0, flags=0): + """Split string by the occurrences of pattern. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type maxsplit: int + :type flags: int + :rtype: list[T] + """ + pass + + +def findall(pattern, string, flags=0): + """Return a list of all non-overlapping matches of pattern in string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: list[T] + """ + pass + + +def finditer(pattern, string, flags=0): + """Return an iterator over all non-overlapping matches for the pattern in + string. For each match, the iterator returns a match object. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + +def sub(pattern, repl, string, count=0, flags=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: T + """ + pass + + +def subn(pattern, repl, string, count=0, flags=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: (T, int) + """ + pass + + +def escape(string): + """Escape all the characters in pattern except ASCII letters and numbers. + + :type string: T <= bytes | unicode + :type: T + """ + pass + + +class __Regex(object): + """Mock class for a regular expression pattern object.""" + + def __init__(self, flags, groups, groupindex, pattern): + """Create a new pattern object. + + :type flags: int + :type groups: int + :type groupindex: dict[bytes | unicode, int] + :type pattern: bytes | unicode + """ + self.flags = flags + self.groups = groups + self.groupindex = groupindex + self.pattern = pattern + + def search(self, string, pos=0, endpos=-1): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def match(self, string, pos=0, endpos=-1): + """Matches zero | more characters at the beginning of the string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def split(self, string, maxsplit=0): + """Split string by the occurrences of pattern. + + :type string: T <= bytes | unicode + :type maxsplit: int + :rtype: list[T] + """ + pass + + def findall(self, string, pos=0, endpos=-1): + """Return a list of all non-overlapping matches of pattern in string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: list[T] + """ + pass + + def finditer(self, string, pos=0, endpos=-1): + """Return an iterator over all non-overlapping matches for the + pattern in string. For each match, the iterator returns a + match object. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + def sub(self, repl, string, count=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: T + """ + pass + + def subn(self, repl, string, count=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: (T, int) + """ + pass + + +class __Match(object): + """Mock class for a match object.""" + + def __init__(self, pos, endpos, lastindex, lastgroup, re, string): + """Create a new match object. + + :type pos: int + :type endpos: int + :type lastindex: int | None + :type lastgroup: int | bytes | unicode | None + :type re: __Regex + :type string: bytes | unicode + :rtype: __Match[T] + """ + self.pos = pos + self.endpos = endpos + self.lastindex = lastindex + self.lastgroup = lastgroup + self.re = re + self.string = string + + def expand(self, template): + """Return the string obtained by doing backslash substitution on the + template string template. + + :type template: T + :rtype: T + """ + pass + + def group(self, *args): + """Return one or more subgroups of the match. + + :rtype: T | tuple + """ + pass + + def groups(self, default=None): + """Return a tuple containing all the subgroups of the match, from 1 up + to however many groups are in the pattern. + + :rtype: tuple + """ + pass + + def groupdict(self, default=None): + """Return a dictionary containing all the named subgroups of the match, + keyed by the subgroup name. + + :rtype: dict[bytes | unicode, T] + """ + pass + + def start(self, group=0): + """Return the index of the start of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def end(self, group=0): + """Return the index of the end of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def span(self, group=0): + """Return a 2-tuple (start, end) for the substring matched by group. + + :type group: int | bytes | unicode + :rtype: (int, int) + """ + pass diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java index e9ef7ebeee19..6aa350224a38 100644 --- a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java @@ -35,7 +35,7 @@ import java.util.List; * @author vlan */ public class PyUserSkeletonsUtil { - public static final String USER_SKELETONS_DIR = "user-skeletons"; + public static final String USER_SKELETONS_DIR = "python-skeletons"; @Nullable private static VirtualFile ourUserSkeletonsDirectory; @NotNull From 46199cb7f64a0b530aed14966fd7e669d9720de4 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 2 Sep 2013 18:53:35 +0400 Subject: [PATCH 07/11] Typos --- python/helpers/python-skeletons/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/helpers/python-skeletons/README.md b/python/helpers/python-skeletons/README.md index 953a0d0a11d7..e557a519e22e 100644 --- a/python/helpers/python-skeletons/README.md +++ b/python/helpers/python-skeletons/README.md @@ -21,7 +21,7 @@ Often library API definitions are quite static and easy to grasp (defined using `class`, `def`), but types of function parameters and return values usually are not specified. Sometimes API definitions involve metaprogramming. -As there is not enough information in API defintion code of libraries, +As there is not enough information in API definition code of libraries, developers of static analysis tools collect extended API data themselves and store it in their own formats. For example, PyLint uses imperative AST transformations of API modules in order to extend them with hard-coded data. @@ -58,7 +58,7 @@ Conventions Skeletons should respect PEP-8 and PEP-257 style guides. The most simple way of specifying types in skeletons is Sphinx docstrings. -Function annoatations could be used for specifying types, but they are +Function annotations could be used for specifying types, but they are available only for Python 3. There is no standard notation for specifying types in Python code. We propose @@ -121,7 +121,7 @@ repository into your PyCharm config directory: where `` is: -* Mac OS X: `~/Library/Prefernces/PyCharmXX/config` +* Mac OS X: `~/Library/Preferences/PyCharmXX/config` * Linux: `~/.PyCharmXX/config` * Windows: `\.PyCharmXX\config` From 24d0f90080e95de04af7ea70829a20002ea1515a Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 2 Sep 2013 20:20:49 +0400 Subject: [PATCH 08/11] fixed PY-5131 Unresolved reference in PyQt for QtGui module actually there were 3 problems - cyclic dependency in PyQt4.7+ from QSignalMapper to QtGui - garbage collection of QCoreApplication (see sip pitfalls) - none type of qApp in PyQt5 --- python/helpers/generator3.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/python/helpers/generator3.py b/python/helpers/generator3.py index 2c76d0f5902f..1b32313354af 100644 --- a/python/helpers/generator3.py +++ b/python/helpers/generator3.py @@ -24,7 +24,7 @@ but seemingly no one uses them in C extensions yet anyway. # * re.search-bound, ~30% time, in likes of builtins and _gtk with complex docstrings. # None of this can seemingly be easily helped. Maybe there's a simpler and faster parser library? -VERSION = "1.127" # Must be a number-dot-number string, updated with each change that affects generated skeletons +VERSION = "1.128" # Must be a number-dot-number string, updated with each change that affects generated skeletons # Note: DON'T FORGET TO UPDATE! import sys @@ -736,8 +736,29 @@ class ModuleRedeclarator(object): self._defined = {} # stores True for every name defined so far, to break circular refs in values self.doing_builtins = doing_builtins self.ret_type_cache = {} - self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actullay used imported names + self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actually used imported names + def initializeQApp(self): + try: # QtGui should be imported _before_ QtCore package. + # This is done for the QWidget references from QtCore (such as QSignalMapper). Known bug in PyQt 4.7+ + # Causes "TypeError: C++ type 'QWidget*' is not supported as a native Qt signal type" + import PyQt4.QtGui + except ImportError: + pass + + # manually instantiate and keep reference to singleton QCoreApplication (we don't want it to be deleted during the introspection) + # use QCoreApplication instead of QApplication to avoid blinking app in Dock on Mac OS + try: + from PyQt4.QtCore import QCoreApplication + self.app = QCoreApplication([]) + return + except ImportError: + pass + try: + from PyQt5.QtCore import QCoreApplication + self.app = QCoreApplication([]) + except ImportError: + pass def indent(self, level): "Return indentation whitespace for given level." @@ -809,6 +830,7 @@ class ModuleRedeclarator(object): REPLACE_MODULE_VALUES = { ("numpy.core.multiarray", "typeinfo"): "{}", ("psycopg2._psycopg", "string_types"): "{}", # badly mangled __eq__ breaks fmtValue + ("PyQt5.QtWidgets", "qApp") : "QApplication()", # instead of None } if version[0] <= 2: REPLACE_MODULE_VALUES[(BUILTIN_MOD_NAME, "None")] = "object()" @@ -988,7 +1010,7 @@ class ModuleRedeclarator(object): # known properties of modules # {{"module": {"class", "property" : ("letters", ("getter", "type"))}}, # where letters is any set of r,w,d (read, write, del) and "getter" is a source of typed getter. - # if vlue is None, the property should be omitted. + # if value is None, the property should be omitted. # read-only properties that return an object are not listed. G_OBJECT = ("lambda self: object()", None) G_TYPE = ("lambda self: type(object)", "type") @@ -1979,6 +2001,10 @@ class ModuleRedeclarator(object): @param p_name name of module """ action("redoing header of module %r %r", p_name, str(self.module)) + + if "pyqt" in p_name.lower(): # qt specific patch + self.initializeQApp() + self.redoSimpleHeader(p_name) # find whatever other self.imported_modules the module knows; effectively these are imports From c48e3a7e920620f50dc089404d0b4db8944c21a6 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 2 Sep 2013 18:29:46 +0200 Subject: [PATCH 09/11] fix path to skeletons --- python/build/pycharm_build.gant | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/pycharm_build.gant b/python/build/pycharm_build.gant index 7c17d12ad023..677d932d0f29 100644 --- a/python/build/pycharm_build.gant +++ b/python/build/pycharm_build.gant @@ -349,7 +349,7 @@ private layoutWin(Map args, String target) { } dir("skeletons") { - fileset(dir: "$home/python/skeletons") { + fileset(dir: "$pythonCommunityHome/skeletons") { include(name: "skeletons-win*.zip") } } @@ -390,7 +390,7 @@ private layoutMac(Map _args, String target) { } dir("skeletons") { - fileset(dir: "$home/python/skeletons") { + fileset(dir: "$pythonCommunityHome/skeletons") { include(name: "skeletons-mac*.zip") } } From 47ecc5fa19204555e58272a3e58dbf6858305f37 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 2 Sep 2013 20:36:58 +0400 Subject: [PATCH 10/11] merge --- python/src/META-INF/python-plugin-core.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/src/META-INF/python-plugin-core.xml b/python/src/META-INF/python-plugin-core.xml index 681304de2d11..16b031bded9d 100644 --- a/python/src/META-INF/python-plugin-core.xml +++ b/python/src/META-INF/python-plugin-core.xml @@ -4,6 +4,7 @@ com.jetbrains.python.PyBundle + @@ -324,6 +325,7 @@ + From e8f2e34f455cd408c8e9f679b26affb631c1d28e Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 3 Sep 2013 13:49:34 +0400 Subject: [PATCH 11/11] fixed PY-4120 PyQt: unresolved reference: false positive for new-style sygnals --- python/src/META-INF/python-plugin-core.xml | 3 ++ .../com/jetbrains/pyqt/PyQtTypeProvider.java | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 python/src/com/jetbrains/pyqt/PyQtTypeProvider.java diff --git a/python/src/META-INF/python-plugin-core.xml b/python/src/META-INF/python-plugin-core.xml index 16b031bded9d..4edcb7367bda 100644 --- a/python/src/META-INF/python-plugin-core.xml +++ b/python/src/META-INF/python-plugin-core.xml @@ -572,6 +572,9 @@ + + + diff --git a/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java b/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java new file mode 100644 index 000000000000..c97f2347c428 --- /dev/null +++ b/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java @@ -0,0 +1,54 @@ +package com.jetbrains.pyqt; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.psi.types.*; + +/** + * User : ktisha + */ +public class PyQtTypeProvider extends PyTypeProviderBase { + private static final String ourQtBoundSignal = "QtCore.pyqtBoundSignal"; + private static final String ourQt4Signal = "pyqtSignal"; + + @Override + public PyType getReferenceExpressionType(PyReferenceExpression referenceExpression, TypeEvalContext context) { + final PsiPolyVariantReference reference = referenceExpression.getReference(); + final PsiElement element = reference.resolve(); + if (element instanceof PyTypedElement) { + final PyType type = context.getType((PyTypedElement)element); + if (type instanceof PyClassType) { + final String classQName = ((PyClassType)type).getClassQName(); + if (classQName != null && classQName.startsWith("PyQt")) { + final PyQualifiedName name = PyQualifiedName.fromDottedString(classQName); + final String qtVersion = name.getComponents().get(0); + if (ourQt4Signal.equals(name.getLastComponent())) { + final PyClass aClass = PyClassNameIndex.findClass(qtVersion + "." + ourQtBoundSignal, referenceExpression.getProject()); + if (aClass != null) + return new PyClassTypeImpl(aClass, false); + } + } + } + else if (type instanceof PyFunctionType) { + final Callable callable = ((PyFunctionType)type).getCallable(); + if (callable instanceof PyFunction) { + final String qualifiedName = callable.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("PyQt")){ + final PyQualifiedName name = PyQualifiedName.fromDottedString(qualifiedName); + final String qtVersion = name.getComponents().get(0); + final String docstring = ((PyFunction)callable).getDocStringValue(); + if (docstring != null && docstring.contains("[signal]")) { + final PyClass aClass = PyClassNameIndex.findClass(qtVersion + "." + ourQtBoundSignal, referenceExpression.getProject()); + if (aClass != null) + return new PyClassTypeImpl(aClass, false); + } + } + } + } + } + return null; + } +}