refactor: enforce named capture groups; clean up regexes (#6524)

* refactor: enforce named capture groups; clean up regexes

* fixes

* fix
This commit is contained in:
Joshua Chen 2022-02-01 17:43:15 +08:00 committed by GitHub
parent c56e6194b4
commit 1cefb643dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 80 additions and 77 deletions

View file

@ -100,6 +100,7 @@ module.exports = {
'no-restricted-syntax': WARNING,
'no-unused-expressions': [WARNING, {allowTaggedTemplates: true}],
'prefer-destructuring': WARNING,
'prefer-named-capture-group': WARNING,
'prefer-template': WARNING,
yoda: WARNING,

View file

@ -59,7 +59,7 @@ export function shouldQuotifyFrontMatter([key, value]: [
if (key === 'tags') {
return false;
}
if (String(value).match(/^("|').+("|')$/)) {
if (String(value).match(/^(?<quote>["']).+\1$/)) {
return false;
}
// title: !something needs quotes because otherwise it's a YAML tag.
@ -69,6 +69,6 @@ export function shouldQuotifyFrontMatter([key, value]: [
// TODO this is not ideal to have to maintain such a list of allowed chars
// maybe we should quotify if gray-matter throws instead?
return !String(value).match(
/^([\w .\-sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+_?'`&#()[\]§%€$])+$/,
/^[\w .\-sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+_?'`&#()[\]§%€$]+$/,
);
}

View file

@ -63,7 +63,7 @@ export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
}
const DATE_FILENAME_REGEX =
/^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
/^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
type ParsedBlogFileName = {
date: Date | undefined;
@ -83,7 +83,7 @@ export function parseBlogFileName(
const slug = `/${slugDate}/${folder}${text}`;
return {date, text, slug};
}
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
const slug = `/${text}`;
return {date: undefined, text, slug};
}

View file

@ -452,7 +452,7 @@ export default async function pluginContentBlog(
module: {
rules: [
{
test: /(\.mdx?)$/,
test: /\.mdx?$/i,
include: contentDirs
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator),

View file

@ -20,7 +20,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
beforeDefaultRehypePlugins: [],
beforeDefaultRemarkPlugins: [],
admonitions: {},
truncateMarker: /<!--\s*(truncate)\s*-->/,
truncateMarker: /<!--\s*truncate\s*-->/,
rehypePlugins: [],
remarkPlugins: [],
showReadingTime: true,

View file

@ -333,7 +333,7 @@ export default async function pluginContentDocs(
function createMDXLoaderRule(): RuleSetRule {
const contentDirs = versionsMetadata.flatMap(getDocsDirPaths);
return {
test: /(\.mdx?)$/,
test: /\.mdx?$/i,
include: contentDirs
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator),

View file

@ -10,7 +10,7 @@ import logger from '@docusaurus/logger';
type FileLastUpdateData = {timestamp?: number; author?: string};
const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(\d+),(.+)$/;
const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(?<timestamp>\d+),(?<author>.+)$/;
let showedGitRequirementError = false;
@ -25,10 +25,10 @@ export async function getFileLastUpdate(
return null;
}
const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX);
return !temp || temp.length < 3
? null
: {timestamp: +temp[1], author: temp[2]};
const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX)?.groups;
return temp
? {timestamp: Number(temp.timestamp), author: temp.author}
: null;
}
// Wrap in try/catch in case the shell commands fail

View file

@ -11,14 +11,14 @@ import type {NumberPrefixParser} from '@docusaurus/plugin-content-docs';
const IgnoredPrefixPatterns = (() => {
// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
const DateLikePrefixRegex =
/^((\d{2}|\d{4})[-_.]\d{2}([-_.](\d{2}|\d{4}))?)(.*)$/;
/^(?:\d{2}|\d{4})[-_.]\d{2}(?:[-_.](?:\d{2}|\d{4}))?.*$/;
// ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653
// note: we could try to parse float numbers in filenames but that is
// probably not worth it as a version such as "8.0" can be interpreted as both
// a version and a float. User can configure her own NumberPrefixParser if
// she wants 8.0 to be interpreted as a float
const VersionLikePrefixRegex = /^(\d+[-_.]\d+)(.*)$/;
const VersionLikePrefixRegex = /^\d+[-_.]\d+.*$/;
return new RegExp(
`${DateLikePrefixRegex.source}|${VersionLikePrefixRegex.source}`,

View file

@ -36,7 +36,7 @@ const sidebarItemAutogeneratedSchema =
type: 'autogenerated',
dirName: Joi.string()
.required()
.pattern(/^[^/](.*[^/])?$/)
.pattern(/^[^/](?:.*[^/])?$/)
.message(
'"dirName" must be a dir path relative to the docs folder root, and should not start or end with slash',
),

View file

@ -199,7 +199,7 @@ export default async function pluginContentPages(
module: {
rules: [
{
test: /(\.mdx?)$/,
test: /\.mdx?$/i,
include: contentDirs
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator),

View file

@ -57,7 +57,7 @@ export default function pluginIdealImage(
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
test: /\.(?:png|jpe?g|gif)$/i,
use: [
require.resolve('@docusaurus/lqip-loader'),
{

View file

@ -174,7 +174,7 @@ export default function pluginPWA(
rules: [
{
test: swSourceFileTest,
exclude: /(node_modules)/,
exclude: /node_modules/,
use: getSWBabelLoader(),
},
],

View file

@ -7,8 +7,8 @@
import rangeParser from 'parse-numeric-range';
const codeBlockTitleRegex = /title=(["'])(.*?)\1/;
const highlightLinesRangeRegex = /{([\d,-]+)}/;
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
const highlightLinesRangeRegex = /{(?<range>[\d,-]+)}/;
const commentTypes = ['js', 'jsBlock', 'jsx', 'python', 'html'] as const;
type CommentType = typeof commentTypes[number];
@ -89,7 +89,7 @@ const magicCommentDirectiveRegex = (lang: string) => {
};
export function parseCodeBlockTitle(metastring?: string): string {
return metastring?.match(codeBlockTitleRegex)?.[2] ?? '';
return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? '';
}
export function parseLanguage(className: string): string | undefined {
@ -114,7 +114,8 @@ export function parseLines(
let code = content.replace(/\n$/, '');
// Highlighted lines specified in props: don't parse the content
if (metastring && highlightLinesRangeRegex.test(metastring)) {
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)![1];
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)!
.groups!.range;
const highlightLines = rangeParser(highlightLinesRange)
.filter((n) => n > 0)
.map((n) => n - 1);

View file

@ -116,7 +116,7 @@ describe('createExcerpt', () => {
export function ItemCol(props) { return <Item {...props} className={'col col--6 margin-bottom--lg'}/> };
Lorem **ipsum** dolor sit \`amet\`[^1], consectetur _adipiscing_ elit. [**Vestibulum**](https://wiktionary.org/wiki/vestibulum) ex urna[^bignote], ~molestie~ et sagittis ut, varius ac justo :wink:.
Lorem **ipsum** dolor sit \`amet\`[^1], consectetur _adipiscing_ elit. [**Vestibulum**](https://wiktionary.org/wiki/vestibulum) ex urna[^bignote], ~~molestie~~ et sagittis ut, varius ac justo :wink:.
Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis.
`),

View file

@ -69,8 +69,8 @@ export async function generate(
}
}
const indexRE = /(^|.*\/)index\.(md|mdx|js|jsx|ts|tsx)$/i;
const extRE = /\.(md|mdx|js|jsx|ts|tsx)$/;
const indexRE = /(?<dirname>^|.*\/)index\.(?:mdx?|jsx?|tsx?)$/i;
const extRE = /\.(?:mdx?|jsx?|tsx?)$/;
/**
* Convert filepath to url path.

View file

@ -67,11 +67,11 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
// ink
// [doc1]: doc1.md -> we replace this doc1.md with correct link
const mdRegex =
/(?:(?:\]\()|(?:\]:\s?))(?!https?:\/\/|@site\/)([^'")\]\s>]+\.mdx?)/g;
/(?:(?:\]\()|(?:\]:\s?))(?!https?:\/\/|@site\/)(?<filename>[^'")\]\s>]+\.mdx?)/g;
let mdMatch = mdRegex.exec(modifiedLine);
while (mdMatch !== null) {
// Replace it to correct html link.
const mdLink = mdMatch[1];
const mdLink = mdMatch.groups!.filename;
const sourcesToTry = [
path.resolve(path.dirname(filePath), decodeURIComponent(mdLink)),

View file

@ -14,12 +14,12 @@ export function parseMarkdownHeadingId(heading: string): {
text: string;
id?: string;
} {
const customHeadingIdRegex = /^(.*?)\s*\{#([\w-]+)\}$/;
const customHeadingIdRegex = /^(?<text>.*?)\s*\{#(?<id>[\w-]+)\}$/;
const matches = customHeadingIdRegex.exec(heading);
if (matches) {
return {
text: matches[1],
id: matches[2],
text: matches.groups!.text,
id: matches.groups!.id,
};
}
return {text: heading, id: undefined};
@ -46,7 +46,7 @@ export function createExcerpt(fileString: string): string | undefined {
}
// Skip import/export declaration.
if (/^\s*?import\s.*(from.*)?;?|export\s.*{.*};?/.test(fileLine)) {
if (/^(?:import|export)\s.*/.test(fileLine)) {
continue;
}
@ -71,25 +71,27 @@ export function createExcerpt(fileString: string): string | undefined {
// Remove HTML tags.
.replace(/<[^>]*>/g, '')
// Remove Title headers
.replace(/^#\s*([^#]*)\s*#?/gm, '')
.replace(/^#\s*[^#]*\s*#?/gm, '')
// Remove Markdown + ATX-style headers
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
// Remove emphasis and strikethroughs.
.replace(/([*_~]{1,3})(\S.*?\S{0,1})\1/g, '$2')
.replace(/^#{1,6}\s*(?<text>[^#]*)\s*(?:#{1,6})?/gm, '$1')
// Remove emphasis.
.replace(/(?<opening>[*_]{1,3})(?<text>.*?)\1/g, '$2')
// Remove strikethroughs.
.replace(/~~(?<text>\S.*\S)~~/g, '$1')
// Remove images.
.replace(/!\[(.*?)\][[(].*?[\])]/g, '$1')
.replace(/!\[(?<alt>.*?)\][[(].*?[\])]/g, '$1')
// Remove footnotes.
.replace(/\[\^.+?\](: .*?$)?/g, '')
.replace(/\[\^.+?\](?:: .*?$)?/g, '')
// Remove inline links.
.replace(/\[(.*?)\][[(].*?[\])]/g, '$1')
.replace(/\[(?<alt>.*?)\][[(].*?[\])]/g, '$1')
// Remove inline code.
.replace(/`(.+?)`/g, '$1')
.replace(/`(?<text>.+?)`/g, '$1')
// Remove blockquotes.
.replace(/^\s{0,3}>\s?/g, '')
// Remove admonition definition.
.replace(/(:{3}.*)/, '')
.replace(/:::.*/, '')
// Remove Emoji names within colons include preceding whitespace.
.replace(/\s?(:(::|[^:\n])+:)/g, '')
.replace(/\s?:(?:::|[^:\n])+:/g, '')
// Remove custom Markdown heading id.
.replace(/{#*[\w-]+}/, '')
.trim();
@ -134,9 +136,9 @@ export function parseMarkdownContentTitle(
const content = contentUntrimmed.trim();
const IMPORT_STATEMENT =
/import\s+(([\w*{}\s\n,]+)from\s+)?["'\s]([@\w/_.-]+)["'\s];?|\n/.source;
/import\s+(?:[\w*{}\s\n,]+from\s+)?["'\s][@\w/_.-]+["'\s];?|\n/.source;
const REGULAR_TITLE =
/(?<pattern>#\s*(?<title>[^#\n{]*)+[ \t]*(?<suffix>({#*[\w-]+})|#)?\n*?)/
/(?<pattern>#\s*(?<title>[^#\n{]*)+[ \t]*(?<suffix>(?:{#*[\w-]+})|#)?\n*?)/
.source;
const ALTERNATE_TITLE = /(?<pattern>\s*(?<title>[^\n]*)\s*\n[=]+)/.source;

View file

@ -27,7 +27,7 @@ export function normalizeUrl(rawUrls: string[]): string {
// There must be two or three slashes in the file protocol,
// two slashes in anything else.
const replacement = urls[0].match(/^file:\/\/\//) ? '$1:///' : '$1://';
urls[0] = urls[0].replace(/^([^/:]+):\/*/, replacement);
urls[0] = urls[0].replace(/^(?<protocol>[^/:]+):\/*/, replacement);
for (let i = 0; i < urls.length; i += 1) {
let component = urls[i];
@ -70,14 +70,14 @@ export function normalizeUrl(rawUrls: string[]): string {
// except the possible first plain protocol part.
// Remove trailing slash before parameters or hash.
str = str.replace(/\/(\?|&|#[^!])/g, '$1');
str = str.replace(/\/(?<search>\?|&|#[^!])/g, '$1');
// Replace ? in parameters with &.
const parts = str.split('?');
str = parts.shift() + (parts.length > 0 ? '?' : '') + parts.join('&');
// Dedupe forward slashes in the entire path, avoiding protocol slashes.
str = str.replace(/([^:/]\/)\/+/g, '$1');
str = str.replace(/(?<textBefore>[^:/]\/)\/+/g, '$1');
// Dedupe forward slashes at the beginning of the path.
str = str.replace(/^\/+/g, '/');

View file

@ -79,12 +79,12 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/
images: () => ({
use: [loaders.url({folder: 'images'})],
test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
test: /\.(?:ico|jpe?g|png|gif|webp)(?:\?.*)?$/i,
}),
fonts: () => ({
use: [loaders.url({folder: 'fonts'})],
test: /\.(woff|woff2|eot|ttf|otf)$/,
test: /\.(?:woff2?|eot|ttf|otf)$/i,
}),
/**
@ -93,11 +93,11 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/
media: () => ({
use: [loaders.url({folder: 'medias'})],
test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
test: /\.(?:mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/i,
}),
svg: () => ({
test: /\.svg?$/,
test: /\.svg$/i,
oneOf: [
{
use: [
@ -126,7 +126,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
// We don't want to use SVGR loader for non-React source code
// ie we don't want to use SVGR for CSS files...
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
and: [/\.(?:tsx?|jsx?|mdx?)$/i],
},
},
{
@ -137,7 +137,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
otherAssets: () => ({
use: [loaders.file({folder: 'files'})],
test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
test: /\.(?:pdf|docx?|xlsx?|zip|rar)$/i,
}),
};

View file

@ -37,7 +37,7 @@ const canPreload = (routePath: string) =>
// Remove the last part containing the route hash
// input: /blog/2018/12/14/Happy-First-Birthday-Slash-fe9
// output: /blog/2018/12/14/Happy-First-Birthday-Slash
const removeRouteNameHash = (str: string) => str.replace(/(-[^-]+)$/, '');
const removeRouteNameHash = (str: string) => str.replace(/-[^-]+$/, '');
const getChunkNamesToLoad = (path: string): string[] =>
Object.entries(routesChunkNames)

View file

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

View file

@ -54,7 +54,7 @@ export default async function render(
${(e as Error).stack!}`;
const isNotDefinedErrorRegex =
/(window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i;
/(?:window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i;
if (isNotDefinedErrorRegex.test((e as Error).message)) {
logger.info`It looks like you are using code that should run on the client-side only.

View file

@ -66,7 +66,7 @@ export function hasSSHProtocol(sourceRepoUrl: string): boolean {
return false;
} catch {
// Fails when there isn't a protocol
return /^([\w-]+@)?[\w.-]+:[\w./_-]+(\.git)?/.test(sourceRepoUrl); // git@github.com:facebook/docusaurus.git
return /^(?:[\w-]+@)?[\w.-]+:[\w./_-]+/.test(sourceRepoUrl); // git@github.com:facebook/docusaurus.git
}
}

View file

@ -50,8 +50,8 @@ export function getPluginNames(plugins: PluginConfig[]): string[] {
const formatComponentName = (componentName: string): string =>
componentName
.replace(/(\/|\\)index\.(js|tsx|ts|jsx)/, '')
.replace(/\.(js|tsx|ts|jsx)/, '');
.replace(/[\\/]index\.(?:jsx?|tsx?)/, '')
.replace(/\.(?:jsx?|tsx?)/, '');
function readComponent(themePath: string) {
function walk(dir: string): Array<string> {

View file

@ -23,7 +23,7 @@ type Options = {
};
function unwrapMarkdownLinks(line: string): string {
return line.replace(/\[([^\]]+)\]\([^)]+\)/g, (match, p1) => p1);
return line.replace(/\[(?<alt>[^\]]+)\]\([^)]+\)/g, (match, p1) => p1);
}
function addHeadingId(

View file

@ -85,7 +85,7 @@ describe('loadConfig', () => {
'docusaurus.config.js',
);
await expect(loadConfig(siteDir)).rejects.toThrowError(
/Config file at "(.*?)__fixtures__[/\\]nonExisting[/\\]docusaurus.config.js" not found.$/,
/Config file at ".*?__fixtures__[/\\]nonExisting[/\\]docusaurus.config.js" not found.$/,
);
});
});

View file

@ -260,7 +260,7 @@ function createMDXFallbackPlugin({
module: {
rules: [
{
test: /(\.mdx?)$/,
test: /\.mdx?$/i,
exclude: getMDXFallbackExcludedPaths(),
use: [
getJSLoader({isServer}),

View file

@ -14,7 +14,7 @@ export function getNamePatterns(
if (!moduleName.includes('/')) {
return [`${moduleName}/docusaurus-${moduleType}`];
}
const [scope, packageName] = moduleName.split(/\/(.*)/);
const [scope, packageName] = moduleName.split(/\/(?<rest>.*)/);
return [
`${scope}/${packageName}`,
`${scope}/docusaurus-${moduleType}-${packageName}`,

View file

@ -28,7 +28,7 @@ type RegistryMap = {
function indent(str: string) {
const spaces = ' ';
return `${spaces}${str.replace(/(\n)/g, `\n${spaces}`)}`;
return `${spaces}${str.replace(/\n/g, `\n${spaces}`)}`;
}
const createRouteCodeString = ({

View file

@ -19,8 +19,8 @@ import {
import {loadPluginsThemeAliases} from '../server/themes';
import {md5Hash, getFileLoaderUtils} from '@docusaurus/utils';
const CSS_REGEX = /\.css$/;
const CSS_MODULE_REGEX = /\.module\.css$/;
const CSS_REGEX = /\.css$/i;
const CSS_MODULE_REGEX = /\.module\.css$/i;
export const clientDir = path.join(__dirname, '..', 'client');
const LibrariesToTranspile = [
@ -39,7 +39,7 @@ export function excludeJS(modulePath: string): boolean {
// Don't transpile node_modules except any docusaurus npm package
return (
/node_modules/.test(modulePath) &&
!/(docusaurus)((?!node_modules).)*\.jsx?$/.test(modulePath) &&
!/docusaurus(?:(?!node_modules).)*\.jsx?$/.test(modulePath) &&
!LibrariesToTranspileRegex.test(modulePath)
);
}
@ -220,7 +220,7 @@ export function createBaseConfig(
fileLoaderUtils.rules.svg(),
fileLoaderUtils.rules.otherAssets(),
{
test: /\.(j|t)sx?$/,
test: /\.[jt]sx?$/i,
exclude: excludeJS,
use: [
getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({

View file

@ -29,27 +29,26 @@ async function lqipLoader(
let content = contentBuffer.toString('utf8');
const contentIsUrlExport =
/^(?:export default|module.exports =) "data:(.*)base64,(.*)/.test(content);
const contentIsFileExport = /^(?:export default|module.exports =) (.*)/.test(
/^(?:export default|module.exports =) "data:.*base64,.*/.test(content);
const contentIsFileExport = /^(?:export default|module.exports =) .*/.test(
content,
);
let source = '';
const SOURCE_CHUNK = 1;
if (contentIsUrlExport) {
source = content.match(/^(?:export default|module.exports =) (.*)/)![
SOURCE_CHUNK
];
source = content.match(
/^(?:export default|module.exports =) (?<source>.*)/,
)!.groups!.source;
} else {
if (!contentIsFileExport) {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const fileLoader = require('file-loader');
content = fileLoader.call(this, contentBuffer);
}
source = content.match(/^(?:export default|module.exports =) (.*);/)![
SOURCE_CHUNK
];
source = content.match(
/^(?:export default|module.exports =) (?<source>.*);/,
)!.groups!.source;
}
const outputPromises: [Promise<string> | null, Promise<string[]> | null] = [

View file

@ -60,7 +60,7 @@ function processSection(section) {
});
}
let hour = 20;
const date = title.match(/ \((.*)\)/)[1];
const date = title.match(/ \((?<date>.*)\)/)?.groups.date;
while (publishTimes.has(`${date}T${hour}:00`)) {
hour -= 1;
}