From 6e2eb44964951861a395682eaa1008b73d5cce43 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 22 Mar 2022 19:40:56 +0800 Subject: [PATCH] refactor: unify how validateOptions is handled (#6961) * refactor: unify how validateOptions is handled * fix types * fix again --- .../src/__tests__/options.test.ts | 7 +- .../src/index.ts | 4 +- .../src/options.ts | 8 +- .../__tests__/__snapshots__/options.test.ts.snap | 5 + .../__snapshots__/pluginOptionSchema.test.ts.snap | 5 - .../src/__tests__/feed.test.ts | 2 +- ...blogFrontMatter.test.ts => frontMatter.test.ts} | 2 +- .../src/__tests__/index.test.ts | 29 ++-- .../src/__tests__/options.test.ts | 160 +++++++++++++++++ .../src/__tests__/pluginOptionSchema.test.ts | 152 ---------------- .../src/__tests__/translations.test.ts | 2 +- .../src/blogUtils.ts | 2 +- .../src/{blogFrontMatter.ts => frontMatter.ts} | 0 .../docusaurus-plugin-content-blog/src/index.ts | 17 +- .../src/{pluginOptionSchema.ts => options.ts} | 18 +- ...{docFrontMatter.test.ts => frontMatter.test.ts} | 2 +- .../src/__tests__/index.test.ts | 133 +++++++++----- .../src/__tests__/options.test.ts | 193 +++++++++------------ .../docusaurus-plugin-content-docs/src/docs.ts | 2 +- .../src/{docFrontMatter.ts => frontMatter.ts} | 0 .../docusaurus-plugin-content-docs/src/options.ts | 14 +- .../src/__tests__/index.test.ts | 21 ++- ...{pluginOptionSchema.test.ts => options.test.ts} | 40 ++--- .../src/{pageFrontMatter.ts => frontMatter.ts} | 0 .../docusaurus-plugin-content-pages/src/index.ts | 20 +-- .../src/{pluginOptionSchema.ts => options.ts} | 13 +- .../src/index.ts | 7 +- .../docusaurus-plugin-google-gtag/src/index.ts | 7 +- .../docusaurus-plugin-ideal-image/src/index.ts | 3 +- packages/docusaurus-plugin-pwa/src/options.ts | 10 +- .../src/__tests__/options.test.ts | 52 ++++++ .../src/__tests__/pluginOptionSchema.test.ts | 55 ------ packages/docusaurus-plugin-sitemap/src/index.ts | 19 +- .../src/{pluginOptionSchema.ts => options.ts} | 13 +- .../src/plugin-sitemap.d.ts | 1 + packages/docusaurus-theme-classic/src/index.ts | 4 +- .../src/validateThemeConfig.ts | 10 +- .../src/validateThemeConfig.ts | 10 +- .../src/validateThemeConfig.ts | 10 +- packages/docusaurus-types/src/index.d.ts | 22 ++- .../src/__tests__/validationUtils.test.ts | 3 + .../src/validationUtils.ts | 3 +- packages/docusaurus/src/server/plugins/init.ts | 2 +- 43 files changed, 542 insertions(+), 540 deletions(-) create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap delete mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap rename packages/docusaurus-plugin-content-blog/src/__tests__/{blogFrontMatter.test.ts => frontMatter.test.ts} (99%) create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts delete mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts rename packages/docusaurus-plugin-content-blog/src/{blogFrontMatter.ts => frontMatter.ts} (100%) rename packages/docusaurus-plugin-content-blog/src/{pluginOptionSchema.ts => options.ts} (91%) rename packages/docusaurus-plugin-content-docs/src/__tests__/{docFrontMatter.test.ts => frontMatter.test.ts} (99%) rename packages/docusaurus-plugin-content-docs/src/{docFrontMatter.ts => frontMatter.ts} (100%) rename packages/docusaurus-plugin-content-pages/src/__tests__/{pluginOptionSchema.test.ts => options.test.ts} (53%) rename packages/docusaurus-plugin-content-pages/src/{pageFrontMatter.ts => frontMatter.ts} (100%) rename packages/docusaurus-plugin-content-pages/src/{pluginOptionSchema.ts => options.ts} (79%) create mode 100644 packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts delete mode 100644 packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts rename packages/docusaurus-plugin-sitemap/src/{pluginOptionSchema.ts => options.ts} (71%) diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts index a8e57816a3..0354dcfe99 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts @@ -15,11 +15,14 @@ function testValidate(options: Options) { describe('normalizePluginOptions', () => { it('returns default options for undefined user options', () => { - expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS); + expect(testValidate(undefined)).toEqual({ + ...DEFAULT_OPTIONS, + id: 'default', + }); }); it('returns default options for empty user options', () => { - expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS); + expect(testValidate({})).toEqual({...DEFAULT_OPTIONS, id: 'default'}); }); it('overrides one default options with valid user options', () => { diff --git a/packages/docusaurus-plugin-client-redirects/src/index.ts b/packages/docusaurus-plugin-client-redirects/src/index.ts index d5a20c4059..21ec1195f3 100644 --- a/packages/docusaurus-plugin-client-redirects/src/index.ts +++ b/packages/docusaurus-plugin-client-redirects/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadContext, Plugin, Props} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginContext, RedirectMetadata} from './types'; import type {PluginOptions} from '@docusaurus/plugin-client-redirects'; @@ -24,7 +24,7 @@ export default function pluginClientRedirectsPages( return { name: 'docusaurus-plugin-client-redirects', - async postBuild(props: Props) { + async postBuild(props) { const pluginContext: PluginContext = { relativeRoutesPaths: props.routesPaths.map( (path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`, diff --git a/packages/docusaurus-plugin-client-redirects/src/options.ts b/packages/docusaurus-plugin-client-redirects/src/options.ts index 1e997227c9..d81b535482 100644 --- a/packages/docusaurus-plugin-client-redirects/src/options.ts +++ b/packages/docusaurus-plugin-client-redirects/src/options.ts @@ -7,12 +7,10 @@ import type { PluginOptions, + Options, RedirectOption, } from '@docusaurus/plugin-client-redirects'; -import type { - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; +import type {OptionValidationContext} from '@docusaurus/types'; import {Joi, PathnameSchema} from '@docusaurus/utils-validation'; export const DEFAULT_OPTIONS: Partial = { @@ -47,6 +45,6 @@ const UserOptionsSchema = Joi.object({ export function validateOptions({ validate, options: userOptions, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(UserOptionsSchema, userOptions); } diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap new file mode 100644 index 0000000000..76f0a53c41 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validateOptions throws Error in case of invalid feed type 1`] = `"\\"feedOptions.type\\" does not match any of the allowed types"`; + +exports[`validateOptions throws Error in case of invalid options 1`] = `"\\"postsPerPage\\" must be greater than or equal to 1"`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap deleted file mode 100644 index c598236fd0..0000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`blog plugin options schema throws Error in case of invalid feed type 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`; - -exports[`blog plugin options schema throws Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be greater than or equal to 1]`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index 03b4abb14d..facf967523 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -11,7 +11,7 @@ import fs from 'fs-extra'; import {createBlogFeedFiles} from '../feed'; import type {LoadContext, I18n} from '@docusaurus/types'; import type {BlogContentPaths} from '../types'; -import {DEFAULT_OPTIONS} from '../pluginOptionSchema'; +import {DEFAULT_OPTIONS} from '../options'; import {generateBlogPosts} from '../blogUtils'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts similarity index 99% rename from packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts rename to packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index b8d7d9047f..5a8dc0ebd8 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {validateBlogPostFrontMatter} from '../blogFrontMatter'; +import {validateBlogPostFrontMatter} from '../frontMatter'; import escapeStringRegexp from 'escape-string-regexp'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 2a6d9d24f7..2a19736683 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -9,9 +9,9 @@ import {jest} from '@jest/globals'; import path from 'path'; import pluginContentBlog from '../index'; import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types'; -import {PluginOptionSchema} from '../pluginOptionSchema'; +import {validateOptions} from '../options'; import type {BlogPost} from '../types'; -import type {Joi} from '@docusaurus/utils-validation'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {posixPath, getFileCommitDate} from '@docusaurus/utils'; import type { PluginOptions, @@ -47,18 +47,6 @@ function getI18n(locale: string): I18n { const DefaultI18N: I18n = getI18n('en'); -function validateAndNormalize( - schema: Joi.ObjectSchema, - options: Partial, -) { - const {value, error} = schema.validate(options); - if (error) { - throw error; - } else { - return value; - } -} - const PluginPath = 'blog'; const BaseEditUrl = 'https://baseEditUrl.com/edit'; @@ -81,11 +69,14 @@ const getPlugin = async ( generatedFilesDir, i18n, } as LoadContext, - validateAndNormalize(PluginOptionSchema, { - path: PluginPath, - editUrl: BaseEditUrl, - ...pluginOptions, - }), + validateOptions({ + validate: normalizePluginOptions, + options: { + path: PluginPath, + editUrl: BaseEditUrl, + ...pluginOptions, + }, + }) as PluginOptions, ); }; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts new file mode 100644 index 0000000000..738884dd02 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts @@ -0,0 +1,160 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-content-blog'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +// the type of remark/rehype plugins can be either function, object or array +const markdownPluginsFunctionStub = () => {}; +const markdownPluginsObjectStub = {}; + +const defaultOptions = {...DEFAULT_OPTIONS, id: 'default'}; + +describe('validateOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate(undefined)).toEqual(defaultOptions); + }); + + it('returns default options for empty user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + ...defaultOptions, + feedOptions: {type: 'rss' as const, title: 'myTitle'}, + path: 'not_blog', + routeBasePath: 'myBlog', + postsPerPage: 5, + include: ['api/*', 'docs/*'], + }; + expect(testValidate(userOptions)).toEqual({ + ...userOptions, + feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''}, + }); + }); + + it('accepts valid user options', () => { + const userOptions = { + ...defaultOptions, + routeBasePath: 'myBlog', + beforeDefaultRemarkPlugins: [], + beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], + remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], + rehypePlugins: [ + markdownPluginsObjectStub, + [markdownPluginsFunctionStub, {option1: '42'}], + ], + }; + expect(testValidate(userOptions)).toEqual(userOptions); + }); + + it('throws Error in case of invalid options', () => { + expect(() => + testValidate({ + path: 'not_blog', + postsPerPage: -1, + include: ['api/*', 'docs/*'], + routeBasePath: 'not_blog', + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('throws Error in case of invalid feed type', () => { + expect(() => + testValidate({ + feedOptions: { + type: 'none', + }, + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('converts all feed type to array with other feed type', () => { + expect( + testValidate({ + feedOptions: {type: 'all'}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''}, + }); + }); + + it('accepts null type and return same', () => { + expect( + testValidate({ + feedOptions: {type: null}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: null}, + }); + }); + + it('contains array with rss + atom for missing feed type', () => { + expect( + testValidate({ + feedOptions: {}, + }), + ).toEqual(defaultOptions); + }); + + it('has array with rss + atom, title for missing feed type', () => { + expect( + testValidate({ + feedOptions: {title: 'title'}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''}, + }); + }); + + it('accepts 0 sidebar count', () => { + const userOptions = {blogSidebarCount: 0}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('accepts "ALL" sidebar count', () => { + const userOptions = {blogSidebarCount: 'ALL' as const}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects "abcdef" sidebar count', () => { + const userOptions = {blogSidebarCount: 'abcdef'}; + expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot( + `"\\"blogSidebarCount\\" must be one of [ALL, number]"`, + ); + }); + + it('accepts "all posts" sidebar title', () => { + const userOptions = {blogSidebarTitle: 'all posts'}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects 42 sidebar title', () => { + const userOptions = {blogSidebarTitle: 42}; + expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot( + `"\\"blogSidebarTitle\\" must be a string"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts deleted file mode 100644 index 5aa55c3535..0000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; - -// the type of remark/rehype plugins can be either function, object or array -const markdownPluginsFunctionStub = () => {}; -const markdownPluginsObjectStub = {}; - -describe('blog plugin options schema', () => { - it('normalizes options', () => { - const {value, error} = PluginOptionSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - expect(error).toBeUndefined(); - }); - - it('accepts correctly defined user options', () => { - const userOptions = { - ...DEFAULT_OPTIONS, - feedOptions: {type: 'rss', title: 'myTitle'}, - path: 'not_blog', - routeBasePath: 'myBlog', - postsPerPage: 5, - include: ['api/*', 'docs/*'], - }; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({ - ...userOptions, - feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''}, - }); - expect(error).toBeUndefined(); - }); - - it('accepts valid user options', async () => { - const userOptions = { - ...DEFAULT_OPTIONS, - routeBasePath: 'myBlog', - beforeDefaultRemarkPlugins: [], - beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], - remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], - rehypePlugins: [ - markdownPluginsObjectStub, - [markdownPluginsFunctionStub, {option1: '42'}], - ], - }; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBeUndefined(); - }); - - it('throws Error in case of invalid options', () => { - const {error} = PluginOptionSchema.validate({ - path: 'not_blog', - postsPerPage: -1, - include: ['api/*', 'docs/*'], - routeBasePath: 'not_blog', - }); - - expect(error).toMatchSnapshot(); - }); - - it('throws Error in case of invalid feed type', () => { - const {error} = PluginOptionSchema.validate({ - feedOptions: { - type: 'none', - }, - }); - - expect(error).toMatchSnapshot(); - }); - - it('converts all feed type to array with other feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {type: 'all'}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''}, - }); - }); - - it('accepts null type and return same', () => { - const {value, error} = PluginOptionSchema.validate({ - feedOptions: {type: null}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: null}, - }); - expect(error).toBeUndefined(); - }); - - it('contains array with rss + atom for missing feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {}, - }); - expect(value).toEqual(DEFAULT_OPTIONS); - }); - - it('has array with rss + atom, title for missing feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {title: 'title'}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''}, - }); - }); -}); - -describe('blog sidebar', () => { - it('accepts 0 sidebar count', () => { - const userOptions = {blogSidebarCount: 0}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBeUndefined(); - }); - - it('accepts "ALL" sidebar count', () => { - const userOptions = {blogSidebarCount: 'ALL'}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBeUndefined(); - }); - - it('rejects "abcdef" sidebar count', () => { - const userOptions = {blogSidebarCount: 'abcdef'}; - const {error} = PluginOptionSchema.validate(userOptions); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "blogSidebarCount" must be one of [ALL, number]]`, - ); - }); - - it('accepts "all posts" sidebar title', () => { - const userOptions = {blogSidebarTitle: 'all posts'}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBeUndefined(); - }); - - it('rejects 42 sidebar title', () => { - const userOptions = {blogSidebarTitle: 42}; - const {error} = PluginOptionSchema.validate(userOptions); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "blogSidebarTitle" must be a string]`, - ); - }); -}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts index b6f950a10e..840a3af4d3 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts @@ -7,7 +7,7 @@ import type {BlogPost, BlogContent} from '../types'; import {getTranslationFiles, translateContent} from '../translations'; -import {DEFAULT_OPTIONS} from '../pluginOptionSchema'; +import {DEFAULT_OPTIONS} from '../options'; import {updateTranslationFileMessages} from '@docusaurus/utils'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index a19f55d641..59b38a5e5e 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -31,7 +31,7 @@ import { getContentPathList, } from '@docusaurus/utils'; import type {LoadContext} from '@docusaurus/types'; -import {validateBlogPostFrontMatter} from './blogFrontMatter'; +import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import logger from '@docusaurus/logger'; import type { diff --git a/packages/docusaurus-plugin-content-blog/src/blogFrontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts similarity index 100% rename from packages/docusaurus-plugin-content-blog/src/blogFrontMatter.ts rename to packages/docusaurus-plugin-content-blog/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 754aee6b11..d8338c97ce 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -30,14 +30,7 @@ import type { BlogContentPaths, BlogMarkdownLoaderOptions, } from './types'; -import {PluginOptionSchema} from './pluginOptionSchema'; -import type { - LoadContext, - Plugin, - HtmlTags, - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; +import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types'; import { generateBlogPosts, getSourceToPermalink, @@ -572,10 +565,4 @@ export default async function pluginContentBlog( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-blog/src/options.ts similarity index 91% rename from packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-content-blog/src/options.ts index 666ec5f22d..c4c3441163 100644 --- a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -13,7 +13,8 @@ import { URISchema, } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type {PluginOptions} from '@docusaurus/plugin-content-blog'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-blog'; +import type {OptionValidationContext} from '@docusaurus/types'; export const DEFAULT_OPTIONS: PluginOptions = { feedOptions: {type: ['rss', 'atom'], copyright: ''}, @@ -46,7 +47,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { sortPosts: 'descending', }; -export const PluginOptionSchema = Joi.object({ +const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), archiveBasePath: Joi.string() .default(DEFAULT_OPTIONS.archiveBasePath) @@ -125,4 +126,15 @@ export const PluginOptionSchema = Joi.object({ sortPosts: Joi.string() .valid('descending', 'ascending') .default(DEFAULT_OPTIONS.sortPosts), -}); +}).default(DEFAULT_OPTIONS); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + const validatedOptions = validate( + PluginOptionSchema, + options, + ) as PluginOptions; + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts similarity index 99% rename from packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts rename to packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 5f8aa9d787..1fa4809c48 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {validateDocFrontMatter} from '../docFrontMatter'; +import {validateDocFrontMatter} from '../frontMatter'; import type {DocFrontMatter} from '../types'; import escapeStringRegexp from 'escape-string-regexp'; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 22c46c6372..0d6df18fe9 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -20,7 +20,7 @@ import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import {sortConfig} from '@docusaurus/core/src/server/plugins'; import * as cliDocs from '../cli'; -import {OptionsSchema} from '../options'; +import {validateOptions} from '../options'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import type {LoadedVersion} from '../types'; import type { @@ -119,8 +119,11 @@ describe('sidebar', () => { const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath, + }, }), ); await expect(plugin.loadContent!()).rejects.toThrowErrorMatchingSnapshot(); @@ -133,8 +136,11 @@ describe('sidebar', () => { await expect(async () => { const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: 'wrong-path-sidebar.json', + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: 'wrong-path-sidebar.json', + }, }), ); await plugin.loadContent!(); @@ -152,8 +158,11 @@ describe('sidebar', () => { const context = await loadContext(siteDir); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: undefined, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: undefined, + }, }), ); const result = await plugin.loadContent!(); @@ -167,8 +176,11 @@ describe('sidebar', () => { const context = await loadContext(siteDir); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: false, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: false, + }, }), ); const result = await plugin.loadContent!(); @@ -186,7 +198,7 @@ describe('empty/no docs website', () => { await fs.ensureDir(path.join(siteDir, 'docs')); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, {}), + validateOptions({validate: normalizePluginOptions, options: {}}), ); await expect( plugin.loadContent!(), @@ -200,8 +212,11 @@ describe('empty/no docs website', () => { await expect( pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: `path/does/not/exist`, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'path/does/not/exist', + }, }), ), ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -217,9 +232,12 @@ describe('simple website', () => { const sidebarPath = path.join(siteDir, 'sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -328,9 +346,12 @@ describe('versioned website', () => { const routeBasePath = 'docs'; const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - routeBasePath, - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + routeBasePath, + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -455,11 +476,14 @@ describe('versioned website (community)', () => { const pluginId = 'community'; const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - id: 'community', - path: 'community', - routeBasePath, - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + id: 'community', + path: 'community', + routeBasePath, + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -558,9 +582,12 @@ describe('site with doc label', () => { const sidebarPath = path.join(siteDir, 'sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath, + }, }), ); @@ -596,8 +623,11 @@ describe('site with full autogenerated sidebar', () => { const context = await loadContext(siteDir); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + }, }), ); @@ -648,14 +678,17 @@ describe('site with partial autogenerated sidebars', () => { const context = await loadContext(siteDir, {}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath: path.join( - __dirname, - '__fixtures__', - 'site-with-autogenerated-sidebar', - 'partialAutogeneratedSidebars.js', - ), + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath: path.join( + __dirname, + '__fixtures__', + 'site-with-autogenerated-sidebar', + 'partialAutogeneratedSidebars.js', + ), + }, }), ); @@ -701,14 +734,17 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { const context = await loadContext(siteDir, {}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath: path.join( - __dirname, - '__fixtures__', - 'site-with-autogenerated-sidebar', - 'partialAutogeneratedSidebars2.js', - ), + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath: path.join( + __dirname, + '__fixtures__', + 'site-with-autogenerated-sidebar', + 'partialAutogeneratedSidebars2.js', + ), + }, }), ); @@ -735,9 +771,12 @@ describe('site with custom sidebar items generator', () => { const context = await loadContext(siteDir); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarItemsGenerator, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarItemsGenerator, + }, }), ); const content = (await plugin.loadContent?.())!; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts index a4833aa6d7..89bec2179d 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {OptionsSchema, DEFAULT_OPTIONS, validateOptions} from '../options'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {DefaultSidebarItemsGenerator} from '../sidebars/generator'; import { @@ -13,27 +13,26 @@ import { DisabledNumberPrefixParser, } from '../numberPrefix'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import type {Options} from '@docusaurus/plugin-content-docs'; // the type of remark/rehype plugins is function const markdownPluginsFunctionStub = () => {}; const markdownPluginsObjectStub = {}; -function testValidateOptions(options: Partial) { - return validateOptions({ - options: { - ...DEFAULT_OPTIONS, - ...options, - }, - validate: normalizePluginOptions, - }); +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); } +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', + // The admonitions plugin is automatically added. Not really worth testing + remarkPlugins: expect.any(Array), +}; + describe('normalizeDocsPluginOptions', () => { it('returns default options for undefined user options', async () => { - const {value, error} = await OptionsSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - expect(error).toBeUndefined(); + expect(testValidate({})).toEqual(defaultOptions); }); it('accepts correctly defined user options', async () => { @@ -77,14 +76,15 @@ describe('normalizeDocsPluginOptions', () => { sidebarCollapsible: false, sidebarCollapsed: false, }; - const {value, error} = await OptionsSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBeUndefined(); + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)], + }); }); it('accepts correctly defined remark and rehype plugin options', async () => { const userOptions = { - ...DEFAULT_OPTIONS, beforeDefaultRemarkPlugins: [], beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], @@ -93,85 +93,71 @@ describe('normalizeDocsPluginOptions', () => { [markdownPluginsFunctionStub, {option1: '42'}], ], }; - const {value, error} = await OptionsSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBeUndefined(); + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)], + }); }); it('accepts admonitions false', async () => { const admonitionsFalse = { - ...DEFAULT_OPTIONS, admonitions: false, }; - const {value, error} = OptionsSchema.validate(admonitionsFalse); - expect(value).toEqual(admonitionsFalse); - expect(error).toBeUndefined(); - }); - - it('accepts numberPrefixParser function', () => { - function customNumberPrefixParser() {} - expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: customNumberPrefixParser, - }), - ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', - numberPrefixParser: customNumberPrefixParser, - }); - }); - - it('accepts numberPrefixParser false', () => { - expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: false, - }), - ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', - numberPrefixParser: DisabledNumberPrefixParser, - }); - }); - - it('accepts numberPrefixParser true', () => { - expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: true, - }), - ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', - numberPrefixParser: DefaultNumberPrefixParser, + expect(testValidate(admonitionsFalse)).toEqual({ + ...defaultOptions, + ...admonitionsFalse, }); }); it('rejects admonitions true', async () => { const admonitionsTrue = { - ...DEFAULT_OPTIONS, admonitions: true, }; - const {error} = OptionsSchema.validate(admonitionsTrue); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "admonitions" contains an invalid value]`, + expect(() => + testValidate(admonitionsTrue), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"admonitions\\" contains an invalid value"`, ); }); + it('accepts numberPrefixParser function', () => { + function customNumberPrefixParser() {} + expect( + testValidate({numberPrefixParser: customNumberPrefixParser}), + ).toEqual({ + ...defaultOptions, + numberPrefixParser: customNumberPrefixParser, + }); + }); + + it('accepts numberPrefixParser false', () => { + expect(testValidate({numberPrefixParser: false})).toEqual({ + ...defaultOptions, + numberPrefixParser: DisabledNumberPrefixParser, + }); + }); + + it('accepts numberPrefixParser true', () => { + expect(testValidate({numberPrefixParser: true})).toEqual({ + ...defaultOptions, + numberPrefixParser: DefaultNumberPrefixParser, + }); + }); + it('rejects invalid remark plugin options', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + expect(() => + testValidate({ remarkPlugins: [[{option1: '42'}, markdownPluginsFunctionStub]], - }); - }).toThrowErrorMatchingInlineSnapshot( + }), + ).toThrowErrorMatchingInlineSnapshot( `"\\"remarkPlugins[0]\\" does not match any of the allowed types"`, ); }); it('rejects invalid rehype plugin options', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + expect(() => + testValidate({ rehypePlugins: [ [ markdownPluginsFunctionStub, @@ -179,61 +165,51 @@ describe('normalizeDocsPluginOptions', () => { markdownPluginsFunctionStub, ], ], - }); - }).toThrowErrorMatchingInlineSnapshot( + }), + ).toThrowErrorMatchingInlineSnapshot( `"\\"rehypePlugins[0]\\" does not match any of the allowed types"`, ); }); it('rejects bad path inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - path: 2, - }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); + expect(() => testValidate({path: 2})).toThrowErrorMatchingInlineSnapshot( + `"\\"path\\" must be a string"`, + ); }); it('rejects bad include inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - include: '**/*.{md,mdx}', - }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`); + expect(() => + testValidate({include: '**/*.{md,mdx}'}), + ).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`); }); it('rejects bad showLastUpdateTime inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - showLastUpdateTime: 'true', - }); - }).toThrowErrorMatchingInlineSnapshot( + expect(() => + testValidate({showLastUpdateTime: 'true'}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"showLastUpdateTime\\" must be a boolean"`, ); }); it('rejects bad remarkPlugins input', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - remarkPlugins: 'remark-math', - }); - }).toThrowErrorMatchingInlineSnapshot( + expect(() => + testValidate({remarkPlugins: 'remark-math'}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"remarkPlugins\\" must be an array"`, ); }); it('rejects bad lastVersion', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - lastVersion: false, - }); - }).toThrowErrorMatchingInlineSnapshot( + expect(() => + testValidate({lastVersion: false}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"lastVersion\\" must be a string"`, ); }); it('rejects bad versions', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + expect(() => + testValidate({ versions: { current: { hey: 3, @@ -243,32 +219,29 @@ describe('normalizeDocsPluginOptions', () => { label: 'world', }, }, - }); - }).toThrowErrorMatchingInlineSnapshot( + }), + ).toThrowErrorMatchingInlineSnapshot( `"\\"versions.current.hey\\" is not allowed"`, ); }); it('handles sidebarCollapsed option inconsistencies', () => { expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: true, sidebarCollapsed: undefined, }).sidebarCollapsed, ).toBe(true); expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: false, sidebarCollapsed: undefined, }).sidebarCollapsed, ).toBe(false); expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: false, sidebarCollapsed: true, }).sidebarCollapsed, diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 9fcfa8ee32..c94ff470b0 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -34,7 +34,7 @@ import getSlug from './slug'; import {CURRENT_VERSION_NAME} from './constants'; import {getDocsDirPaths} from './versions'; import {stripPathNumberPrefixes} from './numberPrefix'; -import {validateDocFrontMatter} from './docFrontMatter'; +import {validateDocFrontMatter} from './frontMatter'; import type {SidebarsUtils} from './sidebars/utils'; import {toDocNavigationLink, toNavigationLink} from './sidebars/utils'; import type { diff --git a/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts similarity index 100% rename from packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts rename to packages/docusaurus-plugin-content-docs/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index 3e5e9f5a7a..258236acaa 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-docs'; import { Joi, RemarkPluginsSchema, @@ -15,10 +15,7 @@ import { } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type { - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; +import type {OptionValidationContext} from '@docusaurus/types'; import logger from '@docusaurus/logger'; import admonitions from 'remark-admonitions'; import {DefaultSidebarItemsGenerator} from './sidebars/generator'; @@ -70,7 +67,7 @@ const VersionsOptionsSchema = Joi.object() .pattern(Joi.string().required(), VersionOptionsSchema) .default(DEFAULT_OPTIONS.versions); -export const OptionsSchema = Joi.object({ +const OptionsSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), editUrl: Joi.alternatives().try(URISchema, Joi.function()), editCurrentVersion: Joi.boolean().default(DEFAULT_OPTIONS.editCurrentVersion), @@ -80,6 +77,7 @@ export const OptionsSchema = Joi.object({ // .allow('') "" .default(DEFAULT_OPTIONS.routeBasePath), tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath), + // @ts-expect-error: deprecated homePageId: Joi.any().forbidden().messages({ 'any.unknown': 'The docs plugin option homePageId is not supported anymore. To make a doc the "home", please add "slug: /" in its front matter. See: https://docusaurus.io/docs/next/docs-introduction#home-page-docs', @@ -146,7 +144,7 @@ export const OptionsSchema = Joi.object({ export function validateOptions({ validate, options: userOptions, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { let options = userOptions; if (options.sidebarCollapsible === false) { @@ -168,7 +166,7 @@ export function validateOptions({ } } - const normalizedOptions = validate(OptionsSchema, options); + const normalizedOptions = validate(OptionsSchema, options) as PluginOptions; if (normalizedOptions.admonitions) { normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([ diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index b71ffcdfad..7e3df9c7d1 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -9,7 +9,8 @@ import path from 'path'; import {loadContext} from '@docusaurus/core/lib/server'; import pluginContentPages from '../index'; -import {PluginOptionSchema} from '../pluginOptionSchema'; +import {validateOptions} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; describe('docusaurus-plugin-content-pages', () => { it('loads simple pages', async () => { @@ -17,9 +18,12 @@ describe('docusaurus-plugin-content-pages', () => { const context = await loadContext(siteDir); const plugin = await pluginContentPages( context, - PluginOptionSchema.validate({ - path: 'src/pages', - }).value, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/pages', + }, + }), ); const pagesMetadata = await plugin.loadContent!(); @@ -37,9 +41,12 @@ describe('docusaurus-plugin-content-pages', () => { currentLocale: 'fr', }, }, - PluginOptionSchema.validate({ - path: 'src/pages', - }).value, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/pages', + }, + }), ); const pagesMetadata = await plugin.loadContent!(); diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts similarity index 53% rename from packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts rename to packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts index 484ffac915..5038527034 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts @@ -5,31 +5,29 @@ * LICENSE file in the root directory of this source tree. */ -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; -import type {PluginOptions} from '@docusaurus/plugin-content-pages'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-content-pages'; -function normalizePluginOptions( - options: Partial, -): PluginOptions { - const {value, error} = PluginOptionSchema.validate(options, { - convert: false, - }); - if (error) { - throw error; - } else { - return value; - } +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); } +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + describe('normalizePagesPluginOptions', () => { it('returns default options for undefined user options', () => { - const value = normalizePluginOptions({}); - expect(value).toEqual(DEFAULT_OPTIONS); + expect(testValidate({})).toEqual(defaultOptions); }); it('fills in default options for partially defined user options', () => { - const value = normalizePluginOptions({path: 'src/pages'}); - expect(value).toEqual(DEFAULT_OPTIONS); + expect(testValidate({path: 'src/foo'})).toEqual({ + ...defaultOptions, + path: 'src/foo', + }); }); it('accepts correctly defined user options', () => { @@ -39,13 +37,15 @@ describe('normalizePagesPluginOptions', () => { include: ['**/*.{js,jsx,ts,tsx}'], exclude: ['**/$*/'], }; - const value = normalizePluginOptions(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); }); it('rejects bad path inputs', () => { expect(() => { - normalizePluginOptions({ + testValidate({ // @ts-expect-error: bad attribute path: 42, }); diff --git a/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts b/packages/docusaurus-plugin-content-pages/src/frontMatter.ts similarity index 100% rename from packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts rename to packages/docusaurus-plugin-content-pages/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index d7f4c48f90..c205743e6a 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -21,15 +21,9 @@ import { DEFAULT_PLUGIN_ID, parseMarkdownString, } from '@docusaurus/utils'; -import type { - LoadContext, - Plugin, - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import admonitions from 'remark-admonitions'; -import {PluginOptionSchema} from './pluginOptionSchema'; -import {validatePageFrontMatter} from './pageFrontMatter'; +import {validatePageFrontMatter} from './frontMatter'; import type {LoadedContent, PagesContentPaths} from './types'; import type {PluginOptions, Metadata} from '@docusaurus/plugin-content-pages'; @@ -176,7 +170,7 @@ export default async function pluginContentPages( ); }, - configureWebpack(_config, isServer, {getJSLoader}) { + configureWebpack(config, isServer, {getJSLoader}) { const { rehypePlugins, remarkPlugins, @@ -241,10 +235,4 @@ export default async function pluginContentPages( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-pages/src/options.ts similarity index 79% rename from packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-content-pages/src/options.ts index 662916af10..9713fea810 100644 --- a/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-content-pages/src/options.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {PluginOptions} from '@docusaurus/plugin-content-pages'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-pages'; import { Joi, RemarkPluginsSchema, @@ -13,6 +13,7 @@ import { AdmonitionsSchema, } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; +import type {OptionValidationContext} from '@docusaurus/types'; export const DEFAULT_OPTIONS: PluginOptions = { path: 'src/pages', // Path to data on filesystem, relative to site dir. @@ -27,7 +28,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { admonitions: {}, }; -export const PluginOptionSchema = Joi.object({ +const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), routeBasePath: Joi.string().default(DEFAULT_OPTIONS.routeBasePath), include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), @@ -43,3 +44,11 @@ export const PluginOptionSchema = Joi.object({ ), admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions), }); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-google-analytics/src/index.ts b/packages/docusaurus-plugin-google-analytics/src/index.ts index 5e75e58b99..0219c748f1 100644 --- a/packages/docusaurus-plugin-google-analytics/src/index.ts +++ b/packages/docusaurus-plugin-google-analytics/src/index.ts @@ -10,11 +10,10 @@ import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, ThemeConfig, ThemeConfigValidationContext, } from '@docusaurus/types'; -import type {PluginOptions} from '@docusaurus/plugin-google-analytics'; +import type {PluginOptions, Options} from '@docusaurus/plugin-google-analytics'; export default function pluginGoogleAnalytics( context: LoadContext, @@ -74,13 +73,13 @@ const pluginOptionsSchema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(pluginOptionsSchema, options); } export function validateThemeConfig({ themeConfig, -}: ThemeConfigValidationContext): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { if ('googleAnalytics' in themeConfig) { throw new Error( 'The "googleAnalytics" field in themeConfig should now be specified as option for plugin-google-analytics. More information at https://github.com/facebook/docusaurus/pull/5832.', diff --git a/packages/docusaurus-plugin-google-gtag/src/index.ts b/packages/docusaurus-plugin-google-gtag/src/index.ts index 238fe699a6..66ed0365e0 100644 --- a/packages/docusaurus-plugin-google-gtag/src/index.ts +++ b/packages/docusaurus-plugin-google-gtag/src/index.ts @@ -10,11 +10,10 @@ import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, ThemeConfig, ThemeConfigValidationContext, } from '@docusaurus/types'; -import type {PluginOptions} from '@docusaurus/plugin-google-gtag'; +import type {PluginOptions, Options} from '@docusaurus/plugin-google-gtag'; export default function pluginGoogleGtag( context: LoadContext, @@ -88,13 +87,13 @@ const pluginOptionsSchema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(pluginOptionsSchema, options); } export function validateThemeConfig({ themeConfig, -}: ThemeConfigValidationContext): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { if ('gtag' in themeConfig) { throw new Error( 'The "gtag" field in themeConfig should now be specified as option for plugin-google-gtag. More information at https://github.com/facebook/docusaurus/pull/5832.', diff --git a/packages/docusaurus-plugin-ideal-image/src/index.ts b/packages/docusaurus-plugin-ideal-image/src/index.ts index de28f087df..4ea9fa4675 100644 --- a/packages/docusaurus-plugin-ideal-image/src/index.ts +++ b/packages/docusaurus-plugin-ideal-image/src/index.ts @@ -9,7 +9,6 @@ import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, } from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-ideal-image'; import {Joi} from '@docusaurus/utils-validation'; @@ -79,7 +78,7 @@ export default function pluginIdealImage( export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { const pluginOptionsSchema = Joi.object({ disableInDev: Joi.boolean().default(true), }).unknown(); diff --git a/packages/docusaurus-plugin-pwa/src/options.ts b/packages/docusaurus-plugin-pwa/src/options.ts index 418313fafb..e96405756a 100644 --- a/packages/docusaurus-plugin-pwa/src/options.ts +++ b/packages/docusaurus-plugin-pwa/src/options.ts @@ -6,11 +6,7 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type { - ThemeConfig, - ValidationResult, - OptionValidationContext, -} from '@docusaurus/types'; +import type {OptionValidationContext} from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-pwa'; const DEFAULT_OPTIONS = { @@ -27,7 +23,7 @@ const DEFAULT_OPTIONS = { reloadPopup: '@theme/PwaReloadPopup', }; -export const Schema = Joi.object({ +const Schema = Joi.object({ debug: Joi.bool().default(DEFAULT_OPTIONS.debug), offlineModeActivationStrategies: Joi.array() .items( @@ -61,6 +57,6 @@ export const Schema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(Schema, options); } diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts new file mode 100644 index 0000000000..e53a2adc6b --- /dev/null +++ b/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-sitemap'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + +describe('validateOptions', () => { + it('returns default values for empty user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + changefreq: 'yearly', + priority: 0.9, + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects out-of-range priority inputs', () => { + expect(() => + testValidate({priority: 2}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"priority\\" must be less than or equal to 1"`, + ); + }); + + it('rejects bad changefreq inputs', () => { + expect(() => + testValidate({changefreq: 'annually'}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"changefreq\\" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts deleted file mode 100644 index b245be7137..0000000000 --- a/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; - -function normalizePluginOptions(options) { - const {value, error} = PluginOptionSchema.validate(options, { - convert: false, - }); - if (error) { - throw error; - } else { - return value; - } -} - -describe('normalizeSitemapPluginOptions', () => { - it('returns default values for empty user options', () => { - const {value} = PluginOptionSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - }); - - it('accepts correctly defined user options', () => { - const userOptions = { - changefreq: 'yearly', - priority: 0.9, - }; - const {value} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual(userOptions); - }); - - it('rejects out-of-range priority inputs', () => { - expect(() => { - normalizePluginOptions({ - priority: 2, - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"priority\\" must be less than or equal to 1"`, - ); - }); - - it('rejects bad changefreq inputs', () => { - expect(() => { - normalizePluginOptions({ - changefreq: 'annually', - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"changefreq\\" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`, - ); - }); -}); diff --git a/packages/docusaurus-plugin-sitemap/src/index.ts b/packages/docusaurus-plugin-sitemap/src/index.ts index e394a9a192..0a66510299 100644 --- a/packages/docusaurus-plugin-sitemap/src/index.ts +++ b/packages/docusaurus-plugin-sitemap/src/index.ts @@ -9,14 +9,7 @@ import fs from 'fs-extra'; import path from 'path'; import type {Options} from '@docusaurus/plugin-sitemap'; import createSitemap from './createSitemap'; -import type { - LoadContext, - Props, - OptionValidationContext, - ValidationResult, - Plugin, -} from '@docusaurus/types'; -import {PluginOptionSchema} from './pluginOptionSchema'; +import type {LoadContext, Plugin} from '@docusaurus/types'; export default function pluginSitemap( context: LoadContext, @@ -25,7 +18,7 @@ export default function pluginSitemap( return { name: 'docusaurus-plugin-sitemap', - async postBuild({siteConfig, routesPaths, outDir}: Props) { + async postBuild({siteConfig, routesPaths, outDir}) { if (siteConfig.noIndex) { return; } @@ -47,10 +40,4 @@ export default function pluginSitemap( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-sitemap/src/options.ts similarity index 71% rename from packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-sitemap/src/options.ts index 7ead9b2e80..e7d7972153 100644 --- a/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-sitemap/src/options.ts @@ -8,13 +8,14 @@ import {Joi} from '@docusaurus/utils-validation'; import {EnumChangefreq} from 'sitemap'; import type {Options} from '@docusaurus/plugin-sitemap'; +import type {OptionValidationContext} from '@docusaurus/types'; -export const DEFAULT_OPTIONS: Required = { +export const DEFAULT_OPTIONS: Options = { changefreq: EnumChangefreq.WEEKLY, priority: 0.5, }; -export const PluginOptionSchema = Joi.object({ +const PluginOptionSchema = Joi.object({ cacheTime: Joi.forbidden().messages({ 'any.unknown': 'Option `cacheTime` in sitemap config is deprecated. Please remove it.', @@ -28,3 +29,11 @@ export const PluginOptionSchema = Joi.object({ 'Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it.', }), }); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): Options { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts index 7706a0cdd2..7f16b96a4c 100644 --- a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts +++ b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts @@ -8,6 +8,7 @@ import type {EnumChangefreq} from 'sitemap'; export type Options = { + id?: string; /** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */ changefreq?: EnumChangefreq; /** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */ diff --git a/packages/docusaurus-theme-classic/src/index.ts b/packages/docusaurus-theme-classic/src/index.ts index a3904f3582..64f56392a4 100644 --- a/packages/docusaurus-theme-classic/src/index.ts +++ b/packages/docusaurus-theme-classic/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadContext, Plugin, PostCssOptions} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import type {ThemeConfig} from '@docusaurus/theme-common'; import {getTranslationFiles, translateThemeConfig} from './translations'; import {createRequire} from 'module'; @@ -169,7 +169,7 @@ export default function docusaurusThemeClassic( }; }, - configurePostCss(postCssOptions: PostCssOptions) { + configurePostCss(postCssOptions) { if (direction === 'rtl') { const resolvedInfimaFile = require.resolve(getInfimaCSSFile(direction)); const plugin: PostCssPlugin = { diff --git a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts index cd068655bf..8de448ea65 100644 --- a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts @@ -6,7 +6,10 @@ */ import {Joi, URISchema} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; const DEFAULT_DOCS_CONFIG = { versionPersistence: 'localStorage', @@ -373,9 +376,6 @@ export const ThemeConfigSchema = Joi.object({ export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(ThemeConfigSchema, themeConfig); } diff --git a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts index 5c3cd1dfde..4b9fb583be 100644 --- a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts @@ -6,7 +6,10 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; export const DEFAULT_CONFIG = { playgroundPosition: 'bottom', @@ -25,9 +28,6 @@ export const Schema = Joi.object({ export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(Schema, themeConfig); } diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index 6ad9f3d677..95ff9e9f7c 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -6,7 +6,10 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; export const DEFAULT_CONFIG = { // enabled by default, as it makes sense in most cases @@ -45,9 +48,6 @@ export const Schema = Joi.object({ export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(Schema, themeConfig); } diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index e5ef1a919b..21bcdf60c9 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -320,7 +320,7 @@ export type PluginModule = { (context: LoadContext, options: Options): | Plugin | Promise>; - validateOptions?: (data: OptionValidationContext) => T; + validateOptions?: (data: OptionValidationContext) => U; validateThemeConfig?: (data: ThemeConfigValidationContext) => T; getSwizzleComponentList?: () => string[] | undefined; // TODO deprecate this one later @@ -436,22 +436,20 @@ interface HtmlTagObject { innerHTML?: string; } -export type ValidationResult = T; - export type ValidationSchema = Joi.ObjectSchema; -export type Validate = ( - validationSchema: ValidationSchema, - options: Partial, -) => ValidationResult; +export type Validate = ( + validationSchema: ValidationSchema, + options: T, +) => U; -export interface OptionValidationContext { - validate: Validate; - options: Partial; -} +export type OptionValidationContext = { + validate: Validate; + options: T; +}; export interface ThemeConfigValidationContext { - validate: Validate; + validate: Validate; themeConfig: Partial; } diff --git a/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts b/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts index d7f0742bfc..25ed7b2f27 100644 --- a/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts +++ b/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts @@ -24,6 +24,9 @@ describe('normalizePluginOptions', () => { options, ), ).toEqual(options); + expect( + normalizePluginOptions(Joi.object({foo: Joi.string()}), undefined), + ).toEqual({id: 'default'}); }); it('normalizes plugin options', () => { diff --git a/packages/docusaurus-utils-validation/src/validationUtils.ts b/packages/docusaurus-utils-validation/src/validationUtils.ts index 4e88d495b3..aae84333b9 100644 --- a/packages/docusaurus-utils-validation/src/validationUtils.ts +++ b/packages/docusaurus-utils-validation/src/validationUtils.ts @@ -20,7 +20,8 @@ export function printWarning(warning?: Joi.ValidationError): void { export function normalizePluginOptions( schema: Joi.ObjectSchema, - options: Partial, + // This allows us to automatically normalize undefined to {id: 'default'} + options: Partial = {}, ): T { // All plugins can be provided an "id" option (multi-instance support) // we add schema validation automatically diff --git a/packages/docusaurus/src/server/plugins/init.ts b/packages/docusaurus/src/server/plugins/init.ts index 504071617a..918476e983 100644 --- a/packages/docusaurus/src/server/plugins/init.ts +++ b/packages/docusaurus/src/server/plugins/init.ts @@ -179,7 +179,7 @@ export default async function initPlugins({ function doValidatePluginOptions( normalizedPluginConfig: NormalizedPluginConfig, - ) { + ): Required { const validateOptions = getOptionValidationFunction(normalizedPluginConfig); if (validateOptions) { return validateOptions({