refactor: fix more type-aware linting errors (#7479)

This commit is contained in:
Joshua Chen 2022-05-24 19:19:24 +08:00 committed by GitHub
parent bf1513a3e3
commit 624735bd92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 192 additions and 189 deletions

4
.eslintrc.js vendored
View file

@ -83,7 +83,7 @@ module.exports = {
'no-restricted-exports': OFF, 'no-restricted-exports': OFF,
'no-restricted-properties': [ 'no-restricted-properties': [
ERROR, ERROR,
...[ .../** @type {[string, string][]} */ ([
// TODO: TS doesn't make Boolean a narrowing function yet, // TODO: TS doesn't make Boolean a narrowing function yet,
// so filter(Boolean) is problematic type-wise // so filter(Boolean) is problematic type-wise
// ['compact', 'Array#filter(Boolean)'], // ['compact', 'Array#filter(Boolean)'],
@ -114,7 +114,7 @@ module.exports = {
['take', 'Array#slice(0, n)'], ['take', 'Array#slice(0, n)'],
['takeRight', 'Array#slice(-n)'], ['takeRight', 'Array#slice(-n)'],
['tail', 'Array#slice(1)'], ['tail', 'Array#slice(1)'],
].map(([property, alternative]) => ({ ]).map(([property, alternative]) => ({
object: '_', object: '_',
property, property,
message: `Use ${alternative} instead.`, message: `Use ${alternative} instead.`,

View file

@ -44,8 +44,9 @@ async function generateTemplateExample(template) {
`npm init docusaurus@latest examples/${template} ${command}`, `npm init docusaurus@latest examples/${template} ${command}`,
); );
const templatePackageJson = await fs.readJSON( const templatePackageJson =
`examples/${template}/package.json`, await /** @type {Promise<import("../../packages/create-docusaurus/templates/classic/package.json") & { scripts: { [name: string]: string }; description: string }>} */ (
fs.readJSON(`examples/${template}/package.json`)
); );
// Attach the dev script which would be used in code sandbox by default // Attach the dev script which would be used in code sandbox by default

View file

@ -14,7 +14,9 @@ import logger from '@docusaurus/logger';
import semver from 'semver'; import semver from 'semver';
import {program} from 'commander'; import {program} from 'commander';
const packageJson = createRequire(import.meta.url)('../package.json'); const packageJson = /** @type {import("../package.json")} */ (
createRequire(import.meta.url)('../package.json')
);
const requiredVersion = packageJson.engines.node; const requiredVersion = packageJson.engines.node;
if (!semver.satisfies(process.version, requiredVersion)) { if (!semver.satisfies(process.version, requiredVersion)) {

View file

@ -78,7 +78,7 @@ async function askForPackageManagerChoice(): Promise<PackageManager> {
logger.info`Falling back to name=${defaultPackageManager}`; logger.info`Falling back to name=${defaultPackageManager}`;
}, },
}, },
)) as {packageManager: PackageManager} )) as {packageManager?: PackageManager}
).packageManager ?? defaultPackageManager ).packageManager ?? defaultPackageManager
); );
} }

View file

@ -15,7 +15,9 @@ import semver from 'semver';
import cli from 'commander'; import cli from 'commander';
const moduleRequire = createRequire(import.meta.url); const moduleRequire = createRequire(import.meta.url);
const requiredVersion = moduleRequire('../package.json').engines.node; const requiredVersion = /** @type {import("../package.json")} */ (
moduleRequire('../package.json')
).engines.node;
if (!semver.satisfies(process.version, requiredVersion)) { if (!semver.satisfies(process.version, requiredVersion)) {
logger.error('Minimum Node.js version not met :('); logger.error('Minimum Node.js version not met :(');
@ -25,7 +27,7 @@ if (!semver.satisfies(process.version, requiredVersion)) {
// See https://github.com/facebook/docusaurus/pull/6860 // See https://github.com/facebook/docusaurus/pull/6860
const {migrateDocusaurusProject, migrateMDToMDX} = const {migrateDocusaurusProject, migrateMDToMDX} =
moduleRequire('../lib/index.js'); /** @type {import("../lib/index.js")} */ (moduleRequire('../lib/index.js'));
cli cli
.command('migrate [siteDir] [newDir]') .command('migrate [siteDir] [newDir]')

View file

@ -323,16 +323,27 @@ declare module '@docusaurus/renderRoutes' {
declare module '@docusaurus/useGlobalData' { declare module '@docusaurus/useGlobalData' {
import type {GlobalData, UseDataOptions} from '@docusaurus/types'; import type {GlobalData, UseDataOptions} from '@docusaurus/types';
export function useAllPluginInstancesData(
pluginName: string,
options: {failfast: true},
): GlobalData[string];
export function useAllPluginInstancesData( export function useAllPluginInstancesData(
pluginName: string, pluginName: string,
options?: UseDataOptions, options?: UseDataOptions,
): GlobalData[string] | undefined; ): GlobalData[string] | undefined;
export function usePluginData(
pluginName: string,
pluginId: string | undefined,
options: {failfast: true},
): NonNullable<GlobalData[string][string]>;
export function usePluginData( export function usePluginData(
pluginName: string, pluginName: string,
pluginId?: string, pluginId?: string,
options?: UseDataOptions, options?: UseDataOptions,
): GlobalData[string][string] | undefined; ): GlobalData[string][string];
export default function useGlobalData(): GlobalData; export default function useGlobalData(): GlobalData;
} }

View file

@ -133,7 +133,9 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
} as PluginOptions, } as PluginOptions,
); );
expect(fsMock.mock.calls.map((call) => call[1])).toMatchSnapshot(); expect(
fsMock.mock.calls.map((call) => call[1] as string),
).toMatchSnapshot();
fsMock.mockClear(); fsMock.mockClear();
}); });
}); });

View file

@ -14,6 +14,7 @@ import {
getVersionDocsDirPath, getVersionDocsDirPath,
getVersionSidebarsPath, getVersionSidebarsPath,
getDocsDirPathLocalized, getDocsDirPathLocalized,
readVersionsFile,
} from './versions/files'; } from './versions/files';
import {validateVersionName} from './versions/validation'; import {validateVersionName} from './versions/validation';
import {loadSidebarsFileUnsafe} from './sidebars'; import {loadSidebarsFileUnsafe} from './sidebars';
@ -69,12 +70,7 @@ export async function cliDocsVersionCommand(
throw err; throw err;
} }
// Load existing versions. const versions = (await readVersionsFile(siteDir, pluginId)) ?? [];
let versions: string[] = [];
const versionsJSONFile = getVersionsFilePath(siteDir, pluginId);
if (await fs.pathExists(versionsJSONFile)) {
versions = await fs.readJSON(versionsJSONFile);
}
// Check if version already exists. // Check if version already exists.
if (versions.includes(version)) { if (versions.includes(version)) {
@ -137,7 +133,7 @@ export async function cliDocsVersionCommand(
// Update versions.json file. // Update versions.json file.
versions.unshift(version); versions.unshift(version);
await fs.outputFile( await fs.outputFile(
versionsJSONFile, getVersionsFilePath(siteDir, pluginId),
`${JSON.stringify(versions, null, 2)}\n`, `${JSON.stringify(versions, null, 2)}\n`,
); );

View file

@ -89,7 +89,7 @@ export function getVersionsFilePath(siteDir: string, pluginId: string): string {
* @throws Throws if validation fails, i.e. `versions.json` doesn't contain an * @throws Throws if validation fails, i.e. `versions.json` doesn't contain an
* array of valid version names. * array of valid version names.
*/ */
async function readVersionsFile( export async function readVersionsFile(
siteDir: string, siteDir: string,
pluginId: string, pluginId: string,
): Promise<string[] | null> { ): Promise<string[] | null> {

View file

@ -19,8 +19,9 @@ import type webpack from 'webpack';
const requireFromDocusaurusCore = createRequire( const requireFromDocusaurusCore = createRequire(
require.resolve('@docusaurus/core/package.json'), require.resolve('@docusaurus/core/package.json'),
); );
const ContextReplacementPlugin: typeof webpack.ContextReplacementPlugin = const ContextReplacementPlugin = requireFromDocusaurusCore(
requireFromDocusaurusCore('webpack/lib/ContextReplacementPlugin'); 'webpack/lib/ContextReplacementPlugin',
) as typeof webpack.ContextReplacementPlugin;
// Need to be inlined to prevent dark mode FOUC // Need to be inlined to prevent dark mode FOUC
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js` // Make sure the key is the same as the one in `/theme/hooks/useTheme.js`

View file

@ -816,7 +816,6 @@ declare module '@theme/NavbarItem/NavbarNavLink' {
declare module '@theme/NavbarItem/DropdownNavbarItem' { declare module '@theme/NavbarItem/DropdownNavbarItem' {
import type {Props as NavbarNavLinkProps} from '@theme/NavbarItem/NavbarNavLink'; import type {Props as NavbarNavLinkProps} from '@theme/NavbarItem/NavbarNavLink';
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem'; import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
export type DesktopOrMobileNavBarItemProps = NavbarNavLinkProps & { export type DesktopOrMobileNavBarItemProps = NavbarNavLinkProps & {
@ -976,7 +975,7 @@ declare module '@theme/NavbarItem' {
} & SearchNavbarItemProps) } & SearchNavbarItemProps)
); );
export type Types = Props['type']; export type NavbarItemType = Props['type'];
export default function NavbarItem(props: Props): JSX.Element; export default function NavbarItem(props: Props): JSX.Element;
} }

View file

@ -10,11 +10,13 @@ import Details from '@theme/Details';
import type {Props} from '@theme/MDXComponents/Details'; import type {Props} from '@theme/MDXComponents/Details';
export default function MDXDetails(props: Props): JSX.Element { export default function MDXDetails(props: Props): JSX.Element {
const items = React.Children.toArray(props.children) as ReactElement[]; const items = React.Children.toArray(props.children);
// Split summary item from the rest to pass it as a separate prop to the // Split summary item from the rest to pass it as a separate prop to the
// Details theme component // Details theme component
const summary: ReactElement<ComponentProps<'summary'>> | undefined = const summary = items.find(
items.find((item) => item?.props?.mdxType === 'summary'); (item): item is ReactElement<ComponentProps<'summary'>> =>
React.isValidElement(item) && item.props?.mdxType === 'summary',
);
const children = <>{items.filter((item) => item !== summary)}</>; const children = <>{items.filter((item) => item !== summary)}</>;
return ( return (

View file

@ -11,8 +11,10 @@ import type {Props} from '@theme/MDXComponents/Head';
// MDX elements are wrapped through the MDX pragma. In some cases (notably usage // MDX elements are wrapped through the MDX pragma. In some cases (notably usage
// with Head/Helmet) we need to unwrap those elements. // with Head/Helmet) we need to unwrap those elements.
function unwrapMDXElement(element: ReactElement) { function unwrapMDXElement(
if (element?.props?.mdxType && element?.props?.originalType) { element: ReactElement<{mdxType?: string; originalType?: string} | undefined>,
) {
if (element.props?.mdxType && element.props.originalType) {
const {mdxType, originalType, ...newProps} = element.props; const {mdxType, originalType, ...newProps} = element.props;
return React.createElement(element.props.originalType, newProps); return React.createElement(element.props.originalType, newProps);
} }
@ -21,7 +23,7 @@ function unwrapMDXElement(element: ReactElement) {
export default function MDXHead(props: Props): JSX.Element { export default function MDXHead(props: Props): JSX.Element {
const unwrappedChildren = React.Children.map(props.children, (child) => const unwrappedChildren = React.Children.map(props.children, (child) =>
unwrapMDXElement(child as ReactElement), React.isValidElement(child) ? unwrapMDXElement(child) : child,
); );
return ( return (
<Head {...(props as ComponentProps<typeof Head>)}>{unwrappedChildren}</Head> <Head {...(props as ComponentProps<typeof Head>)}>{unwrappedChildren}</Head>

View file

@ -14,8 +14,8 @@ export default function MDXPre(props: Props): JSX.Element {
<CodeBlock <CodeBlock
// If this pre is created by a ``` fenced codeblock, unwrap the children // If this pre is created by a ``` fenced codeblock, unwrap the children
{...(isValidElement(props.children) && {...(isValidElement(props.children) &&
props.children.props.originalType === 'code' props.children.props?.originalType === 'code'
? props.children?.props ? props.children.props
: {...props})} : {...props})}
/> />
); );

View file

@ -11,13 +11,12 @@ import {
useNavbarMobileSidebar, useNavbarMobileSidebar,
useThemeConfig, useThemeConfig,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
import NavbarItem from '@theme/NavbarItem'; import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem';
import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle'; import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
import SearchBar from '@theme/SearchBar'; import SearchBar from '@theme/SearchBar';
import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle'; import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';
import NavbarLogo from '@theme/Navbar/Logo'; import NavbarLogo from '@theme/Navbar/Logo';
import NavbarSearch from '@theme/Navbar/Search'; import NavbarSearch from '@theme/Navbar/Search';
import type {Props as NavbarItemConfig} from '@theme/NavbarItem';
import styles from './styles.module.css'; import styles from './styles.module.css';

View file

@ -7,8 +7,7 @@
import React from 'react'; import React from 'react';
import {useNavbarMobileSidebar, useThemeConfig} from '@docusaurus/theme-common'; import {useNavbarMobileSidebar, useThemeConfig} from '@docusaurus/theme-common';
import NavbarItem from '@theme/NavbarItem'; import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem';
import type {Props as NavbarItemConfig} from '@theme/NavbarItem';
function useNavbarItems() { function useNavbarItems() {
// TODO temporary casting until ThemeConfig type is improved // TODO temporary casting until ThemeConfig type is improved

View file

@ -38,13 +38,13 @@ export default function DocsVersionDropdownNavbarItem({
// We try to link to the same doc, in another version // We try to link to the same doc, in another version
// When not possible, fallback to the "main doc" of the version // When not possible, fallback to the "main doc" of the version
const versionDoc = const versionDoc =
activeDocContext?.alternateDocVersions[version.name] ?? activeDocContext.alternateDocVersions[version.name] ??
getVersionMainDoc(version); getVersionMainDoc(version);
return { return {
isNavLink: true, isNavLink: true,
label: version.label, label: version.label,
to: versionDoc.path, to: versionDoc.path,
isActive: () => version === activeDocContext?.activeVersion, isActive: () => version === activeDocContext.activeVersion,
onClick: () => savePreferredVersionName(version.name), onClick: () => savePreferredVersionName(version.name),
}; };
}); });

View file

@ -15,12 +15,11 @@ import {
useLocalPathname, useLocalPathname,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'; import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import NavbarItem from '@theme/NavbarItem'; import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem';
import type { import type {
DesktopOrMobileNavBarItemProps, DesktopOrMobileNavBarItemProps,
Props, Props,
} from '@theme/NavbarItem/DropdownNavbarItem'; } from '@theme/NavbarItem/DropdownNavbarItem';
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
const dropdownLinkActiveClass = 'dropdown__link--active'; const dropdownLinkActiveClass = 'dropdown__link--active';

View file

@ -7,31 +7,22 @@
import React from 'react'; import React from 'react';
import ComponentTypes from '@theme/NavbarItem/ComponentTypes'; import ComponentTypes from '@theme/NavbarItem/ComponentTypes';
import {type Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem'; import type {NavbarItemType, Props} from '@theme/NavbarItem';
import type {Types, Props} from '@theme/NavbarItem';
const getNavbarItemComponent = (type: NonNullable<Types>) => { function normalizeComponentType(type: NavbarItemType, props: object) {
const component = ComponentTypes[type];
if (!component) {
throw new Error(`No NavbarItem component found for type "${type}".`);
}
return component;
};
function getComponentType(type: Types, isDropdown: boolean) {
// Backward compatibility: navbar item with no type set // Backward compatibility: navbar item with no type set
// but containing dropdown items should use the type "dropdown" // but containing dropdown items should use the type "dropdown"
if (!type || type === 'default') { if (!type || type === 'default') {
return isDropdown ? 'dropdown' : 'default'; return 'items' in props ? 'dropdown' : 'default';
} }
return type as NonNullable<Types>; return type;
} }
export default function NavbarItem({type, ...props}: Props): JSX.Element { export default function NavbarItem({type, ...props}: Props): JSX.Element {
const componentType = getComponentType( const componentType = normalizeComponentType(type, props);
type, const NavbarItemComponent = ComponentTypes[componentType];
(props as DropdownNavbarItemProps).items !== undefined, if (!NavbarItemComponent) {
); throw new Error(`No NavbarItem component found for type "${type}".`);
const NavbarItemComponent = getNavbarItemComponent(componentType); }
return <NavbarItemComponent {...(props as never)} />; return <NavbarItemComponent {...(props as never)} />;
} }

View file

@ -75,7 +75,7 @@ function TabsComponent(props: Props): JSX.Element {
? defaultValueProp ? defaultValueProp
: defaultValueProp ?? : defaultValueProp ??
children.find((child) => child.props.default)?.props.value ?? children.find((child) => child.props.default)?.props.value ??
children[0]?.props.value; children[0]!.props.value;
if (defaultValue !== null && !values.some((a) => a.value === defaultValue)) { if (defaultValue !== null && !values.some((a) => a.value === defaultValue)) {
throw new Error( throw new Error(
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValue}" but none of its children has the corresponding value. Available values are: ${values `Docusaurus error: The <Tabs> has a defaultValue "${defaultValue}" but none of its children has the corresponding value. Available values are: ${values
@ -126,12 +126,12 @@ function TabsComponent(props: Props): JSX.Element {
switch (event.key) { switch (event.key) {
case 'ArrowRight': { case 'ArrowRight': {
const nextTab = tabRefs.indexOf(event.currentTarget) + 1; const nextTab = tabRefs.indexOf(event.currentTarget) + 1;
focusElement = tabRefs[nextTab] || tabRefs[0]!; focusElement = tabRefs[nextTab] ?? tabRefs[0]!;
break; break;
} }
case 'ArrowLeft': { case 'ArrowLeft': {
const prevTab = tabRefs.indexOf(event.currentTarget) - 1; const prevTab = tabRefs.indexOf(event.currentTarget) - 1;
focusElement = tabRefs[prevTab] || tabRefs[tabRefs.length - 1]!; focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!;
break; break;
} }
default: default:

View file

@ -92,7 +92,7 @@ export function Details({
} }
}}> }}>
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */} {/* eslint-disable-next-line @docusaurus/no-untranslated-text */}
{summary || <summary>Details</summary>} {summary ?? <summary>Details</summary>}
<Collapsible <Collapsible
lazy={false} // Content might matter for SEO in this case lazy={false} // Content might matter for SEO in this case

View file

@ -151,7 +151,7 @@ export function useTOCHighlight(config: TOCHighlightConfig | undefined): void {
function updateLinkActiveClass(link: HTMLAnchorElement, active: boolean) { function updateLinkActiveClass(link: HTMLAnchorElement, active: boolean) {
if (active) { if (active) {
if (lastActiveLinkRef.current && lastActiveLinkRef.current !== link) { if (lastActiveLinkRef.current && lastActiveLinkRef.current !== link) {
lastActiveLinkRef.current?.classList.remove(linkActiveClassName); lastActiveLinkRef.current.classList.remove(linkActiveClassName);
} }
link.classList.add(linkActiveClassName); link.classList.add(linkActiveClassName);
lastActiveLinkRef.current = link; lastActiveLinkRef.current = link;

View file

@ -97,7 +97,7 @@ export function parseCodeBlockTitle(metastring?: string): string {
} }
export function containsLineNumbers(metastring?: string): boolean { export function containsLineNumbers(metastring?: string): boolean {
return metastring?.includes('showLineNumbers') || false; return Boolean(metastring?.includes('showLineNumbers'));
} }
/** /**
@ -209,7 +209,9 @@ export function parseLines(
lineNumber += 1; lineNumber += 1;
continue; continue;
} }
const directive = match.slice(1).find((item) => item !== undefined)!; const directive = match
.slice(1)
.find((item: string | undefined) => item !== undefined)!;
if (lineToClassName[directive]) { if (lineToClassName[directive]) {
blocks[lineToClassName[directive]!]!.range += `${lineNumber},`; blocks[lineToClassName[directive]!]!.range += `${lineNumber},`;
} else if (blockStartToClassName[directive]) { } else if (blockStartToClassName[directive]) {

View file

@ -85,10 +85,10 @@ export type FooterBase = {
}; };
export type MultiColumnFooter = FooterBase & { export type MultiColumnFooter = FooterBase & {
links: Array<{ links: {
title: string | null; title: string | null;
items: FooterLinkItem[]; items: FooterLinkItem[];
}>; }[];
}; };
export type SimpleFooter = FooterBase & { export type SimpleFooter = FooterBase & {
@ -123,7 +123,7 @@ export type ThemeConfig = {
prism: PrismConfig; prism: PrismConfig;
footer?: Footer; footer?: Footer;
image?: string; image?: string;
metadata: Array<{[key: string]: string}>; metadata: {[key: string]: string}[];
tableOfContents: TableOfContents; tableOfContents: TableOfContents;
}; };

View file

@ -98,7 +98,7 @@ export default function Playground({
{/* @ts-expect-error: type incompatibility with refs */} {/* @ts-expect-error: type incompatibility with refs */}
<LiveProvider <LiveProvider
code={children.replace(/\n$/, '')} code={children.replace(/\n$/, '')}
transformCode={transformCode || ((code) => `${code};`)} transformCode={transformCode ?? ((code) => `${code};`)}
theme={prismTheme} theme={prismTheme}
{...props}> {...props}>
{playgroundPosition === 'top' ? ( {playgroundPosition === 'top' ? (

View file

@ -75,7 +75,7 @@ type FacetFilters = Required<
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters { function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
const normalize = ( const normalize = (
f: FacetFilters, f: FacetFilters,
): readonly string[] | ReadonlyArray<string | readonly string[]> => ): readonly string[] | readonly (string | readonly string[])[] =>
typeof f === 'string' ? [f] : f; typeof f === 'string' ? [f] : f;
return [...normalize(f1), ...normalize(f2)] as FacetFilters; return [...normalize(f1), ...normalize(f2)] as FacetFilters;
} }

View file

@ -22,8 +22,10 @@ describe('theme translations', () => {
.then((files) => .then((files) =>
Promise.all( Promise.all(
files.map( files.map(
(baseMessagesFile): Promise<{[key: string]: string}> => (baseMessagesFile) =>
fs.readJSON(path.join(baseMessagesDirPath, baseMessagesFile)), fs.readJSON(
path.join(baseMessagesDirPath, baseMessagesFile),
) as Promise<{[key: string]: string}>,
), ),
), ),
) )

View file

@ -51,7 +51,7 @@ export async function readDefaultCodeTranslationMessages({
const filePath = path.resolve(dirPath, localeToTry, `${name}.json`); const filePath = path.resolve(dirPath, localeToTry, `${name}.json`);
if (await fs.pathExists(filePath)) { if (await fs.pathExists(filePath)) {
return fs.readJSON(filePath); return fs.readJSON(filePath) as Promise<CodeTranslations>;
} }
} }

View file

@ -37,21 +37,24 @@ describe('isNameTooLong', () => {
}; };
const oldProcessPlatform = process.platform; const oldProcessPlatform = process.platform;
Object.defineProperty(process, 'platform', {value: 'darwin'}); Object.defineProperty(process, 'platform', {value: 'darwin'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(isNameTooLong(file)).toBe( expect(isNameTooLong(file)).toBe(
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].apfs, typeof expected === 'boolean' ? expected : expected.apfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: 'win32'}); Object.defineProperty(process, 'platform', {value: 'win32'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(isNameTooLong(file)).toBe( expect(isNameTooLong(file)).toBe(
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].apfs, typeof expected === 'boolean' ? expected : expected.apfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: 'android'}); Object.defineProperty(process, 'platform', {value: 'android'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(isNameTooLong(file)).toBe( expect(isNameTooLong(file)).toBe(
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].xfs, typeof expected === 'boolean' ? expected : expected.xfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: oldProcessPlatform}); Object.defineProperty(process, 'platform', {value: oldProcessPlatform});
@ -79,21 +82,24 @@ describe('shortName', () => {
}; };
const oldProcessPlatform = process.platform; const oldProcessPlatform = process.platform;
Object.defineProperty(process, 'platform', {value: 'darwin'}); Object.defineProperty(process, 'platform', {value: 'darwin'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(shortName(file)).toBe( expect(shortName(file)).toBe(
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].apfs, typeof expected === 'string' ? expected : expected.apfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: 'win32'}); Object.defineProperty(process, 'platform', {value: 'win32'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(shortName(file)).toBe( expect(shortName(file)).toBe(
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].apfs, typeof expected === 'string' ? expected : expected.apfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: 'android'}); Object.defineProperty(process, 'platform', {value: 'android'});
Object.keys(asserts).forEach((file) => { (Object.keys(asserts) as (keyof typeof asserts)[]).forEach((file) => {
const expected = asserts[file];
expect(shortName(file)).toBe( expect(shortName(file)).toBe(
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].xfs, typeof expected === 'string' ? expected : expected.xfs,
); );
}); });
Object.defineProperty(process, 'platform', {value: oldProcessPlatform}); Object.defineProperty(process, 'platform', {value: oldProcessPlatform});

View file

@ -16,7 +16,9 @@ import updateNotifier from 'update-notifier';
import boxen from 'boxen'; import boxen from 'boxen';
import {DOCUSAURUS_VERSION} from '@docusaurus/utils'; import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
const packageJson = createRequire(import.meta.url)('../package.json'); const packageJson = /** @type {import("../package.json")} */ (
createRequire(import.meta.url)('../package.json')
);
/** @type {Record<string, any>} */ /** @type {Record<string, any>} */
let sitePkg; let sitePkg;
try { try {

View file

@ -15,7 +15,7 @@ export function dispatchLifecycleAction<K extends keyof ClientModule>(
...args: Parameters<NonNullable<ClientModule[K]>> ...args: Parameters<NonNullable<ClientModule[K]>>
): () => void { ): () => void {
const callbacks = clientModules.map((clientModule) => { const callbacks = clientModules.map((clientModule) => {
const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ?? const lifecycleFunction = (clientModule.default?.[lifecycleAction] ??
clientModule[lifecycleAction]) as clientModule[lifecycleAction]) as
| (( | ((
...a: Parameters<NonNullable<ClientModule[K]>> ...a: Parameters<NonNullable<ClientModule[K]>>

View file

@ -41,7 +41,7 @@ describe('flat', () => {
null: null, null: null,
undefined, undefined,
}; };
Object.keys(primitives).forEach((key) => { (Object.keys(primitives) as (keyof typeof primitives)[]).forEach((key) => {
const value = primitives[key]; const value = primitives[key];
expect( expect(
flat({ flat({

View file

@ -70,7 +70,7 @@ const docusaurus = {
// In some cases, webpack might decide to optimize further, leading to // In some cases, webpack might decide to optimize further, leading to
// the chunk assets being merged to another chunk. In this case, we can // the chunk assets being merged to another chunk. In this case, we can
// safely filter it out and don't need to load it. // safely filter it out and don't need to load it.
if (chunkAsset && !/undefined/.test(chunkAsset)) { if (chunkAsset && !chunkAsset.includes('undefined')) {
return prefetchHelper(chunkAsset); return prefetchHelper(chunkAsset);
} }
return Promise.resolve(); return Promise.resolve();

View file

@ -47,10 +47,7 @@ function Link(
const linksCollector = useLinksCollector(); const linksCollector = useLinksCollector();
const innerRef = useRef<HTMLAnchorElement | null>(null); const innerRef = useRef<HTMLAnchorElement | null>(null);
useImperativeHandle( useImperativeHandle(forwardedRef, () => innerRef.current!);
forwardedRef,
() => innerRef.current as HTMLAnchorElement,
);
// IMPORTANT: using to or href should not change anything // IMPORTANT: using to or href should not change anything
// For example, MDX links will ALWAYS give us the href props // For example, MDX links will ALWAYS give us the href props

View file

@ -26,19 +26,6 @@ describe('useGlobalData', () => {
}).result.current, }).result.current,
).toEqual({foo: 'bar'}); ).toEqual({foo: 'bar'});
}); });
it('throws when global data not found', () => {
// Can it actually happen?
expect(
() =>
renderHook(() => useGlobalData(), {
wrapper: ({children}) => (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<Context.Provider value={{}}>{children}</Context.Provider>
),
}).result.current,
).toThrowErrorMatchingInlineSnapshot(`"Docusaurus global data not found."`);
});
}); });
describe('useAllPluginInstancesData', () => { describe('useAllPluginInstancesData', () => {

View file

@ -6,7 +6,7 @@
*/ */
export function hasProtocol(url: string): boolean { export function hasProtocol(url: string): boolean {
return /^(?:\w*:|\/\/)/.test(url) === true; return /^(?:\w*:|\/\/)/.test(url);
} }
export default function isInternalUrl(url?: string): boolean { export default function isInternalUrl(url?: string): boolean {

View file

@ -11,9 +11,6 @@ import type {GlobalData, UseDataOptions} from '@docusaurus/types';
export default function useGlobalData(): GlobalData { export default function useGlobalData(): GlobalData {
const {globalData} = useDocusaurusContext(); const {globalData} = useDocusaurusContext();
if (!globalData) {
throw new Error('Docusaurus global data not found.');
}
return globalData; return globalData;
} }

View file

@ -10,7 +10,7 @@ import type {ChunkNames} from '@docusaurus/types';
type Chunk = ChunkNames[string]; type Chunk = ChunkNames[string];
type Tree = Exclude<Chunk, string>; type Tree = Exclude<Chunk, string>;
const isTree = (x: Chunk): x is Tree => const isTree = (x: unknown): x is Tree =>
typeof x === 'object' && !!x && Object.keys(x).length > 0; typeof x === 'object' && !!x && Object.keys(x).length > 0;
/** /**

View file

@ -8,8 +8,8 @@
function supports(feature: string) { function supports(feature: string) {
try { try {
const fakeLink = document.createElement('link'); const fakeLink = document.createElement('link');
return fakeLink.relList?.supports?.(feature); return fakeLink.relList.supports(feature);
} catch (err) { } catch {
return false; return false;
} }
} }

View file

@ -115,9 +115,9 @@ async function doRender(locals: Locals & {path: string}) {
// Using readJSON seems to fail for users of some plugins, possibly because of // Using readJSON seems to fail for users of some plugins, possibly because of
// the eval sandbox having a different `Buffer` instance (native one instead // the eval sandbox having a different `Buffer` instance (native one instead
// of polyfilled one) // of polyfilled one)
const manifest: Manifest = await fs const manifest = (await fs
.readFile(manifestPath, 'utf-8') .readFile(manifestPath, 'utf-8')
.then(JSON.parse); .then(JSON.parse)) as Manifest;
// Get all required assets for this particular page based on client // Get all required assets for this particular page based on client
// manifest information. // manifest information.

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`normalizeConfig should throw error if plugins is not a string and it's not an array #1 for the input of: [123] 1`] = ` exports[`normalizeConfig should throw error if plugins is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus plugin value as path [plugins,0]. " => Bad Docusaurus plugin value plugins[0].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [
@ -17,7 +17,7 @@ Example valid plugin config:
`; `;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value as path [plugins,0]. " => Bad Docusaurus plugin value plugins[0].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [
@ -33,7 +33,7 @@ Example valid plugin config:
`; `;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value as path [plugins,0]. " => Bad Docusaurus plugin value plugins[0].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [
@ -49,7 +49,7 @@ Example valid plugin config:
`; `;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value as path [plugins,0]. " => Bad Docusaurus plugin value plugins[0].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [
@ -70,7 +70,7 @@ exports[`normalizeConfig should throw error if plugins is not array for the inpu
`; `;
exports[`normalizeConfig should throw error if themes is not a string and it's not an array #1 for the input of: [123] 1`] = ` exports[`normalizeConfig should throw error if themes is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus theme value as path [themes,0]. " => Bad Docusaurus theme value themes[0].
Example valid theme config: Example valid theme config:
{ {
themes: [ themes: [
@ -86,7 +86,7 @@ Example valid theme config:
`; `;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value as path [themes,0]. " => Bad Docusaurus theme value themes[0].
Example valid theme config: Example valid theme config:
{ {
themes: [ themes: [
@ -102,7 +102,7 @@ Example valid theme config:
`; `;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value as path [themes,0]. " => Bad Docusaurus theme value themes[0].
Example valid theme config: Example valid theme config:
{ {
themes: [ themes: [
@ -118,7 +118,7 @@ Example valid theme config:
`; `;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = ` exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value as path [themes,0]. " => Bad Docusaurus theme value themes[0].
Example valid theme config: Example valid theme config:
{ {
themes: [ themes: [

View file

@ -100,7 +100,9 @@ function createPluginSchema(theme: boolean) {
error.message = ` => Bad Docusaurus ${ error.message = ` => Bad Docusaurus ${
theme ? 'theme' : 'plugin' theme ? 'theme' : 'plugin'
} value as path [${error.path}]. } value ${error.path.reduce((acc, cur) =>
typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`,
)}.
${validConfigExample} ${validConfigExample}
`; `;
}); });
@ -247,7 +249,9 @@ export function validateConfig(config: unknown): DocusaurusConfig {
if (error) { if (error) {
const unknownFields = error.details.reduce((formattedError, err) => { const unknownFields = error.details.reduce((formattedError, err) => {
if (err.type === 'object.unknown') { if (err.type === 'object.unknown') {
return `${formattedError}"${err.path}",`; return `${formattedError}"${err.path.reduce((acc, cur) =>
typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`,
)}",`;
} }
return formattedError; return formattedError;
}, ''); }, '');

View file

@ -83,5 +83,5 @@ export function loadHtmlTags(
.trim(), .trim(),
), ),
), ),
); ) as Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
} }

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`initPlugins throws user-friendly error message for plugins with bad values 1`] = ` exports[`initPlugins throws user-friendly error message for plugins with bad values 1`] = `
" => Bad Docusaurus plugin value as path [plugins,0]. " => Bad Docusaurus plugin value plugins[0].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [
@ -13,7 +13,7 @@ Example valid plugin config:
], ],
}; };
=> Bad Docusaurus plugin value as path [plugins,1]. => Bad Docusaurus plugin value plugins[1].
Example valid plugin config: Example valid plugin config:
{ {
plugins: [ plugins: [

View file

@ -21,7 +21,7 @@ describe('initPlugins', () => {
it('parses plugins correctly and loads them in correct order', async () => { it('parses plugins correctly and loads them in correct order', async () => {
const {context, plugins} = await loadSite(); const {context, plugins} = await loadSite();
expect(context.siteConfig.plugins?.length).toBe(4); expect(context.siteConfig.plugins).toHaveLength(4);
expect(plugins).toHaveLength(8); expect(plugins).toHaveLength(8);
expect(plugins[0].name).toBe('preset-plugin1'); expect(plugins[0].name).toBe('preset-plugin1');

View file

@ -184,7 +184,7 @@ function extractSourceCodeAstTranslations(
sourceCodeFilePath: string, sourceCodeFilePath: string,
): SourceCodeFileTranslations { ): SourceCodeFileTranslations {
function sourceWarningPart(node: Node) { function sourceWarningPart(node: Node) {
return `File: ${sourceCodeFilePath} at line ${node.loc?.start.line} return `File: ${sourceCodeFilePath} at line ${node.loc?.start.line ?? '?'}
Full code: ${generate(node).code}`; Full code: ${generate(node).code}`;
} }
@ -313,7 +313,9 @@ ${sourceWarningPart(path.node)}`);
if (isJSXText || isJSXExpressionContainer) { if (isJSXText || isJSXExpressionContainer) {
message = isJSXText message = isJSXText
? singleChildren.node.value.trim().replace(/\s+/g, ' ') ? singleChildren.node.value.trim().replace(/\s+/g, ' ')
: (singleChildren.get('expression') as NodePath).evaluate().value; : String(
(singleChildren.get('expression') as NodePath).evaluate().value,
);
translations[id ?? message] = { translations[id ?? message] = {
message, message,

View file

@ -198,7 +198,7 @@ export function applyConfigurePostCss(
configurePostCss: NonNullable<Plugin['configurePostCss']>, configurePostCss: NonNullable<Plugin['configurePostCss']>,
config: Configuration, config: Configuration,
): Configuration { ): Configuration {
type LocalPostCSSLoader = unknown & { type LocalPostCSSLoader = object & {
options: {postcssOptions: PostCssOptions}; options: {postcssOptions: PostCssOptions};
}; };

View file

@ -6,7 +6,7 @@
*/ */
import * as lqip from './lqip'; import * as lqip from './lqip';
import type {LoaderContext} from 'webpack'; import type {LoaderContext, LoaderModule} from 'webpack';
type Options = { type Options = {
base64: boolean; base64: boolean;
@ -36,8 +36,9 @@ export default async function lqipLoader(
} else { } else {
if (!contentIsFileExport) { if (!contentIsFileExport) {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const fileLoader = require('file-loader') as typeof import('file-loader'); const fileLoader = require('file-loader') as LoaderModule['default'];
content = fileLoader.call(this, contentBuffer); // @ts-expect-error: type is a bit unwieldy...
content = fileLoader!.call(this, contentBuffer) as string;
} }
source = content.match( source = content.match(
/^(?:export default|module.exports =) (?<source>.*);/, /^(?:export default|module.exports =) (?<source>.*);/,

View file

@ -43,9 +43,8 @@ function getOutputCss(output: stylelint.LinterResult) {
function testStylelintRule(config: stylelint.Config, tests: TestSuite) { function testStylelintRule(config: stylelint.Config, tests: TestSuite) {
describe(`${tests.ruleName}`, () => { describe(`${tests.ruleName}`, () => {
const checkTestCaseContent = (testCase: TestCase) => const checkTestCaseContent = (testCase: TestCase) =>
testCase.description || testCase.code; testCase.description ?? testCase.code;
if (tests.accept?.length) {
describe('accept cases', () => { describe('accept cases', () => {
tests.accept.forEach((testCase) => { tests.accept.forEach((testCase) => {
it(`${checkTestCaseContent(testCase)}`, async () => { it(`${checkTestCaseContent(testCase)}`, async () => {
@ -65,9 +64,7 @@ function testStylelintRule(config: stylelint.Config, tests: TestSuite) {
}); });
}); });
}); });
}
if (tests.reject?.length) {
describe('reject cases', () => { describe('reject cases', () => {
tests.reject.forEach((testCase) => { tests.reject.forEach((testCase) => {
it(`${checkTestCaseContent(testCase)}`, async () => { it(`${checkTestCaseContent(testCase)}`, async () => {
@ -104,7 +101,6 @@ function testStylelintRule(config: stylelint.Config, tests: TestSuite) {
}); });
}); });
}); });
}
expect.extend({ expect.extend({
toHaveMessage(testCase: TestCase) { toHaveMessage(testCase: TestCase) {

View file

@ -51,6 +51,7 @@
"allowJs": true, "allowJs": true,
"skipLibCheck": true // @types/webpack and webpack/types.d.ts are not the same thing "skipLibCheck": true // @types/webpack and webpack/types.d.ts are not the same thing
}, },
"include": ["./**/*", "./**/.eslintrc.js"],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"coverage/**", "coverage/**",

View file

@ -45,7 +45,7 @@ export default function Version(): JSX.Element {
const pastVersions = versions.filter( const pastVersions = versions.filter(
(version) => version !== latestVersion && version.name !== 'current', (version) => version !== latestVersion && version.name !== 'current',
); );
const repoUrl = `https://github.com/${organizationName}/${projectName}`; const repoUrl = `https://github.com/${organizationName!}/${projectName!}`;
return ( return (
<Layout <Layout