diff --git a/.changeset/refactor-semgrep-regex-hotspots.md b/.changeset/refactor-semgrep-regex-hotspots.md new file mode 100644 index 000000000000..b4f8bb9c681a --- /dev/null +++ b/.changeset/refactor-semgrep-regex-hotspots.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Improves internal content and preview path handling by removing a few fragile dynamic regex checks diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 6ac7da29f0a9..5440ed6092b5 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -37,6 +37,11 @@ const entryTypeSchema = z }) .passthrough(); +function removeExtension(filePath: string) { + const ext = path.extname(filePath); + return ext ? filePath.slice(0, -ext.length) : filePath; +} + export const loaderReturnSchema = z.union([ z.array(entryTypeSchema), z.record( @@ -347,10 +352,7 @@ export function getDataEntryId({ collection, }: Pick & { entry: URL; collection: string }): string { const relativePath = getRelativeEntryPath(entry, collection, contentDir); - const withoutFileExt = normalizePath(relativePath).replace( - new RegExp(path.extname(relativePath) + '$'), - '', - ); + const withoutFileExt = removeExtension(normalizePath(relativePath)); return withoutFileExt; } @@ -364,7 +366,7 @@ export function getContentEntryIdAndSlug({ slug: string; } { const relativePath = getRelativeEntryPath(entry, collection, contentDir); - const withoutFileExt = relativePath.replace(new RegExp(path.extname(relativePath) + '$'), ''); + const withoutFileExt = removeExtension(relativePath); const rawSlugSegments = withoutFileExt.split(path.sep); const slug = rawSlugSegments @@ -431,16 +433,13 @@ function hasUnderscoreBelowContentDirectoryPath( function getYAMLErrorLine(rawData: string | undefined, objectKey: string) { if (!rawData) return 0; - const indexOfObjectKey = rawData.search( - // Match key either at the top of the file or after a newline - // Ensures matching on top-level object keys only - new RegExp(`(\n|^)${objectKey}`), - ); - if (indexOfObjectKey === -1) return 0; - - const dataBeforeKey = rawData.substring(0, indexOfObjectKey + 1); - const numNewlinesBeforeKey = dataBeforeKey.split('\n').length; - return numNewlinesBeforeKey; + const lines = rawData.split('\n'); + const quotedKeyPatterns = [`${objectKey}:`, `'${objectKey}':`, `"${objectKey}":`]; + const lineNumber = lines.findIndex((line) => { + if (line.startsWith(' ') || line.startsWith('\t')) return false; + return quotedKeyPatterns.some((pattern) => line.startsWith(pattern)); + }); + return lineNumber === -1 ? 0 : lineNumber + 1; } export function safeParseFrontmatter(source: string, id?: string) { diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index 3e62807ed9c7..bedfa0a77db0 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -214,9 +214,7 @@ export const _internal = { transform: { filter: { id: { - include: settings.contentEntryTypes - .filter((t) => t.getRenderModule) - .map((t) => new RegExp(`\\.(${t.extensions.map((e) => e.slice(1)).join('|')})$`)), + include: [/\.[^/?]+$/], }, }, async handler(contents, viteId) { diff --git a/packages/astro/src/core/preview/util.ts b/packages/astro/src/core/preview/util.ts index d02e4c3c8a09..5faacd8298b2 100644 --- a/packages/astro/src/core/preview/util.ts +++ b/packages/astro/src/core/preview/util.ts @@ -15,5 +15,5 @@ export function stripBase(path: string, base: string): string { return '/'; } const baseWithSlash = base.endsWith('/') ? base : base + '/'; - return path.replace(RegExp('^' + baseWithSlash), '/'); + return path.startsWith(baseWithSlash) ? '/' + path.slice(baseWithSlash.length) : path; }