/**
 * @name Replacement of a substring with itself
 * @description Replacing a substring with itself has no effect and may indicate a mistake.
 * @kind problem
 * @problem.severity warning
 * @id js/identity-replacement
 * @precision very-high
 * @tags correctness
 *       security
 *       external/cwe/cwe-116
 */

import javascript

/**
 * Holds if `e`, when used as the first argument of `String.prototype.replace`, matches
 * `s` and nothing else.
 */
predicate matchesString(Expr e, string s) {
  exists(RegExpLiteral rl |
    rl.flow().(DataFlow::SourceNode).flowsToExpr(e) and
    not rl.isIgnoreCase() and
    regExpMatchesString(rl.getRoot(), s)
  )
  or
  s = e.getStringValue()
}

/**
 * Holds if `t` matches `s` and nothing else.
 */
language[monotonicAggregates]
predicate regExpMatchesString(RegExpTerm t, string s) {
  t.isPartOfRegExpLiteral() and
  (
    // constants match themselves
    s = t.(RegExpConstant).getValue()
    or
    // assertions match the empty string
    (
      t instanceof RegExpCaret or
      t instanceof RegExpDollar or
      t instanceof RegExpWordBoundary or
      t instanceof RegExpNonWordBoundary or
      t instanceof RegExpLookahead or
      t instanceof RegExpLookbehind
    ) and
    s = ""
    or
    // groups match their content
    regExpMatchesString(t.(RegExpGroup).getAChild(), s)
    or
    // single-character classes match that character
    exists(RegExpCharacterClass recc | recc = t and not recc.isInverted() |
      recc.getNumChild() = 1 and
      regExpMatchesString(recc.getChild(0), s)
    )
    or
    // sequences match the concatenation of their elements
    exists(RegExpSequence seq | seq = t |
      s = concat(int i, RegExpTerm child |
          child = seq.getChild(i)
        |
          any(string subs | regExpMatchesString(child, subs)) order by i
        )
    )
  )
}

from MethodCallExpr repl, string s, string friendly
where
  repl.getMethodName() = "replace" and
  matchesString(repl.getArgument(0), s) and
  repl.getArgument(1).getStringValue() = s and
  (if s = "" then friendly = "the empty string" else friendly = "'" + s + "'")
select repl.getArgument(0), "This replaces " + friendly + " with itself."
