All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.inspections.PyAssignmentToLoopOrWithParameterInspection Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

//TODO: Try to share logic with AssignmentToForLoopParameterInspection

/**
 * Checks for cases when you rewrite loop variable with inner loop.
 * It finds all with and for statements, takes variables declared by them and ensures none of parent
 * with or for declares variable with the same name
 *
 * @author link
 */
public class PyAssignmentToLoopOrWithParameterInspection extends PyInspection {

  private static final String NAME = PyBundle.message("INSP.NAME.assignment.to.loop.or.with.parameter.display.name");

  @NotNull
  @Override
  public String getDisplayName() {
    return NAME;
  }


  @NotNull
  @Override
  public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
                                        final boolean isOnTheFly,
                                        @NotNull final LocalInspectionToolSession session) {
    return new Visitor(holder, session);
  }

  private static class Visitor extends PyInspectionVisitor {
    private Visitor(@Nullable final ProblemsHolder holder, @NotNull final LocalInspectionToolSession session) {
      super(holder, session);
    }

    @Override
    public void visitPyWithStatement(final PyWithStatement node) {
      checkNotReDeclaringUpperLoopOrStatement(node);
    }

    @Override
    public void visitPyForStatement(final PyForStatement node) {
      checkNotReDeclaringUpperLoopOrStatement(node);
    }

    /**
     * Finds first parent of specific type (See {@link #isRequiredStatement(com.intellij.psi.PsiElement)})
     * that declares one of names, declared in this statement
     */
    private void checkNotReDeclaringUpperLoopOrStatement(@NotNull final NameDefiner statement) {
      for (final PsiElement declaredVar : statement.iterateNames()) {
        final Filter filter = new Filter(handleSubscriptionsAndResolveSafely(declaredVar));
        final PsiElement firstParent = PsiTreeUtil.findFirstParent(statement, true, filter);
        if ((firstParent != null) && isRequiredStatement(firstParent)) {
          // If parent is "for", we need to check that statement not declared in "else": PY-12367
          if ((firstParent instanceof PyForStatement) && isDeclaredInElse(statement, (PyForStatement)firstParent)) {
            continue;
          }
          registerProblem(declaredVar,
                          PyBundle.message("INSP.NAME.assignment.to.loop.or.with.parameter.display.message", declaredVar.getText()));
        }
      }
    }
  }

  /**
   * Checks that element is declared in "else" statement of "for" statement
   *
   * @param elementToCheck element to check
   * @param forStatement   statement to obtain "else" part from
   * @return true if declated in "Else" block
   */
  private static boolean isDeclaredInElse(@NotNull final PsiElement elementToCheck, @NotNull final PyForStatement forStatement) {
    final PyElsePart elsePart = forStatement.getElsePart();
    if (elsePart != null) {
      if (PsiTreeUtil.isAncestor(elsePart, elementToCheck, false)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Filters list of parents trying to find parent that declares var that refers to {@link #myNode}
   * Returns {@link com.jetbrains.python.codeInsight.controlflow.ScopeOwner} if nothing found.
   * Returns parent otherwise.
   */
  private static class Filter implements Condition {
    private final PsiElement myNode;

    private Filter(final PsiElement node) {
      this.myNode = node;
    }

    @Override
    public boolean value(final PsiElement psiElement) {
      if (psiElement instanceof ScopeOwner) {
        return true; //Do not go any further
      }
      if (!isRequiredStatement(psiElement)) {
        return false; //Parent has wrong type, skip
      }
      final Iterable varsDeclaredInStatement = ((NameDefiner)psiElement).iterateNames();
      for (final PsiElement varDeclaredInStatement : varsDeclaredInStatement) {
        //For each variable, declared by this parent take first declaration and open subscription list if any
        final PsiReference reference = handleSubscriptionsAndResolveSafely(varDeclaredInStatement).getReference();
        if ((reference != null) && reference.isReferenceTo(myNode)) {
          return true; //One of variables declared by this parent refers to node
        }
      }
      return false;
    }
  }

  /**
   * Opens subscription list (i[n][q][f] --> i) and resolves ref recursively to the topmost element,
   * but not further than file borders (to prevent Stub to AST conversion)
   *
   * @param element element to open and resolve
   * @return opened and resolved element
   */
  private static PsiElement handleSubscriptionsAndResolveSafely(PsiElement element) {
    assert element != null;
    if (element instanceof PySubscriptionExpression) {
      element = ((PySubscriptionExpression)element).getRootOperand();
    }
    element = PyUtil.resolveToTheTop(element);
    return element;
  }

  /**
   * Checks if element is statement this inspection should work with
   *
   * @param element to check
   * @return true if inspection should work with this element
   */
  private static boolean isRequiredStatement(final PsiElement element) {
    assert element != null;
    return (element instanceof PyWithStatement) || (element instanceof PyForStatement);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy