From f4ab7c65acbbbae412ffee543bb1fa0e2c646cbd Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Mon, 11 Apr 2022 09:36:30 +0800 Subject: [PATCH] feat(preset-classic, content-docs/client): JSDoc (#7148) * refactor: add JSDoc for preset-classic, content-docs/client * fix --- .../src/client/docsClientUtils.ts | 20 +++-- .../src/client/index.ts | 60 +++++++------- .../src/plugin-content-docs.d.ts | 68 ++++++---------- .../src/preset-classic.d.ts | 17 ++++ .../src/theme-classic.d.ts | 78 ++++++++++++++----- .../src/theme/DocPage/index.tsx | 2 +- website/tsconfig.json | 5 +- 7 files changed, 139 insertions(+), 111 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts index a6e3359fc52f..752a53d91edc 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts @@ -59,12 +59,10 @@ export function getActivePlugin( export const getLatestVersion = (data: GlobalPluginData): GlobalVersion => data.versions.find((version) => version.isLast)!; -// Note: return undefined on doc-unrelated pages, -// because there's no version currently considered as active -export const getActiveVersion = ( +export function getActiveVersion( data: GlobalPluginData, pathname: string, -): GlobalVersion | undefined => { +): GlobalVersion | undefined { const lastVersion = getLatestVersion(data); // Last version is a route like /docs/*, // we need to match it last or it would match /docs/version-1.0/* as well @@ -80,12 +78,12 @@ export const getActiveVersion = ( strict: false, }), ); -}; +} -export const getActiveDocContext = ( +export function getActiveDocContext( data: GlobalPluginData, pathname: string, -): ActiveDocContext => { +): ActiveDocContext { const activeVersion = getActiveVersion(data, pathname); const activeDoc = activeVersion?.docs.find( (doc) => @@ -119,15 +117,15 @@ export const getActiveDocContext = ( activeDoc, alternateDocVersions: alternateVersionDocs, }; -}; +} -export const getDocVersionSuggestions = ( +export function getDocVersionSuggestions( data: GlobalPluginData, pathname: string, -): DocVersionSuggestions => { +): DocVersionSuggestions { const latestVersion = getLatestVersion(data); const activeDocContext = getActiveDocContext(data, pathname); const latestDocSuggestion: GlobalDoc | undefined = activeDocContext?.alternateDocVersions[latestVersion.name]; return {latestDocSuggestion, latestVersionSuggestion: latestVersion}; -}; +} diff --git a/packages/docusaurus-plugin-content-docs/src/client/index.ts b/packages/docusaurus-plugin-content-docs/src/client/index.ts index 4923a069d0a6..69fedc1450d3 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/index.ts @@ -43,67 +43,61 @@ export const useDocsData = (pluginId: string | undefined): GlobalPluginData => }) as GlobalPluginData; // TODO this feature should be provided by docusaurus core -export const useActivePlugin = ( +export function useActivePlugin( options: UseDataOptions = {}, -): ActivePlugin | undefined => { +): ActivePlugin | undefined { const data = useAllDocsData(); const {pathname} = useLocation(); return getActivePlugin(data, pathname, options); -}; +} -export const useActivePluginAndVersion = ( +export function useActivePluginAndVersion( options: UseDataOptions = {}, ): - | undefined - | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} => { + | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} + | undefined { const activePlugin = useActivePlugin(options); const {pathname} = useLocation(); - if (activePlugin) { - const activeVersion = getActiveVersion(activePlugin.pluginData, pathname); - return { - activePlugin, - activeVersion, - }; + if (!activePlugin) { + return undefined; } - return undefined; -}; + const activeVersion = getActiveVersion(activePlugin.pluginData, pathname); + return { + activePlugin, + activeVersion, + }; +} -// versions are returned ordered (most recent first) -export const useVersions = (pluginId: string | undefined): GlobalVersion[] => { +export function useVersions(pluginId: string | undefined): GlobalVersion[] { const data = useDocsData(pluginId); return data.versions; -}; +} -export const useLatestVersion = ( - pluginId: string | undefined, -): GlobalVersion => { +export function useLatestVersion(pluginId: string | undefined): GlobalVersion { const data = useDocsData(pluginId); return getLatestVersion(data); -}; +} -// Note: return undefined on doc-unrelated pages, -// because there's no version currently considered as active -export const useActiveVersion = ( +export function useActiveVersion( pluginId: string | undefined, -): GlobalVersion | undefined => { +): GlobalVersion | undefined { const data = useDocsData(pluginId); const {pathname} = useLocation(); return getActiveVersion(data, pathname); -}; +} -export const useActiveDocContext = ( +export function useActiveDocContext( pluginId: string | undefined, -): ActiveDocContext => { +): ActiveDocContext { const data = useDocsData(pluginId); const {pathname} = useLocation(); return getActiveDocContext(data, pathname); -}; +} -// Useful to say "hey, you are not on the latest docs version, please switch" -export const useDocVersionSuggestions = ( +export function useDocVersionSuggestions( pluginId: string | undefined, -): DocVersionSuggestions => { +): DocVersionSuggestions { const data = useDocsData(pluginId); const {pathname} = useLocation(); return getDocVersionSuggestions(data, pathname); -}; +} diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index a8ae90e6afb1..bf5008ed985f 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -554,56 +554,18 @@ declare module '@theme/DocBreadcrumbs' { declare module '@theme/DocPage' { import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs'; - import type {DocumentRoute} from '@theme/DocItem'; + import type {RouteConfigComponentProps} from 'react-router-config'; + import type {Required} from 'utility-types'; - export interface Props { - readonly location: {readonly pathname: string}; + export interface Props extends Required { readonly versionMetadata: PropVersionMetadata; - readonly route: { - readonly path: string; - readonly component: () => JSX.Element; - readonly routes: DocumentRoute[]; - }; } export default function DocPage(props: Props): JSX.Element; } -declare module '@theme/DocPage/Layout' { - import type {ReactNode} from 'react'; - - export interface Props { - children: ReactNode; - } - - export default function DocPageLayout(props: Props): JSX.Element; -} - -declare module '@theme/DocPage/Layout/Aside' { - import type {Dispatch, SetStateAction} from 'react'; - import type {PropSidebar} from '@docusaurus/plugin-content-docs'; - - export interface Props { - sidebar: PropSidebar; - hiddenSidebarContainer: boolean; - setHiddenSidebarContainer: Dispatch>; - } - - export default function DocPageLayoutAside(props: Props): JSX.Element; -} - -declare module '@theme/DocPage/Layout/Main' { - import type {ReactNode} from 'react'; - - export interface Props { - hiddenSidebarContainer: boolean; - children: ReactNode; - } - - export default function DocPageLayoutMain(props: Props): JSX.Element; -} - -// TODO until TS supports exports field... hope it's in 4.6 +// TODO TS only supports reading `exports` in 4.7. We will need to merge the +// type defs (and JSDoc) here with the implementation after that declare module '@docusaurus/plugin-content-docs/client' { import type {UseDataOptions} from '@docusaurus/types'; @@ -617,6 +579,11 @@ declare module '@docusaurus/plugin-content-docs/client' { alternateDocVersions: {[versionName: string]: GlobalDoc}; }; export type GlobalDoc = { + /** + * For generated index pages, this is the `slug`, **not** `permalink` + * (without base URL). Because slugs have leading slashes but IDs don't, + * there won't be clashes. + */ id: string; path: string; sidebar: string | undefined; @@ -627,7 +594,8 @@ declare module '@docusaurus/plugin-content-docs/client' { label: string; isLast: boolean; path: string; - mainDocId: string; // home doc (if docs homepage configured), or first doc + /** The doc with `slug: /`, or first doc in first sidebar */ + mainDocId: string; docs: GlobalDoc[]; sidebars?: {[sidebarId: string]: GlobalSidebar}; }; @@ -645,9 +613,9 @@ declare module '@docusaurus/plugin-content-docs/client' { breadcrumbs: boolean; }; export type DocVersionSuggestions = { - // suggest the latest version + /** suggest the latest version */ latestVersionSuggestion: GlobalVersion; - // suggest the same doc, in latest version (if exist) + /** suggest the same doc, in latest version (if exist) */ latestDocSuggestion?: GlobalDoc; }; @@ -661,12 +629,20 @@ declare module '@docusaurus/plugin-content-docs/client' { ) => | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} | undefined; + /** Versions are returned ordered (most recent first). */ export const useVersions: (pluginId?: string) => GlobalVersion[]; export const useLatestVersion: (pluginId?: string) => GlobalVersion; + /** + * Returns `undefined` on doc-unrelated pages, because there's no version + * currently considered as active. + */ export const useActiveVersion: ( pluginId?: string, ) => GlobalVersion | undefined; export const useActiveDocContext: (pluginId?: string) => ActiveDocContext; + /** + * Useful to say "hey, you are not on the latest docs version, please switch" + */ export const useDocVersionSuggestions: ( pluginId?: string, ) => DocVersionSuggestions; diff --git a/packages/docusaurus-preset-classic/src/preset-classic.d.ts b/packages/docusaurus-preset-classic/src/preset-classic.d.ts index 57fbfdbb5da8..1e65f826fa31 100644 --- a/packages/docusaurus-preset-classic/src/preset-classic.d.ts +++ b/packages/docusaurus-preset-classic/src/preset-classic.d.ts @@ -17,13 +17,30 @@ import type {UserThemeConfig as ClassicThemeConfig} from '@docusaurus/theme-comm import type {UserThemeConfig as AlgoliaThemeConfig} from '@docusaurus/theme-search-algolia'; export type Options = { + /** + * Options for `@docusaurus/plugin-debug`. Use `false` to disable, or `true` + * to enable even in production. + */ debug?: boolean; + /** Options for `@docusaurus/plugin-content-docs`. Use `false` to disable. */ docs?: false | DocsPluginOptions; + /** Options for `@docusaurus/plugin-content-blog`. Use `false` to disable. */ blog?: false | BlogPluginOptions; + /** Options for `@docusaurus/plugin-content-pages`. Use `false` to disable. */ pages?: false | PagesPluginOptions; + /** Options for `@docusaurus/plugin-sitemap`. Use `false` to disable. */ sitemap?: false | SitemapPluginOptions; + /** Options for `@docusaurus/theme-classic`. */ theme?: ThemeOptions; + /** + * Options for `@docusaurus/plugin-google-analytics`. Only enabled when the + * key is present. + */ googleAnalytics?: GAPluginOptions; + /** + * Options for `@docusaurus/plugin-google-gtag`. Only enabled when the key + * is present. + */ gtag?: GtagPluginOptions; }; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index b8a29d637dc1..18da1b9d35d1 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -12,6 +12,14 @@ /// /// +// This file, like all the other ambient declaration files for plugins, is +// needed for TS to understand our `@theme` alias. The export signatures are +// duplicated from the implementation, which is fine, since every module only +// default-exports a React component. +// TODO we'll eventually migrate to TS `paths` option. This is not easy due to +// our theme shadowing—we probably need the user to specify multiple theme paths +// in their tsconfig. + declare module '@docusaurus/theme-classic' { export type Options = { customCss?: string | string[]; @@ -188,6 +196,40 @@ declare module '@theme/DocItemFooter' { export default function DocItemFooter(props: Props): JSX.Element; } +declare module '@theme/DocPage/Layout' { + import type {ReactNode} from 'react'; + + export interface Props { + readonly children: ReactNode; + } + + export default function DocPageLayout(props: Props): JSX.Element; +} + +declare module '@theme/DocPage/Layout/Aside' { + import type {Dispatch, SetStateAction} from 'react'; + import type {PropSidebar} from '@docusaurus/plugin-content-docs'; + + export interface Props { + readonly sidebar: PropSidebar; + readonly hiddenSidebarContainer: boolean; + readonly setHiddenSidebarContainer: Dispatch>; + } + + export default function DocPageLayoutAside(props: Props): JSX.Element; +} + +declare module '@theme/DocPage/Layout/Main' { + import type {ReactNode} from 'react'; + + export interface Props { + readonly hiddenSidebarContainer: boolean; + readonly children: ReactNode; + } + + export default function DocPageLayoutMain(props: Props): JSX.Element; +} + declare module '@theme/DocPaginator' { import type {PropNavigation} from '@docusaurus/plugin-content-docs'; @@ -242,7 +284,7 @@ declare module '@theme/DocSidebar/Desktop/Content' { declare module '@theme/DocSidebar/Desktop/CollapseButton' { export interface Props { - onClick: React.MouseEventHandler; + readonly onClick: React.MouseEventHandler; } export default function CollapseButton(props: Props): JSX.Element; @@ -269,7 +311,7 @@ declare module '@theme/DocSidebarItem/Link' { import type {PropSidebarItemLink} from '@docusaurus/plugin-content-docs'; export interface Props extends DocSidebarItemProps { - item: PropSidebarItemLink; + readonly item: PropSidebarItemLink; } export default function DocSidebarItemLink(props: Props): JSX.Element; @@ -280,7 +322,7 @@ declare module '@theme/DocSidebarItem/Html' { import type {PropSidebarItemHtml} from '@docusaurus/plugin-content-docs'; export interface Props extends DocSidebarItemProps { - item: PropSidebarItemHtml; + readonly item: PropSidebarItemHtml; } export default function DocSidebarItemHtml(props: Props): JSX.Element; @@ -291,7 +333,7 @@ declare module '@theme/DocSidebarItem/Category' { import type {PropSidebarItemCategory} from '@docusaurus/plugin-content-docs'; export interface Props extends DocSidebarItemProps { - item: PropSidebarItemCategory; + readonly item: PropSidebarItemCategory; } export default function DocSidebarItemCategory(props: Props): JSX.Element; @@ -350,7 +392,7 @@ declare module '@theme/Footer/Logo' { import type {FooterLogo} from '@docusaurus/theme-common'; export interface Props { - logo: FooterLogo; + readonly logo: FooterLogo; } export default function FooterLogo(props: Props): JSX.Element; @@ -358,7 +400,7 @@ declare module '@theme/Footer/Logo' { declare module '@theme/Footer/Copyright' { export interface Props { - copyright: string; + readonly copyright: string; } export default function FooterCopyright(props: Props): JSX.Element; @@ -368,7 +410,7 @@ declare module '@theme/Footer/LinkItem' { import type {FooterLinkItem} from '@docusaurus/theme-common'; export interface Props { - item: FooterLinkItem; + readonly item: FooterLinkItem; } export default function FooterLinkItem(props: Props): JSX.Element; @@ -378,10 +420,10 @@ declare module '@theme/Footer/Layout' { import type {ReactNode} from 'react'; export interface Props { - style: 'light' | 'dark'; - links: ReactNode; - logo: ReactNode; - copyright: ReactNode; + readonly style: 'light' | 'dark'; + readonly links: ReactNode; + readonly logo: ReactNode; + readonly copyright: ReactNode; } export default function FooterLayout(props: Props): JSX.Element; @@ -391,7 +433,7 @@ declare module '@theme/Footer/Links' { import type {Footer} from '@docusaurus/theme-common'; export interface Props { - links: Footer['links']; + readonly links: Footer['links']; } export default function FooterLinks(props: Props): JSX.Element; @@ -401,7 +443,7 @@ declare module '@theme/Footer/Links/MultiColumn' { import type {MultiColumnFooter} from '@docusaurus/theme-common'; export interface Props { - columns: MultiColumnFooter['links']; + readonly columns: MultiColumnFooter['links']; } export default function FooterLinksMultiColumn(props: Props): JSX.Element; @@ -411,7 +453,7 @@ declare module '@theme/Footer/Links/Simple' { import type {SimpleFooter} from '@docusaurus/theme-common'; export interface Props { - links: SimpleFooter['links']; + readonly links: SimpleFooter['links']; } export default function FooterLinksSimple(props: Props): JSX.Element; @@ -423,7 +465,7 @@ declare module '@theme/Heading' { type HeadingType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; export interface Props extends ComponentProps { - as: HeadingType; + readonly as: HeadingType; } export default function Heading(props: Props): JSX.Element; @@ -625,9 +667,9 @@ declare module '@theme/Navbar/MobileSidebar/Layout' { import type {ReactNode} from 'react'; interface Props { - header: ReactNode; - primaryMenu: ReactNode; - secondaryMenu: ReactNode; + readonly header: ReactNode; + readonly primaryMenu: ReactNode; + readonly secondaryMenu: ReactNode; } export default function NavbarMobileSidebarLayout(props: Props): JSX.Element; diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx index 2c908cc05535..32a226270459 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx @@ -34,7 +34,7 @@ function extractDocRouteMetadata(props: Props): null | { versionMetadata, location, } = props; - const currentDocRoute = docRoutes.find((docRoute) => + const currentDocRoute = docRoutes!.find((docRoute) => matchPath(location.pathname, docRoute), ); if (!currentDocRoute) { diff --git a/website/tsconfig.json b/website/tsconfig.json index 2e797c69f685..96c58c1bae33 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -7,8 +7,9 @@ "resolveJsonModule": true, "strict": true, // This is important. We run `yarn tsc` in website so we can catch issues - // with our declaration files (mostly name that are forgotten to be - // imported). Removing this would make things harder to catch. + // with our declaration files (mostly names that are forgotten to be + // imported, invalid semantics...). Because we don't have end-to-end type + // tests, removing this would make things much harder to catch. "skipLibCheck": false, "types": ["@types/jest"] },