From 12eb2d7cacc82b331fddb29fe3fc6202af1646d5 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 4 Jul 2017 19:58:39 +0300 Subject: [PATCH] PY-22091 Consider "self" references in f-strings inside potentially static methods --- .../PyFStringAwareRecursiveVisitor.java | 62 +++++++++++++++++++ .../PyMethodMayBeStaticInspection.java | 3 +- .../PyMethodMayBeStaticInspection/fString.py | 3 + .../PyMethodMayBeStaticInspectionTest.java | 5 ++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringAwareRecursiveVisitor.java create mode 100644 python/testData/inspections/PyMethodMayBeStaticInspection/fString.py diff --git a/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringAwareRecursiveVisitor.java b/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringAwareRecursiveVisitor.java new file mode 100644 index 000000000000..2e615369b830 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringAwareRecursiveVisitor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.codeInsight.fstrings; + +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyRecursiveElementVisitor; +import com.jetbrains.python.psi.PyStringLiteralExpression; +import com.jetbrains.python.psi.PyUtil; + +import java.util.List; + +import static com.jetbrains.python.psi.PyUtil.as; + +/** + * Implementation of {@link PyRecursiveElementVisitor} that recursively visits Python files injected into f-strings. + * You can check whether you are inside injected fragment using {@link #myContainingFString} field. + */ +public class PyFStringAwareRecursiveVisitor extends PyRecursiveElementVisitor { + protected PyStringLiteralExpression myContainingFString; + + @Override + public void visitPyStringLiteralExpression(PyStringLiteralExpression pyString) { + final List formatNodeRanges = ContainerUtil.mapNotNull(pyString.getStringNodes(), node -> { + final PyUtil.StringNodeInfo nodeInfo = new PyUtil.StringNodeInfo(node); + return nodeInfo.isFormatted() ? nodeInfo.getAbsoluteContentRange().shiftRight(-pyString.getTextOffset()) : null; + }); + if (!formatNodeRanges.isEmpty()) { + final InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(pyString.getProject()); + for (Pair pair : ContainerUtil.notNullize(injectionManager.getInjectedPsiFiles(pyString))) { + final PyFile pyFile = as(pair.getFirst(), PyFile.class); + if (pyFile != null && ContainerUtil.exists(formatNodeRanges, range -> range.contains(pair.getSecond()))) { + myContainingFString = pyString; + try { + pyFile.accept(this); + } + finally { + myContainingFString = null; + } + } + } + } + super.visitPyStringLiteralExpression(pyString); + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java index 24a3c1f4cf51..9c6f31d6bf2b 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java @@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.fstrings.PyFStringAwareRecursiveVisitor; import com.jetbrains.python.inspections.quickfix.PyMakeFunctionFromMethodQuickFix; import com.jetbrains.python.inspections.quickfix.PyMakeMethodStaticQuickFix; import com.jetbrains.python.psi.*; @@ -92,7 +93,7 @@ public class PyMethodMayBeStaticInspection extends PyInspection { } final boolean[] mayBeStatic = {true}; - PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() { + PyRecursiveElementVisitor visitor = new PyFStringAwareRecursiveVisitor() { @Override public void visitPyRaiseStatement(PyRaiseStatement node) { super.visitPyRaiseStatement(node); diff --git a/python/testData/inspections/PyMethodMayBeStaticInspection/fString.py b/python/testData/inspections/PyMethodMayBeStaticInspection/fString.py new file mode 100644 index 000000000000..323c7470ee30 --- /dev/null +++ b/python/testData/inspections/PyMethodMayBeStaticInspection/fString.py @@ -0,0 +1,3 @@ +class C: + def method(self): + print(r'foobar' f'{self}') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java index 7a84199d2127..1ec0cf7d0a7d 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java @@ -95,6 +95,11 @@ public class PyMethodMayBeStaticInspectionTest extends PyTestCase { doTest(); } + // PY-22091 + public void testFString() { + runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); + } + // PY-18866 public void testSuperSamePy3() { runWithLanguageLevel(LanguageLevel.PYTHON30, () -> doTest());