diff --git a/python/resources/postfixTemplates/PyLenPostfixTemplate/after.py.template b/python/resources/postfixTemplates/PyLenPostfixTemplate/after.py.template new file mode 100644 index 000000000000..6ad7f4e5b8d6 --- /dev/null +++ b/python/resources/postfixTemplates/PyLenPostfixTemplate/after.py.template @@ -0,0 +1,2 @@ +def f(a): + len(a) \ No newline at end of file diff --git a/python/resources/postfixTemplates/PyLenPostfixTemplate/before.py.template b/python/resources/postfixTemplates/PyLenPostfixTemplate/before.py.template new file mode 100644 index 000000000000..7ad4281c8206 --- /dev/null +++ b/python/resources/postfixTemplates/PyLenPostfixTemplate/before.py.template @@ -0,0 +1,2 @@ +def f(a): + a.len \ No newline at end of file diff --git a/python/resources/postfixTemplates/PyLenPostfixTemplate/description.html b/python/resources/postfixTemplates/PyLenPostfixTemplate/description.html new file mode 100644 index 000000000000..6565cb0c6be2 --- /dev/null +++ b/python/resources/postfixTemplates/PyLenPostfixTemplate/description.html @@ -0,0 +1,5 @@ + + +Converts postfix call to '.len' to 'len' function invocation. + + \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/postfix/PyLenPostfixTemplate.kt b/python/src/com/jetbrains/python/codeInsight/postfix/PyLenPostfixTemplate.kt new file mode 100644 index 000000000000..9efca64daec5 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/postfix/PyLenPostfixTemplate.kt @@ -0,0 +1,38 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.codeInsight.postfix + +import com.intellij.codeInsight.template.postfix.templates.SurroundPostfixTemplateBase +import com.intellij.lang.surroundWith.Surrounder +import com.intellij.openapi.util.Condition +import com.intellij.psi.PsiElement +import com.jetbrains.python.PyNames +import com.jetbrains.python.psi.AccessDirection +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyExpression +import com.jetbrains.python.psi.resolve.PyResolveContext +import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.python.refactoring.surround.surrounders.expressions.PyLenExpressionStatementSurrounder + +class PyLenPostfixTemplate : SurroundPostfixTemplateBase("len", DESCR, PyPostfixUtils.PY_PSI_INFO, + PyPostfixUtils.selectorAllExpressionsWithCurrentOffset(sizedFilter)) { + + override fun getSurrounder(): Surrounder = PyLenExpressionStatementSurrounder() + + companion object { + const val DESCR = "len(expr)" + + val sizedFilter: Condition = Condition { element -> + val ref = element.reference + if (ref?.resolve() is PyClass) + return@Condition false + + val expression = element as PyExpression + val context = TypeEvalContext.codeCompletion(expression.project, expression.containingFile) + val type = context.getType(expression) ?: return@Condition false + + val resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context) + val results = type.resolveMember(PyNames.LEN, null, AccessDirection.READ, resolveContext) + return@Condition results?.isNotEmpty() ?: false + } + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/postfix/PyPostfixTemplateProvider.java b/python/src/com/jetbrains/python/codeInsight/postfix/PyPostfixTemplateProvider.java index f76088afcbb7..6a53a100c86e 100644 --- a/python/src/com/jetbrains/python/codeInsight/postfix/PyPostfixTemplateProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/postfix/PyPostfixTemplateProvider.java @@ -28,7 +28,8 @@ public class PyPostfixTemplateProvider implements PostfixTemplateProvider { new PyIsNonePostfixTemplate(), new PyIsNotNonePostfixTemplate(), new PyPrintPostfixTemplate(), - new PyMainPostfixTemplate()); + new PyMainPostfixTemplate(), + new PyLenPostfixTemplate()); } @Override diff --git a/python/src/com/jetbrains/python/refactoring/surround/PyExpressionSurroundDescriptor.java b/python/src/com/jetbrains/python/refactoring/surround/PyExpressionSurroundDescriptor.java index 00632f38ba21..4cf3e2984702 100644 --- a/python/src/com/jetbrains/python/refactoring/surround/PyExpressionSurroundDescriptor.java +++ b/python/src/com/jetbrains/python/refactoring/surround/PyExpressionSurroundDescriptor.java @@ -8,11 +8,12 @@ import com.intellij.psi.PsiFile; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.surround.surrounders.expressions.*; +import com.jetbrains.python.refactoring.surround.surrounders.expressions.PyLenExpressionStatementSurrounder; import org.jetbrains.annotations.NotNull; public class PyExpressionSurroundDescriptor implements SurroundDescriptor { private static final Surrounder[] SURROUNDERS = {new PyWithParenthesesSurrounder(), new PyIfExpressionSurrounder(), - new PyWhileExpressionSurrounder(), new PyIsNoneSurrounder(), new PyIsNotNoneSurrounder()}; + new PyWhileExpressionSurrounder(), new PyIsNoneSurrounder(), new PyIsNotNoneSurrounder(), new PyLenExpressionStatementSurrounder()}; @Override @NotNull diff --git a/python/src/com/jetbrains/python/refactoring/surround/surrounders/expressions/PyLenExpressionStatementSurrounder.kt b/python/src/com/jetbrains/python/refactoring/surround/surrounders/expressions/PyLenExpressionStatementSurrounder.kt new file mode 100644 index 000000000000..13c16a691c23 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/surround/surrounders/expressions/PyLenExpressionStatementSurrounder.kt @@ -0,0 +1,31 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.refactoring.surround.surrounders.expressions + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.util.IncorrectOperationException +import com.jetbrains.python.psi.* + + +class PyLenExpressionStatementSurrounder : PyExpressionSurrounder() { + + @Throws(IncorrectOperationException::class) + override fun surroundExpression(project: Project, editor: Editor, element: PyExpression): TextRange { + val range = element.textRange + val statement = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyExpressionStatement::class.java, "len(a)") + val callExpression = statement.expression as PyCallExpression + val arg = callExpression.arguments[0] + arg.replace(element) + element.replace(callExpression) + return TextRange.from(range.endOffset + FUNCTION_LENGTH, 0) + } + + override fun isApplicable(expr: PyExpression) = true + + override fun getTemplateDescription()= "len(expr)" + + companion object { + const val FUNCTION_LENGTH = "len()".length + } +} diff --git a/python/testData/postfix/len/asExpr.py b/python/testData/postfix/len/asExpr.py new file mode 100644 index 000000000000..af8589306742 --- /dev/null +++ b/python/testData/postfix/len/asExpr.py @@ -0,0 +1 @@ +foo = "something".len \ No newline at end of file diff --git a/python/testData/postfix/len/asExpr_after.py b/python/testData/postfix/len/asExpr_after.py new file mode 100644 index 000000000000..06be9a9af121 --- /dev/null +++ b/python/testData/postfix/len/asExpr_after.py @@ -0,0 +1 @@ +foo = len("something") \ No newline at end of file diff --git a/python/testData/postfix/len/dict.py b/python/testData/postfix/len/dict.py new file mode 100644 index 000000000000..1b29cfbeea73 --- /dev/null +++ b/python/testData/postfix/len/dict.py @@ -0,0 +1 @@ +{"first": 1, "second": 2}.len \ No newline at end of file diff --git a/python/testData/postfix/len/dict_after.py b/python/testData/postfix/len/dict_after.py new file mode 100644 index 000000000000..9edff2023988 --- /dev/null +++ b/python/testData/postfix/len/dict_after.py @@ -0,0 +1 @@ +len({"first": 1, "second": 2}) \ No newline at end of file diff --git a/python/testData/postfix/len/list.py b/python/testData/postfix/len/list.py new file mode 100644 index 000000000000..296d8fd7944a --- /dev/null +++ b/python/testData/postfix/len/list.py @@ -0,0 +1 @@ +[1, 2, 3, 4].len \ No newline at end of file diff --git a/python/testData/postfix/len/list_after.py b/python/testData/postfix/len/list_after.py new file mode 100644 index 000000000000..fbdb4f03d5b8 --- /dev/null +++ b/python/testData/postfix/len/list_after.py @@ -0,0 +1 @@ +len([1, 2, 3, 4]) \ No newline at end of file diff --git a/python/testData/postfix/len/notSized.py b/python/testData/postfix/len/notSized.py new file mode 100644 index 000000000000..252033ffa0e6 --- /dev/null +++ b/python/testData/postfix/len/notSized.py @@ -0,0 +1,5 @@ +class MyNotSizedClass: + pass + + +MyNotSizedClass().len \ No newline at end of file diff --git a/python/testData/postfix/len/notSized_after.py b/python/testData/postfix/len/notSized_after.py new file mode 100644 index 000000000000..d24b65982292 --- /dev/null +++ b/python/testData/postfix/len/notSized_after.py @@ -0,0 +1,5 @@ +class MyNotSizedClass: + pass + + +MyNotSizedClass().len \ No newline at end of file diff --git a/python/testData/postfix/len/sized.py b/python/testData/postfix/len/sized.py new file mode 100644 index 000000000000..4cdbd7d0227c --- /dev/null +++ b/python/testData/postfix/len/sized.py @@ -0,0 +1,6 @@ +class MySizedClass: + def __len__(self): + pass + + +MySizedClass().len \ No newline at end of file diff --git a/python/testData/postfix/len/sizedClassObj.py b/python/testData/postfix/len/sizedClassObj.py new file mode 100644 index 000000000000..cd06e9509d10 --- /dev/null +++ b/python/testData/postfix/len/sizedClassObj.py @@ -0,0 +1,6 @@ +class MySizedClass: + def __len__(self): + pass + + +MySizedClass.len \ No newline at end of file diff --git a/python/testData/postfix/len/sizedClassObj_after.py b/python/testData/postfix/len/sizedClassObj_after.py new file mode 100644 index 000000000000..6f4fb66b3656 --- /dev/null +++ b/python/testData/postfix/len/sizedClassObj_after.py @@ -0,0 +1,6 @@ +class MySizedClass: + def __len__(self): + pass + + +MySizedClass.len \ No newline at end of file diff --git a/python/testData/postfix/len/sized_after.py b/python/testData/postfix/len/sized_after.py new file mode 100644 index 000000000000..5442242ca7f4 --- /dev/null +++ b/python/testData/postfix/len/sized_after.py @@ -0,0 +1,6 @@ +class MySizedClass: + def __len__(self): + pass + + +len(MySizedClass()) \ No newline at end of file diff --git a/python/testData/postfix/len/string.py b/python/testData/postfix/len/string.py new file mode 100644 index 000000000000..f2c3295699a7 --- /dev/null +++ b/python/testData/postfix/len/string.py @@ -0,0 +1 @@ +"something".len \ No newline at end of file diff --git a/python/testData/postfix/len/string_after.py b/python/testData/postfix/len/string_after.py new file mode 100644 index 000000000000..91e17003228f --- /dev/null +++ b/python/testData/postfix/len/string_after.py @@ -0,0 +1 @@ +len("something") \ No newline at end of file diff --git a/python/testData/postfix/len/tuple.py b/python/testData/postfix/len/tuple.py new file mode 100644 index 000000000000..9d244024fa0e --- /dev/null +++ b/python/testData/postfix/len/tuple.py @@ -0,0 +1 @@ +(1, 2, 3, 4).len \ No newline at end of file diff --git a/python/testData/postfix/len/tuple_after.py b/python/testData/postfix/len/tuple_after.py new file mode 100644 index 000000000000..69340e93c409 --- /dev/null +++ b/python/testData/postfix/len/tuple_after.py @@ -0,0 +1 @@ +len((1, 2, 3, 4)) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/postfix/PyLenPostfixTemplateTest.kt b/python/testSrc/com/jetbrains/python/postfix/PyLenPostfixTemplateTest.kt new file mode 100644 index 000000000000..25d81893e458 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/postfix/PyLenPostfixTemplateTest.kt @@ -0,0 +1,23 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.postfix + +class PyLenPostfixTemplateTest : PyPostfixTemplateTestCase() { + + fun testString() = doTest() + + fun testList() = doTest() + + fun testDict() = doTest() + + fun testTuple() = doTest() + + fun testSized() = doTest() + + fun testSizedClassObj() = doTest() + + fun testAsExpr() = doTest() + + fun testNotSized() = doTest() + + override fun getTestDataDir() = "len/" +}