refactor: remove "error" reporting level, move reportMessage to logger (#7642)

This commit is contained in:
Joshua Chen 2022-06-17 20:51:00 +08:00 committed by GitHub
parent 1b9bec1042
commit bfba6a8b02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 117 additions and 116 deletions

View file

@ -25,6 +25,7 @@ It exports a single object as default export: `logger`. `logger` has the followi
- `warn`: prints a warning that should be payed attention to. - `warn`: prints a warning that should be payed attention to.
- `error`: prints an error (not necessarily halting the program) that signals significant problems. - `error`: prints an error (not necessarily halting the program) that signals significant problems.
- `success`: prints a success message. - `success`: prints a success message.
- The `report` function. It takes a `ReportingSeverity` value (`ignore`, `log`, `warn`, `throw`) and reports a message according to the severity.
### A word on the `error` formatter ### A word on the `error` formatter

View file

@ -124,3 +124,33 @@ describe('success', () => {
expect(consoleMock.mock.calls).toMatchSnapshot(); expect(consoleMock.mock.calls).toMatchSnapshot();
}); });
}); });
describe('report', () => {
beforeAll(() => jest.clearAllMocks());
it('works with all severities', () => {
const consoleLog = jest.spyOn(console, 'info').mockImplementation(() => {});
const consoleWarn = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
logger.report('ignore')('hey');
logger.report('log')('hey');
logger.report('warn')('hey');
expect(() =>
logger.report('throw')('hey'),
).toThrowErrorMatchingInlineSnapshot(`"hey"`);
expect(() =>
// @ts-expect-error: for test
logger.report('foo')('hey'),
).toThrowErrorMatchingInlineSnapshot(
`"Unexpected "reportingSeverity" value: foo."`,
);
expect(consoleLog).toBeCalledTimes(1);
expect(consoleLog).toBeCalledWith(
expect.stringMatching(/.*\[INFO\].* hey/),
);
expect(consoleWarn).toBeCalledTimes(1);
expect(consoleWarn).toBeCalledWith(
expect.stringMatching(/.*\[WARNING\].* hey/),
);
});
});

View file

@ -6,6 +6,7 @@
*/ */
import chalk from 'chalk'; import chalk from 'chalk';
import type {ReportingSeverity} from '@docusaurus/types';
type InterpolatableValue = string | number | (string | number)[]; type InterpolatableValue = string | number | (string | number)[];
@ -122,11 +123,54 @@ function success(msg: unknown, ...values: InterpolatableValue[]): void {
}`, }`,
); );
} }
function throwError(msg: unknown): void;
function throwError(
msg: TemplateStringsArray,
...values: [InterpolatableValue, ...InterpolatableValue[]]
): void;
function throwError(msg: unknown, ...values: InterpolatableValue[]): void {
throw new Error(
values.length === 0
? stringify(msg)
: interpolate(msg as TemplateStringsArray, ...values),
);
}
function newLine(): void { function newLine(): void {
console.log(); console.log();
} }
/**
* Takes a message and reports it according to the severity that the user wants.
*
* - `ignore`: completely no-op
* - `log`: uses the `INFO` log level
* - `warn`: uses the `WARN` log level
* - `throw`: aborts the process, throws the error.
*
* Since the logger doesn't have logging level filters yet, these severities
* mostly just differ by their colors.
*
* @throws In addition to throwing when `reportingSeverity === "throw"`, this
* function also throws if `reportingSeverity` is not one of the above.
*/
function report(reportingSeverity: ReportingSeverity): typeof success {
const reportingMethods = {
ignore: () => {},
log: info,
warn,
throw: throwError,
};
if (
!Object.prototype.hasOwnProperty.call(reportingMethods, reportingSeverity)
) {
throw new Error(
`Unexpected "reportingSeverity" value: ${reportingSeverity}.`,
);
}
return reportingMethods[reportingSeverity];
}
const logger = { const logger = {
red: (msg: string | number): string => chalk.red(msg), red: (msg: string | number): string => chalk.red(msg),
yellow: (msg: string | number): string => chalk.yellow(msg), yellow: (msg: string | number): string => chalk.yellow(msg),
@ -144,6 +188,7 @@ const logger = {
warn, warn,
error, error,
success, success,
report,
newLine, newLine,
}; };

View file

@ -6,12 +6,12 @@
*/ */
import path from 'path'; import path from 'path';
import logger from '@docusaurus/logger';
import { import {
normalizeUrl, normalizeUrl,
docuHash, docuHash,
aliasedSitePath, aliasedSitePath,
getPluginI18nPath, getPluginI18nPath,
reportMessage,
posixPath, posixPath,
addTrailingPathSeparator, addTrailingPathSeparator,
createAbsoluteFilePathMatcher, createAbsoluteFilePathMatcher,
@ -391,10 +391,9 @@ export default async function pluginContentBlog(
if (onBrokenMarkdownLinks === 'ignore') { if (onBrokenMarkdownLinks === 'ignore') {
return; return;
} }
reportMessage( logger.report(
`Blog markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath}`,
onBrokenMarkdownLinks, onBrokenMarkdownLinks,
); )`Blog markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath}`;
}, },
}; };

View file

@ -13,7 +13,6 @@ import {
docuHash, docuHash,
aliasedSitePath, aliasedSitePath,
getContentPathList, getContentPathList,
reportMessage,
posixPath, posixPath,
addTrailingPathSeparator, addTrailingPathSeparator,
createAbsoluteFilePathMatcher, createAbsoluteFilePathMatcher,
@ -330,13 +329,9 @@ export default async function pluginContentDocs(
sourceToPermalink: getSourceToPermalink(), sourceToPermalink: getSourceToPermalink(),
versionsMetadata, versionsMetadata,
onBrokenMarkdownLink: (brokenMarkdownLink) => { onBrokenMarkdownLink: (brokenMarkdownLink) => {
if (siteConfig.onBrokenMarkdownLinks === 'ignore') { logger.report(
return;
}
reportMessage(
`Docs markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath} for version ${brokenMarkdownLink.contentPaths.versionName}`,
siteConfig.onBrokenMarkdownLinks, siteConfig.onBrokenMarkdownLinks,
); )`Docs markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath} for version number=${brokenMarkdownLink.contentPaths.versionName}`;
}, },
}; };

View file

@ -21,7 +21,7 @@ import type {Location} from 'history';
// === Configuration === // === Configuration ===
export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'error' | 'throw'; export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
export type PluginOptions = {id?: string} & {[key: string]: unknown}; export type PluginOptions = {id?: string} & {[key: string]: unknown};

View file

@ -12,7 +12,6 @@ import {
removePrefix, removePrefix,
mapAsyncSequential, mapAsyncSequential,
findAsyncSequential, findAsyncSequential,
reportMessage,
} from '../jsUtils'; } from '../jsUtils';
describe('removeSuffix', () => { describe('removeSuffix', () => {
@ -108,40 +107,3 @@ describe('findAsyncSequential', () => {
expect(timeTotal).toBeLessThan(1000); expect(timeTotal).toBeLessThan(1000);
}); });
}); });
describe('reportMessage', () => {
it('works with all severities', () => {
const consoleLog = jest.spyOn(console, 'info').mockImplementation(() => {});
const consoleWarn = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
const consoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
reportMessage('hey', 'ignore');
reportMessage('hey', 'log');
reportMessage('hey', 'warn');
reportMessage('hey', 'error');
expect(() =>
reportMessage('hey', 'throw'),
).toThrowErrorMatchingInlineSnapshot(`"hey"`);
expect(() =>
// @ts-expect-error: for test
reportMessage('hey', 'foo'),
).toThrowErrorMatchingInlineSnapshot(
`"Unexpected "reportingSeverity" value: foo."`,
);
expect(consoleLog).toBeCalledTimes(1);
expect(consoleLog).toBeCalledWith(
expect.stringMatching(/.*\[INFO\].* hey/),
);
expect(consoleWarn).toBeCalledTimes(1);
expect(consoleWarn).toBeCalledWith(
expect.stringMatching(/.*\[WARNING\].* hey/),
);
expect(consoleError).toBeCalledTimes(1);
expect(consoleError).toBeCalledWith(
expect.stringMatching(/.*\[ERROR\].* hey/),
);
});
});

View file

@ -40,7 +40,6 @@ export {
removePrefix, removePrefix,
mapAsyncSequential, mapAsyncSequential,
findAsyncSequential, findAsyncSequential,
reportMessage,
} from './jsUtils'; } from './jsUtils';
export { export {
normalizeUrl, normalizeUrl,

View file

@ -5,9 +5,6 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import logger from '@docusaurus/logger';
import type {ReportingSeverity} from '@docusaurus/types';
/** Removes a given string suffix from `str`. */ /** Removes a given string suffix from `str`. */
export function removeSuffix(str: string, suffix: string): string { export function removeSuffix(str: string, suffix: string): string {
if (suffix === '') { if (suffix === '') {
@ -60,43 +57,3 @@ export async function findAsyncSequential<T>(
} }
return undefined; return undefined;
} }
/**
* Takes a message and reports it according to the severity that the user wants.
*
* - `ignore`: completely no-op
* - `log`: uses the `INFO` log level
* - `warn`: uses the `WARN` log level
* - `error`: uses the `ERROR` log level
* - `throw`: aborts the process, throws the error.
*
* Since the logger doesn't have logging level filters yet, these severities
* mostly just differ by their colors.
*
* @throws In addition to throwing when `reportingSeverity === "throw"`, this
* function also throws if `reportingSeverity` is not one of the above.
*/
export function reportMessage(
message: string,
reportingSeverity: ReportingSeverity,
): void {
switch (reportingSeverity) {
case 'ignore':
break;
case 'log':
logger.info(message);
break;
case 'warn':
logger.warn(message);
break;
case 'error':
logger.error(message);
break;
case 'throw':
throw new Error(message);
default:
throw new Error(
`Unexpected "reportingSeverity" value: ${reportingSeverity}.`,
);
}
}

View file

@ -161,3 +161,8 @@ exports[`normalizeConfig throws error for unknown field 1`] = `
If you still want these fields to be in your configuration, put them in the "customFields" field. If you still want these fields to be in your configuration, put them in the "customFields" field.
See https://docusaurus.io/docs/api/docusaurus-config/#customfields" See https://docusaurus.io/docs/api/docusaurus-config/#customfields"
`; `;
exports[`normalizeConfig throws for "error" reporting severity 1`] = `
""onBrokenLinks" must be one of [ignore, log, warn, throw]
"
`;

View file

@ -308,6 +308,20 @@ describe('normalizeConfig', () => {
), ),
).toThrowErrorMatchingSnapshot(); ).toThrowErrorMatchingSnapshot();
}); });
it('throws for "error" reporting severity', () => {
expect(() =>
validateConfig(
{
title: 'Site',
url: 'https://example.com',
baseUrl: '/',
onBrokenLinks: 'error',
},
'docusaurus.config.js',
),
).toThrowErrorMatchingSnapshot();
});
}); });
describe('config warnings', () => { describe('config warnings', () => {

View file

@ -11,12 +11,7 @@ import _ from 'lodash';
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import combinePromises from 'combine-promises'; import combinePromises from 'combine-promises';
import {matchRoutes} from 'react-router-config'; import {matchRoutes} from 'react-router-config';
import { import {removePrefix, removeSuffix, resolvePathname} from '@docusaurus/utils';
removePrefix,
removeSuffix,
reportMessage,
resolvePathname,
} from '@docusaurus/utils';
import {getAllFinalRoutes} from './utils'; import {getAllFinalRoutes} from './utils';
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types'; import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
@ -247,6 +242,6 @@ export async function handleBrokenLinks({
const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks); const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks);
if (errorMessage) { if (errorMessage) {
reportMessage(errorMessage, onBrokenLinks); logger.report(onBrokenLinks)(errorMessage);
} }
} }

View file

@ -174,13 +174,13 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior! trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior!
i18n: I18N_CONFIG_SCHEMA, i18n: I18N_CONFIG_SCHEMA,
onBrokenLinks: Joi.string() onBrokenLinks: Joi.string()
.equal('ignore', 'log', 'warn', 'error', 'throw') .equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_CONFIG.onBrokenLinks), .default(DEFAULT_CONFIG.onBrokenLinks),
onBrokenMarkdownLinks: Joi.string() onBrokenMarkdownLinks: Joi.string()
.equal('ignore', 'log', 'warn', 'error', 'throw') .equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_CONFIG.onBrokenMarkdownLinks), .default(DEFAULT_CONFIG.onBrokenMarkdownLinks),
onDuplicateRoutes: Joi.string() onDuplicateRoutes: Joi.string()
.equal('ignore', 'log', 'warn', 'error', 'throw') .equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_CONFIG.onDuplicateRoutes), .default(DEFAULT_CONFIG.onDuplicateRoutes),
organizationName: Joi.string().allow(''), organizationName: Joi.string().allow(''),
staticDirectories: Joi.array() staticDirectories: Joi.array()

View file

@ -7,12 +7,12 @@
import query from 'querystring'; import query from 'querystring';
import _ from 'lodash'; import _ from 'lodash';
import logger from '@docusaurus/logger';
import { import {
docuHash, docuHash,
normalizeUrl, normalizeUrl,
simpleHash, simpleHash,
escapePath, escapePath,
reportMessage,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {getAllFinalRoutes} from './utils'; import {getAllFinalRoutes} from './utils';
import type { import type {
@ -228,15 +228,13 @@ export function handleDuplicateRoutes(
return false; return false;
}); });
if (duplicatePaths.length > 0) { if (duplicatePaths.length > 0) {
const finalMessage = `Duplicate routes found! logger.report(
${duplicatePaths onDuplicateRoutes,
.map( )`Duplicate routes found!${duplicatePaths.map(
(duplicateRoute) => (duplicateRoute) =>
`- Attempting to create page at ${duplicateRoute}, but a page already exists at this route.`, logger.interpolate`Attempting to create page at url=${duplicateRoute}, but a page already exists at this route.`,
) )}
.join('\n')}
This could lead to non-deterministic routing behavior.`; This could lead to non-deterministic routing behavior.`;
reportMessage(finalMessage, onDuplicateRoutes);
} }
} }

View file

@ -177,7 +177,7 @@ module.exports = {
### `onBrokenLinks` {#onBrokenLinks} ### `onBrokenLinks` {#onBrokenLinks}
- Type: `'ignore' | 'log' | 'warn' | 'error' | 'throw'` - Type: `'ignore' | 'log' | 'warn' | 'throw'`
The behavior of Docusaurus when it detects any broken link. The behavior of Docusaurus when it detects any broken link.
@ -191,7 +191,7 @@ The broken links detection is only available for a production build (`docusaurus
### `onBrokenMarkdownLinks` {#onBrokenMarkdownLinks} ### `onBrokenMarkdownLinks` {#onBrokenMarkdownLinks}
- Type: `'ignore' | 'log' | 'warn' | 'error' | 'throw'` - Type: `'ignore' | 'log' | 'warn' | 'throw'`
The behavior of Docusaurus when it detects any broken Markdown link. The behavior of Docusaurus when it detects any broken Markdown link.
@ -199,7 +199,7 @@ By default, it prints a warning, to let you know about your broken Markdown link
### `onDuplicateRoutes` {#onDuplicateRoutes} ### `onDuplicateRoutes` {#onDuplicateRoutes}
- Type: `'ignore' | 'log' | 'warn' | 'error' | 'throw'` - Type: `'ignore' | 'log' | 'warn' | 'throw'`
The behavior of Docusaurus when it detects any [duplicate routes](/guides/creating-pages.md#duplicate-routes). The behavior of Docusaurus when it detects any [duplicate routes](/guides/creating-pages.md#duplicate-routes).

View file

@ -32,6 +32,7 @@ It exports a single object as default export: `logger`. `logger` has the followi
- `warn`: prints a warning that should be payed attention to. - `warn`: prints a warning that should be payed attention to.
- `error`: prints an error (not necessarily halting the program) that signals significant problems. - `error`: prints an error (not necessarily halting the program) that signals significant problems.
- `success`: prints a success message. - `success`: prints a success message.
- The `report` function. It takes a `ReportingSeverity` value (`ignore`, `log`, `warn`, `throw`) and reports a message according to the severity.
:::caution A word on the `error` formatter :::caution A word on the `error` formatter