function* extractClassNames(node, textOnly) {
  if (node.type === 'Literal') {
    yield* `${node.value}`
      .split(/\s+/)
      .map((className) => ({ className, reportNode: node }))
    return
  }
  if (node.type === 'TemplateLiteral') {
    for (const templateElement of node.quasis) {
      yield* templateElement.value.cooked
        .split(/\s+/)
        .map((className) => ({ className, reportNode: templateElement }))
    }
    for (const expr of node.expressions) {
      yield* extractClassNames(expr, true)
    }
    return
  }
  if (node.type === 'BinaryExpression') {
    if (node.operator !== '+') {
      return
    }
    yield* extractClassNames(node.left, true)
    yield* extractClassNames(node.right, true)
    return
  }
  if (textOnly) {
    return
  }
  if (node.type === 'ObjectExpression') {
    for (const prop of node.properties) {
      if (prop.type !== 'Property') {
        continue
      }
      const classNames = utils.getStaticPropertyName(prop)
      if (!classNames) {
        continue
      }
      yield* classNames
        .split(/\s+/)
        .map((className) => ({ className, reportNode: prop.key }))
    }
    return
  }
  if (node.type === 'ArrayExpression') {
    for (const element of node.elements) {
      if (element == null) {
        continue
      }
      if (element.type === 'SpreadElement') {
        continue
      }
      yield* extractClassNames(element)
    }
    return
  }
}
@param {Expression} node
@param {boolean} [textOnly]
@returns {IterableIterator<{ className:string, reportNode: ESNode }>}