/*
 * Copyright 2020 The Error Prone Authors.
 *
 * 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 com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link IgnoredPureGetter}. */
@RunWith(JUnit4.class)
public final class IgnoredPureGetterTest {
  private final CompilationTestHelper helper =
      CompilationTestHelper.newInstance(IgnoredPureGetter.class, getClass());
  private final BugCheckerRefactoringTestHelper refactoringHelper =
      BugCheckerRefactoringTestHelper.newInstance(IgnoredPureGetter.class, getClass());

  @Test
  public void autoValue() {
    helper
        .addSourceLines(
            "A.java", //
            "import com.google.auto.value.AutoValue;",
            "@AutoValue",
            "abstract class A {",
            "  abstract int foo();",
            "}")
        .addSourceLines(
            "B.java", //
            "class B {",
            "  void test(A a) {",
            "    // BUG: Diagnostic contains:",
            "    a.foo();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void autoValueBuilder() {
    helper
        .addSourceLines(
            "Animal.java",
            "import com.google.auto.value.AutoValue;",
            "@AutoValue",
            "abstract class Animal {",
            "  abstract String name();",
            "  @AutoValue.Builder",
            "  abstract static class Builder {",
            "    abstract Builder setName(String name);",
            "    abstract Animal build();",
            "  }",
            "}")
        .addSourceLines(
            "B.java",
            "class B {",
            "  void test(Animal.Builder builder) {",
            // The setters are OK
            "    builder.setName(\"dog\");",
            "    // BUG: Diagnostic contains:",
            "    builder.build();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void autoValueStepBuilder() {
    helper
        .addSourceLines(
            "Animal.java",
            "import com.google.auto.value.AutoValue;",
            "@AutoValue",
            "public abstract class Animal {",
            "  public abstract String name();",
            "  public abstract int legs();",
            "  public interface NameStep { LegStep setName(String name); }",
            "  public interface LegStep { Build setLegs(int legs); }",
            "  public interface Build { Animal build(); }",
            "  @AutoValue.Builder",
            "  abstract static class Builder implements NameStep, LegStep, Build {}",
            "}")
        .addSourceLines(
            "B.java",
            "class B {",
            "  void test(Animal.Builder builder) {",
            // The setters are OK
            "    builder.setName(\"dog\");",
            "    builder.setLegs(4);",
            // We don't currently catch this, but maybe we should?
            "    builder.build();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void autoValue_secondFix() {
    refactoringHelper
        .addInputLines(
            "A.java", //
            "import com.google.auto.value.AutoValue;",
            "@AutoValue",
            "abstract class A {",
            "  abstract int foo();",
            "  static A of(int foo) {",
            "    return null;",
            "  }",
            "}")
        .expectUnchanged()
        .addInputLines(
            "B.java", //
            "class B {",
            "  void test() {",
            "    A.of(1).foo();",
            "  }",
            "}")
        .addOutputLines(
            "B.java", //
            "class B {",
            "  void test() {",
            "    A.of(1);",
            "  }",
            "}")
        .setFixChooser(FixChoosers.SECOND)
        .doTest();
  }

  @Test
  public void autoBuilder_getters() {
    helper
        .addSourceLines(
            "Named.java",
            "import com.google.auto.value.AutoBuilder;",
            "import java.util.Optional;",
            "public class Named {",
            "  Named(String name, String nickname) {}",
            "  @AutoBuilder",
            "  public abstract static class Builder {",
            "    public abstract Builder setName(String x);",
            "    public abstract Builder setNickname(String x);",
            "    abstract String getName();",
            "    abstract Optional<String> getNickname();",
            "    abstract Named autoBuild();",
            "    public Named build() {",
            "      if (!getNickname().isPresent()) {",
            "        setNickname(getName());",
            "      }",
            "      return autoBuild();",
            "    }",
            "  }",
            "}")
        .addSourceLines(
            "B.java",
            "class B {",
            "  void test(Named.Builder builder) {",
            // The setters are OK
            "    builder.setName(\"Stumpy\");",
            "    builder.setNickname(\"Stumps\");",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getName();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getNickname();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.autoBuild();",
            // build() isn't covered since it's non-abstract
            "    builder.build();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void autoBuilder_buildIsntCrv() {
    helper
        .addSourceLines(
            "Named.java",
            "import com.google.auto.value.AutoBuilder;",
            "import java.time.LocalTime;",
            "@AutoBuilder(callMethod = \"of\", ofClass = LocalTime.class)",
            "interface LocalTimeBuilder {",
            "  LocalTimeBuilder setHour(int hour);",
            "  LocalTimeBuilder setMinute(int minute);",
            "  LocalTimeBuilder setSecond(int second);",
            "  LocalTimeBuilder setNanoOfSecond(int nanoOfSecond);",
            "  int getHour();",
            "  int getMinute();",
            "  int getSecond();",
            "  int getNanoOfSecond();",
            "  LocalTime build();", // calls: LocalTime.of(...)
            "}")
        .addSourceLines(
            "B.java",
            "class B {",
            "  void test(LocalTimeBuilder builder) {",
            // the setters are treated as CIRV:
            "    builder.setHour(12);",
            "    builder.setMinute(12);",
            "    builder.setSecond(12);",
            // the getters are treated as CRV:
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getHour();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getMinute();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getSecond();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.getNanoOfSecond();",
            // TODO(b/229107551): this shouldn't be treated as CRV
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    builder.build();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void refactoringHelper() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            "import com.google.errorprone.bugpatterns.proto.ProtoTest.TestProtoMessage;",
            "class Test {",
            "  void test(TestProtoMessage message) {",
            "    message.getMessage();",
            "    message.hasMessage();",
            "  }",
            "}")
        .addOutputLines(
            "Test.java",
            "import com.google.errorprone.bugpatterns.proto.ProtoTest.TestProtoMessage;",
            "class Test {",
            "  void test(TestProtoMessage message) {",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void protoInstanceMethodsFlagged() {
    helper
        .addSourceLines(
            "Test.java",
            "import com.google.errorprone.bugpatterns.proto.ProtoTest.TestProtoMessage;",
            "class Test {",
            "  void test(TestProtoMessage message) {",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getMultiField(1);",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getWeightMap();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getWeightOrDefault(1, 42);",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getWeightOrThrow(1);",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.containsWeight(1);",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getWeightCount();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.getMessage();",
            "    // BUG: Diagnostic contains: IgnoredPureGetter",
            "    message.hasMessage();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void protoReturnValueIgnored_noFinding() {
    helper
        .addSourceLines(
            "Test.java",
            "import com.google.errorprone.bugpatterns.proto.ProtoTest.TestProtoMessage;",
            "class Test {",
            "  void test(TestProtoMessage message) {",
            "    Object o = message.getMessage();",
            "    boolean b = message.hasMessage();",
            "  }",
            "}")
        .doTest();
  }
}
