0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-31 23:31:30 -05:00

Use eslint-plugin-regexp (#9993)

This commit is contained in:
Bjorn Lu 2024-02-07 20:43:19 +08:00 committed by GitHub
parent ce4283331f
commit 436841e97e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 207 additions and 139 deletions

View file

@ -7,13 +7,14 @@ module.exports = {
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'prettier',
'plugin:regexp/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./packages/*/tsconfig.json', './tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'prettier', 'no-only-tests'],
plugins: ['@typescript-eslint', 'prettier', 'no-only-tests', 'regexp'],
rules: {
// These off/configured-differently-by-default rules fit well for us
'@typescript-eslint/switch-exhaustiveness-check': 'error',
@ -72,6 +73,9 @@ module.exports = {
// These rules enabled by the preset configs don't work well for us
'@typescript-eslint/await-thenable': 'off',
'prefer-const': 'off',
// In some cases, using explicit letter-casing is more performant than the `i` flag
'regexp/use-ignore-case': 'off',
},
overrides: [
{

View file

@ -59,12 +59,13 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-regexp": "^2.2.0",
"globby": "^14.0.0",
"only-allow": "^1.1.1",
"organize-imports-cli": "^0.10.0",
"prettier": "^3.1.0",
"prettier-plugin-astro": "^0.12.2",
"tiny-glob": "^0.2.9",
"globby": "^14.0.0",
"turbo": "^1.10.12",
"typescript": "~5.2.2"
},

View file

@ -16,6 +16,7 @@ export function addAstro(Prism: typeof import('prismjs')) {
let script = Prism.util.clone(Prism.languages[scriptLang]);
// eslint-disable-next-line regexp/no-useless-assertions
let space = /(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source;
let braces = /(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source;
let spread = /(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;
@ -39,13 +40,13 @@ export function addAstro(Prism: typeof import('prismjs')) {
Prism.languages.astro = Prism.languages.extend('markup', script);
(Prism.languages.astro as any).tag.pattern = re(
/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/
/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/
.source
);
(Prism.languages.astro as any).tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
(Prism.languages.astro as any).tag.inside['tag'].pattern = /^<\/?[^\s>/]*/;
(Prism.languages.astro as any).tag.inside['attr-value'].pattern =
/=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i;
/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/;
(Prism.languages.astro as any).tag.inside['tag'].inside['class-name'] =
/^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
(Prism.languages.astro as any).tag.inside['comment'] = script['comment'];
@ -71,7 +72,7 @@ export function addAstro(Prism: typeof import('prismjs')) {
pattern: re(/=<BRACES>/.source),
inside: {
'script-punctuation': {
pattern: /^=(?={)/,
pattern: /^=(?=\{)/,
alias: 'punctuation',
},
rest: Prism.languages.astro,

View file

@ -10,10 +10,10 @@ export function createCanonicalURL(
let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical
if (trailingSlash === false) {
// remove the trailing slash
pathname = pathname.replace(/(\/+)?$/, '');
pathname = pathname.replace(/\/*$/, '');
} else if (!getUrlExtension(url)) {
// add trailing slash if theres no extension or `trailingSlash` is true
pathname = pathname.replace(/(\/+)?$/, '/');
pathname = pathname.replace(/\/*$/, '/');
}
pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() wont)

View file

@ -291,6 +291,8 @@ export const codecs = {
avif: {
name: 'AVIF',
extension: 'avif',
// Disable eslint rule to not touch the original code
// eslint-disable-next-line no-control-regex, regexp/control-character-escape
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
dec: () =>
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm),
@ -321,6 +323,8 @@ export const codecs = {
oxipng: {
name: 'OxiPNG',
extension: 'png',
// Disable eslint rule to not touch the original code
// eslint-disable-next-line no-control-regex, regexp/control-character-escape
detectors: [/^\x89PNG\x0D\x0A\x1A\x0A/],
dec: async () => {
await pngEncDecInit()

View file

@ -384,11 +384,11 @@ const toIdent = (name: string) => {
const ident = name
.trim()
// Remove astro or (astrojs) prefix and suffix
.replace(/[-_\.\/]?astro(?:js)?[-_\.]?/g, '')
.replace(/[-_./]?astro(?:js)?[-_.]?/g, '')
// drop .js suffix
.replace(/\.js/, '')
// convert to camel case
.replace(/(?:[\.\-\_\/]+)([a-zA-Z])/g, (_, w) => w.toUpperCase())
.replace(/[.\-_/]+([a-zA-Z])/g, (_, w) => w.toUpperCase())
// drop invalid first characters
.replace(/^[^a-zA-Z$_]+/, '');
return `${ident[0].toLowerCase()}${ident.slice(1)}`;

View file

@ -103,7 +103,7 @@ function getFirstParentId(parents: [ModuleInfo, number, number][]) {
return parents[0]?.[0].id;
}
const charsToReplaceRe = /[.\[\]]/g;
const charsToReplaceRe = /[.[\]]/g;
const underscoresRe = /_+/g;
/**
* Prettify base names so they're easier to read:

View file

@ -19,7 +19,7 @@ import type { StaticBuildOptions } from '../types.js';
import { normalizeTheLocale } from '../../../i18n/index.js';
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
const replaceExp = new RegExp(`['"](${manifestReplace})['"]`, 'g');
const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g');
export const SSR_MANIFEST_VIRTUAL_MODULE_ID = '@astrojs-manifest';
export const RESOLVED_SSR_MANIFEST_VIRTUAL_MODULE_ID = '\0' + SSR_MANIFEST_VIRTUAL_MODULE_ID;

View file

@ -524,7 +524,7 @@ export function makeAstroPageEntryPointFileName(
const name = route?.route ?? pageModuleId;
return `pages${name
.replace(/\/$/, '/index')
.replaceAll(/[\[\]]/g, '_')
.replaceAll(/[[\]]/g, '_')
.replaceAll('...', '---')}.astro.mjs`;
}

View file

@ -90,7 +90,7 @@ export async function createVite(
pkgJson.keywords?.includes('astro') ||
pkgJson.keywords?.includes('astro-component') ||
// Attempt: package is named `astro-something` or `@scope/astro-something`. ✅ Likely a community package
/^(@[^\/]+\/)?astro\-/.test(pkgJson.name)
/^(?:@[^/]+\/)?astro-/.test(pkgJson.name)
);
},
isFrameworkPkgByName(pkgName) {

View file

@ -29,8 +29,8 @@ async function createRestartedContainer(
return newContainer;
}
const configRE = new RegExp(`.*astro\.config\.((mjs)|(cjs)|(js)|(ts))$`);
const preferencesRE = new RegExp(`.*\.astro\/settings\.json$`);
const configRE = /.*astro.config.(?:mjs|cjs|js|ts)$/;
const preferencesRE = /.*\.astro\/settings.json$/;
export function shouldRestartContainer(
{ settings, inlineConfig, restartInFlight }: Container,

View file

@ -132,7 +132,7 @@ export function collectErrorMetadata(e: any, rootFolder?: URL | undefined): Erro
function generateHint(err: ErrorWithMetadata): string | undefined {
const commonBrowserAPIs = ['document', 'window'];
if (/Unknown file extension \"\.(jsx|vue|svelte|astro|css)\" for /.test(err.message)) {
if (/Unknown file extension "\.(?:jsx|vue|svelte|astro|css)" for /.test(err.message)) {
return 'You likely need to add this package to `vite.ssr.noExternal` in your astro config file.';
} else if (commonBrowserAPIs.some((api) => err.toString().includes(api))) {
const hint = `Browser APIs are not available on the server.
@ -172,10 +172,12 @@ function collectInfoFromStacktrace(error: SSRError & { stack: string }): StackIn
error.id ||
// TODO: this could be better, `src` might be something else
stackText.split('\n').find((ln) => ln.includes('src') || ln.includes('node_modules'));
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
const source = possibleFilePath?.replace(/^[^(]+\(([^)]+).*$/, '$1').replace(/^\s+at\s+/, '');
let file = source?.replace(/(:[0-9]+)/g, '');
const location = /:([0-9]+):([0-9]+)/g.exec(source!) ?? [];
let file = source?.replace(/:\d+/g, '');
const location = /:(\d+):(\d+)/.exec(source!) ?? [];
const line = location[1];
const column = location[2];
@ -195,8 +197,8 @@ function collectInfoFromStacktrace(error: SSRError & { stack: string }): StackIn
// Derive plugin from stack (if possible)
if (!stackInfo.plugin) {
stackInfo.plugin =
/withastro\/astro\/packages\/integrations\/([\w-]+)/gim.exec(stackText)?.at(1) ||
/(@astrojs\/[\w-]+)\/(server|client|index)/gim.exec(stackText)?.at(1) ||
/withastro\/astro\/packages\/integrations\/([\w-]+)/i.exec(stackText)?.at(1) ||
/(@astrojs\/[\w-]+)\/(server|client|index)/i.exec(stackText)?.at(1) ||
undefined;
}
@ -208,7 +210,7 @@ function collectInfoFromStacktrace(error: SSRError & { stack: string }): StackIn
function cleanErrorStack(stack: string) {
return stack
.split(/\n/g)
.split(/\n/)
.map((l) => l.replace(/\/@fs\//g, '/'))
.join('\n');
}
@ -233,10 +235,10 @@ export function getDocsForError(err: ErrorWithMetadata): string | undefined {
* Render a subset of Markdown to HTML or a CLI output
*/
export function renderErrorMarkdown(markdown: string, target: 'html' | 'cli') {
const linkRegex = /\[([^\[]+)\]\((.*)\)/gm;
const boldRegex = /\*\*(.+)\*\*/gm;
const urlRegex = / (\b(https?|ftp):\/\/[-A-Z0-9+&@#\\/%?=~_|!:,.;]*[-A-Z0-9+&@#\\/%=~_|])/gim;
const codeRegex = /`([^`]+)`/gim;
const linkRegex = /\[([^[]+)\]\((.*)\)/g;
const boldRegex = /\*\*(.+)\*\*/g;
const urlRegex = / ((?:https?|ftp):\/\/[-\w+&@#\\/%?=~|!:,.;]*[-\w+&@#\\/%=~|])/gi;
const codeRegex = /`([^`]+)`/g;
if (target === 'html') {
return escape(markdown)

View file

@ -587,7 +587,7 @@ export const PrerenderDynamicEndpointPathCollide = {
message: (pathname: string) =>
`Could not render \`${pathname}\` with an \`undefined\` param as the generated path will collide during prerendering. Prevent passing \`undefined\` as \`params\` for the endpoint's \`getStaticPaths()\` function, or add an additional extension to the endpoint's filename.`,
hint: (filename: string) =>
`Rename \`${filename}\` to \`${filename.replace(/\.(js|ts)/, (m) => `.json` + m)}\``,
`Rename \`${filename}\` to \`${filename.replace(/\.(?:js|ts)/, (m) => `.json` + m)}\``,
} satisfies ErrorData;
/**
* @docs

View file

@ -12,15 +12,15 @@ function isAstroSrcFile(id: string | null) {
}
// capture "page reload some/Component.vue (additional info)" messages
const vitePageReloadMsg = /page reload (.*)( \(.*\))?/;
const vitePageReloadMsg = /page reload (.*)/;
// capture "hmr update some/Component.vue" messages
const viteHmrUpdateMsg = /hmr update (.*)/;
// capture "vite v5.0.0 building SSR bundle for production..." and "vite v5.0.0 building for production..." messages
const viteBuildMsg = /vite.*building.*for production/;
// capture "\n Shortcuts" messages
const viteShortcutTitleMsg = /^\s*Shortcuts\s*$/s;
const viteShortcutTitleMsg = /^\s*Shortcuts\s*$/;
// capture "press * + enter to ..." messages
const viteShortcutHelpMsg = /press\s+(.*?)\s+to\s+(.*)$/s;
const viteShortcutHelpMsg = /press (.+?) to (.+)$/s;
export function createViteLogger(
astroLogger: AstroLogger,
@ -39,8 +39,7 @@ export function createViteLogger(
// Rewrite HMR page reload message
if ((m = vitePageReloadMsg.exec(stripped))) {
if (isAstroSrcFile(m[1])) return;
const extra = m[2] ?? '';
astroLogger.info('watch', m[1] + extra);
astroLogger.info('watch', m[1]);
}
// Rewrite HMR update message
else if ((m = viteHmrUpdateMsg.exec(stripped))) {

View file

@ -225,7 +225,7 @@ export function formatConfigErrorMessage(err: ZodError) {
// a regex to match the first line of a stack trace
const STACK_LINE_REGEXP = /^\s+at /g;
const IRRELEVANT_STACK_REGEXP = /(node_modules|astro[\/\\]dist)/g;
const IRRELEVANT_STACK_REGEXP = /node_modules|astro[/\\]dist/g;
function formatErrorStackTrace(
err: Error | ErrorWithMetadata,
showFullStacktrace: boolean

View file

@ -7,7 +7,7 @@ import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js'
import { cleanUrl } from '../../vite-plugin-utils/index.js';
import { stripBase } from './util.js';
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
const HAS_FILE_EXTENSION_REGEXP = /\.[^/]+$/;
export function vitePluginAstroPreview(settings: AstroSettings): Plugin {
const { base, outDir, trailingSlash } = settings.config;

View file

@ -43,13 +43,15 @@ function countOccurrences(needle: string, haystack: string) {
function getParts(part: string, file: string) {
const result: RoutePart[] = [];
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
if (!str) return;
const dynamic = i % 2 === 1;
const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
if (!content || (dynamic && !/^(\.\.\.)?[a-zA-Z0-9_$]+$/.test(content))) {
if (!content || (dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content))) {
throw new Error(`Invalid route ${file} — parameter name must match /^[a-zA-Z0-9_$]+$/`);
}

View file

@ -26,7 +26,7 @@ interface ConfigErrorEventPayload extends ErrorEventPayload {
* This is only used for errors that do not come from us so we can get a basic
* and anonymous idea of what the error is about.
*/
const ANONYMIZE_MESSAGE_REGEX = /^(\w| )+/;
const ANONYMIZE_MESSAGE_REGEX = /^(?:\w| )+/;
function anonymizeErrorMessage(msg: string): string | undefined {
const matchedMessage = msg.match(ANONYMIZE_MESSAGE_REGEX);
if (!matchedMessage?.[0]) {
@ -100,7 +100,7 @@ function getSafeErrorMessage(message: string | Function): string {
.trim()
.slice(1, -1)
.replace(
/\${([^}]+)}/gm,
/\$\{([^}]+)\}/g,
(str, match1) =>
`${match1
.split(/\.?(?=[A-Z])/)

View file

@ -32,13 +32,6 @@ function pathnameHasLocale(pathname: string, locales: Locales): boolean {
return false;
}
type MiddlewareOptions = {
i18n: SSRManifest['i18n'];
base: SSRManifest['base'];
trailingSlash: SSRManifest['trailingSlash'];
buildFormat: SSRManifest['buildFormat'];
};
export function createI18nMiddleware(
i18n: SSRManifest['i18n'],
base: SSRManifest['base'],

View file

@ -240,7 +240,7 @@ export const a11y: AuditRuleWithSelector[] = [
message:
'Screen readers already announce `img` elements as an image. There is no need to use words such as "image", "photo", and/or "picture".',
selector: 'img[alt]:not([aria-hidden])',
match: (img: HTMLImageElement) => /\b(image|picture|photo)\b/i.test(img.alt),
match: (img: HTMLImageElement) => /\b(?:image|picture|photo)\b/i.test(img.alt),
},
{
code: 'a11y-incorrect-aria-attribute-type',

View file

@ -65,8 +65,8 @@ function isHTMLComponent(Component: unknown) {
return Component && (Component as any)['astro:html'] === true;
}
const ASTRO_SLOT_EXP = /\<\/?astro-slot\b[^>]*>/g;
const ASTRO_STATIC_SLOT_EXP = /\<\/?astro-static-slot\b[^>]*>/g;
const ASTRO_SLOT_EXP = /<\/?astro-slot\b[^>]*>/g;
const ASTRO_STATIC_SLOT_EXP = /<\/?astro-static-slot\b[^>]*>/g;
function removeStaticAstroSlot(html: string, supportsAstroStaticSlot: boolean) {
const exp = supportsAstroStaticSlot ? ASTRO_STATIC_SLOT_EXP : ASTRO_SLOT_EXP;
return html.replace(exp, '');
@ -390,7 +390,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
}
function sanitizeElementName(tag: string) {
const unsafe = /[&<>'"\s]+/g;
const unsafe = /[&<>'"\s]+/;
if (!unsafe.test(tag)) return tag;
return tag.trim().split(unsafe)[0].trim();
}

View file

@ -7,17 +7,17 @@ import { HTMLString, markHTMLString } from '../escape.js';
export const voidElementNames =
/^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
const htmlBooleanAttributes =
/^(allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i;
const htmlEnumAttributes = /^(contenteditable|draggable|spellcheck|value)$/i;
/^(?:allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i;
const htmlEnumAttributes = /^(?:contenteditable|draggable|spellcheck|value)$/i;
// Note: SVG is case-sensitive!
const svgEnumAttributes = /^(autoReverse|externalResourcesRequired|focusable|preserveAlpha)$/i;
const svgEnumAttributes = /^(?:autoReverse|externalResourcesRequired|focusable|preserveAlpha)$/i;
const STATIC_DIRECTIVES = new Set(['set:html', 'set:text']);
// converts (most) arbitrary strings to valid JS identifiers
const toIdent = (k: string) =>
k.trim().replace(/(?:(?!^)\b\w|\s+|[^\w]+)/g, (match, index) => {
if (/[^\w]|\s/.test(match)) return '';
k.trim().replace(/(?!^)\b\w|\s+|\W+/g, (match, index) => {
if (/\W/.test(match)) return '';
return index === 0 ? match : match.toUpperCase();
});

View file

@ -66,6 +66,7 @@ export const reference = noop;
/** Run `astro sync` to generate high fidelity types */
export type CollectionKey = any;
/** Run `astro sync` to generate high fidelity types */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type CollectionEntry<C> = any;
/** Run `astro sync` to generate high fidelity types */
export type ContentCollectionKey = any;

View file

@ -108,7 +108,7 @@ export async function matchRoute(
// Try without `.html` extensions or `index.html` in request URLs to mimic
// routing behavior in production builds. This supports both file and directory
// build formats, and is necessary based on how the manifest tracks build targets.
const altPathname = pathname.replace(/(index)?\.html$/, '');
const altPathname = pathname.replace(/(?:index)?\.html$/, '');
if (altPathname !== pathname) {
return await matchRoute(altPathname, manifestData, pipeline);
}
@ -229,6 +229,8 @@ export async function handleRoute({
return '';
},
params: [],
// Disable eslint as we only want to generate an empty RegExp
// eslint-disable-next-line prefer-regex-literals
pattern: new RegExp(''),
prerender: false,
segments: [],

View file

@ -4,6 +4,7 @@ import { compile, type CompileProps, type CompileResult } from '../core/compile/
import type { Logger } from '../core/logger/core.js';
import { getFileInfo } from '../vite-plugin-utils/index.js';
import type { CompileMetadata } from './types.js';
import { frontmatterRE } from './utils.js';
interface CompileAstroOption {
compileProps: CompileProps;
@ -23,8 +24,6 @@ interface EnhanceCompilerErrorOptions {
logger: Logger;
}
const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
export async function compileAstro({
compileProps,
astroFileToCompileMetadata,
@ -107,7 +106,7 @@ async function enhanceCompileError({
// Before throwing, it is better to verify the frontmatter here, and
// let esbuild throw a more specific exception if the code is invalid.
// If frontmatter is valid or cannot be parsed, then continue.
const scannedFrontmatter = FRONTMATTER_PARSE_REGEXP.exec(source);
const scannedFrontmatter = frontmatterRE.exec(source);
if (scannedFrontmatter) {
// Top-level return is not supported, so replace `return` with throw
const frontmatter = scannedFrontmatter[1].replace(/\breturn\b/g, 'throw');

View file

@ -4,6 +4,7 @@ import type { HmrContext } from 'vite';
import type { Logger } from '../core/logger/core.js';
import type { CompileAstroResult } from './compile.js';
import type { CompileMetadata } from './types.js';
import { frontmatterRE } from './utils.js';
export interface HandleHotUpdateOptions {
logger: Logger;
@ -58,8 +59,10 @@ export async function handleHotUpdate(
}
}
const frontmatterRE = /^\-\-\-.*?^\-\-\-/ms;
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
const scriptRE = /<script(?:\s.*?)?>.*?<\/script>/gs;
// eslint-disable-next-line regexp/no-super-linear-backtracking
const styleRE = /<style(?:\s.*?)?>.*?<\/style>/gs;
function isStyleOnlyChanged(oldCode: string, newCode: string) {

View file

@ -0,0 +1 @@
export const frontmatterRE = /^---(.*?)^---/ms;

View file

@ -13,7 +13,7 @@ interface EnvPluginOptions {
const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
// except that the first character cannot be a number.
const isValidIdentifierRe = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
// Match `export const prerender = import.meta.env.*` since `vite=plugin-scanner` requires
// the `import.meta.env.*` to always be replaced.
const exportConstPrerenderRe = /\bexport\s+const\s+prerender\s*=\s*import\.meta\.env\.(.+?)\b/;

View file

@ -9,7 +9,7 @@ import type { BuildInternals } from '../core/build/internal.js';
import { getAstroMetadata } from '../vite-plugin-astro/index.js';
// Detect this in comments, both in .astro components and in js/ts files.
const injectExp = /(^\/\/|\/\/!)\s*astro-head-inject/;
const injectExp = /(?:^\/\/|\/\/!)\s*astro-head-inject/;
export default function configHeadVitePlugin(): vite.Plugin {
let server: vite.ViteDevServer;

View file

@ -1,7 +1,7 @@
import type { Element } from 'hast';
import type MagicString from 'magic-string';
const splitAttrsTokenizer = /([\$\{\}\@a-z0-9_\:\-]*)\s*?=\s*?(['"]?)(.*?)\2\s+/gim;
const splitAttrsTokenizer = /([${}@\w:\-]*)\s*=\s*?(['"]?)(.*?)\2\s+/g;
export function replaceAttribute(s: MagicString, node: Element, key: string, newValue: string) {
splitAttrsTokenizer.lastIndex = 0;
@ -12,7 +12,7 @@ export function replaceAttribute(s: MagicString, node: Element, key: string, new
if (offset === -1) return;
const start = node.position!.start.offset! + offset;
const tokens = text.slice(offset).split(splitAttrsTokenizer);
const token = tokens[0].replace(/([^>])(\>[\s\S]*$)/gim, '$1');
const token = tokens[0].replace(/([^>])>[\s\S]*$/gm, '$1');
if (token.trim() === key) {
const end = start + key.length;
return s.overwrite(start, end, newValue, { contentOnly: true });

View file

@ -65,7 +65,7 @@ export async function scan(
.trim();
// For a given export, check the value of the first non-whitespace token.
// Basically extract the `true` from the statement `export const prerender = true`
const suffix = code.slice(endOfLocalName).trim().replace(/\=/, '').trim().split(/[;\n]/)[0];
const suffix = code.slice(endOfLocalName).trim().replace(/=/, '').trim().split(/[;\n]/)[0];
if (prefix !== 'const' || !(isTruthy(suffix) || isFalsy(suffix))) {
throw new AstroError({
...AstroErrorData.InvalidPrerenderExport,

View file

@ -17,7 +17,7 @@ export function getFileInfo(id: string, config: AstroConfig) {
let fileUrl = fileId.includes('/pages/')
? fileId
.replace(/^.*?\/pages\//, sitePathname)
.replace(/(\/index)?\.(md|markdown|mdown|mkdn|mkd|mdwn|md|astro)$/, '')
.replace(/(?:\/index)?\.(?:md|markdown|mdown|mkdn|mkd|mdwn|astro)$/, '')
: undefined;
if (fileUrl && config.trailingSlash === 'always') {
fileUrl = appendForwardSlash(fileUrl);

View file

@ -42,7 +42,7 @@ describe('CSS', function () {
const classes = $('#class');
let scopedAttribute;
for (const [key] of Object.entries(classes[0].attribs)) {
if (/^data-astro-cid-[A-Za-z0-9-]+/.test(key)) {
if (/^data-astro-cid-[A-Za-z\d-]+/.test(key)) {
// Ema: this is ugly, but for reasons that I don't want to explore, cheerio
// lower case the hash of the attribute
scopedAttribute = key;
@ -72,7 +72,7 @@ describe('CSS', function () {
it('Child inheritance', (done) => {
for (const [key] of Object.entries($('#passed-in')[0].attribs)) {
if (/^data-astro-cid-[A-Za-z0-9-]+/.test(key)) {
if (/^data-astro-cid-[A-Za-z\d-]+/.test(key)) {
done();
}
}
@ -84,25 +84,25 @@ describe('CSS', function () {
});
it('<style lang="sass">', async () => {
expect(bundledCSS).to.match(new RegExp('h1\\[data-astro-cid-[^{]*{color:#90ee90}'));
expect(bundledCSS).to.match(/h1\[data-astro-cid-[^{]*\{color:#90ee90\}/);
});
it('<style lang="scss">', async () => {
expect(bundledCSS).to.match(new RegExp('h1\\[data-astro-cid-[^{]*{color:#ff69b4}'));
expect(bundledCSS).to.match(/h1\[data-astro-cid-[^{]*\{color:#ff69b4\}/);
});
});
describe('Styles in src/', () => {
it('.css', async () => {
expect(bundledCSS).to.match(new RegExp('.linked-css[^{]*{color:gold'));
expect(bundledCSS).to.match(/.linked-css[^{]*\{color:gold/);
});
it('.sass', async () => {
expect(bundledCSS).to.match(new RegExp('.linked-sass[^{]*{color:#789'));
expect(bundledCSS).to.match(/.linked-sass[^{]*\{color:#789/);
});
it('.scss', async () => {
expect(bundledCSS).to.match(new RegExp('.linked-scss[^{]*{color:#6b8e23'));
expect(bundledCSS).to.match(/.linked-scss[^{]*\{color:#6b8e23/);
});
});
@ -118,7 +118,7 @@ describe('CSS', function () {
it('.module.css', async () => {
const el = $('#react-module-css');
const classes = el.attr('class').split(' ');
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
const moduleClass = classes.find((name) => /^_title_[\w-]+/.test(name));
// 1. check HTML
expect(el.attr('class')).to.include(moduleClass);
@ -134,7 +134,7 @@ describe('CSS', function () {
expect(el.attr('class')).to.include('react-sass-title');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.react-sass-title[^{]*{font-family:fantasy`));
expect(bundledCSS).to.match(/.react-sass-title[^{]*\{font-family:fantasy/);
});
it('.scss', async () => {
@ -144,13 +144,13 @@ describe('CSS', function () {
expect(el.attr('class')).to.include('react-scss-title');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.react-scss-title[^{]*{font-family:fantasy`));
expect(bundledCSS).to.match(/.react-scss-title[^{]*\{font-family:fantasy/);
});
it('.module.sass', async () => {
const el = $('#react-module-sass');
const classes = el.attr('class').split(' ');
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
const moduleClass = classes.find((name) => /^_title_[\w-]+/.test(name));
// 1. check HTML
expect(el.attr('class')).to.include(moduleClass);
@ -162,7 +162,7 @@ describe('CSS', function () {
it('.module.scss', async () => {
const el = $('#react-module-scss');
const classes = el.attr('class').split(' ');
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
const moduleClass = classes.find((name) => /^_title_[\w-]+/.test(name));
// 1. check HTML
expect(el.attr('class')).to.include(moduleClass);
@ -189,7 +189,7 @@ describe('CSS', function () {
expect(el.attr('class')).to.include('vue-css');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.vue-css[^{]*{font-family:cursive`));
expect(bundledCSS).to.match(/.vue-css[^{]*\{font-family:cursive/);
});
it('<style scoped>', async () => {
@ -210,7 +210,7 @@ describe('CSS', function () {
it('<style module>', async () => {
const el = $('#vue-modules');
const classes = el.attr('class').split(' ');
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
const moduleClass = classes.find((name) => /^_title_[\w-]+/.test(name));
// 1. check HTML
expect(el.attr('class')).to.include(moduleClass);
@ -226,7 +226,7 @@ describe('CSS', function () {
expect(el.attr('class')).to.include('vue-sass');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.vue-sass[^{]*{font-family:cursive`));
expect(bundledCSS).to.match(/.vue-sass[^{]*\{font-family:cursive/);
});
it('<style lang="scss">', async () => {
@ -236,7 +236,7 @@ describe('CSS', function () {
expect(el.attr('class')).to.include('vue-scss');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.vue-scss[^{]*{font-family:cursive`));
expect(bundledCSS).to.match(/.vue-scss[^{]*\{font-family:cursive/);
});
});
@ -245,7 +245,7 @@ describe('CSS', function () {
const el = $('#svelte-css');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find(
(name) => name !== 'svelte-css' && /^svelte-[A-Za-z0-9-]+/.test(name)
(name) => name !== 'svelte-css' && /^svelte-[A-Za-z\d-]+/.test(name)
);
// 1. check HTML
@ -261,7 +261,7 @@ describe('CSS', function () {
const el = $('#svelte-sass');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find(
(name) => name !== 'svelte-sass' && /^svelte-[A-Za-z0-9-]+/.test(name)
(name) => name !== 'svelte-sass' && /^svelte-[A-Za-z\d-]+/.test(name)
);
// 1. check HTML
@ -277,7 +277,7 @@ describe('CSS', function () {
const el = $('#svelte-scss');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find(
(name) => name !== 'svelte-scss' && /^svelte-[A-Za-z0-9-]+/.test(name)
(name) => name !== 'svelte-scss' && /^svelte-[A-Za-z\d-]+/.test(name)
);
// 1. check HTML

View file

@ -102,7 +102,7 @@ describe('CSS Bundling', function () {
it('CSS does not include hashes', async () => {
const [firstFound] = await fixture.readdir('/assets');
expect(firstFound).to.not.match(/[a-z]+\.[0-9a-z]{8}\.css/);
expect(firstFound).to.not.match(/[a-z]+\.[\da-z]{8}\.css/);
});
it('there are 2 index named CSS files', async () => {

View file

@ -29,10 +29,7 @@ describe('Doctype', () => {
// test that Doctype included was preserved
expect(html).to.match(
new RegExp(
'^<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
'i'
)
/^<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD HTML 4.01 Transitional\/\/EN" "http:\/\/www.w3.org\/TR\/html4\/loose.dtd">/i
);
});

View file

@ -26,7 +26,7 @@ describe('Partial HTML', async () => {
// test 2: correct CSS present
const allInjectedStyles = $('style').text();
expect(allInjectedStyles).to.match(/\[data-astro-cid-[^{]+{color:red}/);
expect(allInjectedStyles).to.match(/\[data-astro-cid-[^{]+\{color:red\}/);
});
it('injects framework styles', async () => {
@ -38,7 +38,7 @@ describe('Partial HTML', async () => {
// test 2: link tag present
const allInjectedStyles = $('style').text().replace(/\s*/g, '');
expect(allInjectedStyles).to.match(/h1{color:red;}/);
expect(allInjectedStyles).to.match(/h1\{color:red;\}/);
});
it('pages with a head, injection happens inside', async () => {

View file

@ -58,7 +58,7 @@ describe('Component Libraries', () => {
expect($('button').text()).to.equal('Click me', "Rendered the component's slot");
const findEvidence = createFindEvidence(/border-radius:( )*1rem/);
const findEvidence = createFindEvidence(/border-radius:\s*1rem/);
expect(await findEvidence('with-astro/index.html')).to.equal(
true,
"Included the .astro component's <style>"
@ -136,7 +136,7 @@ describe('Component Libraries', () => {
expect($('button').text()).to.equal('Click me', "Rendered the component's slot");
const findEvidence = createFindEvidence(/border-radius:( )*1rem/);
const findEvidence = createFindEvidence(/border-radius:\s*1rem/);
expect(await findEvidence('/with-astro/')).to.equal(
true,
"Included the .astro component's <style>"

View file

@ -36,7 +36,7 @@ describe('CSS', function () {
it('vite.build.cssTarget is respected', async () => {
expect(bundledCSS).to.match(
new RegExp('.class\\[data-astro-[^{]*{top:0;right:0;bottom:0;left:0}')
/\.class\[data-astro-[^{]*\{top:0;right:0;bottom:0;left:0\}/
);
});
});

View file

@ -17,6 +17,6 @@ describe('Vite Config', async () => {
it('Allows overriding bundle naming options in the build', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
expect($('link').attr('href')).to.match(/\/assets\/testing-[a-z0-9]+\.css/);
expect($('link').attr('href')).to.match(/\/assets\/testing-[a-z\d]+\.css/);
});
});

View file

@ -955,7 +955,7 @@ describe('astro:image', () => {
let $script = $('script');
// Find image
const regex = /src:"([^"]*)/gm;
const regex = /src:"([^"]*)/;
const imageSrc = regex.exec($script.html())[1];
const data = await fixture.readFile(imageSrc, null);
expect(data).to.be.an.instanceOf(Buffer);
@ -967,7 +967,7 @@ describe('astro:image', () => {
const srcset = $('#local-2-widths-with-spaces img').attr('srcset');
// Find image
const regex = /^(.+?) [0-9]+[wx]$/gm;
const regex = /^(.+?) \d+[wx]$/m;
const imageSrcset = regex.exec(srcset)[1];
expect(imageSrcset).to.not.contain(' ');
});

View file

@ -1,7 +1,7 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
const cssAssetReferenceRegExp = /_astro\/[A-Za-z0-9\-]+\.[a0-9a-f]{8}\.css/g;
const cssAssetReferenceRegExp = /_astro\/[A-Za-z\d\-]+\.[\da-f]{8}\.css/g;
describe("When Vite's preloadModule polyfill is used", async () => {
let fixture;

View file

@ -19,7 +19,7 @@ describe('Hoisted Imports', () => {
const $ = cheerio.load(html);
const scriptText = [];
const importRegex = /import\s*?['"]([^'"]+?)['"]/g;
const importRegex = /import\s*['"]([^'"]+)['"]/g;
async function resolveImports(text) {
const matches = text.matchAll(importRegex);
for (const match of matches) {

View file

@ -70,7 +70,7 @@ describe('Markdown tests', () => {
it('Does not unescape entities', async () => {
const html = await fixture.readFile('/entities/index.html');
expect(html).to.match(new RegExp('&#x3C;i>This should NOT be italic&#x3C;/i>'));
expect(html).to.match(/&#x3C;i>This should NOT be italic&#x3C;\/i>/);
});
});
});

View file

@ -2,7 +2,7 @@ import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
const NEW_LINES = /[\r\n]+/gm;
const NEW_LINES = /[\r\n]+/g;
/**
* The doctype declaration is on a line between the rest of the HTML in SSG.

View file

@ -26,23 +26,23 @@ describe('PostCSS', function () {
/** All test cases check whether nested styles (i.e. &.nested {}) are correctly transformed */
it('works in Astro page styles', () => {
expect(bundledCSS).to.match(new RegExp(`\.astro-page(\.(\w|-)*)*\.nested`));
expect(bundledCSS).to.match(/\.astro-page\[data-astro-cid-.*?\]\.nested/);
});
it('works in Astro component styles', () => {
expect(bundledCSS).to.match(new RegExp(`\.astro-component(\.(\w|-)*)*\.nested`));
expect(bundledCSS).to.match(/\.astro-component\[data-astro-cid-.*?\]\.nested/);
});
it('works in JSX', () => {
expect(bundledCSS).to.match(new RegExp(`\.solid(\.(\w|-)*)*\.nested`));
expect(bundledCSS).to.match(/\.solid(\.(w|-)*)*\.nested/);
});
it('works in Vue', () => {
expect(bundledCSS).to.match(new RegExp(`\.vue(\.(\w|-)*)*\.nested`));
expect(bundledCSS).to.match(/\.vue(\.(w|-)*)*\.nested/);
});
it('works in Svelte', () => {
expect(bundledCSS).to.match(new RegExp(`\.svelte(\.(\w|-)*)*\.nested`));
expect(bundledCSS).to.match(/\.svelte(\.(w|-)*)*\.nested/);
});
it('ignores CSS in public/', async () => {

View file

@ -20,6 +20,6 @@ describe('srcDir', () => {
const relPath = $('link').attr('href');
const css = await fixture.readFile(relPath);
expect(css).to.match(/body{color:green}/);
expect(css).to.match(/body\{color:green\}/);
});
});

View file

@ -108,7 +108,7 @@ describe('API routes in SSR', () => {
});
let count = 0;
let exp = /set-cookie\:/g;
let exp = /set-cookie:/g;
while (exp.exec(response)) {
count++;
}

View file

@ -128,7 +128,7 @@ describe('Static build', () => {
}
describe('Page CSS', () => {
const findEvidence = createFindEvidence(/height:( )*45vw/);
const findEvidence = createFindEvidence(/height:\s*45vw/);
it('Page level CSS is added', async () => {
const found = await findEvidence('/index.html');
@ -186,7 +186,7 @@ describe('Static build', () => {
let found = false;
for (const log of logs) {
if (
/\`Astro\.request\.headers\` is not available in "static" output mode/.test(log.message)
/`Astro\.request\.headers` is not available in "static" output mode/.test(log.message)
) {
found = true;
}

View file

@ -28,20 +28,20 @@ describe('Tailwind', () => {
});
it('resolves CSS in src/styles', async () => {
expect(bundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/);
expect(bundledCSS, 'includes used component classes').to.match(/\.bg-purple-600\{/);
// tests a random tailwind class that isn't used on the page
expect(bundledCSS, 'purges unused classes').not.to.match(/\.bg-blue-600{/);
expect(bundledCSS, 'purges unused classes').not.to.match(/\.bg-blue-600\{/);
// tailwind escapes colons, `lg:py-3` compiles to `lg\:py-3`
expect(bundledCSS, 'includes responsive classes').to.match(/\.lg\\:py-3{/);
expect(bundledCSS, 'includes responsive classes').to.match(/\.lg\\:py-3\{/);
// tailwind escapes brackets, `font-[900]` compiles to `font-\[900\]`
expect(bundledCSS, 'supports arbitrary value classes').to.match(/\.font-\\\[900\\\]{/);
expect(bundledCSS, 'supports arbitrary value classes').to.match(/\.font-\\\[900\\\]\{/);
// custom theme colors were included
expect(bundledCSS, 'includes custom theme colors').to.match(/\.text-midnight{/);
expect(bundledCSS, 'includes custom theme colors').to.match(/\.bg-dawn{/);
expect(bundledCSS, 'includes custom theme colors').to.match(/\.text-midnight\{/);
expect(bundledCSS, 'includes custom theme colors').to.match(/\.bg-dawn\{/);
});
it('maintains classes in HTML', async () => {
@ -64,7 +64,7 @@ describe('Tailwind', () => {
const $md = cheerio.load(html);
const bundledCSSHREF = $md('link[rel=stylesheet][href^=/_astro/]').attr('href');
const mdBundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/);
expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600\{/);
});
});
});

View file

@ -302,7 +302,7 @@ export async function parseCliDevStart(proc) {
const messages = stdout
.split('\n')
.filter((ln) => !!ln.trim())
.map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim());
.map((ln) => ln.replace(/[🚀┃]/gu, '').replace(/\s+/g, ' ').trim());
return { messages };
}

View file

@ -40,7 +40,7 @@ function normalizePath(str, stripTrailing) {
if (typeof str !== 'string') {
throw new TypeError('expected a string');
}
str = str.replace(/[\\\/]+/g, '/');
str = str.replace(/[\\/]+/g, '/');
if (stripTrailing !== false) {
str = removeTrailingSeparator(str);
}

View file

@ -79,6 +79,8 @@ async function verifyTemplate(tmpl: string, ref?: string) {
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// Disable eslint rule to not touch the original code
// eslint-disable-next-line regexp/no-misleading-capturing-group
const GIT_RE = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
function parseGitURI(input: string) {

View file

@ -17,7 +17,7 @@ export function parseInlineCSSToReactLikeObject(
function convertCssDirectiveNameToReactCamelCase(original: string): string {
// capture group 1 is the character to capitalize, the hyphen is omitted by virtue of being outside the capture group
const replaced = original.replace(/-([a-z0-9])/gi, (_match, char) => {
const replaced = original.replace(/-([a-z\d])/gi, (_match, char) => {
return char.toUpperCase();
});
return replaced;

View file

@ -23,9 +23,11 @@ const NEWLINE_REGEX = /\n/g;
const WHITESPACE_REGEX = /^\s*/;
// declaration
const PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
const PROPERTY_REGEX = /^([-#/*\\\w]+(\[[\da-z_-]+\])?)\s*/;
const COLON_REGEX = /^:\s*/;
const VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/;
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
const VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*\)|[^};])+)/;
const SEMICOLON_REGEX = /^[;\s]*/;
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill

View file

@ -153,7 +153,7 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
.filter(({ n }) => n === 'astro/jsx-runtime')
.map(({ ss, se }) => code.substring(ss, se));
const hasFragmentImport = importsFromJSXRuntime.some((statement) =>
/[\s,{](Fragment,|Fragment\s*})/.test(statement)
/[\s,{](?:Fragment,|Fragment\s*\})/.test(statement)
);
if (!hasFragmentImport) {
code = 'import { Fragment } from "astro/jsx-runtime"\n' + code;

View file

@ -31,7 +31,7 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
let fileUrl: string;
const isPage = fileId.includes('/pages/');
if (isPage) {
fileUrl = fileId.replace(/^.*?\/pages\//, sitePathname).replace(/(\/index)?\.mdx$/, '');
fileUrl = fileId.replace(/^.*?\/pages\//, sitePathname).replace(/(?:\/index)?\.mdx$/, '');
} else if (url?.pathname.startsWith(config.root.pathname)) {
fileUrl = url.pathname.slice(config.root.pathname.length);
} else {

View file

@ -175,7 +175,9 @@ export default function (dir, opts = {}) {
let ignores = [];
if (opts.ignores !== false) {
ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
ignores.push(/\/([\w\s~$.-]+\.\w+)+$/); // any extn
if (opts.dotfiles) ignores.push(/\/\.\w/);
else ignores.push(/\/\.well-known/);
[].concat(opts.ignores || []).forEach((x) => {
@ -189,9 +191,9 @@ export default function (dir, opts = {}) {
if (!opts.dev) {
totalist(dir, (name, abs, stats) => {
if (/\.well-known[\\+\/]/.test(name)) {
if (/\.well-known[\\+/]/.test(name)) {
} // keep
else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
else if (!opts.dotfiles && /^\.|[\\+|/]\./.test(name)) return;
let headers = toHeaders(name, stats, isEtag);
if (cc) headers['Cache-Control'] = cc;
@ -212,7 +214,7 @@ export default function (dir, opts = {}) {
// NEW END
let val = req.headers['accept-encoding'] || '';
if (gzips && val.includes('gzip')) extns.unshift(...gzips);
if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
if (brots && /br/i.test(val)) extns.unshift(...brots);
extns.push(...extensions); // [...br, ...gz, orig, ...exts]
if (pathname.indexOf('%') !== -1) {

View file

@ -37,7 +37,7 @@ async function check(
// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>
// It also might render an empty sting.
return html == '' ? false : !/\<undefined\>/.test(html);
return html == '' ? false : !/<undefined>/.test(html);
} catch (err) {
return false;
}

View file

@ -38,6 +38,6 @@ describe('getStaticPaths support', () => {
it('should render the endpoint', async () => {
const page = await fixture.readFile('./it/manifest');
assert.match(page, /I\'m a route in the "it" language./);
assert.match(page, /I'm a route in the "it" language./);
});
});

View file

@ -23,7 +23,7 @@ export function toTSX(code: string, className: string): string {
const { scriptSetup } = parsedResult.descriptor;
if (scriptSetup) {
const definePropsType = scriptSetup.content.match(/defineProps<([\S\s]+?)>\s?\(\)/m);
const definePropsType = scriptSetup.content.match(/defineProps<([\s\S]+?)>\s?\(\)/);
const propsGeneric = scriptSetup.attrs.generic;
const propsGenericType = propsGeneric ? `<${propsGeneric}>` : '';
@ -40,7 +40,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 = scriptSetup.content.match(/defineProps\([\s\S]+\)/m);
const defineProps = scriptSetup.content.match(/defineProps\([\s\S]+\)/);
if (defineProps) {
result = `

View file

@ -41,7 +41,7 @@ describe('App Entrypoint', () => {
assert.notEqual(client, undefined);
const js = await fixture.readFile(client);
assert.match(js, /\w+\.component\(\"Bar\"/gm);
assert.match(js, /\w+\.component\("Bar"/g);
});
it('loads svg components without transforming them to assets', async () => {
@ -112,7 +112,7 @@ describe('App Entrypoint no export default', () => {
const client = island.getAttribute('renderer-url');
assert.notEqual(client, undefined);
const js = await fixture.readFile(client);
assert.doesNotMatch(js, /\w+\.component\(\"Bar\"/gm);
assert.doesNotMatch(js, /\w+\.component\("Bar"/g);
});
it('loads svg components without transforming them to assets', async () => {
@ -151,7 +151,7 @@ describe('App Entrypoint relative', () => {
assert.notEqual(client, undefined);
const js = await fixture.readFile(client);
assert.doesNotMatch(js, /\w+\.component\(\"Bar\"/gm);
assert.doesNotMatch(js, /\w+\.component\("Bar"/g);
});
});
@ -182,7 +182,7 @@ describe('App Entrypoint /src/absolute', () => {
assert.notEqual(client, undefined);
const js = await fixture.readFile(client);
assert.doesNotMatch(js, /\w+\.component\(\"Bar\"/gm);
assert.doesNotMatch(js, /\w+\.component\("Bar"/g);
});
});

View file

@ -16,7 +16,7 @@ export function prependForwardSlash(path: string) {
}
export function collapseDuplicateSlashes(path: string) {
return path.replace(/(?<!:)\/\/+/g, '/');
return path.replace(/(?<!:)\/{2,}/g, '/');
}
export function removeTrailingForwardSlash(path: string) {
@ -86,7 +86,7 @@ export function removeQueryString(path: string) {
}
export function isRemotePath(src: string) {
return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:');
return /^(?:http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:');
}
export function slash(path: string) {

View file

@ -13,7 +13,7 @@ const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = {
'--astro-code-background': '--astro-code-color-background',
};
const COLOR_REPLACEMENT_REGEX = new RegExp(
`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
`${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')}`,
'g'
);

View file

@ -91,7 +91,7 @@ function getProjectId(isCI: boolean): Pick<ProjectInfo, 'anonymousProjectId' | '
// If we're running in CI, the current working directory is not unique.
// If the cwd is a single level deep (ex: '/app'), it's probably not unique.
const cwd = process.cwd();
const isCwdGeneric = (cwd.match(/[\/|\\]/g) || []).length === 1;
const isCwdGeneric = (cwd.match(/[/|\\]/g) || []).length === 1;
if (isCI || isCwdGeneric) {
return {
isGit: false,

53
pnpm-lock.yaml generated
View file

@ -51,6 +51,9 @@ importers:
eslint-plugin-prettier:
specifier: ^5.0.0
version: 5.1.2(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.1.1)
eslint-plugin-regexp:
specifier: ^2.2.0
version: 2.2.0(eslint@8.56.0)
globby:
specifier: ^14.0.0
version: 14.0.0
@ -8894,6 +8897,11 @@ packages:
engines: {node: ^12.20.0 || >=14}
dev: true
/comment-parser@1.4.1:
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
engines: {node: '>= 12.0.0'}
dev: true
/common-ancestor-path@1.0.1:
resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==}
dev: false
@ -9633,6 +9641,22 @@ packages:
synckit: 0.8.8
dev: true
/eslint-plugin-regexp@2.2.0(eslint@8.56.0):
resolution: {integrity: sha512-0kwpiWiLRVBkVr3oIRQLl196sXP/NF6DQFefv9jtR4ZOgQR+6WID2pIZ0I+wIt54qgBPwBB7Gm2a+ueh8/WsFQ==}
engines: {node: ^18 || >=20}
peerDependencies:
eslint: '>=8.44.0'
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
'@eslint-community/regexpp': 4.10.0
comment-parser: 1.4.1
eslint: 8.56.0
jsdoc-type-pratt-parser: 4.0.0
refa: 0.12.1
regexp-ast-analysis: 0.7.1
scslre: 0.3.0
dev: true
/eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -11165,6 +11189,11 @@ packages:
dependencies:
argparse: 2.0.1
/jsdoc-type-pratt-parser@4.0.0:
resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==}
engines: {node: '>=12.0.0'}
dev: true
/jsdom@22.1.0:
resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==}
engines: {node: '>=16'}
@ -13755,6 +13784,13 @@ packages:
strip-indent: 3.0.0
dev: true
/refa@0.12.1:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dependencies:
'@eslint-community/regexpp': 4.10.0
dev: true
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: true
@ -13763,6 +13799,14 @@ packages:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
dev: true
/regexp-ast-analysis@0.7.1:
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dependencies:
'@eslint-community/regexpp': 4.10.0
refa: 0.12.1
dev: true
/regexp.prototype.flags@1.5.1:
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
engines: {node: '>= 0.4'}
@ -14162,6 +14206,15 @@ packages:
dependencies:
loose-envify: 1.4.0
/scslre@0.3.0:
resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
engines: {node: ^14.0.0 || >=16.0.0}
dependencies:
'@eslint-community/regexpp': 4.10.0
refa: 0.12.1
regexp-ast-analysis: 0.7.1
dev: true
/section-matter@1.0.0:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}