diff --git a/.changeset/lucky-wolves-whisper.md b/.changeset/lucky-wolves-whisper.md new file mode 100644 index 00000000..025c77ac --- /dev/null +++ b/.changeset/lucky-wolves-whisper.md @@ -0,0 +1,5 @@ +--- +"@alauda/doom": patch +--- + +feat: support linting site name usage diff --git a/packages/doom/src/remark-lint/index.ts b/packages/doom/src/remark-lint/index.ts index 3c5a2576..63599576 100644 --- a/packages/doom/src/remark-lint/index.ts +++ b/packages/doom/src/remark-lint/index.ts @@ -14,6 +14,7 @@ export * from './no-heading-punctuation.ts' export * from './no-heading-special-characters.ts' export * from './no-heading-sup-sub.ts' export * from './no-paragraph-indent.ts' +export * from './site.ts' export * from './table-size.ts' export * from './unit-case.ts' diff --git a/packages/doom/src/remark-lint/site.ts b/packages/doom/src/remark-lint/site.ts new file mode 100644 index 00000000..554a68d0 --- /dev/null +++ b/packages/doom/src/remark-lint/site.ts @@ -0,0 +1,59 @@ +import path from 'node:path' + +import type { Root } from 'mdast' +import type { MdxJsxAttribute } from 'mdast-util-mdx-jsx' +import { lintRule } from 'unified-lint-rule' +import { visitParents } from 'unist-util-visit-parents' + +import { SITES_FILE } from '../cli/constants.ts' +import type { DoomSite } from '../shared/types.ts' +import { pathExists } from '../utils/fs.ts' +import { resolveStaticConfig } from '../utils/helpers.ts' + +const SITE_ELEMENTS = new Set([ + 'ExternalApisOverview', + 'ExternalSite', + 'ExternalSiteLink', +]) + +const sitesConfigFilePath = path.resolve(SITES_FILE) + +let sites: DoomSite[] | undefined + +export const site = lintRule('doom:lint-site', async (root, vfile) => { + if (!sites) { + if (await pathExists(sitesConfigFilePath, 'file')) { + sites = await resolveStaticConfig(sitesConfigFilePath) + } else { + sites = [] + } + } + + visitParents(root, 'mdxJsxFlowElement', (element, parents) => { + if (!sites?.length || !element.name || !SITE_ELEMENTS.has(element.name)) { + return + } + + const nameAttr = element.attributes.find( + (attr): attr is MdxJsxAttribute => + attr.type === 'mdxJsxAttribute' && attr.name === 'name', + ) + + const nameVal = nameAttr?.value + + if ( + typeof nameVal === 'string' && + sites.some((site) => site.name === nameVal) + ) { + return + } + + vfile.message( + `Invalid site \`name\` property value \`${typeof nameVal === 'string' ? nameVal : nameVal?.value}\` which should be static string matching a site name from \`${SITES_FILE}\` config.`, + { + ancestors: [...parents, element], + place: (nameAttr ?? element).position, + }, + ) + }) +}) diff --git a/packages/doom/src/remarkrc.ts b/packages/doom/src/remarkrc.ts index 90e1b1fe..eb0ffbdd 100644 --- a/packages/doom/src/remarkrc.ts +++ b/packages/doom/src/remarkrc.ts @@ -16,6 +16,7 @@ import doomLint, { noDeepHeading, noDeepList, noParagraphIndent, + site, tableSize, unitCase, } from './remark-lint/index.ts' @@ -36,6 +37,7 @@ export default { noDeepHeading, noDeepList, noParagraphIndent, + site, tableSize, unitCase, ], diff --git a/packages/doom/src/runtime/components/ExternalSiteLink.tsx b/packages/doom/src/runtime/components/ExternalSiteLink.tsx index 5860fb39..99166ee0 100644 --- a/packages/doom/src/runtime/components/ExternalSiteLink.tsx +++ b/packages/doom/src/runtime/components/ExternalSiteLink.tsx @@ -1,4 +1,4 @@ -import { useLang, useSite } from '@rspress/core/runtime' +import { isProduction, useLang, useSite } from '@rspress/core/runtime' import { addTrailingSlash, isExternalUrl, @@ -38,12 +38,11 @@ const ExternalSiteLink_ = ({ const lang = useLang() if (!site) { - return ( - - No site with name `{name}` found, please ensure it's already defined at - `sites.yaml` - - ) + const message = `No site with name \`${name}\` found, please ensure it's already defined at \`sites.yaml\`` + if (isProduction()) { + throw new Error(message) + } + return {message} } if (isExternalUrl(href)) { diff --git a/packages/doom/src/runtime/components/_ExternalSiteBase.tsx b/packages/doom/src/runtime/components/_ExternalSiteBase.tsx index 3e66855b..235ff8d1 100644 --- a/packages/doom/src/runtime/components/_ExternalSiteBase.tsx +++ b/packages/doom/src/runtime/components/_ExternalSiteBase.tsx @@ -1,4 +1,4 @@ -import { useLang } from '@rspress/core/runtime' +import { isProduction, useLang } from '@rspress/core/runtime' import virtual from 'doom-@global-virtual' import { type FC, useMemo } from 'react' @@ -107,12 +107,11 @@ export const ExternalSiteBase = ({ name, template }: ExternalSiteBaseProps) => { ) if (!site) { - return ( - - No site with name `{name}` found, please ensure it's already defined at - `sites.yaml` - - ) + const message = `No site with name \`${name}\` found, please ensure it's already defined at \`sites.yaml\`` + if (isProduction()) { + throw new Error(message) + } + return {message} } const Notes = template === 'apisOverview' ? ApisOverviewNotes : SiteNotes