Repository: mdx-js/mdx-analyzer
Pull request #368 (2023-12-14 12:16:05)

Remove the language module dependency on TypeScript
### 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

TypeScript isn’t needed to parse MDX into virtual files. The removal of this dependency, means that the language module could later be used in places where TypeScript is unavailable.

<!--do not edit: pr-->


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 7a58a69a9b2eb98c4f0b85b46420ebb79626f85b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/mdx-js/mdx-analyzer/new/remove-typescript-dependency?filename=.changeset/two-otters-change.md&value=---%0A%22%40mdx-js%2Flanguage-server%22%3A%20patch%0A%22%40mdx-js%2Flanguage-service%22%3A%20patch%0A---%0A%0ARemove%20the%20language%20module%20dependency%20on%20TypeScript%0A)


remcohaszing: @johnsoncodehk I would love your opinion on this

Patch:
diff --git a/.changeset/purple-kings-unite.md b/.changeset/purple-kings-unite.md
new file mode 100644
index 0000000..a255f5d
--- /dev/null
+++ b/.changeset/purple-kings-unite.md
@@ -0,0 +1,5 @@
+---
+"@mdx-js/language-service": minor
+---
+
+Remove the dependency on an injected TypeScript module instance.
diff --git a/packages/language-server/lib/language-server-plugin.js b/packages/language-server/lib/language-server-plugin.js
index ba13442..912201e 100644
--- a/packages/language-server/lib/language-server-plugin.js
+++ b/packages/language-server/lib/language-server-plugin.js
@@ -41,7 +41,7 @@ export function plugin({modules}) {
       )
 
       config.languages ||= {}
-      config.languages.mdx ||= getLanguageModule(modules.typescript, plugins)
+      config.languages.mdx ||= getLanguageModule(plugins)
 
       config.services ||= {}
       config.services.markdown = createMarkdownService()
diff --git a/packages/language-service/lib/language-module.js b/packages/language-service/lib/language-module.js
index ff9ae5c..b27519b 100644
--- a/packages/language-service/lib/language-module.js
+++ b/packages/language-service/lib/language-module.js
@@ -18,6 +18,44 @@ import remarkMdx from 'remark-mdx'
 import remarkParse from 'remark-parse'
 import {unified} from 'unified'
 
+/**
+ * A TypeScript compatible script snapshot that wraps a string of text.
+ *
+ * @implements {IScriptSnapshot}
+ */
+export class ScriptSnapshot {
+  /**
+   * @param {string} text
+   *   The text to wrap.
+   */
+  constructor(text) {
+    this.text = text
+  }
+
+  /**
+   * Not implemented.
+   *
+   * @returns {undefined}
+   */
+  getChangeRange() {}
+
+  /**
+   * @returns {number}
+   */
+  getLength() {
+    return this.text.length
+  }
+
+  /**
+   * @param {number} start
+   * @param {number} end
+   * @returns {string}
+   */
+  getText(start, end) {
+    return this.text.slice(start, end)
+  }
+}
+
 /**
  * @param {string} propsName
  */
@@ -228,11 +266,10 @@ function processExports(mdx, node, mapping, esm) {
 /**
  * @param {string} fileName
  * @param {IScriptSnapshot} snapshot
- * @param {typeof import('typescript')} ts
  * @param {Processor} processor
  * @returns {VirtualFile[]}
  */
-function getVirtualFiles(fileName, snapshot, ts, processor) {
+function getVirtualFiles(fileName, snapshot, processor) {
   const mdx = snapshot.getText(0, snapshot.getLength())
   /** @type {Mapping[]} */
   const jsMappings = []
@@ -248,17 +285,17 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
         fileName: fileName + '.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: ts.ScriptKind.JSX
+          scriptKind: 2
         },
         mappings: jsMappings,
-        snapshot: ts.ScriptSnapshot.fromString(fallback)
+        snapshot: new ScriptSnapshot(fallback)
       },
       {
         embeddedFiles: [],
         fileName: fileName + '.md',
         languageId: 'markdown',
         mappings: [],
-        snapshot: ts.ScriptSnapshot.fromString(mdx)
+        snapshot: new ScriptSnapshot(mdx)
       }
     ]
   }
@@ -417,7 +454,7 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
                 }
               }
             ],
-            snapshot: ts.ScriptSnapshot.fromString(node.value)
+            snapshot: new ScriptSnapshot(node.value)
           })
 
           break
@@ -525,17 +562,17 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
       fileName: fileName + '.jsx',
       languageId: 'javascriptreact',
       typescript: {
-        scriptKind: ts.ScriptKind.JSX
+        scriptKind: 2
       },
       mappings: jsMappings,
-      snapshot: ts.ScriptSnapshot.fromString(esm)
+      snapshot: new ScriptSnapshot(esm)
     },
     {
       embeddedFiles: [],
       fileName: fileName + '.md',
       languageId: 'markdown',
       mappings: [markdownMapping],
-      snapshot: ts.ScriptSnapshot.fromString(markdown)
+      snapshot: new ScriptSnapshot(markdown)
     }
   )
 
@@ -545,15 +582,13 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
 /**
  * Create a [Volar](https://volarjs.dev) language module to support MDX.
  *
- * @param {typeof import('typescript')} ts
- *   The TypeScript module.
  * @param {PluggableList} [plugins]
  *   A list of remark syntax plugins. Only syntax plugins are supported.
  *   Transformers are unused.
  * @returns {LanguagePlugin}
  *   A Volar language module to support MDX.
  */
-export function getLanguageModule(ts, plugins) {
+export function getLanguageModule(plugins) {
   const processor = unified().use(remarkParse).use(remarkMdx)
   if (plugins) {
     processor.use(plugins)
@@ -570,7 +605,7 @@ export function getLanguageModule(ts, plugins) {
       const length = snapshot.getLength()
 
       return {
-        embeddedFiles: getVirtualFiles(fileName, snapshot, ts, processor),
+        embeddedFiles: getVirtualFiles(fileName, snapshot, processor),
         fileName,
         languageId: 'mdx',
         mappings: [
@@ -615,7 +650,6 @@ export function getLanguageModule(ts, plugins) {
       mdxFile.embeddedFiles = getVirtualFiles(
         mdxFile.fileName,
         snapshot,
-        ts,
         processor
       )
     },
@@ -632,7 +666,7 @@ export function getLanguageModule(ts, plugins) {
           ...host,
           getCompilationSettings: () => ({
             // Default to the JSX automatic runtime, because that’s what MDX does.
-            jsx: ts.JsxEmit.ReactJSX,
+            jsx: 4,
             // Set these defaults to match MDX if the user explicitly sets the classic runtime.
             jsxFactory: 'React.createElement',
             jsxFragmentFactory: 'React.Fragment',


Test patch:
diff --git a/packages/language-service/test/language-module.js b/packages/language-service/test/language-module.js
index b74f805..c56bd75 100644
--- a/packages/language-service/test/language-module.js
+++ b/packages/language-service/test/language-module.js
@@ -7,9 +7,10 @@ import {test} from 'node:test'
 import remarkFrontmatter from 'remark-frontmatter'
 import typescript from 'typescript'
 import {getLanguageModule} from '@mdx-js/language-service'
+import {ScriptSnapshot} from '../lib/language-module.js'
 
 test('create virtual file w/ mdxjsEsm', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('import {Planet} from "./Planet.js"', '')
 
@@ -40,7 +41,7 @@ test('create virtual file w/ mdxjsEsm', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -101,7 +102,7 @@ test('create virtual file w/ mdxjsEsm', () => {
 })
 
 test('create virtual file w/o MDX layout in case of named re-export', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('export {named} from "./Layout.js"', '')
 
@@ -132,7 +133,7 @@ test('create virtual file w/o MDX layout in case of named re-export', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -193,7 +194,7 @@ test('create virtual file w/o MDX layout in case of named re-export', () => {
 })
 
 test('create virtual file w/ MDX layout in case of default re-export', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('export {default} from "./Layout.js"', '')
 
@@ -224,7 +225,7 @@ test('create virtual file w/ MDX layout in case of default re-export', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -285,7 +286,7 @@ test('create virtual file w/ MDX layout in case of default re-export', () => {
 })
 
 test('create virtual file w/ MDX layout in case of named and default re-export', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export {named, default} from "./Layout.js"',
@@ -319,7 +320,7 @@ test('create virtual file w/ MDX layout in case of named and default re-export',
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -380,7 +381,7 @@ test('create virtual file w/ MDX layout in case of named and default re-export',
 })
 
 test('create virtual file w/ MDX layout in case of default and named re-export', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export {default, named} from "./Layout.js"',
@@ -414,7 +415,7 @@ test('create virtual file w/ MDX layout in case of default and named re-export',
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -475,7 +476,7 @@ test('create virtual file w/ MDX layout in case of default and named re-export',
 })
 
 test('create virtual file w/ MDX layout in case of a default exported arrow function', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('export default () => {}', '')
 
@@ -506,7 +507,7 @@ test('create virtual file w/ MDX layout in case of a default exported arrow func
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -581,7 +582,7 @@ test('create virtual file w/ MDX layout in case of a default exported arrow func
 })
 
 test('create virtual file w/ MDX layout in case of a default exported function declaration', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export default function MDXLayout() {}',
@@ -615,7 +616,7 @@ test('create virtual file w/ MDX layout in case of a default exported function d
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -690,7 +691,7 @@ test('create virtual file w/ MDX layout in case of a default exported function d
 })
 
 test('create virtual file w/ MDX layout in case of a default exported constant', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('export default "main"', '')
 
@@ -721,7 +722,7 @@ test('create virtual file w/ MDX layout in case of a default exported constant',
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -783,7 +784,7 @@ test('create virtual file w/ MDX layout in case of a default exported constant',
 })
 
 test('create virtual file w/ MDX layout and matching argument name', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export default function MDXLayout(properties) {}',
@@ -817,7 +818,7 @@ test('create virtual file w/ MDX layout and matching argument name', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -892,7 +893,7 @@ test('create virtual file w/ MDX layout and matching argument name', () => {
 })
 
 test('create virtual file w/ MDX layout in case of a default export followed by a named', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export default function MDXLayout() {}',
@@ -927,7 +928,7 @@ test('create virtual file w/ MDX layout in case of a default export followed by
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1003,7 +1004,7 @@ test('create virtual file w/ MDX layout in case of a default export followed by
 })
 
 test('create virtual file w/ MDX layout in case of a default export preceded by a named', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     'export function named() {}',
@@ -1038,7 +1039,7 @@ test('create virtual file w/ MDX layout in case of a default export preceded by
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1114,7 +1115,7 @@ test('create virtual file w/ MDX layout in case of a default export preceded by
 })
 
 test('create virtual file w/ mdxFlowExpression', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('{Math.PI}', '')
 
@@ -1145,7 +1146,7 @@ test('create virtual file w/ mdxFlowExpression', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1205,7 +1206,7 @@ test('create virtual file w/ mdxFlowExpression', () => {
 })
 
 test('create virtual file w/ mdxJsxFlowElement w/ children', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     '<div>',
@@ -1243,7 +1244,7 @@ test('create virtual file w/ mdxJsxFlowElement w/ children', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1310,7 +1311,7 @@ test('create virtual file w/ mdxJsxFlowElement w/ children', () => {
 })
 
 test('create virtual file w/ mdxJsxFlowElement w/o children', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('<div />', '')
 
@@ -1372,7 +1373,7 @@ test('create virtual file w/ mdxJsxFlowElement w/o children', () => {
           ''
         ),
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         }
       },
       {
@@ -1401,7 +1402,7 @@ test('create virtual file w/ mdxJsxFlowElement w/o children', () => {
 })
 
 test('create virtual file w/ mdxJsxTextElement', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('A <div />', '')
 
@@ -1432,7 +1433,7 @@ test('create virtual file w/ mdxJsxTextElement', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1492,7 +1493,7 @@ test('create virtual file w/ mdxJsxTextElement', () => {
 })
 
 test('create virtual file w/ mdxTextExpression', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('3 < {Math.PI} < 4', '')
 
@@ -1523,7 +1524,7 @@ test('create virtual file w/ mdxTextExpression', () => {
         fileName: '/test.mdx.jsx',
         languageId: 'javascriptreact',
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         },
         mappings: [
           {
@@ -1583,7 +1584,7 @@ test('create virtual file w/ mdxTextExpression', () => {
 })
 
 test('create virtual file w/ dedented markdown content', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines(
     '     | Language |',
@@ -1672,7 +1673,7 @@ test('create virtual file w/ dedented markdown content', () => {
 })
 
 test('create virtual file w/ syntax error', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const snapshot = snapshotFromLines('<', '')
 
@@ -1720,7 +1721,7 @@ test('create virtual file w/ syntax error', () => {
           ''
         ),
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         }
       },
       {
@@ -1735,7 +1736,7 @@ test('create virtual file w/ syntax error', () => {
 })
 
 test('create virtual file w/ yaml frontmatter', () => {
-  const module = getLanguageModule(typescript, [remarkFrontmatter])
+  const module = getLanguageModule([remarkFrontmatter])
 
   const snapshot = snapshotFromLines('---', 'hello: frontmatter', '---', '')
 
@@ -1783,7 +1784,7 @@ test('create virtual file w/ yaml frontmatter', () => {
           ''
         ),
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         }
       },
       {
@@ -1833,7 +1834,7 @@ test('create virtual file w/ yaml frontmatter', () => {
 })
 
 test('update virtual file', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   const file = module.createVirtualFile(
     '/test.mdx',
@@ -1886,7 +1887,7 @@ test('update virtual file', () => {
           ''
         ),
         typescript: {
-          scriptKind: 2
+          scriptKind: typescript.ScriptKind.JSX
         }
       },
       {
@@ -1915,7 +1916,7 @@ test('update virtual file', () => {
 })
 
 test('compilation setting defaults', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   // @ts-expect-error
   const host = module.typescript?.resolveLanguageServiceHost?.({
@@ -1935,7 +1936,7 @@ test('compilation setting defaults', () => {
 })
 
 test('compilation setting overrides', () => {
-  const module = getLanguageModule(typescript)
+  const module = getLanguageModule()
 
   // @ts-expect-error
   const host = module.typescript?.resolveLanguageServiceHost?.({
@@ -1966,5 +1967,5 @@ test('compilation setting overrides', () => {
  * @returns {typescript.IScriptSnapshot}
  */
 function snapshotFromLines(...lines) {
-  return typescript.ScriptSnapshot.fromString(lines.join('\n'))
+  return new ScriptSnapshot(lines.join('\n'))
 }