( target: any, originalText: string, newText: string, source?: string, cursorOffset?: number, textAnchor?: TextEditAnchor, )
| 566 | * 3. Expression match: text is in a JSXExpressionContainer StringLiteral |
| 567 | */ |
| 568 | export function mutateTextContent( |
| 569 | target: any, |
| 570 | originalText: string, |
| 571 | newText: string, |
| 572 | source?: string, |
| 573 | cursorOffset?: number, |
| 574 | textAnchor?: TextEditAnchor, |
| 575 | ): boolean { |
| 576 | const children = target.node.children; |
| 577 | const tag = target.node.openingElement?.name?.name || target.node.openingElement?.name?.property?.name || "?"; |
| 578 | const line = target.node.openingElement?.loc?.start?.line; |
| 579 | const finish = (): true => { |
| 580 | canonicalizeEditedTextSubtree(target.node, source); |
| 581 | return true; |
| 582 | }; |
| 583 | logger.debug(`[mutateText] target=<${tag}> line=${line} children=${children?.length ?? "null"} original="${originalText.slice(0,60)}" new="${newText.slice(0,60)}"`); |
| 584 | if (!children) return false; |
| 585 | |
| 586 | // Diagnostic: dump children types and values to see what we're working with |
| 587 | for (let i = 0; i < children.length; i++) { |
| 588 | const c = children[i]; |
| 589 | if (c.type === "JSXText") { |
| 590 | logger.debug(`[mutateText] child[${i}] JSXText: "${c.value.slice(0, 80).replace(/\n/g, "\\n")}"`); |
| 591 | } else if (c.type === "JSXElement") { |
| 592 | const childTag = c.openingElement?.name?.name || c.openingElement?.name?.property?.name || "?"; |
| 593 | logger.debug(`[mutateText] child[${i}] JSXElement: <${childTag}>`); |
| 594 | } else if (c.type === "JSXExpressionContainer") { |
| 595 | logger.debug(`[mutateText] child[${i}] JSXExpr: ${c.expression?.type} = "${String(c.expression?.value ?? c.expression?.type).slice(0, 60)}"`); |
| 596 | } else { |
| 597 | logger.debug(`[mutateText] child[${i}] ${c.type}`); |
| 598 | } |
| 599 | } |
| 600 | |
| 601 | // Case 1: Exact match against a single JSXText child |
| 602 | // Use normalizeWs because React collapses JSX whitespace (newlines → spaces) |
| 603 | // but the AST retains raw whitespace — DOM textContent won't match without this. |
| 604 | for (let i = 0; i < children.length; i++) { |
| 605 | const child = children[i]; |
| 606 | if (child.type === "JSXText") { |
| 607 | const trimmed = child.value.trim(); |
| 608 | if (normalizeWs(trimmed) === normalizeWs(originalText.trim())) { |
| 609 | const idx = child.value.indexOf(trimmed); |
| 610 | const prefixWs = child.value.slice(0, idx); |
| 611 | const suffixWs = child.value.slice(idx + trimmed.length); |
| 612 | child.value = prefixWs + newText + suffixWs; |
| 613 | return finish(); |
| 614 | } |
| 615 | } |
| 616 | if ( |
| 617 | child.type === "JSXExpressionContainer" && |
| 618 | child.expression.type === "StringLiteral" && |
| 619 | child.expression.value === originalText |
| 620 | ) { |
| 621 | child.expression.value = newText; |
| 622 | return finish(); |
| 623 | } |
| 624 | } |
| 625 |
no test coverage detected