Skip to content

Commit

Permalink
Improve regex performance (#11635)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy authored Aug 7, 2024
1 parent 74a0930 commit ea82b03
Show file tree
Hide file tree
Showing 23 changed files with 39 additions and 32 deletions.
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/e2e/errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 *;`);

Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/actions/runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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".'
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
9 changes: 5 additions & 4 deletions packages/astro/src/core/errors/dev/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/events/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/transitions/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/css-order-import.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/css-order.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
6 changes: 4 additions & 2 deletions packages/astro/test/solid-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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;
}
2 changes: 1 addition & 1 deletion packages/astro/test/ssr-api-route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/create-astro/src/actions/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async function verifyTemplate(tmpl: string, ref?: string) {
const GIT_RE = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\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,
Expand Down
3 changes: 1 addition & 2 deletions packages/db/test/unit/column-queries.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
5 changes: 2 additions & 3 deletions packages/db/test/unit/reference-queries.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
4 changes: 2 additions & 2 deletions packages/integrations/markdoc/src/content-entry-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
);
});

Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/node/src/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/integrations/vue/src/editor.cts
Original file line number Diff line number Diff line change
Expand Up @@ -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}>` : '';

Expand All @@ -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<T[]> } })`
// 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';
Expand Down
4 changes: 2 additions & 2 deletions packages/markdown/remark/src/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/markdown/remark/src/rehype-collect-headings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> {
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);

Expand All @@ -30,7 +30,7 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> {
return;
}
if (child.type === 'raw') {
if (child.value.match(/^\n?<.*>\n?$/)) {
if (/^\n?<.*>\n?$/.test(child.value)) {
return;
}
}
Expand Down

0 comments on commit ea82b03

Please sign in to comment.