mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
refactor: fix more type-aware linting errors (#7479)
This commit is contained in:
parent
bf1513a3e3
commit
624735bd92
51 changed files with 192 additions and 189 deletions
4
.eslintrc.js
vendored
4
.eslintrc.js
vendored
|
@ -83,7 +83,7 @@ module.exports = {
|
|||
'no-restricted-exports': OFF,
|
||||
'no-restricted-properties': [
|
||||
ERROR,
|
||||
...[
|
||||
.../** @type {[string, string][]} */ ([
|
||||
// TODO: TS doesn't make Boolean a narrowing function yet,
|
||||
// so filter(Boolean) is problematic type-wise
|
||||
// ['compact', 'Array#filter(Boolean)'],
|
||||
|
@ -114,7 +114,7 @@ module.exports = {
|
|||
['take', 'Array#slice(0, n)'],
|
||||
['takeRight', 'Array#slice(-n)'],
|
||||
['tail', 'Array#slice(1)'],
|
||||
].map(([property, alternative]) => ({
|
||||
]).map(([property, alternative]) => ({
|
||||
object: '_',
|
||||
property,
|
||||
message: `Use ${alternative} instead.`,
|
||||
|
|
|
@ -44,9 +44,10 @@ async function generateTemplateExample(template) {
|
|||
`npm init docusaurus@latest examples/${template} ${command}`,
|
||||
);
|
||||
|
||||
const templatePackageJson = await fs.readJSON(
|
||||
`examples/${template}/package.json`,
|
||||
);
|
||||
const templatePackageJson =
|
||||
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
|
||||
templatePackageJson.scripts.dev = 'docusaurus start';
|
||||
|
|
|
@ -14,7 +14,9 @@ import logger from '@docusaurus/logger';
|
|||
import semver from 'semver';
|
||||
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;
|
||||
|
||||
if (!semver.satisfies(process.version, requiredVersion)) {
|
||||
|
|
|
@ -78,7 +78,7 @@ async function askForPackageManagerChoice(): Promise<PackageManager> {
|
|||
logger.info`Falling back to name=${defaultPackageManager}`;
|
||||
},
|
||||
},
|
||||
)) as {packageManager: PackageManager}
|
||||
)) as {packageManager?: PackageManager}
|
||||
).packageManager ?? defaultPackageManager
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import semver from 'semver';
|
|||
import cli from 'commander';
|
||||
|
||||
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)) {
|
||||
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
|
||||
const {migrateDocusaurusProject, migrateMDToMDX} =
|
||||
moduleRequire('../lib/index.js');
|
||||
/** @type {import("../lib/index.js")} */ (moduleRequire('../lib/index.js'));
|
||||
|
||||
cli
|
||||
.command('migrate [siteDir] [newDir]')
|
||||
|
|
|
@ -323,16 +323,27 @@ declare module '@docusaurus/renderRoutes' {
|
|||
declare module '@docusaurus/useGlobalData' {
|
||||
import type {GlobalData, UseDataOptions} from '@docusaurus/types';
|
||||
|
||||
export function useAllPluginInstancesData(
|
||||
pluginName: string,
|
||||
options: {failfast: true},
|
||||
): GlobalData[string];
|
||||
|
||||
export function useAllPluginInstancesData(
|
||||
pluginName: string,
|
||||
options?: UseDataOptions,
|
||||
): GlobalData[string] | undefined;
|
||||
|
||||
export function usePluginData(
|
||||
pluginName: string,
|
||||
pluginId: string | undefined,
|
||||
options: {failfast: true},
|
||||
): NonNullable<GlobalData[string][string]>;
|
||||
|
||||
export function usePluginData(
|
||||
pluginName: string,
|
||||
pluginId?: string,
|
||||
options?: UseDataOptions,
|
||||
): GlobalData[string][string] | undefined;
|
||||
): GlobalData[string][string];
|
||||
|
||||
export default function useGlobalData(): GlobalData;
|
||||
}
|
||||
|
|
|
@ -133,7 +133,9 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
|||
} as PluginOptions,
|
||||
);
|
||||
|
||||
expect(fsMock.mock.calls.map((call) => call[1])).toMatchSnapshot();
|
||||
expect(
|
||||
fsMock.mock.calls.map((call) => call[1] as string),
|
||||
).toMatchSnapshot();
|
||||
fsMock.mockClear();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getVersionDocsDirPath,
|
||||
getVersionSidebarsPath,
|
||||
getDocsDirPathLocalized,
|
||||
readVersionsFile,
|
||||
} from './versions/files';
|
||||
import {validateVersionName} from './versions/validation';
|
||||
import {loadSidebarsFileUnsafe} from './sidebars';
|
||||
|
@ -69,12 +70,7 @@ export async function cliDocsVersionCommand(
|
|||
throw err;
|
||||
}
|
||||
|
||||
// Load existing versions.
|
||||
let versions: string[] = [];
|
||||
const versionsJSONFile = getVersionsFilePath(siteDir, pluginId);
|
||||
if (await fs.pathExists(versionsJSONFile)) {
|
||||
versions = await fs.readJSON(versionsJSONFile);
|
||||
}
|
||||
const versions = (await readVersionsFile(siteDir, pluginId)) ?? [];
|
||||
|
||||
// Check if version already exists.
|
||||
if (versions.includes(version)) {
|
||||
|
@ -137,7 +133,7 @@ export async function cliDocsVersionCommand(
|
|||
// Update versions.json file.
|
||||
versions.unshift(version);
|
||||
await fs.outputFile(
|
||||
versionsJSONFile,
|
||||
getVersionsFilePath(siteDir, pluginId),
|
||||
`${JSON.stringify(versions, null, 2)}\n`,
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
* array of valid version names.
|
||||
*/
|
||||
async function readVersionsFile(
|
||||
export async function readVersionsFile(
|
||||
siteDir: string,
|
||||
pluginId: string,
|
||||
): Promise<string[] | null> {
|
||||
|
|
|
@ -19,8 +19,9 @@ import type webpack from 'webpack';
|
|||
const requireFromDocusaurusCore = createRequire(
|
||||
require.resolve('@docusaurus/core/package.json'),
|
||||
);
|
||||
const ContextReplacementPlugin: typeof webpack.ContextReplacementPlugin =
|
||||
requireFromDocusaurusCore('webpack/lib/ContextReplacementPlugin');
|
||||
const ContextReplacementPlugin = requireFromDocusaurusCore(
|
||||
'webpack/lib/ContextReplacementPlugin',
|
||||
) as typeof webpack.ContextReplacementPlugin;
|
||||
|
||||
// Need to be inlined to prevent dark mode FOUC
|
||||
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
|
||||
|
|
|
@ -816,7 +816,6 @@ declare module '@theme/NavbarItem/NavbarNavLink' {
|
|||
|
||||
declare module '@theme/NavbarItem/DropdownNavbarItem' {
|
||||
import type {Props as NavbarNavLinkProps} from '@theme/NavbarItem/NavbarNavLink';
|
||||
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
export type DesktopOrMobileNavBarItemProps = NavbarNavLinkProps & {
|
||||
|
@ -976,7 +975,7 @@ declare module '@theme/NavbarItem' {
|
|||
} & SearchNavbarItemProps)
|
||||
);
|
||||
|
||||
export type Types = Props['type'];
|
||||
export type NavbarItemType = Props['type'];
|
||||
|
||||
export default function NavbarItem(props: Props): JSX.Element;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@ import Details from '@theme/Details';
|
|||
import type {Props} from '@theme/MDXComponents/Details';
|
||||
|
||||
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
|
||||
// Details theme component
|
||||
const summary: ReactElement<ComponentProps<'summary'>> | undefined =
|
||||
items.find((item) => item?.props?.mdxType === 'summary');
|
||||
const summary = items.find(
|
||||
(item): item is ReactElement<ComponentProps<'summary'>> =>
|
||||
React.isValidElement(item) && item.props?.mdxType === 'summary',
|
||||
);
|
||||
const children = <>{items.filter((item) => item !== summary)}</>;
|
||||
|
||||
return (
|
||||
|
|
|
@ -11,8 +11,10 @@ import type {Props} from '@theme/MDXComponents/Head';
|
|||
|
||||
// MDX elements are wrapped through the MDX pragma. In some cases (notably usage
|
||||
// with Head/Helmet) we need to unwrap those elements.
|
||||
function unwrapMDXElement(element: ReactElement) {
|
||||
if (element?.props?.mdxType && element?.props?.originalType) {
|
||||
function unwrapMDXElement(
|
||||
element: ReactElement<{mdxType?: string; originalType?: string} | undefined>,
|
||||
) {
|
||||
if (element.props?.mdxType && element.props.originalType) {
|
||||
const {mdxType, originalType, ...newProps} = element.props;
|
||||
return React.createElement(element.props.originalType, newProps);
|
||||
}
|
||||
|
@ -21,7 +23,7 @@ function unwrapMDXElement(element: ReactElement) {
|
|||
|
||||
export default function MDXHead(props: Props): JSX.Element {
|
||||
const unwrappedChildren = React.Children.map(props.children, (child) =>
|
||||
unwrapMDXElement(child as ReactElement),
|
||||
React.isValidElement(child) ? unwrapMDXElement(child) : child,
|
||||
);
|
||||
return (
|
||||
<Head {...(props as ComponentProps<typeof Head>)}>{unwrappedChildren}</Head>
|
||||
|
|
|
@ -14,8 +14,8 @@ export default function MDXPre(props: Props): JSX.Element {
|
|||
<CodeBlock
|
||||
// If this pre is created by a ``` fenced codeblock, unwrap the children
|
||||
{...(isValidElement(props.children) &&
|
||||
props.children.props.originalType === 'code'
|
||||
? props.children?.props
|
||||
props.children.props?.originalType === 'code'
|
||||
? props.children.props
|
||||
: {...props})}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -11,13 +11,12 @@ import {
|
|||
useNavbarMobileSidebar,
|
||||
useThemeConfig,
|
||||
} 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 SearchBar from '@theme/SearchBar';
|
||||
import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';
|
||||
import NavbarLogo from '@theme/Navbar/Logo';
|
||||
import NavbarSearch from '@theme/Navbar/Search';
|
||||
import type {Props as NavbarItemConfig} from '@theme/NavbarItem';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import {useNavbarMobileSidebar, useThemeConfig} from '@docusaurus/theme-common';
|
||||
import NavbarItem from '@theme/NavbarItem';
|
||||
import type {Props as NavbarItemConfig} from '@theme/NavbarItem';
|
||||
import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem';
|
||||
|
||||
function useNavbarItems() {
|
||||
// TODO temporary casting until ThemeConfig type is improved
|
||||
|
|
|
@ -38,13 +38,13 @@ export default function DocsVersionDropdownNavbarItem({
|
|||
// We try to link to the same doc, in another version
|
||||
// When not possible, fallback to the "main doc" of the version
|
||||
const versionDoc =
|
||||
activeDocContext?.alternateDocVersions[version.name] ??
|
||||
activeDocContext.alternateDocVersions[version.name] ??
|
||||
getVersionMainDoc(version);
|
||||
return {
|
||||
isNavLink: true,
|
||||
label: version.label,
|
||||
to: versionDoc.path,
|
||||
isActive: () => version === activeDocContext?.activeVersion,
|
||||
isActive: () => version === activeDocContext.activeVersion,
|
||||
onClick: () => savePreferredVersionName(version.name),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -15,12 +15,11 @@ import {
|
|||
useLocalPathname,
|
||||
} from '@docusaurus/theme-common';
|
||||
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
|
||||
import NavbarItem from '@theme/NavbarItem';
|
||||
import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
import type {
|
||||
DesktopOrMobileNavBarItemProps,
|
||||
Props,
|
||||
} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
const dropdownLinkActiveClass = 'dropdown__link--active';
|
||||
|
||||
|
|
|
@ -7,31 +7,22 @@
|
|||
|
||||
import React from 'react';
|
||||
import ComponentTypes from '@theme/NavbarItem/ComponentTypes';
|
||||
import {type Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {Types, Props} from '@theme/NavbarItem';
|
||||
import type {NavbarItemType, Props} from '@theme/NavbarItem';
|
||||
|
||||
const getNavbarItemComponent = (type: NonNullable<Types>) => {
|
||||
const component = ComponentTypes[type];
|
||||
if (!component) {
|
||||
throw new Error(`No NavbarItem component found for type "${type}".`);
|
||||
}
|
||||
return component;
|
||||
};
|
||||
|
||||
function getComponentType(type: Types, isDropdown: boolean) {
|
||||
function normalizeComponentType(type: NavbarItemType, props: object) {
|
||||
// Backward compatibility: navbar item with no type set
|
||||
// but containing dropdown items should use the type "dropdown"
|
||||
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 {
|
||||
const componentType = getComponentType(
|
||||
type,
|
||||
(props as DropdownNavbarItemProps).items !== undefined,
|
||||
);
|
||||
const NavbarItemComponent = getNavbarItemComponent(componentType);
|
||||
const componentType = normalizeComponentType(type, props);
|
||||
const NavbarItemComponent = ComponentTypes[componentType];
|
||||
if (!NavbarItemComponent) {
|
||||
throw new Error(`No NavbarItem component found for type "${type}".`);
|
||||
}
|
||||
return <NavbarItemComponent {...(props as never)} />;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ function TabsComponent(props: Props): JSX.Element {
|
|||
? defaultValueProp
|
||||
: defaultValueProp ??
|
||||
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)) {
|
||||
throw new Error(
|
||||
`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) {
|
||||
case 'ArrowRight': {
|
||||
const nextTab = tabRefs.indexOf(event.currentTarget) + 1;
|
||||
focusElement = tabRefs[nextTab] || tabRefs[0]!;
|
||||
focusElement = tabRefs[nextTab] ?? tabRefs[0]!;
|
||||
break;
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
const prevTab = tabRefs.indexOf(event.currentTarget) - 1;
|
||||
focusElement = tabRefs[prevTab] || tabRefs[tabRefs.length - 1]!;
|
||||
focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -92,7 +92,7 @@ export function Details({
|
|||
}
|
||||
}}>
|
||||
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */}
|
||||
{summary || <summary>Details</summary>}
|
||||
{summary ?? <summary>Details</summary>}
|
||||
|
||||
<Collapsible
|
||||
lazy={false} // Content might matter for SEO in this case
|
||||
|
|
|
@ -151,7 +151,7 @@ export function useTOCHighlight(config: TOCHighlightConfig | undefined): void {
|
|||
function updateLinkActiveClass(link: HTMLAnchorElement, active: boolean) {
|
||||
if (active) {
|
||||
if (lastActiveLinkRef.current && lastActiveLinkRef.current !== link) {
|
||||
lastActiveLinkRef.current?.classList.remove(linkActiveClassName);
|
||||
lastActiveLinkRef.current.classList.remove(linkActiveClassName);
|
||||
}
|
||||
link.classList.add(linkActiveClassName);
|
||||
lastActiveLinkRef.current = link;
|
||||
|
|
|
@ -97,7 +97,7 @@ export function parseCodeBlockTitle(metastring?: string): string {
|
|||
}
|
||||
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
const directive = match.slice(1).find((item) => item !== undefined)!;
|
||||
const directive = match
|
||||
.slice(1)
|
||||
.find((item: string | undefined) => item !== undefined)!;
|
||||
if (lineToClassName[directive]) {
|
||||
blocks[lineToClassName[directive]!]!.range += `${lineNumber},`;
|
||||
} else if (blockStartToClassName[directive]) {
|
||||
|
|
|
@ -85,10 +85,10 @@ export type FooterBase = {
|
|||
};
|
||||
|
||||
export type MultiColumnFooter = FooterBase & {
|
||||
links: Array<{
|
||||
links: {
|
||||
title: string | null;
|
||||
items: FooterLinkItem[];
|
||||
}>;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type SimpleFooter = FooterBase & {
|
||||
|
@ -123,7 +123,7 @@ export type ThemeConfig = {
|
|||
prism: PrismConfig;
|
||||
footer?: Footer;
|
||||
image?: string;
|
||||
metadata: Array<{[key: string]: string}>;
|
||||
metadata: {[key: string]: string}[];
|
||||
tableOfContents: TableOfContents;
|
||||
};
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ export default function Playground({
|
|||
{/* @ts-expect-error: type incompatibility with refs */}
|
||||
<LiveProvider
|
||||
code={children.replace(/\n$/, '')}
|
||||
transformCode={transformCode || ((code) => `${code};`)}
|
||||
transformCode={transformCode ?? ((code) => `${code};`)}
|
||||
theme={prismTheme}
|
||||
{...props}>
|
||||
{playgroundPosition === 'top' ? (
|
||||
|
|
|
@ -75,7 +75,7 @@ type FacetFilters = Required<
|
|||
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
|
||||
const normalize = (
|
||||
f: FacetFilters,
|
||||
): readonly string[] | ReadonlyArray<string | readonly string[]> =>
|
||||
): readonly string[] | readonly (string | readonly string[])[] =>
|
||||
typeof f === 'string' ? [f] : f;
|
||||
return [...normalize(f1), ...normalize(f2)] as FacetFilters;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,10 @@ describe('theme translations', () => {
|
|||
.then((files) =>
|
||||
Promise.all(
|
||||
files.map(
|
||||
(baseMessagesFile): Promise<{[key: string]: string}> =>
|
||||
fs.readJSON(path.join(baseMessagesDirPath, baseMessagesFile)),
|
||||
(baseMessagesFile) =>
|
||||
fs.readJSON(
|
||||
path.join(baseMessagesDirPath, baseMessagesFile),
|
||||
) as Promise<{[key: string]: string}>,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -51,7 +51,7 @@ export async function readDefaultCodeTranslationMessages({
|
|||
const filePath = path.resolve(dirPath, localeToTry, `${name}.json`);
|
||||
|
||||
if (await fs.pathExists(filePath)) {
|
||||
return fs.readJSON(filePath);
|
||||
return fs.readJSON(filePath) as Promise<CodeTranslations>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,21 +37,24 @@ describe('isNameTooLong', () => {
|
|||
};
|
||||
const oldProcessPlatform = process.platform;
|
||||
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(
|
||||
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].apfs,
|
||||
typeof expected === 'boolean' ? expected : expected.apfs,
|
||||
);
|
||||
});
|
||||
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(
|
||||
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].apfs,
|
||||
typeof expected === 'boolean' ? expected : expected.apfs,
|
||||
);
|
||||
});
|
||||
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(
|
||||
typeof asserts[file] === 'boolean' ? asserts[file] : asserts[file].xfs,
|
||||
typeof expected === 'boolean' ? expected : expected.xfs,
|
||||
);
|
||||
});
|
||||
Object.defineProperty(process, 'platform', {value: oldProcessPlatform});
|
||||
|
@ -79,21 +82,24 @@ describe('shortName', () => {
|
|||
};
|
||||
const oldProcessPlatform = process.platform;
|
||||
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(
|
||||
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].apfs,
|
||||
typeof expected === 'string' ? expected : expected.apfs,
|
||||
);
|
||||
});
|
||||
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(
|
||||
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].apfs,
|
||||
typeof expected === 'string' ? expected : expected.apfs,
|
||||
);
|
||||
});
|
||||
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(
|
||||
typeof asserts[file] === 'string' ? asserts[file] : asserts[file].xfs,
|
||||
typeof expected === 'string' ? expected : expected.xfs,
|
||||
);
|
||||
});
|
||||
Object.defineProperty(process, 'platform', {value: oldProcessPlatform});
|
||||
|
|
|
@ -16,7 +16,9 @@ import updateNotifier from 'update-notifier';
|
|||
import boxen from 'boxen';
|
||||
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>} */
|
||||
let sitePkg;
|
||||
try {
|
||||
|
|
|
@ -15,7 +15,7 @@ export function dispatchLifecycleAction<K extends keyof ClientModule>(
|
|||
...args: Parameters<NonNullable<ClientModule[K]>>
|
||||
): () => void {
|
||||
const callbacks = clientModules.map((clientModule) => {
|
||||
const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ??
|
||||
const lifecycleFunction = (clientModule.default?.[lifecycleAction] ??
|
||||
clientModule[lifecycleAction]) as
|
||||
| ((
|
||||
...a: Parameters<NonNullable<ClientModule[K]>>
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('flat', () => {
|
|||
null: null,
|
||||
undefined,
|
||||
};
|
||||
Object.keys(primitives).forEach((key) => {
|
||||
(Object.keys(primitives) as (keyof typeof primitives)[]).forEach((key) => {
|
||||
const value = primitives[key];
|
||||
expect(
|
||||
flat({
|
||||
|
|
|
@ -70,7 +70,7 @@ const docusaurus = {
|
|||
// In some cases, webpack might decide to optimize further, leading to
|
||||
// the chunk assets being merged to another chunk. In this case, we can
|
||||
// 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 Promise.resolve();
|
||||
|
|
|
@ -47,10 +47,7 @@ function Link(
|
|||
const linksCollector = useLinksCollector();
|
||||
const innerRef = useRef<HTMLAnchorElement | null>(null);
|
||||
|
||||
useImperativeHandle(
|
||||
forwardedRef,
|
||||
() => innerRef.current as HTMLAnchorElement,
|
||||
);
|
||||
useImperativeHandle(forwardedRef, () => innerRef.current!);
|
||||
|
||||
// IMPORTANT: using to or href should not change anything
|
||||
// For example, MDX links will ALWAYS give us the href props
|
||||
|
|
|
@ -26,19 +26,6 @@ describe('useGlobalData', () => {
|
|||
}).result.current,
|
||||
).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', () => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
export function hasProtocol(url: string): boolean {
|
||||
return /^(?:\w*:|\/\/)/.test(url) === true;
|
||||
return /^(?:\w*:|\/\/)/.test(url);
|
||||
}
|
||||
|
||||
export default function isInternalUrl(url?: string): boolean {
|
||||
|
|
|
@ -11,9 +11,6 @@ import type {GlobalData, UseDataOptions} from '@docusaurus/types';
|
|||
|
||||
export default function useGlobalData(): GlobalData {
|
||||
const {globalData} = useDocusaurusContext();
|
||||
if (!globalData) {
|
||||
throw new Error('Docusaurus global data not found.');
|
||||
}
|
||||
return globalData;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import type {ChunkNames} from '@docusaurus/types';
|
|||
type Chunk = ChunkNames[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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
function supports(feature: string) {
|
||||
try {
|
||||
const fakeLink = document.createElement('link');
|
||||
return fakeLink.relList?.supports?.(feature);
|
||||
} catch (err) {
|
||||
return fakeLink.relList.supports(feature);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,9 +115,9 @@ async function doRender(locals: Locals & {path: string}) {
|
|||
// Using readJSON seems to fail for users of some plugins, possibly because of
|
||||
// the eval sandbox having a different `Buffer` instance (native one instead
|
||||
// of polyfilled one)
|
||||
const manifest: Manifest = await fs
|
||||
const manifest = (await fs
|
||||
.readFile(manifestPath, 'utf-8')
|
||||
.then(JSON.parse);
|
||||
.then(JSON.parse)) as Manifest;
|
||||
|
||||
// Get all required assets for this particular page based on client
|
||||
// manifest information.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 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`] = `
|
||||
" => Bad Docusaurus plugin value as path [plugins,0].
|
||||
" => Bad Docusaurus plugin value plugins[0].
|
||||
Example valid plugin config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus plugin value as path [plugins,0].
|
||||
" => Bad Docusaurus plugin value plugins[0].
|
||||
Example valid plugin config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus plugin value as path [plugins,0].
|
||||
" => Bad Docusaurus plugin value plugins[0].
|
||||
Example valid plugin config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus plugin value as path [plugins,0].
|
||||
" => Bad Docusaurus plugin value plugins[0].
|
||||
Example valid plugin config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus theme value as path [themes,0].
|
||||
" => Bad Docusaurus theme value themes[0].
|
||||
Example valid theme config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus theme value as path [themes,0].
|
||||
" => Bad Docusaurus theme value themes[0].
|
||||
Example valid theme config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus theme value as path [themes,0].
|
||||
" => Bad Docusaurus theme value themes[0].
|
||||
Example valid theme config:
|
||||
{
|
||||
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`] = `
|
||||
" => Bad Docusaurus theme value as path [themes,0].
|
||||
" => Bad Docusaurus theme value themes[0].
|
||||
Example valid theme config:
|
||||
{
|
||||
themes: [
|
||||
|
|
|
@ -100,7 +100,9 @@ function createPluginSchema(theme: boolean) {
|
|||
|
||||
error.message = ` => Bad Docusaurus ${
|
||||
theme ? 'theme' : 'plugin'
|
||||
} value as path [${error.path}].
|
||||
} value ${error.path.reduce((acc, cur) =>
|
||||
typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`,
|
||||
)}.
|
||||
${validConfigExample}
|
||||
`;
|
||||
});
|
||||
|
@ -247,7 +249,9 @@ export function validateConfig(config: unknown): DocusaurusConfig {
|
|||
if (error) {
|
||||
const unknownFields = error.details.reduce((formattedError, err) => {
|
||||
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;
|
||||
}, '');
|
||||
|
|
|
@ -83,5 +83,5 @@ export function loadHtmlTags(
|
|||
.trim(),
|
||||
),
|
||||
),
|
||||
);
|
||||
) as Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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:
|
||||
{
|
||||
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:
|
||||
{
|
||||
plugins: [
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('initPlugins', () => {
|
|||
|
||||
it('parses plugins correctly and loads them in correct order', async () => {
|
||||
const {context, plugins} = await loadSite();
|
||||
expect(context.siteConfig.plugins?.length).toBe(4);
|
||||
expect(context.siteConfig.plugins).toHaveLength(4);
|
||||
expect(plugins).toHaveLength(8);
|
||||
|
||||
expect(plugins[0].name).toBe('preset-plugin1');
|
||||
|
|
|
@ -184,7 +184,7 @@ function extractSourceCodeAstTranslations(
|
|||
sourceCodeFilePath: string,
|
||||
): SourceCodeFileTranslations {
|
||||
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}`;
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,9 @@ ${sourceWarningPart(path.node)}`);
|
|||
if (isJSXText || isJSXExpressionContainer) {
|
||||
message = isJSXText
|
||||
? 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] = {
|
||||
message,
|
||||
|
|
|
@ -198,7 +198,7 @@ export function applyConfigurePostCss(
|
|||
configurePostCss: NonNullable<Plugin['configurePostCss']>,
|
||||
config: Configuration,
|
||||
): Configuration {
|
||||
type LocalPostCSSLoader = unknown & {
|
||||
type LocalPostCSSLoader = object & {
|
||||
options: {postcssOptions: PostCssOptions};
|
||||
};
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as lqip from './lqip';
|
||||
import type {LoaderContext} from 'webpack';
|
||||
import type {LoaderContext, LoaderModule} from 'webpack';
|
||||
|
||||
type Options = {
|
||||
base64: boolean;
|
||||
|
@ -36,8 +36,9 @@ export default async function lqipLoader(
|
|||
} else {
|
||||
if (!contentIsFileExport) {
|
||||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||
const fileLoader = require('file-loader') as typeof import('file-loader');
|
||||
content = fileLoader.call(this, contentBuffer);
|
||||
const fileLoader = require('file-loader') as LoaderModule['default'];
|
||||
// @ts-expect-error: type is a bit unwieldy...
|
||||
content = fileLoader!.call(this, contentBuffer) as string;
|
||||
}
|
||||
source = content.match(
|
||||
/^(?:export default|module.exports =) (?<source>.*);/,
|
||||
|
|
|
@ -43,68 +43,64 @@ function getOutputCss(output: stylelint.LinterResult) {
|
|||
function testStylelintRule(config: stylelint.Config, tests: TestSuite) {
|
||||
describe(`${tests.ruleName}`, () => {
|
||||
const checkTestCaseContent = (testCase: TestCase) =>
|
||||
testCase.description || testCase.code;
|
||||
testCase.description ?? testCase.code;
|
||||
|
||||
if (tests.accept?.length) {
|
||||
describe('accept cases', () => {
|
||||
tests.accept.forEach((testCase) => {
|
||||
it(`${checkTestCaseContent(testCase)}`, async () => {
|
||||
const options: stylelint.LinterOptions = {
|
||||
code: testCase.code,
|
||||
config,
|
||||
};
|
||||
describe('accept cases', () => {
|
||||
tests.accept.forEach((testCase) => {
|
||||
it(`${checkTestCaseContent(testCase)}`, async () => {
|
||||
const options: stylelint.LinterOptions = {
|
||||
code: testCase.code,
|
||||
config,
|
||||
};
|
||||
|
||||
const output = await stylelint.lint(options);
|
||||
expect(output.results[0]!.warnings).toEqual([]);
|
||||
if (!tests.fix) {
|
||||
return;
|
||||
}
|
||||
const fixedOutput = await stylelint.lint({...options, fix: true});
|
||||
const fixedCode = getOutputCss(fixedOutput);
|
||||
expect(fixedCode).toBe(testCase.code);
|
||||
});
|
||||
const output = await stylelint.lint(options);
|
||||
expect(output.results[0]!.warnings).toEqual([]);
|
||||
if (!tests.fix) {
|
||||
return;
|
||||
}
|
||||
const fixedOutput = await stylelint.lint({...options, fix: true});
|
||||
const fixedCode = getOutputCss(fixedOutput);
|
||||
expect(fixedCode).toBe(testCase.code);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (tests.reject?.length) {
|
||||
describe('reject cases', () => {
|
||||
tests.reject.forEach((testCase) => {
|
||||
it(`${checkTestCaseContent(testCase)}`, async () => {
|
||||
const options = {
|
||||
code: testCase.code,
|
||||
config,
|
||||
};
|
||||
describe('reject cases', () => {
|
||||
tests.reject.forEach((testCase) => {
|
||||
it(`${checkTestCaseContent(testCase)}`, async () => {
|
||||
const options = {
|
||||
code: testCase.code,
|
||||
config,
|
||||
};
|
||||
|
||||
const output = await stylelint.lint(options);
|
||||
const {warnings} = output.results[0]!;
|
||||
const warning = warnings[0]!;
|
||||
expect(warnings.length).toBeGreaterThanOrEqual(1);
|
||||
expect(testCase).toHaveMessage();
|
||||
if (testCase.message != null) {
|
||||
expect(warning.text).toBe(testCase.message);
|
||||
}
|
||||
if (testCase.line != null) {
|
||||
expect(warning.line).toBe(testCase.line);
|
||||
}
|
||||
if (testCase.column != null) {
|
||||
expect(warning.column).toBe(testCase.column);
|
||||
}
|
||||
if (!tests.fix) {
|
||||
return;
|
||||
}
|
||||
if (!testCase.fixed) {
|
||||
throw new Error(
|
||||
'If using { fix: true } in test tests, all reject cases must have { fixed: .. }',
|
||||
);
|
||||
}
|
||||
const fixedOutput = await stylelint.lint({...options, fix: true});
|
||||
const fixedCode = getOutputCss(fixedOutput);
|
||||
expect(fixedCode).toBe(testCase.fixed);
|
||||
});
|
||||
const output = await stylelint.lint(options);
|
||||
const {warnings} = output.results[0]!;
|
||||
const warning = warnings[0]!;
|
||||
expect(warnings.length).toBeGreaterThanOrEqual(1);
|
||||
expect(testCase).toHaveMessage();
|
||||
if (testCase.message != null) {
|
||||
expect(warning.text).toBe(testCase.message);
|
||||
}
|
||||
if (testCase.line != null) {
|
||||
expect(warning.line).toBe(testCase.line);
|
||||
}
|
||||
if (testCase.column != null) {
|
||||
expect(warning.column).toBe(testCase.column);
|
||||
}
|
||||
if (!tests.fix) {
|
||||
return;
|
||||
}
|
||||
if (!testCase.fixed) {
|
||||
throw new Error(
|
||||
'If using { fix: true } in test tests, all reject cases must have { fixed: .. }',
|
||||
);
|
||||
}
|
||||
const fixedOutput = await stylelint.lint({...options, fix: true});
|
||||
const fixedCode = getOutputCss(fixedOutput);
|
||||
expect(fixedCode).toBe(testCase.fixed);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
expect.extend({
|
||||
toHaveMessage(testCase: TestCase) {
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"allowJs": true,
|
||||
"skipLibCheck": true // @types/webpack and webpack/types.d.ts are not the same thing
|
||||
},
|
||||
"include": ["./**/*", "./**/.eslintrc.js"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"coverage/**",
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function Version(): JSX.Element {
|
|||
const pastVersions = versions.filter(
|
||||
(version) => version !== latestVersion && version.name !== 'current',
|
||||
);
|
||||
const repoUrl = `https://github.com/${organizationName}/${projectName}`;
|
||||
const repoUrl = `https://github.com/${organizationName!}/${projectName!}`;
|
||||
|
||||
return (
|
||||
<Layout
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue