Repository: mdx-js/mdx-analyzer
Pull request #448 (2024-06-05 16:19:23)

Fix virtual code when JSX child starts with >
### Initial checklist

*   [x] I read the support docs <!-- https://github.com/unifiedjs/.github/blob/main/support.md -->
*   [x] I read the contributing guide <!-- https://github.com/unifiedjs/.github/blob/main/contributing.md -->
*   [x] I agree to follow the code of conduct <!-- https://github.com/unifiedjs/.github/blob/main/code-of-conduct.md -->
*   [x] I searched issues and couldn’t find anything (or linked relevant results below) <!-- https://github.com/search?q=user%3Aunifiedjs&type=Issues -->
*   [x] If applicable, I’ve added docs and tests

### Description of changes

This fixes an offset issue if the child of a JSX element in the MDX starts with a `>` character. Typically these are blockquotes.

Refs https://github.com/orgs/mdx-js/discussions/2489

<!--do not edit: pr-->


changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 27af1e98b08bb390aa1058a56cccfd315a2b0d79

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 4 packages</summary>

  | Name                      | Type  |
| ------------------------- | ----- |
| @mdx-js/language-service  | Patch |
| @mdx-js/language-server   | Patch |
| @mdx-js/typescript-plugin | Patch |
| vscode-mdx                | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/mdx-js/mdx-analyzer/new/fix-blockquotes-inside-jsx?filename=.changeset/modern-laws-act.md&value=---%0A%22%40mdx-js%2Flanguage-service%22%3A%20patch%0A---%0A%0AFix%20virtual%20code%20when%20JSX%20child%20starts%20with%20%3E%0A)


github-actions[bot]: Hi! This is accepted and can go somewhere!

Team: please review this PR and use the `area/*` (to describe the scope of the change), `platform/*` (if this is related to a specific one), and `semver/*` and `type/*` labels to annotate this.

<!--xxx:beep-boop:yes-maintainers-->

Patch:
diff --git a/.changeset/wild-kangaroos-travel.md b/.changeset/wild-kangaroos-travel.md
new file mode 100644
index 0000000..5cdeb63
--- /dev/null
+++ b/.changeset/wild-kangaroos-travel.md
@@ -0,0 +1,8 @@
+---
+'@mdx-js/language-service': patch
+'@mdx-js/language-server': patch
+'@mdx-js/typescript-plugin': patch
+'vscode-mdx': patch
+---
+
+Fix virtual code if a child of a JSX node starts with a `>` character.
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index 51b9170..d34c7c8 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -553,7 +553,8 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
         case 'mdxJsxFlowElement':
         case 'mdxJsxTextElement': {
           if (node.children.length > 0) {
-            end = mdx.lastIndexOf('>', getNodeStartOffset(node.children[0])) + 1
+            end =
+              mdx.lastIndexOf('>', getNodeStartOffset(node.children[0]) - 1) + 1
           }
 
           updateMarkdownFromOffsets(start, end)


Test patch:
diff --git a/packages/language-service/test/language-plugin.js b/packages/language-service/test/language-plugin.js
index a812f1c..057fe16 100644
--- a/packages/language-service/test/language-plugin.js
+++ b/packages/language-service/test/language-plugin.js
@@ -2204,6 +2204,127 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
   ])
 })
 
+test('create virtual code w/ mdxJsxFlowElement w/ blockquote child', () => {
+  const plugin = createMdxLanguagePlugin()
+
+  const snapshot = snapshotFromLines('<div>', '>', '</div>', '<div>></div>', '')
+
+  const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot)
+
+  assert.ok(code instanceof VirtualMdxCode)
+  assert.equal(code.id, 'mdx')
+  assert.equal(code.languageId, 'mdx')
+  assert.ifError(code.error)
+  assert.equal(code.snapshot, snapshot)
+  assert.deepEqual(code.mappings, [
+    {
+      sourceOffsets: [0],
+      generatedOffsets: [0],
+      lengths: [snapshot.getLength()],
+      data: {
+        completion: true,
+        format: true,
+        navigation: true,
+        semantic: true,
+        structure: true,
+        verification: true
+      }
+    }
+  ])
+  assert.deepEqual(code.embeddedCodes, [
+    {
+      id: 'jsx',
+      languageId: 'javascriptreact',
+      mappings: [
+        {
+          sourceOffsets: [0, 8, 15, 21],
+          generatedOffsets: [779, 804, 822, 841],
+          lengths: [5, 6, 5, 6],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        }
+      ],
+      snapshot: snapshotFromLines(
+        '/* @jsxRuntime automatic',
+        '@jsxImportSource react */',
+        '',
+        '/**',
+        ' * @internal',
+        ' *   **Do not use.** This function is generated by MDX for internal use.',
+        ' *',
+        ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+        ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+        ' */',
+        'function _createMdxContent(props) {',
+        '  /**',
+        '   * @internal',
+        '   *   **Do not use.** This variable is generated by MDX for internal use.',
+        '   */',
+        '  const _components = {',
+        '    // @ts-ignore',
+        '    .../** @type {0 extends 1 & MDXProvidedComponents ? {} : MDXProvidedComponents} */ ({}),',
+        '    ...props.components,',
+        '    /** The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component. */',
+        '    props',
+        '  }',
+        '  _components',
+        '  return <>',
+        '    <div>',
+        '    <>',
+        '    </>',
+        '    </div>',
+        '    <>',
+        '    <div>',
+        "    {''}",
+        '    </div>',
+        '    </>',
+        '  </>',
+        '}',
+        '',
+        '/**',
+        ' * Render the MDX contents.',
+        ' *',
+        ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+        ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+        ' */',
+        'export default function MDXContent(props) {',
+        '  return <_createMdxContent {...props} />',
+        '}',
+        '',
+        '// @ts-ignore',
+        '/** @typedef {(0 extends 1 & Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        ''
+      )
+    },
+    {
+      id: 'md',
+      languageId: 'markdown',
+      mappings: [
+        {
+          sourceOffsets: [5, 14, 20, 27],
+          generatedOffsets: [0, 10, 18, 26],
+          lengths: [3, 1, 1, 1],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        }
+      ],
+      snapshot: snapshotFromLines('', '>', '<!---->', '<!---->><!---->', '')
+    }
+  ])
+})
+
 test('create virtual code w/ mdxJsxFlowElement w/o children', () => {
   const plugin = createMdxLanguagePlugin()