|
| 1 | +/** |
| 2 | + * Finds method parameters where always the same field with the same name as the parameter is used |
| 3 | + * as argument for that parameter. |
| 4 | + * It could improve readability to remove the parameter and directly access the field in the |
| 5 | + * method. |
| 6 | + * |
| 7 | + * For example: |
| 8 | + * ```java |
| 9 | + * class MyCollection<E> { |
| 10 | + * int size; |
| 11 | + * |
| 12 | + * public E removeLast() { |
| 13 | + * // Note: Passes the field to the instance method |
| 14 | + * if (isEmpty(size)) { |
| 15 | + * ... |
| 16 | + * } |
| 17 | + * ... |
| 18 | + * } |
| 19 | + * |
| 20 | + * // Note: Uses the parameter instead of directly the field |
| 21 | + * private boolean isEmpty(size) { |
| 22 | + * return size == 0; |
| 23 | + * } |
| 24 | + * } |
| 25 | + * ``` |
| 26 | + * |
| 27 | + * Could be written as: |
| 28 | + * ```java |
| 29 | + * ... |
| 30 | + * public E popLast() { |
| 31 | + * if (isEmpty()) { |
| 32 | + * ... |
| 33 | + * } |
| 34 | + * ... |
| 35 | + * } |
| 36 | + * |
| 37 | + * private boolean isEmpty() { |
| 38 | + * return size == 0; |
| 39 | + * } |
| 40 | + * ... |
| 41 | + * ``` |
| 42 | + * |
| 43 | + * Note that if the field is reassigned while the method is running (possibly concurrently, or directly |
| 44 | + * or indirectly by the method), then switching to direct field access within the method could lead to |
| 45 | + * an undesired behavior change. |
| 46 | + * |
| 47 | + * @kind problem |
| 48 | + * @id TODO |
| 49 | + */ |
| 50 | + |
| 51 | +import java |
| 52 | + |
| 53 | +from Method m, Parameter p, int pIndex, Field f |
| 54 | +where |
| 55 | + p = m.getParameter(pIndex) and |
| 56 | + // Only consider if param and field have same name; using same field regardless of name |
| 57 | + // could be coincidence and maybe future code changes will use other args |
| 58 | + f.getName() = p.getName() and |
| 59 | + // Field must be accessible by method, otherwise cannot refactor it to directly access field |
| 60 | + // (e.g. if method is only called by subclasses and field is declared in subclass, |
| 61 | + // cannot refactor method) |
| 62 | + // Note: Don't have to explicitly check field or method visibility; given that there are calls |
| 63 | + // to the method with the field as argument it is most likely also accessible directly by method |
| 64 | + f.getDeclaringType() = m.getDeclaringType().getASourceSupertype*() and |
| 65 | + // Ignore if parameter is reassigned, switching to direct field access would be behavior change |
| 66 | + not p.getAnAccess().isVarWrite() and |
| 67 | + forex(MethodCall call | call.getMethod().getSourceDeclaration() = m | |
| 68 | + ( |
| 69 | + // Either not in varargs position |
| 70 | + pIndex < m.getNumberOfParameters() - 1 |
| 71 | + or |
| 72 | + // Or there is only a single argument for varargs (or parameter is not varargs) |
| 73 | + call.getNumArgument() = m.getNumberOfParameters() |
| 74 | + ) and |
| 75 | + exists(FieldRead fRead | fRead = call.getArgument(pIndex) and fRead.getField() = f | |
| 76 | + f.isStatic() |
| 77 | + or |
| 78 | + // Note: Could also cover "instance field for static method", in which case the method |
| 79 | + // could be turned into an instance method, but maybe that should be a separate query? |
| 80 | + fRead.isOwnFieldAccess() and call.isOwnMethodCall() |
| 81 | + ) |
| 82 | + ) and |
| 83 | + // Ignore cases where omitting the parameters from the method signature is not possible |
| 84 | + not m.isAbstract() and |
| 85 | + not m.overrides(_) and |
| 86 | + // And is not overridden |
| 87 | + not exists(Method override | override.getAnOverride() = m) |
| 88 | +select p, "Can omit method parameter and directly access $@ in method", f, "field of same name" |
0 commit comments