Files
openide/java/java-tests/testSrc/com/siyeh/ig/bugs/IgnoreResultOfCallInspectionTest.java
Bas Leijdekkers ae7c9e0075 Java: teach inspection about existence of Jetbrains and Spring CheckReturnValue annotations (IDEA-374480)
for "Result of method call ignored" inspection

GitOrigin-RevId: e3face53d7745d416fba8a22f2e02a1954f71442
2025-06-16 13:05:53 +00:00

806 lines
25 KiB
Java

// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.bugs;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.JavaCodeFragmentFactory;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("ALL")
public class IgnoreResultOfCallInspectionTest extends LightJavaInspectionTestCase {
@Override
protected LocalInspectionTool getInspection() {
return new IgnoreResultOfCallInspection();
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_15;
}
@Override
protected String[] getEnvironmentClasses() {
return new String[]{
"""
package java.util.regex;
public class Pattern {
public static Pattern compile(String regex) {return null;}
public Matcher matcher(CharSequence input) {return null;}
}
""",
"""
package java.util.regex;
public class Matcher {
public boolean find() {return true;}
}
""",
"""
package javax.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.annotation.meta.When;
@Documented
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PACKAGE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckReturnValue {
When when() default When.ALWAYS;
}
""",
"""
package com.google.errorprone.annotations;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Documented
@Target({METHOD,CONSTRUCTOR,TYPE,PACKAGE})
@Retention(value=RUNTIME)
public @interface CheckReturnValue {}
""",
"""
package org.jetbrains.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PACKAGE})
public @interface CheckReturnValue {}
""",
"""
package org.springframework.lang;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PACKAGE})
public @interface CheckReturnValue {}
""",
"""
package org.assertj.core.util;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target({CONSTRUCTOR,METHOD,PACKAGE,TYPE})
@Retention(value=CLASS)
public @interface CheckReturnValue {}
""",
"""
package a;
public @interface CheckReturnValue {}
""",
"""
package com.google.errorprone.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface CanIgnoreReturnValue {}
""",
"""
package org.assertj.core.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({CONSTRUCTOR,METHOD,PACKAGE,TYPE})
@Retention(value=CLASS)
public @interface CanIgnoreReturnValue{}
""",
"""
package org.apache.commons.lang3;
public class Validate {
public native static <T> T notNull(T object);
}""",
"""
package org.junit.jupiter.api.function;
public interface Executable {
void execute() throws Throwable;
}""",
"""
package org.junit.jupiter.api;
import org.junit.jupiter.api.function.Executable;
import java.util.function.Supplier;
public class Assertions {
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
return null;
}
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, Supplier<String> messageSupplier) {
return null;
}
}""",
"""
package org.assertj.core.api.ThrowableAssert;
public interface ThrowingCallable {
void call() throws Throwable;
}""",
"""
package org.assertj.core.api;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
public class Assertions {
public static Object assertThatThrownBy(ThrowingCallable shouldRaiseThrowable) {
return null;
}
}""",
"""
package org.mockito;
public interface MockedStatic<T> {
Object when(Verification v);
interface Verification {
void apply();
}
}""",
"""
package java.nio;
public abstract class ByteBuffer{
abstract boolean hasRemaining();
}
""",
"""
package java.nio.channels;
import java.nio.ByteBuffer;
import java.io.IOException;
public abstract class FileChannel{
public abstract int write(ByteBuffer src) throws IOException;
}
"""};
}
public void testJetbrainsAnnotationOnClass() {
doTest("""
import org.jetbrains.annotations.CheckReturnValue;
@CheckReturnValue
class Test {
String foo() {
return "foo";
}
public static void main(String[] args){
new Test()./*Result of 'Test.foo()' is ignored*/foo/**/();
}
}
""");
}
public void testSpringAnnotationOnClass() {
doTest("""
import org.springframework.lang.CheckReturnValue;
@CheckReturnValue
class Test {
String foo() {
return "foo";
}
public static void main(String[] args){
new Test()./*Result of 'Test.foo()' is ignored*/foo/**/();
}
}
""");
}
public void testCanIgnoreReturnValue() {
doTest("""
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import javax.annotation.CheckReturnValue;
@CheckReturnValue
class Test {
int lookAtMe() { return 1; }
@CanIgnoreReturnValue
int ignoreMe() { return 2; }
void run() {
/*Result of 'Test.lookAtMe()' is ignored*/lookAtMe/**/(); // Bad! This line should produce a warning.
ignoreMe(); // OK. This line should *not* produce a warning.
}
}""");
}
public void testCanIgnoreReturnValue2() {
doTest(
"""
class TestClass {
public void m() {
var javax = new Javax();
javax./*Result of 'Javax.unannotated()' is ignored*/unannotated/**/();
javax.assertJ();
javax.errorProne();
var errorProne = new ErrorProne();
errorProne./*Result of 'ErrorProne.unannotated()' is ignored*/unannotated/**/();
errorProne.assertJ();
errorProne.errorProne();
var assertJ = new AssertJ();
assertJ./*Result of 'AssertJ.unannotated()' is ignored*/unannotated/**/();
assertJ.assertJ();
assertJ.errorProne();
}
@javax.annotation.CheckReturnValue
public static class Javax {
int unannotated() {
return 3;
}
@org.assertj.core.util.CanIgnoreReturnValue
int assertJ() {
return 3;
}
@com.google.errorprone.annotations.CanIgnoreReturnValue
int errorProne() {
return 3;
}
}
@com.google.errorprone.annotations.CheckReturnValue
public static class ErrorProne {
int unannotated() {
return 3;
}
@org.assertj.core.util.CanIgnoreReturnValue
int assertJ() {
return 3;
}
@com.google.errorprone.annotations.CanIgnoreReturnValue
int errorProne() {
return 3;
}
}
@org.assertj.core.util.CheckReturnValue
public static class AssertJ {
int unannotated() {
return 3;
}
@org.assertj.core.util.CanIgnoreReturnValue
int assertJ() {
return 3;
}
@com.google.errorprone.annotations.CanIgnoreReturnValue
int errorProne() {
return 3;
}
}
}
""");
}
public void testCanIgnoreReturnValue3() {
doTest(
"""
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
@CheckReturnValue
class Test {
static int lookAtMe() { return 1; }
@CanIgnoreReturnValue
static int ignoreMe() { return 2; }
void run() {
/*Result of 'Test.lookAtMe()' is ignored*/lookAtMe/**/(); // <- inspection\s
ignoreMe(); // <- also inspection
}
}
""");
}
public void testCustomCheckReturnValue() {
doTest("""
import a.CheckReturnValue;
class Test {
@CheckReturnValue
int lookAtMe() { return 1; }
void run() {
/*Result of 'Test.lookAtMe()' is ignored*/lookAtMe/**/();
}
}""");
}
public void testObjectMethods() {
doTest("""
class C {
void foo(Object o, String s) {
o./*Result of 'Object.equals()' is ignored*/equals/**/(s);
}
}
""");
}
public void testMatcher() {
doTest("""
class C {
void matcher() {
final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("baaaa");
final java.util.regex.Matcher matcher = pattern.matcher("babaaaaaaaa");
matcher./*Result of 'Matcher.find()' is ignored*/find/**/();
matcher.notifyAll();
}
}
""");
}
public void testReader() {
doTest("import java.io.Reader;" +
"import java.io.IOException;" +
"class U {" +
" void m(Reader r) throws IOException {" +
" r./*Result of 'Reader.read()' is ignored*/read/**/();" +
" }" +
"}");
}
public void testJSR305Annotation() {
doTest("import javax.annotation.CheckReturnValue;" +
"class A {" +
" @CheckReturnValue" +
" static Object a() {" +
" return null;" +
" }" +
" void b() {" +
" /*Result of 'A.a()' is ignored*/a/**/();" +
" }" +
"}");
}
public void testRandomGetter() {
doTest("class A {" +
" private String name;" +
" public String getName() {" +
" return name;" +
" }" +
" void m() {" +
" /*Result of 'A.getName()' is ignored*/getName/**/();" +
" }" +
"}");
}
public void testJSR305Annotation2() {
doTest("import javax.annotation.CheckReturnValue;" +
"@CheckReturnValue " +
"class A {" +
" static Object a() {" +
" return null;" +
" }" +
" void b() {" +
" /*Result of 'A.a()' is ignored*/a/**/();" +
" }" +
"}");
}
public void testJSR305Annotation3() {
doTest("import javax.annotation.CheckReturnValue;" +
"@CheckReturnValue " +
"class Parent {" +
" class A {" +
" Object a() {" +
" return null;" +
" }" +
" void b() {" +
" /*Result of 'A.a()' is ignored*/a/**/();" +
" }" +
" }" +
"}");
}
public void testInference() {
Registry.get("ide.ignore.call.result.inspection.honor.inferred.pure").setValue(true, getTestRootDisposable());
doTest(
"""
class Test {
private static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
\s
private static String twice(String s) {
return s+s;
}
\s
void test(String string) {
checkNotNull(string);
/*Result of 'Test.twice()' is ignored*/twice/**/("foo");
/*Result of 'Test.twice()' is ignored*/twice/**/("bar");
}
}
""");
}
public void testPureMethod() {
doTest(
"""
import org.jetbrains.annotations.Contract;
class Util {
@Contract(pure=true)
static Object util() { return null; }
}
class C {
static {
Util./*Result of 'Util.util()' is ignored*/util/**/();
}
}
""");
}
public void testPureMethodReturningThis() {
doTest(
"""
import org.jetbrains.annotations.Contract;
class Test {
boolean closed;
\s
@Contract(pure=true, value="->this")
Test validate() {
if(closed) throw new IllegalStateException();
return this;
}
\s
void test() {
validate();
System.out.println("ok");
}
}
""");
}
public void testPureMethodInVoidFunctionalExpression() {
doTest(
"""
import org.jetbrains.annotations.Contract;
class Util {
@Contract(pure=true)
static Object util() { return null; }
}
class C {
static {
Runnable r = () -> Util./*Result of 'Util.util()' is ignored*/util/**/();
Runnable r1 = Util::/*Result of 'Util.util()' is ignored*/util/**/;
Runnable r2 = Object::new; // Reported by ResultOfObjectAllocationIgnoredInspection
}
}
""");
}
public void testStream() {
doTest(
"""
import java.util.stream.*;
import java.util.*;
class Test {
void test() {
Stream.of("a", "b", "c")./*Result of 'Stream.collect()' is ignored*/collect/**/(Collectors.toSet());
Stream.of("a", "b", "c")./*Result of 'Stream.collect()' is ignored*/collect/**/(Collectors.toCollection(() -> new ArrayList<>()));
Set<String> result = new TreeSet<>();
result.add("-");
// violates stream principles, but quite widely used (IDEA-164501);
// this inspection should not warn here; probably some other inspection should suggest better option\s
Stream.of("a", "b", "c").collect(Collectors.toCollection(() -> result));
}
}""");
}
public void testPattern() {
doTest(
"""
import java.util.regex.Pattern;
import java.util.Set;
class Test {
void test(Set<String> names) {
names.forEach(Pattern::/*Result of 'Pattern.compile()' is ignored*/compile/**/);
}
}
""");
}
public void testPatternCaught() {
doTest(
"""
import java.util.regex.*;
import java.util.Set;
class Test {
void test(Set<String> names) {
try {
names.forEach(Pattern::compile);
}
catch (PatternSyntaxException e) {
throw new RuntimeException("Pattern error", e);
}
}
}
""");
}
public void testOptionalOrElseThrow() {
//noinspection OptionalUsedAsFieldOrParameterType
doTest(
"""
import java.util.Optional;
class Test {
void test(Optional<String> opt) {
opt.orElseThrow(RuntimeException::new);
}
}
""");
}
public void testParamContract() {
doTest(
"""
class X{
public static int atLeast(int min, int actual, String varName) {
if (actual < min) throw new IllegalArgumentException('\\'' + varName + " must be at least " + min + ": " + actual);
return actual;
}
public byte[] getMemory(int address, int length) {
atLeast(0, address, "address");
atLeast(1, length, "length");
return new byte[length];
}
}
""");
}
public void testInForExpressionList() {
//noinspection StatementWithEmptyBody
doTest(
"""
class X {
void test(String s) {
for(int i=0; i<10; i++, s./*Result of 'String.trim()' is ignored*/trim/**/()) {}
}
}
""");
}
public void testInSwitchExpression() {
//noinspection SwitchStatementWithTooFewBranches
doTest("""
class X {
String test(String s) {
return switch(s) {
default -> s.trim();
};
}
}
""");
}
public void testOptionalGet() {
//noinspection ALL
doTest(
"""
class X {
void test(java.util.Optional<String> opt) {
opt.get();
if (opt.isPresent()) opt./*Result of 'Optional.get()' is ignored*/get/**/();
}
}""");
}
public void testCommonsLang3NotNull() {
doTest(
"""
import org.apache.commons.lang3.Validate;
class X{
void test(String foo) {
if (foo == null) return;
Validate.notNull(foo);
}
}
""");
}
public void testVoidType() {
doTest("""
class X {
void a() {
b();
}
\s
static Void b() {
return null;
}
}
""");
}
public void testIgnoreMethodDefinedInSubclasses() {
doTest(
"""
import java.util.stream.Stream;
import java.util.*;
abstract class StreamEx<T> implements Stream<T> {
public <C extends Collection<? super T>> C into(C collection) {
return null;
}
\s
public static<T> StreamEx<T> of(T... values) {
return null;\s
}
\s
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
StreamEx.of(1, 2, 3).into(list);
}
}
""");
}
public void testStringBuilderMethods() {
//noinspection DataFlowIssue,EqualsOnSuspiciousObject,TooBroadScope
doTest(
"""
class X {
void x() {
String abc = "abc";
StringBuilder sb = new StringBuilder(abc);
abc./*Result of 'String.substring()' is ignored*/substring/**/(0, 1); // OK, warning: Result of `String.substring()` is ignored
abc./*Result of 'String.compareTo()' is ignored*/compareTo/**/(null);
sb./*Result of 'AbstractStringBuilder.substring()' is ignored*/substring/**/(0, 1); // KO, no warning
sb./*Result of 'Object.equals()' is ignored*/equals/**/(null);
sb./*Result of 'AbstractStringBuilder.capacity()' is ignored*/capacity/**/();
sb./*Result of 'AbstractStringBuilder.chars()' is ignored*/chars/**/();
sb./*Result of 'AbstractStringBuilder.codePointAt()' is ignored*/codePointAt/**/(1);
sb./*Result of 'AbstractStringBuilder.codePointBefore()' is ignored*/codePointBefore/**/(1);
sb./*Result of 'AbstractStringBuilder.codePointCount()' is ignored*/codePointCount/**/(0, 1);
sb./*Result of 'AbstractStringBuilder.codePoints()' is ignored*/codePoints/**/();
sb./*Result of 'StringBuilder.compareTo()' is ignored*/compareTo/**/(null);
sb./*Result of 'AbstractStringBuilder.offsetByCodePoints()' is ignored*/offsetByCodePoints/**/(0, 1);
System.out.println(sb);
}
}
""");
}
public void testParseShort() {
doTest(
"""
class X {
boolean validate(String s) {
try {
Short.parseShort(s);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
}
""");
}
@SuppressWarnings("Convert2Lambda")
public void testIgnoreInTestContainers() {
doTest(
"""
import org.junit.jupiter.api.function.Executable;
import org.mockito.MockedStatic;
class X {
public static String name(){return "name";}
public void test(String s, MockedStatic<Object> mockedStatic){
mockedStatic.when(X::name);
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, ()->Short.parseShort(s));
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, ()->Short.parseShort(s),
() -> {
Short./*Result of 'Short.parseShort()' is ignored*/parseShort/**/(s);
return "test";
});
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, ()-> {
Short.parseShort("s");
});
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, ()-> {
twice("abc");
});
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, (new Executable() {
@Override
public void execute() {
twice("abc");
}}));
org.assertj.core.api.Assertions.assertThatThrownBy((((()->Short.parseShort(s)))));
}
private static String twice(String s) {
return s+s;
}
}
""");
}
public void testArgumentSideEffects() {
doTest("""
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.io.IOException;
class X {
public static void withoutSideEffects(FileChannel fch, ByteBuffer[] srcs) throws IOException {
fch./*Result of 'FileChannel.write()' is ignored*/write/**/(srcs);
}
public static long useResult(FileChannel fch, ByteBuffer[] srcs) throws IOException {
return fch.write(srcs);
}
public static void hasSideEffect(FileChannel fch, ByteBuffer[] srcs) throws IOException {
do{
fch.write(srcs);
} while (test(srcs));
}
public static boolean test(ByteBuffer[] srcs){
return srcs[0].hasRemaining();
}
}
""");
}
public void testFragment() {
PsiFile file = JavaCodeFragmentFactory.getInstance(getProject()).createCodeBlockCodeFragment("String.format(\"hello\");", null, true);
myFixture.configureFromExistingVirtualFile(file.getVirtualFile());
myFixture.testHighlighting(true, false, false);
}
}