docs: add util to generate clean markdown for a file (#11303)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-02-04 16:44:17 +02:00
1 parent 3c51709daf
commit 604f46f8bc
23 files changed
+832 -37

No files matched your search

@@ -0,0 +1,20 @@
import { UnistNode, UnistTree } from "types"
import { Transformer } from "unified"
type Options = {
url: string
}
export function addUrlToRelativeLink(options: Options): Transformer {
return async (tree) => {
const { visit } = await import("unist-util-visit")
visit(tree as UnistTree, "link", (node: UnistNode) => {
if (!node.url || !node.url.startsWith("/")) {
return
}
node.url = `${options.url}${node.url}`
})
}
}
@@ -6,12 +6,12 @@ import type {
UnistNode,
UnistNodeWithData,
UnistTree,
} from "./types/index.js"
} from "types"
import type { VFile } from "vfile"
import { parseCrossProjectLink } from "./utils/cross-project-link-utils.js"
import { SlugChange } from "types"
import getAttribute from "./utils/get-attribute.js"
import { estreeToJs } from "./utils/estree-to-js.js"
import { estreeToJs } from "docs-utils"
import { performActionOnLiteral } from "./utils/perform-action-on-literal.js"
import { MD_LINK_REGEX } from "./constants.js"
@@ -1,5 +1,5 @@
import type { Transformer } from "unified"
import type { CloudinaryConfig, UnistNode, UnistTree } from "./types/index.js"
import type { CloudinaryConfig, UnistNode, UnistTree } from "types"
const cloudinaryImageRegex =
/^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?<imageId>.*)$/
@@ -4,8 +4,8 @@ import type {
UnistNode,
UnistNodeWithData,
UnistTree,
} from "./types/index.js"
import { estreeToJs } from "./utils/estree-to-js.js"
} from "types"
import { estreeToJs } from "docs-utils"
import getAttribute from "./utils/get-attribute.js"
import { performActionOnLiteral } from "./utils/perform-action-on-literal.js"
@@ -48,6 +48,14 @@ function linkElmFixer(node: UnistNode, options: CrossProjectLinksOptions) {
node.properties.href = matchAndFixLinks(node.properties.href, options)
}
function linkNodeFixer(node: UnistNode, options: CrossProjectLinksOptions) {
if (!node.url) {
return
}
node.url = matchAndFixLinks(node.url, options)
}
function componentFixer(
node: UnistNodeWithData,
options: CrossProjectLinksOptions
@@ -128,20 +136,23 @@ export function crossProjectLinksPlugin(
visit(
tree as UnistTree,
["element", "mdxJsxFlowElement"],
["element", "mdxJsxFlowElement", "link"],
(node: UnistNode) => {
const isComponent =
node.name && allowedComponentNames.includes(node.name)
const isLink = node.tagName === "a" && node.properties?.href
if (!isComponent && !isLink) {
const isLinkElm = node.tagName === "a" && node.properties?.href
const isLinkNode = node.type === "link" && node.url
if (!isComponent && !isLinkElm && !isLinkNode) {
return
}
if (isComponent) {
componentFixer(node as UnistNodeWithData, options)
} else if (isLinkElm) {
linkElmFixer(node, options)
} else {
linkNodeFixer(node, options)
}
linkElmFixer(node, options)
}
)
}
@@ -1,3 +1,4 @@
export * from "./add-url-to-relative-link.js"
export * from "./broken-link-checker.js"
export * from "./cloudinary-img.js"
export * from "./cross-project-links.js"
@@ -1,6 +1,6 @@
import path from "path"
import type { Transformer } from "unified"
import type { LocalLinkOptions, UnistNode, UnistTree } from "./types/index.js"
import type { LocalLinkOptions, UnistNode, UnistTree } from "types"
import { fixLinkUtil } from "./index.js"
export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
@@ -25,16 +25,28 @@ export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
""
)
const appsPath = basePath || path.join(file.cwd, "app")
visit(tree as UnistTree, "element", (node: UnistNode) => {
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
return
}
visit(tree as UnistTree, ["element", "link"], (node: UnistNode) => {
if (node.tagName === "a") {
if (!node.properties?.href?.match(/page\.mdx?/)) {
return
}
node.properties.href = fixLinkUtil({
currentPageFilePath,
linkedPath: node.properties.href,
appsPath,
})
node.properties.href = fixLinkUtil({
currentPageFilePath,
linkedPath: node.properties.href,
appsPath,
})
} else if (node.type === "link") {
if (!node.url?.match(/page\.mdx?/)) {
return
}
node.url = fixLinkUtil({
currentPageFilePath,
linkedPath: node.url,
appsPath,
})
}
})
}
}
@@ -1,7 +1,7 @@
import path from "path"
import type { Transformer } from "unified"
import type { RawSidebarItem } from "types"
import type { UnistTree } from "./types/index.js"
import type { UnistTree } from "types"
export function pageNumberRehypePlugin({
sidebar,
@@ -1,5 +1,5 @@
import { Transformer } from "unified"
import { ComponentLinkFixerOptions } from "./types/index.js"
import { ComponentLinkFixerOptions } from "types"
import { componentLinkFixer } from "./utils/component-link-fixer.js"
export function prerequisitesLinkFixerPlugin(
@@ -1,5 +1,5 @@
import { Transformer } from "unified"
import { UnistNode } from "./types/index.js"
import { UnistNode } from "types"
const ALLOWED_NODE_NAMES = ["note", "tip", "info", "warning"]
@@ -1,5 +1,5 @@
import { Transformer } from "unified"
import { ComponentLinkFixerOptions } from "./types/index.js"
import { ComponentLinkFixerOptions } from "types"
import { componentLinkFixer } from "./utils/component-link-fixer.js"
export function typeListLinkFixerPlugin(
@@ -1,153 +0,0 @@
import { Node } from "unist-builder"
export interface UnistNode extends Node {
type: string
name?: string
tagName?: string
value?: string
properties?: {
[key: string]: string
}
attributes?: {
name: string
value: unknown
type?: string
}[]
children?: UnistNode[]
}
export type ArrayExpression = {
type: "ArrayExpression"
elements: Expression[]
}
export type ObjectExpression = {
type: "ObjectExpression"
properties: AttributeProperty[]
}
export type LiteralExpression = {
type: "Literal"
value: unknown
raw: string
}
export type Expression =
| {
type: string
}
| ArrayExpression
| ObjectExpression
| LiteralExpression
export interface Estree {
body?: {
type?: string
expression?: Expression
}[]
}
export interface UnistNodeWithData extends UnistNode {
attributes: {
name: string
value:
| {
data?: {
estree?: Estree
}
value?: string
}
| string
type?: string
}[]
}
export interface AttributeProperty {
key: {
name?: string
value?: string
raw: string
}
value:
| {
type: "Literal"
value: unknown
raw: string
}
| {
type: "JSXElement"
// TODO add correct type if necessary
openingElement: unknown
}
| ArrayExpression
}
export interface UnistTree extends Node {
children: UnistNode[]
}
export declare type CloudinaryConfig = {
cloudName?: string
flags?: string[]
resize?: {
action: string
width?: number
height?: number
aspectRatio?: string
}
roundCorners?: number
}
export declare type CrossProjectLinksOptions = {
baseUrl: string
projectUrls?: {
[k: string]: {
url: string
path?: string
}
}
useBaseUrl?: boolean
}
export declare type BrokenLinkCheckerOptions = {
crossProjects: {
[k: string]: {
projectPath: string
contentPath?: string
hasGeneratedSlugs?: boolean
}
}
}
export declare type ComponentLinkFixerLinkType = "md" | "value"
export declare type ComponentLinkFixerOptions = {
filePath?: string
basePath?: string
checkLinksType: ComponentLinkFixerLinkType
}
export declare type LocalLinkOptions = {
filePath?: string
basePath?: string
}
export type ExpressionJsVarItem = {
original: AttributeProperty
data?: unknown
}
export type ExpressionJsVarLiteral = {
original: {
type: "Literal"
value: unknown
raw: string
}
data?: unknown
}
export type ExpressionJsVarObj = {
[k: string]: ExpressionJsVarItem | ExpressionJsVar | ExpressionJsVar[]
}
export type ExpressionJsVar = ExpressionJsVarObj | ExpressionJsVarLiteral
@@ -1,10 +1,9 @@
import path from "path"
import { Transformer } from "unified"
import { UnistNodeWithData, UnistTree } from "../types/index.js"
import { UnistNodeWithData, UnistTree, ComponentLinkFixerOptions } from "types"
import { FixLinkOptions, fixLinkUtil } from "../index.js"
import getAttribute from "../utils/get-attribute.js"
import { estreeToJs } from "../utils/estree-to-js.js"
import { ComponentLinkFixerOptions } from "../types/index.js"
import { estreeToJs } from "docs-utils"
import { performActionOnLiteral } from "./perform-action-on-literal.js"
import { MD_LINK_REGEX } from "../constants.js"
@@ -1,70 +0,0 @@
/* eslint-disable no-case-declarations */
import {
ArrayExpression,
Estree,
Expression,
ExpressionJsVar,
ExpressionJsVarLiteral,
LiteralExpression,
ObjectExpression,
} from "../types/index.js"
export function estreeToJs(estree: Estree) {
// TODO improve on this utility. Currently it's implemented to work
// for specific use cases as we don't have a lot of info on other
// use cases.
if (
!estree.body?.length ||
estree.body[0].type !== "ExpressionStatement" ||
!estree.body[0].expression
) {
return
}
return expressionToJs(estree.body[0].expression)
}
function expressionToJs(
expression: Expression
): ExpressionJsVar | ExpressionJsVar[] | undefined {
switch (expression.type) {
case "ArrayExpression":
const arrVar: ExpressionJsVar[] = []
;(expression as ArrayExpression).elements.forEach((elm) => {
const elmJsVar = expressionToJs(elm)
if (!elmJsVar) {
return
}
if (Array.isArray(elmJsVar)) {
arrVar.push(...elmJsVar)
} else {
arrVar.push(elmJsVar)
}
})
return arrVar
case "ObjectExpression":
const objVar: ExpressionJsVar = {}
;(expression as ObjectExpression).properties.forEach((property) => {
const keyName = property.key.name ?? property.key.value
if (!keyName) {
return
}
const jsVal = expressionToJs(property.value)
if (!jsVal) {
return
}
objVar[keyName] = jsVal
})
return objVar
case "Literal":
return {
original: expression,
data: (expression as LiteralExpression).value,
} as ExpressionJsVarLiteral
case "JSXElement":
// ignore JSXElements
return
}
}
@@ -1,21 +0,0 @@
import { ExpressionJsVarLiteral, ExpressionJsVarObj } from "../types/index.js"
export function isExpressionJsVarLiteral(
expression: unknown
): expression is ExpressionJsVarLiteral {
return (
typeof expression === "object" &&
expression !== null &&
Object.hasOwn(expression, "original")
)
}
export function isExpressionJsVarObj(
expression: unknown
): expression is ExpressionJsVarObj {
return (
typeof expression === "object" &&
expression !== null &&
!Object.hasOwn(expression, "original")
)
}
@@ -1,4 +1,4 @@
import { UnistNodeWithData } from "../types/index.js"
import { UnistNodeWithData } from "types"
export default function getAttribute(
node: UnistNodeWithData,
@@ -1,8 +1,5 @@
import { ExpressionJsVar, ExpressionJsVarLiteral } from "../types/index.js"
import {
isExpressionJsVarLiteral,
isExpressionJsVarObj,
} from "./expression-is-utils.js"
import { ExpressionJsVar, ExpressionJsVarLiteral } from "types"
import { isExpressionJsVarLiteral, isExpressionJsVarObj } from "docs-utils"
export const performActionOnLiteral = (
item: ExpressionJsVar[] | ExpressionJsVar,
@@ -1,5 +1,5 @@
import { Transformer } from "unified"
import { ComponentLinkFixerOptions } from "./types/index.js"
import { ComponentLinkFixerOptions } from "types"
import { componentLinkFixer } from "./utils/component-link-fixer.js"
export function workflowDiagramLinkFixerPlugin(