refactor: remove a lot of implicit anys (#7468)

This commit is contained in:
Joshua Chen 2022-05-23 15:40:53 +08:00 committed by GitHub
parent 0c8e57de67
commit 3666a2ede5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 148 additions and 163 deletions

View file

@ -1,4 +1,5 @@
__fixtures__ __fixtures__
__mocks__
dist dist
node_modules node_modules
.yarn .yarn

View file

@ -5,19 +5,27 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
// @ts-check
import fs from 'fs-extra'; import fs from 'fs-extra';
import shell from 'shelljs'; import shell from 'shelljs';
const NODE_MAJOR_VERSION = parseInt(process.versions.node.split('.')[0], 10); const NODE_MAJOR_VERSION = parseInt(
/** @type {string} */ (process.versions.node.split('.')[0]),
10,
);
if (NODE_MAJOR_VERSION < 16) { if (NODE_MAJOR_VERSION < 16) {
throw new Error( throw new Error(
'This generateExamples Docusaurus script requires at least Node.js 16 and npm 7. See why here: https://github.com/facebook/docusaurus/pull/5722#issuecomment-948847891', 'This generateExamples Docusaurus script requires at least Node.js 16 and npm 7. See why here: https://github.com/facebook/docusaurus/pull/5722#issuecomment-948847891',
); );
} }
// Generate one example per init template /**
// We use those generated examples as CodeSandbox projects * Generate one example per init template
// See https://github.com/facebook/docusaurus/issues/1699 * We use those generated examples as CodeSandbox projects
* See https://github.com/facebook/docusaurus/issues/1699
* @param {string} template
*/
async function generateTemplateExample(template) { async function generateTemplateExample(template) {
try { try {
console.log( console.log(
@ -103,6 +111,12 @@ async function generateTemplateExample(template) {
* Button visible here: https://jamstack.org/generators/ * Button visible here: https://jamstack.org/generators/
*/ */
function updateStarters() { function updateStarters() {
/**
* @param {Object} param0
* @param {string} param0.subfolder
* @param {string} param0.remote
* @param {string} param0.remoteBranch
*/
function forcePushGitSubtree({subfolder, remote, remoteBranch}) { function forcePushGitSubtree({subfolder, remote, remoteBranch}) {
console.log(''); console.log('');
// See https://stackoverflow.com/questions/33172857/how-do-i-force-a-subtree-push-to-overwrite-remote-changes // See https://stackoverflow.com/questions/33172857/how-do-i-force-a-subtree-push-to-overwrite-remote-changes

View file

@ -45,24 +45,12 @@ program
\`custom\`: enter your custom git clone command. We will prompt you for it.`, \`custom\`: enter your custom git clone command. We will prompt you for it.`,
) )
.description('Initialize website.') .description('Initialize website.')
.action( .action((siteName, template, rootDir, options) => {
(
siteName,
template,
rootDir = '.',
{packageManager, skipInstall, typescript, gitStrategy} = {},
) => {
// See https://github.com/facebook/docusaurus/pull/6860 // See https://github.com/facebook/docusaurus/pull/6860
import('../lib/index.js').then(({default: init}) => { import('../lib/index.js').then(({default: init}) => {
init(path.resolve(rootDir), siteName, template, { init(path.resolve(rootDir ?? '.'), siteName, template, options);
packageManager,
skipInstall,
typescript,
gitStrategy,
}); });
}); });
},
);
program.parse(process.argv); program.parse(process.argv);

View file

@ -14,6 +14,7 @@ import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit'; import visit from 'unist-util-visit';
import slug from '../index'; import slug from '../index';
import type {Plugin} from 'unified'; import type {Plugin} from 'unified';
import type {Parent} from 'unist';
function process(doc: string, plugins: Plugin[] = []) { function process(doc: string, plugins: Plugin[] = []) {
const processor = remark().use({plugins: [...plugins, slug]}); const processor = remark().use({plugins: [...plugins, slug]});
@ -58,11 +59,8 @@ describe('headings remark plugin', () => {
it('does not overwrite `data` on headings', () => { it('does not overwrite `data` on headings', () => {
const result = process('# Normal\n', [ const result = process('# Normal\n', [
() => { () => (root) => {
function transform(tree) { (root as Parent).children[0]!.data = {foo: 'bar'};
tree.children[0].data = {foo: 'bar'};
}
return transform;
}, },
]); ]);
const expected = u('root', [ const expected = u('root', [
@ -81,11 +79,10 @@ describe('headings remark plugin', () => {
it('does not overwrite `data.hProperties` on headings', () => { it('does not overwrite `data.hProperties` on headings', () => {
const result = process('# Normal\n', [ const result = process('# Normal\n', [
() => { () => (root) => {
function transform(tree) { (root as Parent).children[0]!.data = {
tree.children[0].data = {hProperties: {className: ['foo']}}; hProperties: {className: ['foo']},
} };
return transform;
}, },
]); ]);
const expected = u('root', [ const expected = u('root', [
@ -111,12 +108,9 @@ describe('headings remark plugin', () => {
'## Something also', '## Something also',
].join('\n\n'), ].join('\n\n'),
[ [
() => { () => (root) => {
function transform(tree) { (root as Parent).children[1]!.data = {hProperties: {id: 'here'}};
tree.children[1].data = {hProperties: {id: 'here'}}; (root as Parent).children[3]!.data = {hProperties: {id: 'something'}};
tree.children[3].data = {hProperties: {id: 'something'}};
}
return transform;
}, },
], ],
); );
@ -270,9 +264,9 @@ describe('headings remark plugin', () => {
# {#text-after} custom ID # {#text-after} custom ID
`); `);
const headers = []; const headers: {text: string; id: string}[] = [];
visit(result, 'heading', (node) => { visit(result, 'heading', (node) => {
headers.push({text: toString(node), id: node.data.id}); headers.push({text: toString(node), id: node.data!.id as string});
}); });
expect(headers).toEqual([ expect(headers).toEqual([

View file

@ -75,50 +75,6 @@ exports[`toc remark plugin exports even with existing name 1`] = `
" "
`; `;
exports[`toc remark plugin exports with custom name 1`] = `
"export const customName = [
{
value: 'Endi',
id: 'endi',
level: 3
},
{
value: 'Endi',
id: 'endi-1',
level: 2
},
{
value: 'Yangshun',
id: 'yangshun',
level: 3
},
{
value: 'I ♥ unicode.',
id: 'i--unicode',
level: 2
}
];
### Endi
\`\`\`md
## This is ignored
\`\`\`
## Endi
Lorem ipsum
### Yangshun
Some content here
## I ♥ unicode.
export const c = 1;
"
`;
exports[`toc remark plugin handles empty headings 1`] = ` exports[`toc remark plugin handles empty headings 1`] = `
"export const toc = []; "export const toc = [];

View file

@ -12,13 +12,13 @@ import vfile from 'to-vfile';
import plugin from '../index'; import plugin from '../index';
import headings from '../../headings/index'; import headings from '../../headings/index';
const processFixture = async (name, options?) => { const processFixture = async (name: string) => {
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`); const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(filePath); const file = await vfile.read(filePath);
const result = await remark() const result = await remark()
.use(headings) .use(headings)
.use(mdx) .use(mdx)
.use(plugin, options) .use(plugin)
.process(file); .process(file);
return result.toString(); return result.toString();
@ -45,14 +45,6 @@ describe('toc remark plugin', () => {
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
it('exports with custom name', async () => {
const options = {
name: 'customName',
};
const result = await processFixture('just-content', options);
expect(result).toMatchSnapshot();
});
it('inserts below imports', async () => { it('inserts below imports', async () => {
const result = await processFixture('insert-below-imports'); const result = await processFixture('insert-below-imports');
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();

View file

@ -23,15 +23,13 @@ const parseOptions: ParserOptions = {
sourceType: 'module', sourceType: 'module',
}; };
const name = 'toc';
const isImport = (child: Node): child is Literal => child.type === 'import'; const isImport = (child: Node): child is Literal => child.type === 'import';
const hasImports = (index: number) => index > -1; const hasImports = (index: number) => index > -1;
const isExport = (child: Node): child is Literal => child.type === 'export'; const isExport = (child: Node): child is Literal => child.type === 'export';
type PluginOptions = { const isTarget = (child: Literal) => {
name?: string;
};
const isTarget = (child: Literal, name: string) => {
let found = false; let found = false;
const ast = parse(child.value, parseOptions); const ast = parse(child.value, parseOptions);
traverse(ast, { traverse(ast, {
@ -44,14 +42,14 @@ const isTarget = (child: Literal, name: string) => {
return found; return found;
}; };
const getOrCreateExistingTargetIndex = (children: Node[], name: string) => { const getOrCreateExistingTargetIndex = (children: Node[]) => {
let importsIndex = -1; let importsIndex = -1;
let targetIndex = -1; let targetIndex = -1;
children.forEach((child, index) => { children.forEach((child, index) => {
if (isImport(child)) { if (isImport(child)) {
importsIndex = index; importsIndex = index;
} else if (isExport(child) && isTarget(child, name)) { } else if (isExport(child) && isTarget(child)) {
targetIndex = index; targetIndex = index;
} }
}); });
@ -70,9 +68,7 @@ const getOrCreateExistingTargetIndex = (children: Node[], name: string) => {
return targetIndex; return targetIndex;
}; };
export default function plugin(options: PluginOptions = {}): Transformer { export default function plugin(): Transformer {
const name = options.name || 'toc';
return (root) => { return (root) => {
const headings: TOCItem[] = []; const headings: TOCItem[] = [];
@ -91,7 +87,7 @@ export default function plugin(options: PluginOptions = {}): Transformer {
}); });
}); });
const {children} = root as Parent<Literal>; const {children} = root as Parent<Literal>;
const targetIndex = getOrCreateExistingTargetIndex(children, name); const targetIndex = getOrCreateExistingTargetIndex(children);
if (headings.length) { if (headings.length) {
children[targetIndex]!.value = `export const ${name} = ${stringifyObject( children[targetIndex]!.value = `export const ${name} = ${stringifyObject(

View file

@ -10,16 +10,19 @@ import path from 'path';
import remark from 'remark'; import remark from 'remark';
import mdx from 'remark-mdx'; import mdx from 'remark-mdx';
import vfile from 'to-vfile'; import vfile from 'to-vfile';
import plugin from '../index'; import plugin, {type PluginOptions} from '../index';
import headings from '../../headings/index'; import headings from '../../headings/index';
const processFixture = async (name, options) => { const processFixture = async (
name: string,
options: Partial<PluginOptions>,
) => {
const filePath = path.join(__dirname, `__fixtures__/${name}.md`); const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
const file = await vfile.read(filePath); const file = await vfile.read(filePath);
const result = await remark() const result = await remark()
.use(headings) .use(headings)
.use(mdx) .use(mdx)
.use(plugin, {...options, filePath}) .use(plugin, {siteDir: __dirname, staticDirs: [], ...options})
.process(file); .process(file);
return result.toString(); return result.toString();

View file

@ -27,7 +27,7 @@ const {
loaders: {inlineMarkdownImageFileLoader}, loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils(); } = getFileLoaderUtils();
type PluginOptions = { export type PluginOptions = {
staticDirs: string[]; staticDirs: string[];
siteDir: string; siteDir: string;
}; };

View file

@ -10,9 +10,9 @@ import remark from 'remark';
import mdx from 'remark-mdx'; import mdx from 'remark-mdx';
import vfile from 'to-vfile'; import vfile from 'to-vfile';
import plugin from '..'; import plugin from '..';
import transformImage from '../../transformImage'; import transformImage, {type PluginOptions} from '../../transformImage';
const processFixture = async (name: string, options?) => { const processFixture = async (name: string, options?: PluginOptions) => {
const filePath = path.join(__dirname, `__fixtures__/${name}.md`); const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
const staticDirs = [ const staticDirs = [
path.join(__dirname, '__fixtures__/static'), path.join(__dirname, '__fixtures__/static'),
@ -24,7 +24,6 @@ const processFixture = async (name: string, options?) => {
.use(transformImage, {...options, filePath, staticDirs}) .use(transformImage, {...options, filePath, staticDirs})
.use(plugin, { .use(plugin, {
...options, ...options,
filePath,
staticDirs, staticDirs,
siteDir: path.join(__dirname, '__fixtures__'), siteDir: path.join(__dirname, '__fixtures__'),
}) })

View file

@ -25,7 +25,7 @@ const {
loaders: {inlineMarkdownLinkFileLoader}, loaders: {inlineMarkdownLinkFileLoader},
} = getFileLoaderUtils(); } = getFileLoaderUtils();
type PluginOptions = { export type PluginOptions = {
staticDirs: string[]; staticDirs: string[];
siteDir: string; siteDir: string;
}; };

View file

@ -5,6 +5,14 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
declare module '@mapbox/hast-util-to-jsx'; declare module '@mapbox/hast-util-to-jsx' {
import type {Node} from 'unist';
declare module 'hast-util-to-string'; export default function toJsx(node: Node): string;
}
declare module 'hast-util-to-string' {
import type {Node} from 'unist';
export default function toString(node: Node): string;
}

View file

@ -11,6 +11,7 @@ import {normalizeThemeConfig} from '@docusaurus/utils-validation';
import theme from 'prism-react-renderer/themes/github'; import theme from 'prism-react-renderer/themes/github';
import darkTheme from 'prism-react-renderer/themes/dracula'; import darkTheme from 'prism-react-renderer/themes/dracula';
import {ThemeConfigSchema, DEFAULT_CONFIG} from '../validateThemeConfig'; import {ThemeConfigSchema, DEFAULT_CONFIG} from '../validateThemeConfig';
import type {ThemeConfig} from '@docusaurus/theme-common';
function testValidateThemeConfig(partialThemeConfig: {[key: string]: unknown}) { function testValidateThemeConfig(partialThemeConfig: {[key: string]: unknown}) {
return normalizeThemeConfig(ThemeConfigSchema, { return normalizeThemeConfig(ThemeConfigSchema, {
@ -656,7 +657,7 @@ describe('themeConfig', () => {
}); });
describe('color mode config', () => { describe('color mode config', () => {
const withDefaultValues = (colorMode) => const withDefaultValues = (colorMode: ThemeConfig['colorMode']) =>
_.merge({}, DEFAULT_CONFIG.colorMode, colorMode); _.merge({}, DEFAULT_CONFIG.colorMode, colorMode);
it('switch config', () => { it('switch config', () => {

View file

@ -7,12 +7,10 @@
import defaultPrismTheme from 'prism-react-renderer/themes/palenight'; import defaultPrismTheme from 'prism-react-renderer/themes/palenight';
import {Joi, URISchema} from '@docusaurus/utils-validation'; import {Joi, URISchema} from '@docusaurus/utils-validation';
import type { import type {ThemeConfig} from '@docusaurus/theme-common';
ThemeConfig, import type {ThemeConfigValidationContext} from '@docusaurus/types';
ThemeConfigValidationContext,
} from '@docusaurus/types';
const DEFAULT_DOCS_CONFIG = { const DEFAULT_DOCS_CONFIG: ThemeConfig['docs'] = {
versionPersistence: 'localStorage', versionPersistence: 'localStorage',
sidebar: { sidebar: {
hideable: false, hideable: false,
@ -31,13 +29,13 @@ const DocsSchema = Joi.object({
}).default(DEFAULT_DOCS_CONFIG.sidebar), }).default(DEFAULT_DOCS_CONFIG.sidebar),
}).default(DEFAULT_DOCS_CONFIG); }).default(DEFAULT_DOCS_CONFIG);
const DEFAULT_COLOR_MODE_CONFIG = { const DEFAULT_COLOR_MODE_CONFIG: ThemeConfig['colorMode'] = {
defaultMode: 'light', defaultMode: 'light',
disableSwitch: false, disableSwitch: false,
respectPrefersColorScheme: false, respectPrefersColorScheme: false,
}; };
export const DEFAULT_CONFIG = { export const DEFAULT_CONFIG: ThemeConfig = {
colorMode: DEFAULT_COLOR_MODE_CONFIG, colorMode: DEFAULT_COLOR_MODE_CONFIG,
docs: DEFAULT_DOCS_CONFIG, docs: DEFAULT_DOCS_CONFIG,
metadata: [], metadata: [],
@ -301,8 +299,9 @@ const CustomCssSchema = Joi.alternatives()
.try(Joi.array().items(Joi.string().required()), Joi.string().required()) .try(Joi.array().items(Joi.string().required()), Joi.string().required())
.optional(); .optional();
export const ThemeConfigSchema = Joi.object({ export const ThemeConfigSchema = Joi.object<ThemeConfig>({
// TODO temporary (@alpha-58) // TODO temporary (@alpha-58)
// @ts-expect-error: forbidden
disableDarkMode: Joi.any().forbidden().messages({ disableDarkMode: Joi.any().forbidden().messages({
'any.unknown': 'any.unknown':
'disableDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.disableSwitch = true', 'disableDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.disableSwitch = true',

View file

@ -32,7 +32,7 @@ export type NavbarLogo = {
// TODO improve // TODO improve
export type Navbar = { export type Navbar = {
style: 'dark' | 'primary'; style?: 'dark' | 'primary';
hideOnScroll: boolean; hideOnScroll: boolean;
title?: string; title?: string;
items: NavbarItem[]; items: NavbarItem[];

View file

@ -6,9 +6,13 @@
*/ */
import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig'; import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig';
import type {Joi} from '@docusaurus/utils-validation';
function testValidateThemeConfig(themeConfig) { function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) {
function validate(schema, cfg) { function validate(
schema: Joi.ObjectSchema<{[key: string]: unknown}>,
cfg: {[key: string]: unknown},
) {
const {value, error} = schema.validate(cfg, { const {value, error} = schema.validate(cfg, {
convert: false, convert: false,
}); });

View file

@ -9,7 +9,10 @@ import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig';
import type {Joi} from '@docusaurus/utils-validation'; import type {Joi} from '@docusaurus/utils-validation';
function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) { function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) {
function validate(schema: Joi.Schema, cfg: {[key: string]: unknown}) { function validate(
schema: Joi.ObjectSchema<{[key: string]: unknown}>,
cfg: {[key: string]: unknown},
) {
const {value, error} = schema.validate(cfg, { const {value, error} = schema.validate(cfg, {
convert: false, convert: false,
}); });

View file

@ -237,10 +237,14 @@ function SearchPageContent(): JSX.Element {
url, url,
_highlightResult: {hierarchy}, _highlightResult: {hierarchy},
_snippetResult: snippet = {}, _snippetResult: snippet = {},
}: {
url: string;
_highlightResult: {hierarchy: {[key: string]: {value: string}}};
_snippetResult: {content?: {value: string}};
}) => { }) => {
const parsedURL = new URL(url); const parsedURL = new URL(url);
const titles = Object.keys(hierarchy).map((key) => const titles = Object.keys(hierarchy).map((key) =>
sanitizeValue(hierarchy[key].value), sanitizeValue(hierarchy[key]!.value),
); );
return { return {

View file

@ -480,19 +480,19 @@ export type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[];
export type ValidationSchema<T> = Joi.ObjectSchema<T>; export type ValidationSchema<T> = Joi.ObjectSchema<T>;
export type Validate<T, U> = ( export type Validate<In, Out> = (
validationSchema: ValidationSchema<U>, validationSchema: ValidationSchema<Out>,
options: T, options: In,
) => U; ) => Out;
export type OptionValidationContext<T, U> = { export type OptionValidationContext<In, Out> = {
validate: Validate<T, U>; validate: Validate<In, Out>;
options: T; options: In;
}; };
export type ThemeConfigValidationContext<T> = { export type ThemeConfigValidationContext<In, Out = In> = {
validate: Validate<T, T>; validate: Validate<In, Out>;
themeConfig: Partial<T>; themeConfig: In;
}; };
export type Plugin<Content = unknown> = { export type Plugin<Content = unknown> = {

View file

@ -12,6 +12,7 @@ import registry from '@generated/registry';
import Loading from '@theme/Loading'; import Loading from '@theme/Loading';
import flat from '../flat'; import flat from '../flat';
import {RouteContextProvider} from '../routeContext'; import {RouteContextProvider} from '../routeContext';
import type {RouteContext} from '@docusaurus/types';
declare global { declare global {
interface NodeRequire { interface NodeRequire {
@ -71,14 +72,21 @@ export default function ComponentCreator(
loader, loader,
modules, modules,
webpack: () => optsWebpack, webpack: () => optsWebpack,
render(loaded, props) { render(
loaded: {[keyPath: string]: {[exportedName: string]: unknown}},
props,
) {
// `loaded` will be a map from key path (as returned from the flattened // `loaded` will be a map from key path (as returned from the flattened
// chunk names) to the modules loaded from the loaders. We now have to // chunk names) to the modules loaded from the loaders. We now have to
// restore the chunk names' previous shape from this flat record. // restore the chunk names' previous shape from this flat record.
// We do so by taking advantage of the existing `chunkNames` and replacing // We do so by taking advantage of the existing `chunkNames` and replacing
// each chunk name with its loaded module, so we don't create another // each chunk name with its loaded module, so we don't create another
// object from scratch. // object from scratch.
const loadedModules = JSON.parse(JSON.stringify(chunkNames)); const loadedModules = JSON.parse(JSON.stringify(chunkNames)) as {
__comp?: React.ComponentType<object>;
__context?: RouteContext;
[propName: string]: unknown;
};
Object.entries(loaded).forEach(([keyPath, loadedModule]) => { Object.entries(loaded).forEach(([keyPath, loadedModule]) => {
// JSON modules are also loaded as `{ default: ... }` (`import()` // JSON modules are also loaded as `{ default: ... }` (`import()`
// semantics) but we just want to pass the actual value to props. // semantics) but we just want to pass the actual value to props.
@ -100,7 +108,8 @@ export default function ComponentCreator(
Object.keys(loadedModule) Object.keys(loadedModule)
.filter((k) => k !== 'default') .filter((k) => k !== 'default')
.forEach((nonDefaultKey) => { .forEach((nonDefaultKey) => {
chunk[nonDefaultKey] = loadedModule[nonDefaultKey]; (chunk as {[key: string]: unknown})[nonDefaultKey] =
loadedModule[nonDefaultKey];
}); });
} }
// We now have this chunk prepared. Go down the key path and replace the // We now have this chunk prepared. Go down the key path and replace the
@ -108,15 +117,15 @@ export default function ComponentCreator(
let val = loadedModules; let val = loadedModules;
const keyPaths = keyPath.split('.'); const keyPaths = keyPath.split('.');
keyPaths.slice(0, -1).forEach((k) => { keyPaths.slice(0, -1).forEach((k) => {
val = val[k]; val = val[k] as {[propName: string]: unknown};
}); });
val[keyPaths[keyPaths.length - 1]!] = chunk; val[keyPaths[keyPaths.length - 1]!] = chunk;
}); });
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
const Component = loadedModules.__comp; const Component = loadedModules.__comp!;
delete loadedModules.__comp; delete loadedModules.__comp;
const routeContext = loadedModules.__context; const routeContext = loadedModules.__context!;
delete loadedModules.__context; delete loadedModules.__context;
/* eslint-enable no-underscore-dangle */ /* eslint-enable no-underscore-dangle */

View file

@ -7,11 +7,9 @@
/* eslint-disable jest/no-conditional-expect */ /* eslint-disable jest/no-conditional-expect */
import path from 'path'; import path from 'path';
import stylelint, {type LinterResult} from 'stylelint'; import stylelint from 'stylelint';
import rule from '../index'; import rule from '../index';
const {ruleName} = rule;
declare global { declare global {
namespace jest { namespace jest {
interface Matchers<R> { interface Matchers<R> {
@ -20,29 +18,44 @@ declare global {
} }
} }
function getOutputCss(output: LinterResult) { type TestSuite = {
ruleName: string;
fix: boolean;
accept: TestCase[];
reject: TestCase[];
};
type TestCase = {
code: string;
description?: string;
fixed?: string;
message?: string;
line?: number;
column?: number;
};
function getOutputCss(output: stylelint.LinterResult) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const result = output.results[0]._postcssResult; const result = output.results[0]!._postcssResult!;
return result.root.toString(result.opts.syntax); return result.root.toString(result.opts!.syntax);
} }
function testStylelintRule(config, tests) { function testStylelintRule(config: stylelint.Config, tests: TestSuite) {
describe(`${tests.ruleName}`, () => { describe(`${tests.ruleName}`, () => {
const checkTestCaseContent = (testCase) => const checkTestCaseContent = (testCase: TestCase) =>
testCase.description || testCase.code || 'no description'; testCase.description || testCase.code;
if (tests.accept?.length) { 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 () => {
const options = { const options: stylelint.LinterOptions = {
code: testCase.code, code: testCase.code,
config, config,
syntax: tests.syntax,
}; };
const output = await stylelint.lint(options); const output = await stylelint.lint(options);
expect(output.results[0].warnings).toEqual([]); expect(output.results[0]!.warnings).toEqual([]);
if (!tests.fix) { if (!tests.fix) {
return; return;
} }
@ -61,12 +74,11 @@ function testStylelintRule(config, tests) {
const options = { const options = {
code: testCase.code, code: testCase.code,
config, config,
syntax: tests.syntax,
}; };
const output = await stylelint.lint(options); const output = await stylelint.lint(options);
const {warnings} = output.results[0]; const {warnings} = output.results[0]!;
const warning = warnings[0]; const warning = warnings[0]!;
expect(warnings.length).toBeGreaterThanOrEqual(1); expect(warnings.length).toBeGreaterThanOrEqual(1);
expect(testCase).toHaveMessage(); expect(testCase).toHaveMessage();
if (testCase.message != null) { if (testCase.message != null) {
@ -95,7 +107,7 @@ function testStylelintRule(config, tests) {
} }
expect.extend({ expect.extend({
toHaveMessage(testCase) { toHaveMessage(testCase: TestCase) {
if (testCase.message == null) { if (testCase.message == null) {
return { return {
message: () => message: () =>
@ -118,11 +130,11 @@ testStylelintRule(
{ {
plugins: [path.join(__dirname, '../../lib/index.js')], plugins: [path.join(__dirname, '../../lib/index.js')],
rules: { rules: {
[ruleName]: [true, {header: '*\n * Copyright'}], [rule.ruleName]: [true, {header: '*\n * Copyright'}],
}, },
}, },
{ {
ruleName, ruleName: rule.ruleName,
fix: true, fix: true,
accept: [ accept: [
{ {

View file

@ -53,9 +53,11 @@
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",
"coverage/**",
"**/lib/**/*", "**/lib/**/*",
"website/**", "website/**",
"**/__mocks__/**/*", "**/__mocks__/**/*",
"**/__fixtures__/**/*",
"examples/**", "examples/**",
"packages/create-docusaurus/templates/**" "packages/create-docusaurus/templates/**"
] ]