/**
 * @name Missed 'readonly' opportunity
 * @description A private field where all assignments occur as part of the declaration or
 *              in a constructor in the same class can be 'readonly'.
 * @kind problem
 * @problem.severity recommendation
 * @precision high
 * @id cs/missed-readonly-modifier
 * @tags maintainability
 *       language-features
 */

import csharp

predicate isReadonlyCompatibleDefinition(AssignableDefinition def, Field f) {
  def.getTarget().getSourceDeclaration() = f
  and
  (
    def.getEnclosingCallable().(Constructor).getDeclaringType() = f.getDeclaringType()
    or
    def instanceof AssignableDefinitions::InitializerDefinition
  )
}

predicate canBeReadonly(Field f) {
  forex(AssignableDefinition def |
    def.getTarget().getSourceDeclaration() = f |
    isReadonlyCompatibleDefinition(def, f)
  )
}

from Field f
where canBeReadonly(f)
  and not f.isConst()
  and not f.isReadOnly()
  and (f.isEffectivelyPrivate() or f.isEffectivelyInternal())
select f, "Field '" + f.getName() + "' can be 'readonly'."
