/**
 * @name Comparison of identical values
 * @description If the same expression occurs on both sides of a comparison
 *              operator, the operator is redundant, and probably indicates a mistake.
 * @kind problem
 * @problem.severity warning
 * @id js/comparison-of-identical-expressions
 * @tags reliability
 *       correctness
 *       readability
 *       convention
 *       external/cwe/cwe-570
 *       external/cwe/cwe-571
 * @precision medium
 */

import Clones

/**
 * Holds if `e` is a reference to variable `v`, possibly with parentheses or
 * numeric conversions (that is, the unary operators `+` or `-` or a call to `Number`)
 * applied.
 */
predicate accessWithConversions(Expr e, Variable v) {
  e = v.getAnAccess()
  or
  accessWithConversions(e.(ParExpr).getExpression(), v)
  or
  exists (UnaryExpr ue | ue instanceof NegExpr or ue instanceof PlusExpr |
    ue = e and accessWithConversions(ue.getOperand(), v)
  ) or
  exists (CallExpr ce | ce = e |
    ce = DataFlow::globalVarRef("Number").getACall().asExpr() and
    ce.getNumArgument() = 1 and
    accessWithConversions(ce.getArgument(0), v)
  )
}

/**
 * A comment containing the word "NaN".
 */
predicate isNaNComment(Comment c, string filePath, int startLine) {
  c.getText().matches("%NaN%") and
  c.getLocation().hasLocationInfo(filePath, startLine, _, _, _)
}

/**
 * Holds if the equality test `eq` looks like a NaN check.
 *
 * In order to qualify as a NaN check, both sides of the equality have
 * to be references to the same variable `x`, possibly with added parentheses
 * or numeric conversions, and one of the following holds:
 *
 *   - `x` is a parameter of the enclosing function, which is called
 *     `isNaN` (modulo capitalization);
 *   - there is a comment next to the comparison (that is, no further than
 *     one line away in either direction) that contains the word `NaN`.
 */
predicate isNaNCheck(EqualityTest eq) {
  exists (Variable v |
    accessWithConversions(eq.getLeftOperand(), v) and
    accessWithConversions(eq.getRightOperand(), v) |

    // `v` is a parameter of the enclosing function, which is called `isNaN`
    exists (Function isNaN |
      isNaN = eq.getEnclosingFunction() and
      isNaN.getName().toLowerCase() = "isnan" and
      v = isNaN.getAParameter().getAVariable()
    ) or

    // there is a comment containing the word "NaN" next to the comparison
    exists (string f, int l |
      eq.getLocation().hasLocationInfo(f, l, _, _, _) and
      isNaNComment(_, f, [l-1..l+1])
    )
  )
}

from Comparison selfComparison, OperandComparedToSelf e
where e = selfComparison.getAnOperand() and e.same(_) and
      not isNaNCheck(selfComparison)
select selfComparison, "This expression compares $@ to itself.", e, e.toString()
