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

org.sonar.python.checks.WildcardImportCheck Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.python.checks;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BooleanSupplier;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.AssignmentStatement;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.ExpressionList;
import org.sonar.plugins.python.api.tree.FileInput;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.ImportFrom;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.StatementList;
import org.sonar.plugins.python.api.tree.Tree;

@Rule(key = "S2208")
public class WildcardImportCheck extends PythonSubscriptionCheck {

  private static final String MESSAGE = "Import only needed names or import the module and then use its members.";

  private static class WildcardImportVisitor extends BaseTreeVisitor {
    private boolean shouldRaiseIssues = false;
    private List wildcardImports = new ArrayList<>();

    @Override
    public void visitImportFrom(ImportFrom pyImportFromTree) {
      if (pyImportFromTree.isWildcardImport()) {
        wildcardImports.add(pyImportFromTree);
      }
    }

    @Override
    public void visitFunctionDef(FunctionDef pyFunctionDefTree) {
      shouldRaiseIssues = true;
      // No point visiting further - wildcard imports are only allowed on the module level
    }

    @Override
    public void visitClassDef(ClassDef pyClassDefTree) {
      shouldRaiseIssues = true;
      // No point visiting further - wildcard imports are only allowed on the module level
    }

    @Override
    public void visitStatementList(StatementList statementList) {
      // We should raise if one of the statements may contain application logic
      this.raiseIssuesIf(() -> statementList.statements().stream().anyMatch(WildcardImportVisitor::mayContainApplicationLogic));
      super.visitStatementList(statementList);
    }

    @Override
    public void visitAssignmentStatement(AssignmentStatement pyAssignmentStatementTree) {
      List lhsExpressions = pyAssignmentStatementTree.lhsExpressions();
      this.raiseIssuesIf(() -> lhsExpressions.stream().anyMatch(
        expressionList -> expressionList.expressions().stream().anyMatch(WildcardImportVisitor::isDisallowedAssignment)
      ));
    }

    @Override
    public void visitCallExpression(CallExpression pyCallExpressionTree) {
      Symbol symbol = pyCallExpressionTree.calleeSymbol();
      this.raiseIssuesIf(() -> symbol == null || !"warnings.warn".equals(symbol.fullyQualifiedName()));
    }

    private void raiseIssuesIf(BooleanSupplier condition) {
      shouldRaiseIssues = shouldRaiseIssues || condition.getAsBoolean();
    }

    private static boolean isDisallowedAssignment(Expression expression) {
      return !expression.is(Tree.Kind.NAME) || !"__all__".equals(((Name) expression).name());
    }

    private static boolean mayContainApplicationLogic(Tree tree) {
      // Some common control structures and statements that probably describe application logic
      return tree.is(Tree.Kind.WHILE_STMT, Tree.Kind.FOR_STMT, Tree.Kind.WITH_STMT, Tree.Kind.COMPOUND_ASSIGNMENT);
    }
  }

  @Override
  public void initialize(Context context) {
    context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, ctx -> {
      if (ctx.pythonFile().fileName().equals("__init__.py")) {
        // Ignore __init__.py files, as wildcard imports are commonly used to populate those.
        return;
      }

      FileInput fileInput = (FileInput) ctx.syntaxNode();
      StatementList statements = fileInput.statements();
      if (statements == null) {
        return;
      }

      WildcardImportVisitor visitor = new WildcardImportVisitor();
      statements.accept(visitor);

      if (visitor.shouldRaiseIssues) {
        visitor.wildcardImports.forEach(importFrom -> ctx.addIssue(importFrom, MESSAGE));
      }
    });
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy