findMutating(props) {
    /** @type {MemberExpression[]} */
    const pathNodes = []
    /** @type {MemberExpression | Identifier | ChainExpression} */
    let node = props
    let target = node.parent
    while (true) {
      switch (target.type) {
        case 'AssignmentExpression': {
          if (target.left === node) {
            // this.xxx <=|+=|-=>
            return {
              kind: 'assignment',
              node: target,
              pathNodes
            }
          }
          break
        }
        case 'UpdateExpression': {
          // this.xxx <++|-->
          return {
            kind: 'update',
            node: target,
            pathNodes
          }
        }
        case 'UnaryExpression': {
          if (target.operator === 'delete') {
            return {
              kind: 'update',
              node: target,
              pathNodes
            }
          }
          break
        }
        case 'CallExpression': {
          if (pathNodes.length > 0 && target.callee === node) {
            const mem = pathNodes[pathNodes.length - 1]
            const callName = getStaticPropertyName(mem)
            if (
              callName &&
              /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.test(
                callName
              )
            ) {
              // this.xxx.push()
              pathNodes.pop()
              return {
                kind: 'call',
                node: target,
                pathNodes
              }
            }
          }
          break
        }
        case 'MemberExpression': {
          if (target.object === node) {
            pathNodes.push(target)
            node = target
            target = target.parent
            continue // loop
          }
          break
        }
        case 'ChainExpression': {
          node = target
          target = target.parent
          continue // loop
        }
      }

      return null
    }
  }
@param {MemberExpression|Identifier} props
@returns { { kind: 'assignment' | 'update' | 'call' , node: ESNode, pathNodes: MemberExpression[] } | null }