From ea82b03cd6d40c6bd541046f2f9aedfed058ff4f Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 7 Aug 2024 16:01:23 +0800 Subject: [PATCH] Improve regex performance (#11635) --- eslint.config.js | 2 ++ packages/astro/e2e/errors.test.js | 2 +- packages/astro/src/actions/runtime/middleware.ts | 1 + .../src/assets/utils/vendor/image-size/types/svg.ts | 1 + packages/astro/src/core/app/index.ts | 2 +- packages/astro/src/core/build/generate.ts | 6 +++--- packages/astro/src/core/errors/dev/vite.ts | 9 +++++---- packages/astro/src/events/error.ts | 2 +- .../client/dev-toolbar/ui-library/radio-checkbox.ts | 1 + packages/astro/src/transitions/router.ts | 1 + packages/astro/test/css-order-import.test.js | 2 +- packages/astro/test/css-order.test.js | 4 ++-- packages/astro/test/solid-component.test.js | 6 ++++-- packages/astro/test/ssr-api-route.test.js | 2 +- packages/create-astro/src/actions/verify.ts | 2 +- packages/db/test/unit/column-queries.test.js | 3 +-- packages/db/test/unit/reference-queries.test.js | 5 ++--- packages/integrations/markdoc/src/content-entry-type.ts | 4 ++-- .../mdx/test/units/rehype-optimize-static.test.js | 2 +- packages/integrations/node/src/serve-static.ts | 2 +- packages/integrations/vue/src/editor.cts | 4 ++-- packages/markdown/remark/src/highlight.ts | 4 ++-- packages/markdown/remark/src/rehype-collect-headings.ts | 4 ++-- 23 files changed, 39 insertions(+), 32 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index ab7efa3df1..f12bf49d1f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -111,6 +111,8 @@ export default [ // In some cases, using explicit letter-casing is more performant than the `i` flag 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', }, }, diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js index 2f13585489..9087ac484a 100644 --- a/packages/astro/e2e/errors.test.js +++ b/packages/astro/e2e/errors.test.js @@ -88,7 +88,7 @@ test.describe('Error display', () => { expect(fileExists).toBeTruthy(); const fileContent = await astro.readFile(absoluteFileUrl); - const lineNumber = absoluteFileLocation.match(/:(\d+):\d+$/)[1]; + const lineNumber = /:(\d+):\d+$/.exec(absoluteFileLocation)[1]; const highlightedLine = fileContent.split('\n')[lineNumber - 1]; expect(highlightedLine).toContain(`@use '../styles/inexistent' as *;`); diff --git a/packages/astro/src/actions/runtime/middleware.ts b/packages/astro/src/actions/runtime/middleware.ts index 2cc1b1e28a..3d430b04a4 100644 --- a/packages/astro/src/actions/runtime/middleware.ts +++ b/packages/astro/src/actions/runtime/middleware.ts @@ -32,6 +32,7 @@ export const onRequest = defineMiddleware(async (context, next) => { // Heuristic: If body is null, Astro might've reset this for prerendering. if (import.meta.env.DEV && request.method === 'POST' && request.body === null) { + // eslint-disable-next-line no-console console.warn( yellow('[astro:actions]'), 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".' diff --git a/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts b/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts index 11baaf6d2a..a0099d0a0a 100644 --- a/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts +++ b/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */ +/* eslint-disable regexp/prefer-regexp-exec */ import type { IImage, ISize } from './interface.ts' import { toUTF8String } from './utils.js' diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index a2b1d4f2e5..42027098f3 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -501,7 +501,7 @@ export class App { } #getDefaultStatusCode(routeData: RouteData, pathname: string): number { - if (!routeData.pattern.exec(pathname)) { + if (!routeData.pattern.test(pathname)) { for (const fallbackRoute of routeData.fallbackRoutes) { if (fallbackRoute.pattern.test(pathname)) { return 302; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index de98093c74..e3fca61206 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -306,7 +306,7 @@ function getInvalidRouteSegmentError( route: RouteData, staticPath: GetStaticPathsItem ): AstroError { - const invalidParam = e.message.match(/^Expected "([^"]+)"/)?.[1]; + const invalidParam = /^Expected "([^"]+)"/.exec(e.message)?.[1]; const received = invalidParam ? staticPath.params[invalidParam] : undefined; let hint = 'Learn about dynamic routes at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes'; @@ -421,7 +421,7 @@ async function generatePath( // always be rendered route.pathname !== '/' && // Check if there is a translated page with the same path - Object.values(options.allPages).some((val) => pathname.match(val.route.pattern)) + Object.values(options.allPages).some((val) => val.route.pattern.test(pathname)) ) { return; } @@ -503,7 +503,7 @@ function getPrettyRouteName(route: RouteData): string { } else if (route.component.includes('node_modules/')) { // For routes from node_modules (usually injected by integrations), // prettify it by only grabbing the part after the last `node_modules/` - return route.component.match(/.*node_modules\/(.+)/)?.[1] ?? route.component; + return /.*node_modules\/(.+)/.exec(route.component)?.[1] ?? route.component; } else { return route.component; } diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts index 1612754d00..b63d696f50 100644 --- a/packages/astro/src/core/errors/dev/vite.ts +++ b/packages/astro/src/core/errors/dev/vite.ts @@ -41,7 +41,7 @@ export function enhanceViteSSRError({ // Vite has a fairly generic error message when it fails to load a module, let's try to enhance it a bit // https://github.com/vitejs/vite/blob/ee7c28a46a6563d54b828af42570c55f16b15d2c/packages/vite/src/node/ssr/ssrModuleLoader.ts#L91 let importName: string | undefined; - if ((importName = safeError.message.match(/Failed to load url (.*?) \(resolved id:/)?.[1])) { + if ((importName = /Failed to load url (.*?) \(resolved id:/.exec(safeError.message)?.[1])) { safeError.title = FailedToLoadModuleSSR.title; safeError.name = 'FailedToLoadModuleSSR'; safeError.message = FailedToLoadModuleSSR.message(importName); @@ -64,9 +64,10 @@ export function enhanceViteSSRError({ // Vite throws a syntax error trying to parse MDX without a plugin. // Suggest installing the MDX integration if none is found. if ( + fileId && !renderers?.find((r) => r.name === '@astrojs/mdx') && - safeError.message.match(/Syntax error/) && - fileId?.match(/\.mdx$/) + /Syntax error/.test(safeError.message) && + /.mdx$/.test(fileId) ) { safeError = new AstroError({ ...MdxIntegrationMissingError, @@ -78,7 +79,7 @@ export function enhanceViteSSRError({ // Since Astro.glob is a wrapper around Vite's import.meta.glob, errors don't show accurate information, let's fix that if (/Invalid glob/.test(safeError.message)) { - const globPattern = safeError.message.match(/glob: "(.+)" \(/)?.[1]; + const globPattern = /glob: "(.+)" \(/.exec(safeError.message)?.[1]; if (globPattern) { safeError.message = InvalidGlob.message(globPattern); diff --git a/packages/astro/src/events/error.ts b/packages/astro/src/events/error.ts index 8b8e9767e6..77de088c57 100644 --- a/packages/astro/src/events/error.ts +++ b/packages/astro/src/events/error.ts @@ -28,7 +28,7 @@ interface ConfigErrorEventPayload extends ErrorEventPayload { */ const ANONYMIZE_MESSAGE_REGEX = /^(?:\w| )+/; function anonymizeErrorMessage(msg: string): string | undefined { - const matchedMessage = msg.match(ANONYMIZE_MESSAGE_REGEX); + const matchedMessage = ANONYMIZE_MESSAGE_REGEX.exec(msg); if (!matchedMessage?.[0]) { return undefined; } diff --git a/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts index a223bf1a84..79508cc694 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts @@ -14,6 +14,7 @@ export class DevToolbarRadioCheckbox extends HTMLElement { set radioStyle(value) { if (!styles.includes(value)) { + // eslint-disable-next-line no-console console.error(`Invalid style: ${value}, expected one of ${styles.join(', ')}.`); return; } diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts index b5e1a22354..673eb4eb23 100644 --- a/packages/astro/src/transitions/router.ts +++ b/packages/astro/src/transitions/router.ts @@ -542,6 +542,7 @@ async function transition( // This log doesn't make it worse than before, where we got error messages about uncaught exceptions, which can't be caught when the trigger was a click or history traversal. // Needs more investigation on root causes if errors still occur sporadically const err = e as Error; + // eslint-disable-next-line no-console console.log('[astro]', err.name, err.message, err.stack); } } diff --git a/packages/astro/test/css-order-import.test.js b/packages/astro/test/css-order-import.test.js index d8ee74a1d0..b64125d70a 100644 --- a/packages/astro/test/css-order-import.test.js +++ b/packages/astro/test/css-order-import.test.js @@ -126,7 +126,7 @@ describe('CSS ordering - import order', () => { const content = await Promise.all(getLinks(html).map((href) => getLinkContent(href))); const css = content.map((c) => c.css).join(''); - assert.equal(css.match(/\.astro-jsx/).length, 1, '.astro-jsx class is duplicated'); + assert.equal(/\.astro-jsx/.exec(css).length, 1, '.astro-jsx class is duplicated'); }); }); diff --git a/packages/astro/test/css-order.test.js b/packages/astro/test/css-order.test.js index ee2992a31b..4c1b41cfc7 100644 --- a/packages/astro/test/css-order.test.js +++ b/packages/astro/test/css-order.test.js @@ -92,8 +92,8 @@ describe('CSS production ordering', () => { assert.ok(content.length, 3, 'there are 3 stylesheets'); const [, sharedStyles, pageStyles] = content; - assert.ok(sharedStyles.css.match(/red/)); - assert.ok(pageStyles.css.match(/#00f/)); + assert.ok(/red/.exec(sharedStyles.css)); + assert.ok(/#00f/.exec(pageStyles.css)); }); it('CSS injected by injectScript comes first because of import order', async () => { diff --git a/packages/astro/test/solid-component.test.js b/packages/astro/test/solid-component.test.js index bd2c4f64f4..3d0eb31615 100644 --- a/packages/astro/test/solid-component.test.js +++ b/packages/astro/test/solid-component.test.js @@ -183,11 +183,12 @@ describe.skip('Solid component dev', { todo: 'Check why the test hangs.', skip: const createHydrationScriptRegex = (flags) => new RegExp(/_\$HY=/, flags); function countHydrationScripts(/** @type {string} */ html) { + // eslint-disable-next-line regexp/prefer-regexp-exec return html.match(createHydrationScriptRegex('g'))?.length ?? 0; } function getFirstHydrationScriptLocation(/** @type {string} */ html) { - return html.match(createHydrationScriptRegex())?.index; + return createHydrationScriptRegex().exec(html)?.index; } /** @@ -202,9 +203,10 @@ function countHydrationEvents(/** @type {string} */ html) { // Number of times a component was hydrated during rendering // We look for the hint "_$HY.r[" + // eslint-disable-next-line regexp/prefer-regexp-exec return html.match(createHydrationEventRegex('g'))?.length ?? 0; } function getFirstHydrationEventLocation(/** @type {string} */ html) { - return html.match(createHydrationEventRegex())?.index; + return createHydrationEventRegex().exec(html)?.index; } diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index 4c2e796e12..6c5d1ad09d 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -118,7 +118,7 @@ describe('API routes in SSR', () => { let count = 0; let exp = /set-cookie:/g; - while (exp.exec(response)) { + while (exp.test(response)) { count++; } diff --git a/packages/create-astro/src/actions/verify.ts b/packages/create-astro/src/actions/verify.ts index a6c9cc7e17..605b6959d7 100644 --- a/packages/create-astro/src/actions/verify.ts +++ b/packages/create-astro/src/actions/verify.ts @@ -84,7 +84,7 @@ async function verifyTemplate(tmpl: string, ref?: string) { const GIT_RE = /^(?[\w.-]+\/[\w.-]+)(?[^#]+)?(?#[\w.-]+)?/; function parseGitURI(input: string) { - const m = input.match(GIT_RE)?.groups; + const m = GIT_RE.exec(input)?.groups; if (!m) throw new Error(`Unable to parse "${input}"`); return { repo: m.repo, diff --git a/packages/db/test/unit/column-queries.test.js b/packages/db/test/unit/column-queries.test.js index ebb8656702..bd59fd4583 100644 --- a/packages/db/test/unit/column-queries.test.js +++ b/packages/db/test/unit/column-queries.test.js @@ -492,6 +492,5 @@ describe('column queries', () => { /** @param {string} query */ function getTempTableName(query) { - // eslint-disable-next-line regexp/no-unused-capturing-group - return query.match(/Users_([a-z\d]+)/)?.[0]; + return /Users_[a-z\d]+/.exec(query)?.[0]; } diff --git a/packages/db/test/unit/reference-queries.test.js b/packages/db/test/unit/reference-queries.test.js index 791344146b..3a457192f6 100644 --- a/packages/db/test/unit/reference-queries.test.js +++ b/packages/db/test/unit/reference-queries.test.js @@ -163,8 +163,7 @@ describe('reference queries', () => { }); }); -/** @param {string | undefined} query */ +/** @param {string} query */ function getTempTableName(query) { - // eslint-disable-next-line regexp/no-unused-capturing-group - return query.match(/User_([a-z\d]+)/)?.[0]; + return /User_[a-z\d]+/.exec(query)?.[0]; } diff --git a/packages/integrations/markdoc/src/content-entry-type.ts b/packages/integrations/markdoc/src/content-entry-type.ts index 8fc4bd77cc..bd19e1ccda 100644 --- a/packages/integrations/markdoc/src/content-entry-type.ts +++ b/packages/integrations/markdoc/src/content-entry-type.ts @@ -245,7 +245,7 @@ function raiseValidationErrors({ e.error.id !== 'variable-undefined' && // Ignore missing partial errors. // We will resolve these in `resolvePartials`. - !(e.error.id === 'attribute-value-invalid' && e.error.message.match(/^Partial .+ not found/)) + !(e.error.id === 'attribute-value-invalid' && /^Partial .+ not found/.test(e.error.message)) ); }); @@ -275,7 +275,7 @@ function getUsedTags(markdocAst: Node) { // This is our signal that a tag is being used! for (const { error } of validationErrors) { if (error.id === 'tag-undefined') { - const [, tagName] = error.message.match(/Undefined tag: '(.*)'/) ?? []; + const [, tagName] = /Undefined tag: '(.*)'/.exec(error.message) ?? []; tags.add(tagName); } } diff --git a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js index 132f3849f5..675bc3478e 100644 --- a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js +++ b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js @@ -15,7 +15,7 @@ async function compile(mdxCode, options) { }); const code = result.toString(); // Capture the returned JSX code for testing - const jsx = code.match(/return (.+);\n\}\nexport default function MDXContent/s)?.[1]; + const jsx = /return (.+);\n\}\nexport default function MDXContent/s.exec(code)?.[1]; if (jsx == null) throw new Error('Could not find JSX code in compiled MDX'); return dedent(jsx); } diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts index 0ec129d9f9..8256c588ec 100644 --- a/packages/integrations/node/src/serve-static.ts +++ b/packages/integrations/node/src/serve-static.ts @@ -52,7 +52,7 @@ export function createStaticHandler(app: NodeApp, options: Options) { break; case 'always': // trailing slash is not added to "subresources" - if (!hasSlash && !urlPath.match(isSubresourceRegex)) { + if (!hasSlash && !isSubresourceRegex.test(urlPath)) { pathname = urlPath + '/' + (urlQuery ? '?' + urlQuery : ''); res.statusCode = 301; res.setHeader('Location', pathname); diff --git a/packages/integrations/vue/src/editor.cts b/packages/integrations/vue/src/editor.cts index d4f10aab66..64b34b45b1 100644 --- a/packages/integrations/vue/src/editor.cts +++ b/packages/integrations/vue/src/editor.cts @@ -24,7 +24,7 @@ export function toTSX(code: string, className: string): string { if (scriptSetup) { const codeWithoutComments = scriptSetup.content.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''); - const definePropsType = codeWithoutComments.match(/defineProps<([\s\S]+?)>\s?\(\)/); + const definePropsType = /defineProps<([\s\S]+?)>\s?\(\)/.exec(codeWithoutComments); const propsGeneric = scriptSetup.attrs.generic; const propsGenericType = propsGeneric ? `<${propsGeneric}>` : ''; @@ -41,7 +41,7 @@ export function toTSX(code: string, className: string): string { // TODO. Find a way to support generics when using defineProps without passing explicit types. // Right now something like this `defineProps({ prop: { type: Array as PropType } })` // won't be correctly typed in Astro. - const defineProps = codeWithoutComments.match(/defineProps\([\s\S]+?\)/); + const defineProps = /defineProps\([\s\S]+?\)/.exec(codeWithoutComments); if (defineProps) { result = ` import { defineProps } from 'vue'; diff --git a/packages/markdown/remark/src/highlight.ts b/packages/markdown/remark/src/highlight.ts index 41ec8880b2..ef1a734ba5 100644 --- a/packages/markdown/remark/src/highlight.ts +++ b/packages/markdown/remark/src/highlight.ts @@ -43,14 +43,14 @@ export async function highlightCodeBlocks(tree: Root, highlighter: Highlighter) let languageMatch: RegExpMatchArray | null | undefined; let { className } = node.properties; if (typeof className === 'string') { - languageMatch = className.match(languagePattern); + languageMatch = languagePattern.exec(className); } else if (Array.isArray(className)) { for (const cls of className) { if (typeof cls !== 'string') { continue; } - languageMatch = cls.match(languagePattern); + languageMatch = languagePattern.exec(cls); if (languageMatch) { break; } diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 8624005450..3bff443e2e 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -20,7 +20,7 @@ export function rehypeHeadingIds(): ReturnType { if (node.type !== 'element') return; const { tagName } = node; if (tagName[0] !== 'h') return; - const [, level] = tagName.match(/h([0-6])/) ?? []; + const [, level] = /h([0-6])/.exec(tagName) ?? []; if (!level) return; const depth = Number.parseInt(level); @@ -30,7 +30,7 @@ export function rehypeHeadingIds(): ReturnType { return; } if (child.type === 'raw') { - if (child.value.match(/^\n?<.*>\n?$/)) { + if (/^\n?<.*>\n?$/.test(child.value)) { return; } }