diff --git a/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html b/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html new file mode 100644 index 000000000000..5944aee07d3c --- /dev/null +++ b/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html @@ -0,0 +1,13 @@ + + + + This inspection checks for cases when loop variable is redeclared inside of loop. + +
+    for i in xrange(5):
+      for i in xrange(20, 25):
+          print("Inner", i)
+      print("Outer", i)
+  
+ + diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index f11f009b3a5c..424f0f79a19b 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -329,6 +329,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 84d4c6d7423b..f780607189a9 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -511,6 +511,9 @@ INSP.none.function.assignment=Function ''{0}'' doesn''t return anything INSP.NAME.global.undefined=Global variable is undefined at the module level INSP.NAME.global.$0.undefined=Global variable ''{0}'' is undefined at the module level +#PyAssignmentToForLoopParameterInspection +INSP.NAME.assignment.to.for.loop.parameter.display.name=Assignment to 'for' loop parameter + # Refactoring # introduce refactoring.introduce.name.error=Incorrect name diff --git a/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java b/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java new file mode 100644 index 000000000000..fcfb364e338f --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyForPart; +import com.jetbrains.python.psi.PyTargetExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +//TODO: Try to share logic with AssignmentToForLoopParameterInspection + +/** + * Checks for cases like + *
+ *   for i in range(1, 10):
+ *    i = "new value"
+ * 
+ * + * @author link + */ +public class PyAssignmentToForLoopParameterInspection extends PyInspection { + + private static final String MESSAGE = PyBundle.message("INSP.NAME.assignment.to.for.loop.parameter.display.name"); + + @NotNull + @Override + public String getDisplayName() { + return MESSAGE; + } + + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, + boolean isOnTheFly, + @NotNull final LocalInspectionToolSession session) { + return new Visitor(holder, session); + } + + private static class Visitor extends PyInspectionVisitor { + private Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyTargetExpression(PyTargetExpression node) { + PsiElement variableDeclaration = node.getReference().resolve(); + if (variableDeclaration == null) { + return; + } + PsiElement variableFirstTimeDeclaration = variableDeclaration.getParent(); + + if (variableFirstTimeDeclaration.equals(node.getParent())) { + return; //We are checking first time declaration + } + + //Find "for" between predecessors until we tree root + PsiElement element = variableFirstTimeDeclaration; + while (!(element instanceof PsiFile) && element != null) { + if (element instanceof PyForPart) { + registerProblem(node, MESSAGE); + return; + } + element = element.getParent(); + } + } + } +} diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py new file mode 100644 index 000000000000..3ea1ee1b26c5 --- /dev/null +++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py @@ -0,0 +1,9 @@ +for a in (1, 12): + for b in (2, 24): + for (c, d) in {"C": "D"}.items(): + (e, f) = (a, d) + +i = 12 +print(i) +(z, x) = (i, 12) +print(z) \ No newline at end of file diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py new file mode 100644 index 000000000000..130176454320 --- /dev/null +++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py @@ -0,0 +1,3 @@ +for i in range(1, 2): + print(i) + i = 12 \ No newline at end of file diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py new file mode 100644 index 000000000000..07281c7c9784 --- /dev/null +++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py @@ -0,0 +1,3 @@ +for i in [1, 2, 3]: + print(i) + (i, f) = (1, 2) \ No newline at end of file diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py new file mode 100644 index 000000000000..52905d42e134 --- /dev/null +++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py @@ -0,0 +1,3 @@ +for (k, v) in {"K": "V"}.items(): + print(k) + k = "12" \ No newline at end of file diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py new file mode 100644 index 000000000000..c39f58244512 --- /dev/null +++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py @@ -0,0 +1,4 @@ +for i in range(5): + for i in range(20, 25): + print("Inner", i) + print("Outer", i) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java new file mode 100644 index 000000000000..96fca13931a9 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java @@ -0,0 +1,44 @@ +package com.jetbrains.python.fixtures; + +import com.jetbrains.python.inspections.PyInspection; +import org.jetbrains.annotations.NotNull; + +/** + * Helps you to create inspection tests. + *
+ * For each case + *
    + *
  1. Create file testData/inspections/_YOUR_INSPECTION_CLASS_SIMPLE_NAME_/CASE_NAME_CAMEL_CASE.py
  2. + *
  3. Create method test_YOUR_CASE_NAME_PASCAL_CASE that runs {@link #doTest()}
  4. + *
  5. Overwrite {@link #isInfo()}, {@link #isWarning()} or {@link #isWeakWarning()} to configure what to check
  6. + *
+ * + * @author link + */ +public abstract class PyInspectionTestCase extends PyTestCase { + + @NotNull + protected abstract Class getInspectionClass(); + + /** + * Launches test. To be called by test author + */ + protected void doTest() { + myFixture.configureByFile("inspections/" + getInspectionClass().getSimpleName() + "/" + getTestName(true) + ".py"); + myFixture.enableInspections(getInspectionClass()); + myFixture.checkHighlighting(isWarning(), isInfo(), isWeakWarning()); + } + + + protected boolean isWeakWarning() { + return true; + } + + protected boolean isInfo() { + return false; + } + + protected boolean isWarning() { + return true; + } +} diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java new file mode 100644 index 000000000000..9231a3d3a55b --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyInspectionTestCase; +import org.jetbrains.annotations.NotNull; + +/** + * @author link + */ +public class PyAssignmentToForLoopParameterInspectionTest extends PyInspectionTestCase { + + public void testGood() { + doTest(); + } + + public void testSimpleReassignment() { + doTest(); + } + + public void testTupleAssignment() { + doTest(); + } + + public void testTupleDeclaration() { + doTest(); + } + + public void testTwoLoops() { + doTest(); + } + + + @NotNull + @Override + protected Class getInspectionClass() { + return PyAssignmentToForLoopParameterInspection.class; + } + + @Override + protected boolean isWeakWarning() { + return false; + } +}