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-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.`,

View file

@ -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';

View file

@ -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)) {

View file

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

View file

@ -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]')

View file

@ -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;
}

View file

@ -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();
});
});

View file

@ -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`,
);

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
* array of valid version names.
*/
async function readVersionsFile(
export async function readVersionsFile(
siteDir: string,
pluginId: string,
): Promise<string[] | null> {

View file

@ -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`

View file

@ -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;
}

View file

@ -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 (

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
// 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>

View file

@ -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})}
/>
);

View file

@ -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';

View file

@ -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

View file

@ -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),
};
});

View file

@ -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';

View file

@ -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)} />;
}

View file

@ -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:

View file

@ -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

View file

@ -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;

View file

@ -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]) {

View file

@ -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;
};

View file

@ -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' ? (

View file

@ -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;
}

View file

@ -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}>,
),
),
)

View file

@ -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>;
}
}

View file

@ -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});

View file

@ -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 {

View file

@ -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]>>

View file

@ -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({

View file

@ -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();

View file

@ -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

View file

@ -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', () => {

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;
/**

View file

@ -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;
}
}

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
// 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.

View file

@ -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: [

View file

@ -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;
}, '');

View file

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

View file

@ -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: [

View file

@ -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');

View file

@ -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,

View file

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

View file

@ -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>.*);/,

View file

@ -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) {

View file

@ -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/**",

View file

@ -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