From e2c25ecb84cedf53ecae04e214e3ad6081fdc96d Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 11 Mar 2026 10:59:55 -0400 Subject: [PATCH 1/5] refactor(remove-comments): replace deprecated tokenizer API with parse wrapper Replace this.Parser.prototype.blockTokenizers/eat() approach with a self.parse wrapper that strips HTML comments from raw vfile contents before parsing. Compatible with both unified v9 (current) and v11 (target). Co-Authored-By: Claude Opus 4.6 --- .../scripts/md/remove-comments.js | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/documentation-framework/scripts/md/remove-comments.js b/packages/documentation-framework/scripts/md/remove-comments.js index 7ceac4867a..b66b1f7646 100644 --- a/packages/documentation-framework/scripts/md/remove-comments.js +++ b/packages/documentation-framework/scripts/md/remove-comments.js @@ -1,21 +1,24 @@ -// https://github.com/remarkjs/remark/tree/main/packages/remark-parse#processoruseparse-options +// Strips HTML comments from markdown source before the parser sees them. +// This replaces the old tokenizer-based approach that used deprecated +// this.Parser.prototype.blockTokenizers APIs (removed in remark-parse v11). +// +// Wraps the processor's parse method to strip comments from the raw +// vfile contents before remark-parse and remark-mdx process the text. +function plugin() { + const self = this; + const originalParse = self.parse; -function plugin({ beginMarker = '' } = {}) { - const Parser = this.Parser - const tokenizers = Parser.prototype.blockTokenizers - const methods = Parser.prototype.blockMethods - - tokenizers.comments = function tokenizeComment(eat, value) { - const trimmed = value.trimRight(); - const endIndex = trimmed.indexOf(endMarker); - if (trimmed.startsWith(beginMarker) && endIndex >= 1) { - eat(value.substr(0, endIndex + endMarker.length)); + self.parse = function (file) { + const str = String(file.contents || file.value || ''); + const stripped = str.replace(//g, ''); + if (file.contents !== undefined) { + file.contents = stripped; } - return true; - } - - // Run it just before `html`. - methods.splice(methods.indexOf('html'), 0, 'comments') + if (file.value !== undefined) { + file.value = stripped; + } + return originalParse.call(this, file); + }; } module.exports = plugin; From ec186b7a512b38bd7c2e4e747869c68accf1f5af Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 11 Mar 2026 11:16:22 -0400 Subject: [PATCH 2/5] refactor(auto-link-url): replace deprecated tokenizer API with parse wrapper Replace this.Parser.prototype.inlineTokenizers/eat() approach with a self.parse wrapper that converts autolinks to standard [url](url) markdown syntax before parsing. This prevents remark-mdx from interpreting autolinks as JSX elements. Compatible with both unified v9 (current) and v11 (target). Co-Authored-By: Claude Opus 4.6 --- .../scripts/md/auto-link-url.js | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/documentation-framework/scripts/md/auto-link-url.js b/packages/documentation-framework/scripts/md/auto-link-url.js index ae81a5322d..7fa8050761 100644 --- a/packages/documentation-framework/scripts/md/auto-link-url.js +++ b/packages/documentation-framework/scripts/md/auto-link-url.js @@ -1,33 +1,26 @@ -// https://github.com/remarkjs/remark/tree/main/packages/remark-parse#processoruseparse-options -// https://github.com/remarkjs/remark/blob/main/packages/remark-parse/lib/tokenize/auto-link.js -const decode = require('parse-entities'); +// Re-adds support for autolinks in MDX. +// remark-mdx treats angle brackets as JSX, so +// fails to parse. This plugin converts autolinks to standard markdown +// link syntax [url](url) before the parser sees them. +// +// Replaces the old this.Parser.prototype.inlineTokenizers approach +// (removed in remark-parse v11). Compatible with unified v9 and v11. +function plugin() { + const self = this; + const originalParse = self.parse; -// Support `` -// This is removed in MDX 2: https://github.com/mdx-js/mdx/issues/1049 -function plugin({ beginMarker = ' or autolinks and convert to [url](url) + const converted = str.replace(/<(https?:\/\/[^>]+)>/g, '[$1]($1)'); + if (file.contents !== undefined) { + file.contents = converted; } - const endIndex = value.indexOf(endMarker); - if (endIndex >= 1) { - const link = value.substr(1, endIndex); - return eat(value.substr(0, endIndex))({ - type: 'link', - title: null, - url: decode(link, { nonTerminated: false }) - }); + if (file.value !== undefined) { + file.value = converted; } - return true; - } - tokenizers.autoLinkUrl.locator = (value, fromIndex) => value.indexOf(beginMarker, fromIndex); - - // Put before existing `autoLink` which will be deleted in remark-mdx - methods.splice(methods.indexOf('autoLink'), 0, 'autoLinkUrl') + return originalParse.call(this, file); + }; } module.exports = plugin; From dc6caa3066cef9f7ec97791ea418134df3542647 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 11 Mar 2026 14:07:54 -0400 Subject: [PATCH 3/5] refactor(mdx-hast-to-jsx): inline private remark-mdx and @mdx-js/util APIs Replace private imports from remark-mdx/lib/serialize/ and @mdx-js/util with inlined equivalents: - serializeTags (from remark-mdx/lib/serialize/mdx-element) - serializeMdxExpression (from remark-mdx/lib/serialize/mdx-expression) - toTemplateLiteral (from @mdx-js/util) - hastToProps replaces hast-to-hyperscript + fakeReactCreateElement Generated output is identical. Removes dependencies on private internal APIs that won't exist in newer remark-mdx versions. Co-Authored-By: Claude Opus 4.6 --- .../scripts/md/mdx-hast-to-jsx.js | 85 ++++++++++++++----- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/packages/documentation-framework/scripts/md/mdx-hast-to-jsx.js b/packages/documentation-framework/scripts/md/mdx-hast-to-jsx.js index ef248fdca7..b2f89ea965 100644 --- a/packages/documentation-framework/scripts/md/mdx-hast-to-jsx.js +++ b/packages/documentation-framework/scripts/md/mdx-hast-to-jsx.js @@ -1,14 +1,72 @@ const path = require('path'); const fs = require('fs'); -const { serializeTags } = require('remark-mdx/lib/serialize/mdx-element'); -const serializeMdxExpression = require('remark-mdx/lib/serialize/mdx-expression'); -const toH = require('hast-to-hyperscript'); -const { toTemplateLiteral } = require('@mdx-js/util'); const { parse } = require('@patternfly/ast-helpers'); const { capitalize } = require('../../helpers/capitalize'); const { slugger } = require('../../helpers/slugger'); const { liveCodeTypes } = require('../../helpers/liveCodeTypes'); +// Inlined from remark-mdx/lib/serialize/mdx-expression (private API) +function serializeMdxExpression(node) { + const value = node.value || ''; + const block = node.type === 'mdxBlockExpression'; + const around = block ? '\n' : ''; + const content = block ? indentStr(value) : value; + return '{' + around + content + around + '}'; +} + +// Inlined from remark-mdx/lib/util/indent (private API) +function indentStr(value) { + return value.split('\n').map(line => /\S/.test(line) ? ' ' + line : line).join('\n'); +} + +// Inlined from remark-mdx/lib/serialize/mdx-element serializeTags (private API) +function serializeTags(node) { + const name = String(node.name || ''); + const selfClosing = name && node.children.length === 0; + const attrs = (node.attributes || []).map(attr => { + if (attr.type === 'mdxAttributeExpression') { + return serializeMdxExpression(attr); + } + const attrName = String(attr.name || ''); + if (attr.value === null || attr.value === undefined) { + return attrName; + } + if (typeof attr.value === 'object') { + return attrName + '=' + serializeMdxExpression(attr.value); + } + return attrName + '="' + attr.value.replace(/"/g, '"') + '"'; + }); + const attrStr = attrs.length > 0 ? ' ' + attrs.join(' ') : ''; + + return { + open: '<' + name + attrStr + (selfClosing ? '/' : '') + '>', + close: selfClosing ? null : '' + }; +} + +// Inlined from @mdx-js/util toTemplateLiteral (private API) +function toTemplateLiteral(text) { + const escaped = text + .replace(/\\(?!\$)/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/(\\\$)/g, '\\$1') + .replace(/(\\\$)(\{)/g, '\\$1\\$2') + .replace(/\$\{/g, '\\${'); + return '{`' + escaped + '`}'; +} + +// Replacement for hast-to-hyperscript + fakeReactCreateElement. +// Extracts type and props from a HAST element node, normalizing +// className arrays to space-separated strings. +function hastToProps(node) { + const props = {}; + for (const [key, val] of Object.entries(node.properties || {})) { + if (val === null || val === undefined) continue; + props[key] = Array.isArray(val) ? val.join(' ') : val; + } + return { type: node.tagName, props }; +} + // Adapted from https://github.com/mdx-js/mdx/blob/next/packages/mdx/mdx-hast-to-jsx.js function toJSX(node, parentNode = {}, options = {}) { options.examples = options.examples || {}; @@ -133,15 +191,9 @@ export default Component;\n`; function serializeElement(node, options) { const { indent, examples } = options; - const { type, props } = toH( - fakeReactCreateElement, - Object.assign({}, node, {children: []}), - {prefix: false} - ); + const { type, props } = hastToProps(node); const content = serializeChildren(node, {...options}); - delete props.key; - const srcImport = node.properties.src; if (type === 'img') { delete props.src; @@ -236,15 +288,4 @@ function serializeChildren(node, options) { .join(`\n${indentText}`); } -// We only do this for the props, so we’re ignoring children. -function fakeReactCreateElement(name, props) { - return { - type: name, - props: props, - // Needed for `toH` to think this is React. - key: null, - _owner: null - } -} - module.exports = compile; From 7aa7ba28048e8d91528b510df942baab80e45918 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 11 Mar 2026 15:39:18 -0400 Subject: [PATCH 4/5] refactor(mdx-ast-to-mdx-hast): inline private mdast-util-to-hast/lib/all API Replace `require('mdast-util-to-hast/lib/all')` with inlined `all` and `one` functions. This removes the last private API import, making the code resilient to internal restructuring in newer mdast-util-to-hast versions. Co-Authored-By: Claude Opus 4.6 --- .../scripts/md/mdx-ast-to-mdx-hast.js | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/documentation-framework/scripts/md/mdx-ast-to-mdx-hast.js b/packages/documentation-framework/scripts/md/mdx-ast-to-mdx-hast.js index 62b1d22c6f..bec147bb6a 100644 --- a/packages/documentation-framework/scripts/md/mdx-ast-to-mdx-hast.js +++ b/packages/documentation-framework/scripts/md/mdx-ast-to-mdx-hast.js @@ -3,7 +3,50 @@ const path = require('path'); const toHAST = require('mdast-util-to-hast'); const detab = require('detab'); const normalize = require('mdurl/encode'); -const all = require('mdast-util-to-hast/lib/all'); +// Inlined from mdast-util-to-hast/lib/all and lib/one (private APIs). +// `all` recursively transforms a node's children through the handler +// dispatch system. `one` dispatches a single node to the appropriate +// handler registered on `h`. +function one(h, node, parent) { + const type = node && node.type; + if (!type) { + throw new Error('Expected node, got `' + node + '`'); + } + const fn = h.handlers[type] || h.unknownHandler; + if (typeof fn === 'function') { + return fn(h, node, parent); + } + // Fallback for unknown node types: wrap children in a div, or + // return a text value if the node carries one. + if (node.children) { + return h(node, 'div', all(h, node)); + } + if (node.value) { + return { type: 'text', value: node.value }; + } +} + +function all(h, parent) { + const nodes = parent.children || []; + const values = []; + for (let i = 0; i < nodes.length; i++) { + const result = one(h, nodes[i], parent); + if (result) { + // After a break node, trim leading whitespace from the next sibling + if (i && nodes[i - 1].type === 'break') { + if (result.value) { + result.value = result.value.replace(/^\s+/, ''); + } + const head = result.children && result.children[0]; + if (head && head.value) { + head.value = head.value.replace(/^\s+/, ''); + } + } + values.push(...(Array.isArray(result) ? result : [result])); + } + } + return values; +} const styleToObject = require('style-to-object'); const camelCaseCSS = require('camelcase-css'); const { parseJSXAttributes } = require('./jsxAttributes'); From fe664bef60cad16bcf353e10764b2a5deebe0627 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 11 Mar 2026 16:16:34 -0400 Subject: [PATCH 5/5] chore(deps): remove inlined dependencies from package.json Remove @mdx-js/util and hast-to-hyperscript since their functionality was inlined in the previous commits. These packages are no longer imported anywhere. Co-Authored-By: Claude Opus 4.6 --- packages/documentation-framework/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/documentation-framework/package.json b/packages/documentation-framework/package.json index 95e1b0d078..b0ed41c47a 100644 --- a/packages/documentation-framework/package.json +++ b/packages/documentation-framework/package.json @@ -11,7 +11,6 @@ "@babel/core": "^7.29.0", "@babel/preset-env": "7.29.0", "@babel/preset-react": "^7.28.5", - "@mdx-js/util": "1.6.22", "@patternfly/ast-helpers": "^1.26.2", "@reach/router": "npm:@gatsbyjs/reach-router@1.3.9", "@rspack/core": "^1.7.7", @@ -30,7 +29,6 @@ "fs-extra": "9.1.0", "glob": "12.0.0", "handlebars": "4.7.8", - "hast-to-hyperscript": "9.0.1", "hast-util-to-text": "2.0.1", "html-formatter": "0.1.9", "html-webpack-plugin": "5.6.6",