<commit_before>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.google.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.Category.GUAVA;
import static com.google.errorprone.BugPattern.MaturityLevel.EXPERIMENTAL;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.TreeScanner;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;
import javax.lang.model.element.ElementKind;

/**
 * Enforce § 3.3.1 of the style guide.
 *
 * <p>https://google-styleguide.googlecode.com/svn/trunk/javaguide.html#s3.3.1-wildcard-imports
 *
 * @author <EMAIL> (<NAME>)
*/
@BugPattern(name = "WildcardImport",
    summary =  "Use of wildcard imports is forbidden",
    explanation = "Wildcard imports are forbidden by §3.3.1 of the Google Java Style Guide.\n\n"
        + " They make code brittle and difficult to reason about, both for programmers and for"
        + " tools."
        + " In the following example, the processing of the first import requires reasoning about"
        + " the classes `Outer` and `Nested`, including their supertypes, and ends up depending"
        + " on information in the second import statement. This code is (incorrectly) rejected"
        + " by the latest version of javac. Also, note that `I` is actually being imported via"
        + " the name `p.q.C.I` even though it's not declared in `C`! Its canonical name is"
        + " `p.q.D.I`. Regular single-type imports require that all types are imported by"
        + " their canonical name, but static imports do not.\n\n"
        + "```java\n"
        + "package p;\n\n"
        + "import static p.Outer.Nested.*;\n"
        + "import static p.q.C.*;\n\n"
        + "public class Outer {\n"
        + "  public static class Nested implements I {\n"
        + "  }\n"
        + "}\n"
        + "```\n"
        + "```java\n"
        + "package p.q;\n\n"
        + "public class C extends D {\n"
        + "}\n"
        + "```\n"
        + "```java\n"
        + "package p.q;\n\n"
        + "public class D {\n"
        + "  public interface I {\n"
        + "  }\n"
        + "}\n"
        + "```\n",
    category = GUAVA, severity = ERROR, maturity = EXPERIMENTAL)
public class WildcardImport extends BugChecker implements ClassTreeMatcher {

  private static final String ASTERISK = "*";

  /**
   * An abstraction over the logic for generating fixes. The 'test' version exists because we
   * don't have a good way to test import-only fixes, so it pretty-prints the replacements as a
   * suffix of each on-demand import being deleted.
   */
  // TODO(user): create a way to test import-only fixes.
  private final FixStrategy fixStrategy;

  public WildcardImport() {
    this(FixStrategies.PRODUCTION);
  }

  @VisibleForTesting
  public WildcardImport(FixStrategy fixStrategy) {
    this.fixStrategy = fixStrategy;
  }

  /** A type or member that needs to be imported. */
  @AutoValue
  abstract static class TypeToImport {

    /**
     * Returns the fully-qualified canonical name.
     */
    abstract String typeName();

    /**
     * Returns true if the import needs to be static (i.e. the imported is for a field or method).
     */
    abstract boolean isStatic();

    /**
     * Generates an import statement.
     */
    String generateImportStatement() {
      return "import " + (isStatic() ? "static " : "") + typeName() + ";";
    }

    @Override
    public String toString() {
      return generateImportStatement();
    }

    static TypeToImport create(String type, boolean stat) {
      return new AutoValue_WildcardImport_TypeToImport(type, stat);
    }
  }

  @Override
  public Description matchClass(ClassTree tree, VisitorState state) {
    ClassSymbol sym = ASTHelpers.getSymbol(tree);
    if (sym == null) {
      return Description.NO_MATCH;
    }
    // Only match on top-level classes. The analysis walks the entire class declaration looking
    // for types that need to be imported, so matching on nested classes will produce incomplete
    // results.
    if (sym.getEnclosingElement() != null
        && sym.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
      return Description.NO_MATCH;
    }

    // First, look for on-demand imports. If there aren't any, we're done. This ensures that we
    // only walk the class declaration if there's an error.
    CompilationUnitTree unit = state.getPath().getCompilationUnit();
    ImmutableList<ImportTree> wildcardImports = getWildcardImports(unit.getImports());
    if (wildcardImports.isEmpty()) {
      return Description.NO_MATCH;
    }

    // Find all of the types that need to be imported.
    Set<TypeToImport> typesToImport = ImportCollector.collect((JCClassDecl) tree);

    // Group the imported types by the on-demand import they replace.
    Multimap<ImportTree, TypeToImport> toFix = groupImports(wildcardImports, typesToImport);

    // Generate fixes.
    fixStrategy.apply(tree, state, wildcardImports, toFix, this);

    // TODO(user): this is kind of a hack. We allow the fix strategy to generate multiple small
    // fixes, so by the time we get here all of the fixes were emitted and there's nothing to do.
    return Description.NO_MATCH;
  }

  /**
   * Creates a multimap from existing on-demand imports to the single-type imports we're going to
   * add, e.g.: java.util.* -> [java.util.List, java.util.ArrayList]
   */
  private Multimap<ImportTree, TypeToImport> groupImports(<commit_msg>Skip emitting a fix when there are multiple top-level type declarations<commit_after>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.google.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.Category.GUAVA;
import static com.google.errorprone.BugPattern.MaturityLevel.EXPERIMENTAL;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.TreeScanner;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;
import javax.lang.model.element.ElementKind;

/**
 * Enforce § 3.3.1 of the style guide.
 *
 * <p>https://google-styleguide.googlecode.com/svn/trunk/javaguide.html#s3.3.1-wildcard-imports
 *
 * @author <EMAIL> (<NAME>)
*/
@BugPattern(name = "WildcardImport",
    summary =  "Use of wildcard imports is forbidden",
    explanation = "Wildcard imports are forbidden by §3.3.1 of the Google Java Style Guide.\n\n"
        + " They make code brittle and difficult to reason about, both for programmers and for"
        + " tools."
        + " In the following example, the processing of the first import requires reasoning about"
        + " the classes `Outer` and `Nested`, including their supertypes, and ends up depending"
        + " on information in the second import statement. This code is (incorrectly) rejected"
        + " by the latest version of javac. Also, note that `I` is actually being imported via"
        + " the name `p.q.C.I` even though it's not declared in `C`! Its canonical name is"
        + " `p.q.D.I`. Regular single-type imports require that all types are imported by"
        + " their canonical name, but static imports do not.\n\n"
        + "```java\n"
        + "package p;\n\n"
        + "import static p.Outer.Nested.*;\n"
        + "import static p.q.C.*;\n\n"
        + "public class Outer {\n"
        + "  public static class Nested implements I {\n"
        + "  }\n"
        + "}\n"
        + "```\n"
        + "```java\n"
        + "package p.q;\n\n"
        + "public class C extends D {\n"
        + "}\n"
        + "```\n"
        + "```java\n"
        + "package p.q;\n\n"
        + "public class D {\n"
        + "  public interface I {\n"
        + "  }\n"
        + "}\n"
        + "```\n",
    category = GUAVA, severity = ERROR, maturity = EXPERIMENTAL)
public class WildcardImport extends BugChecker implements ClassTreeMatcher {

  private static final String ASTERISK = "*";

  /**
   * An abstraction over the logic for generating fixes. The 'test' version exists because we
   * don't have a good way to test import-only fixes, so it pretty-prints the replacements as a
   * suffix of each on-demand import being deleted.
   */
  // TODO(user): create a way to test import-only fixes.
  private final FixStrategy fixStrategy;

  public WildcardImport() {
    this(FixStrategies.PRODUCTION);
  }

  @VisibleForTesting
  public WildcardImport(FixStrategy fixStrategy) {
    this.fixStrategy = fixStrategy;
  }

  /** A type or member that needs to be imported. */
  @AutoValue
  abstract static class TypeToImport {

    /**
     * Returns the fully-qualified canonical name.
     */
    abstract String typeName();

    /**
     * Returns true if the import needs to be static (i.e. the imported is for a field or method).
     */
    abstract boolean isStatic();

    /**
     * Generates an import statement.
     */
    String generateImportStatement() {
      return "import " + (isStatic() ? "static " : "") + typeName() + ";";
    }

    @Override
    public String toString() {
      return generateImportStatement();
    }

    static TypeToImport create(String type, boolean stat) {
      return new AutoValue_WildcardImport_TypeToImport(type, stat);
    }
  }

  @Override
  public Description matchClass(ClassTree tree, VisitorState state) {
    ClassSymbol sym = ASTHelpers.getSymbol(tree);
    if (sym == null) {
      return Description.NO_MATCH;
    }
    // Only match on top-level classes. The analysis walks the entire class declaration looking
    // for types that need to be imported, so matching on nested classes will produce incomplete
    // results.
    if (sym.getEnclosingElement() != null
        && sym.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
      return Description.NO_MATCH;
    }

    // First, look for on-demand imports. If there aren't any, we're done. This ensures that we
    // only walk the class declaration if there's an error.
    CompilationUnitTree unit = state.getPath().getCompilationUnit();
    ImmutableList<ImportTree> wildcardImports = getWildcardImports(unit.getImports());
    if (wildcardImports.isEmpty()) {
      return Description.NO_MATCH;
    }

    if (unit.getTypeDecls().size() > 1) {
      // There are multiple top-level types, so visiting this class isn't sufficient to find all
      // of the imports to add. Visiting all of the classes is hard (see the docs for
      // CompilationUnitTreeInfo), so we skip the fix.
      return describeMatch(wildcardImports.get(0));
    }

    // Find all of the types that need to be imported.
    Set<TypeToImport> typesToImport = ImportCollector.collect((JCClassDecl) tree);

    // Group the imported types by the on-demand import they replace.
    Multimap<ImportTree, TypeToImport> toFix = groupImports(wildcardImports, typesToImport);

    // Generate fixes.
    fixStrategy.apply(tree, state, wildcardImports, toFix, this);

    // TODO(user): this is kind of a hack. We allow the fix strategy to generate multiple small
    // fixes, so by the time we get here all of the fixes were emitted and there's nothing to do.
    return Description.NO_MATCH;
  }

  /**
   * Creates a multimap from existing on-demand imports to the single-type imports we're going to
   * add, e.g.: java.util.* -> [java.util.List, java.util.ArrayList]
   */
  private Multimap<ImportTree, TypeToImport> groupImports(
{'fraction_non_alphanumeric': 0.1069700892552974, 'fraction_numerical': 0.0024528173332424882, 'mean_word_length': 3.1937142857142855, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 20, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7152268', 'n_tokens_mistral': 4458, 'n_tokens_neox': 4286, 'n_words': 1376}