Repository: prettier/prettier (JavaScript)
Date: 2023-01-12 10:15:59+00:00
Task type: Bug Fix

Issue:
Wrong cursorOffset with formatted JSX text
In a similar vein to #12491 (which I am still intending to fix), I found a misplaced cursorOffset issue with code like this

```jsx
<>a•
  <div>hi</div>
</>
```
(where `•` denotes the given cursor position)

When passed into prettier we get:
```jsx
<>•
  a<div>hi</div>
</>
```

Which is unexpected, because the a correctly moves down, but the cursor stays up.

Here is code for testing it:
```js
const prettier = require("prettier");
prettier.formatWithCursor(["<>a", "  <div>hi</div>", "</>"].join("\n"), { cursorOffset: 3, parser: 'babel' });
```
and the runkit to see it in the browser:
https://runkit.com/gregoor/prettier-jsx-cursor-offset-bug


Issue discussion:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f3b3a55ccd0..1eb365cf9890 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -663,7 +663,7 @@ Error: Comment "comment" was not printed. Please report this error!
 <Same as input>
 ```
 
-#### Fix formatting for comments inside JSX attribute ([#14082](https://github.com/prettier/prettier/pull/14082) with by [@fisker](https://github.com/fisker))
+#### Fix formatting for comments inside JSX attribute ([#14082](https://github.com/prettier/prettier/pull/14082) by [@fisker](https://github.com/fisker))
 
 <!-- prettier-ignore -->
 ```jsx
diff --git a/changelog_unreleased/javascript/14163.md b/changelog_unreleased/javascript/14163.md
new file mode 100644
index 000000000000..8214e8856f66
--- /dev/null
+++ b/changelog_unreleased/javascript/14163.md
@@ -0,0 +1,18 @@
+#### Fix cursor tracking inside JSX Text (#14163 by @fisker)
+
+<!-- prettier-ignore -->
+```js
+// Prettier stable
+formatWithCursor(
+  ["<>a", "  <div>hi</div>", "</>"].join("\n"),
+  { cursorOffset: 3, parser: "babel" }
+).cursorOffset;
+// -> 2
+
+// Prettier main
+(await formatWithCursor(
+  ["<>a", "  <div>hi</div>", "</>"].join("\n"),
+  { cursorOffset: 3, parser: "babel" }
+)).cursorOffset;
+// -> 6
+```
diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js
index b552300f3830..cbdfd6ecc346 100644
--- a/src/language-js/print/jsx.js
+++ b/src/language-js/print/jsx.js
@@ -13,6 +13,7 @@ import {
   ifBreak,
   lineSuffixBoundary,
   join,
+  cursor,
 } from "../../document/builders.js";
 import { willBreak, replaceEndOfLine } from "../../document/utils.js";
 import UnexpectedNodeError from "../../utils/unexpected-node-error.js";
@@ -50,6 +51,7 @@ const isEmptyStringOrAnyLine = (doc) =>
  * @typedef {import("../../common/ast-path.js").default} AstPath
  * @typedef {import("../types/estree.js").Node} Node
  * @typedef {import("../types/estree.js").JSXElement} JSXElement
+ * @typedef {import("../../document/builders.js").Doc} Doc
  */
 
 // JSX expands children from the inside-out, instead of the outside-in.
@@ -236,10 +238,22 @@ function printJsxElementInternal(path, options, print) {
   // If there is text we use `fill` to fit as much onto each line as possible.
   // When there is no text (just tags and expressions) we use `group`
   // to output each on a separate line.
-  const content = containsText
+  /** @type {Doc} */
+  let content = containsText
     ? fill(multilineChildren)
     : group(multilineChildren, { shouldBreak: true });
 
+  /*
+  `printJsxChildren` won't call `print` on `JSXText`
+  When the cursorNode is inside `cursor` won't get print.
+  */
+  if (
+    options.cursorNode?.type === "JSXText" &&
+    node.children.includes(options.cursorNode)
+  ) {
+    content = [cursor, content, cursor];
+  }
+
   if (isMdxBlock) {
     return content;
   }


Test patch:
diff --git a/tests/format/jsx/cursor/__snapshots__/jsfmt.spec.js.snap b/tests/format/jsx/cursor/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..496e57842171
--- /dev/null
+++ b/tests/format/jsx/cursor/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`in-jsx-text.js format 1`] = `
+====================================options=====================================
+cursorOffset: 3
+parsers: ["babel", "typescript", "flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<>a<|>
+  <div>hi</div>
+</>
+
+=====================================output=====================================
+<>
+  a<|><div>hi</div>
+</>;
+
+================================================================================
+`;
diff --git a/tests/format/jsx/cursor/in-jsx-text.js b/tests/format/jsx/cursor/in-jsx-text.js
new file mode 100644
index 000000000000..aaa114af6af5
--- /dev/null
+++ b/tests/format/jsx/cursor/in-jsx-text.js
@@ -0,0 +1,3 @@
+<>a<|>
+  <div>hi</div>
+</>
diff --git a/tests/format/jsx/cursor/jsfmt.spec.js b/tests/format/jsx/cursor/jsfmt.spec.js
new file mode 100644
index 000000000000..f7fd785f69f4
--- /dev/null
+++ b/tests/format/jsx/cursor/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(import.meta, ["babel", "typescript", "flow"]);