TNG / ArchUnit

A Java architecture test library, to specify and assert architecture rules in plain Java
http://archunit.org
Apache License 2.0
3.25k stars 299 forks source link

Studying lambda parameters for a forEach method call #1379

Open lasselindqvist opened 2 weeks ago

lasselindqvist commented 2 weeks ago

I am trying to write a rule such as:

    @ArchTest
    public static final ArchRule ruleSimple = methods().that()
            .areAnnotatedWith(RequestMapping.class)
            .should(new ArchCondition<JavaMethod>("call validator") {
                @Override
                public void check(JavaMethod item, ConditionEvents events) {
                    if (!item.isAnnotatedWith(ValidationNotNeeded.class)) {
                        List<JavaMethodCall> callsToValidator = item.getMethodCallsFromSelf().stream()
                                .filter(call -> call.getTargetOwner().isAssignableFrom(Validator.class))
                                .toList();
                        if (callsToValidator.isEmpty()) {
                            events.add(SimpleConditionEvent.violated(item,"No calls to Validator from " + item.getFullName()));
                        }
                    }
                }
            });

so as to enforce that in certain places all methods must call a method from Validator class. This works fine and also works fine even if the calls are inside for loops. But case of forEach, the calls go to forEach method, not the methods that are called via the lambda passed onto it.

So

List<JavaMethodCall> calls = item.getMethodCallsFromSelf().stream()
.filter(jmc -> jmc.getName().equals("forEach"))
.toList();

find the forEach calls just fine. From these JavaMethodCall objects it is also possible to find the parameters passed on to forEach, but those are Consumers, not the lambdas themselves.

Is it even possible with ArchUnit to study what is passed as the Consumer?

As a bonus, this works differently with javac and Eclipse Compiler. When compiled with javac, the calls are found directly, but with Eclipse Compiler, the forEach calls are found.

Workaround: Simple workaround is to just replace forEach calls with normal loops, but it is not ideal in all cases.