/**
 * @name Local scope variable shadows member
 * @description A local scope variable that shadows a member with the same name can be confusing,
 *              and can result in logic errors.
 * @kind problem
 * @problem.severity recommendation
 * @precision high
 * @id cs/local-shadows-member
 * @tags maintainability
 *       readability
 */
import csharp

pragma [noinline]
private string localVarInType(ValueOrRefType t, LocalScopeVariable v) {
  v.getCallable().getDeclaringType() = t and
  result = v.getName() and
  not v instanceof ImplicitAccessorParameter
}

private string memberInType(ValueOrRefType t, Member m) {
  t.hasMember(m) and
  result = m.getName() and
  not m instanceof Method and
  not m instanceof Constructor and
  not m instanceof NestedType and
  t.isSourceDeclaration()
}

private predicate acceptableShadowing(LocalScopeVariable v, Member m) {
  exists(ValueOrRefType t |
    localVarInType(t, v) = memberInType(t, m) |
    // If the callable declaring the local also accesses the shadowed member
    // using an explicit `this` qualifier, the shadowing is likely deliberate.
    exists(MemberAccess ma |
      ma.getTarget() = m |
      ma.getEnclosingCallable() = v.getCallable() and
      ma.targetIsLocalInstance() and
      not ma.getQualifier().isImplicit()
    )
    or
    t.getAConstructor().getAParameter() = v
  )
}

private predicate shadowing(ValueOrRefType t, LocalScopeVariable v, Member m) {
  localVarInType(t, v) = memberInType(t, m) and
  not acceptableShadowing(v, m)
}

from LocalScopeVariable v, Callable c, ValueOrRefType t, Member m
where c = v.getCallable()
  and shadowing(t, v, m)
  and (c.(Modifiable).isStatic() implies m.isStatic())
select v, "Local scope variable '" + v.getName() + "' shadows $@.", m, t.getName() + "." + m.getName()
