/**
 * @name Misleading indentation
 * @description If a control structure does not use braces, misleading indentation makes it
 *              difficult to see which statements are within its scope.
 * @kind problem
 * @problem.severity warning
 * @precision medium
 * @id cs/misleading-indentation
 * @tags changeability
 *       correctness
 *       logic
 */

import csharp

// Iterate the control flow until we reach a Stmt
Stmt findSuccessorStmt(ControlFlow::Node n)
{
  result=n.getElement() or
  not n.getElement() instanceof Stmt and result=findSuccessorStmt(n.getASuccessor())
}

// Return a successor statement to s
Stmt getASuccessorStmt(Stmt s)
{
  result=findSuccessorStmt(s.getAControlFlowNode().getASuccessor())
}

class IfThenStmt extends IfStmt
{
  IfThenStmt()
  {
    not exists(Stmt s | getElse()=s)
  }
}

class IfThenElseStmt extends IfStmt
{
  IfThenElseStmt()
  {
    exists(Stmt s | getElse()=s)
  }
}

Stmt getTrailingBody(Stmt s)
{
  result=s.(ForStmt).getBody() or
  result=s.(ForeachStmt).getBody() or
  result=s.(WhileStmt).getBody() or
  result=s.(IfThenStmt).getThen() or
  result=s.(IfThenElseStmt).getElse()
}

// Any control statement which has a trailing block
// which could cause indentation confusion
abstract class UnbracedControlStmt extends Stmt
{
  abstract Stmt getBody();
  abstract Stmt getSuccessorStmt();

  private Stmt getACandidate() {
    getSuccessorStmt() = result
    and getBlockStmt(this) = getBlockStmt(result)
  }

  private Location getBodyLocation() {
    result = getBody().getLocation()
  }

  pragma[noopt]
  Stmt getAConfusingTrailingStmt() {
    result = getACandidate()
    and
    exists(Location l1, Location l2 |
      l1 = getBodyLocation() and l2 = result.getLocation() |
      // This test is slightly unreliable
      // because tabs are counted as 1 column.
      // But it's accurate enough to be useful, and will
      // work for consistently formatted text.
      l1.getStartColumn() = l2.getStartColumn() or
      l1.getStartLine() = l2.getStartLine()
    )
  }
}

class UnbracedIfStmt extends UnbracedControlStmt
{
  UnbracedIfStmt()
  {
    this instanceof IfStmt and
    not getTrailingBody(this) instanceof BlockStmt and
    not getTrailingBody(this) instanceof IfStmt
  }

  override Stmt getBody()
  {
    result=getTrailingBody(this)
  }

  override Stmt getSuccessorStmt()
  {
    result = getASuccessorStmt(getBody()) and
    result!=this
  }
}

class UnbracedLoopStmt extends UnbracedControlStmt
{
  UnbracedLoopStmt()
  {
    this instanceof LoopStmt and
    not this instanceof DoStmt and
    not getTrailingBody(this) instanceof BlockStmt
  }

  override Stmt getBody()
  {
    result=getTrailingBody(this)
  }

  override Stmt getSuccessorStmt()
  {
    result = getASuccessorStmt(this) and
    result != getBody()
  }
}

BlockStmt getBlockStmt(Element e)
{
  result = e.getParent() or
  result = getBlockStmt(e.(IfStmt).getParent())  // Handle chained ifs
}

from UnbracedControlStmt s, Stmt n
where n = s.getAConfusingTrailingStmt()
select s, "Missing braces? Inspect indentation of $@.", n, "the control flow successor"
