diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6333ec251973c0..59a3579c80fc2e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -7,9 +7,10 @@ module.exports = defineConfig({ extends: [ 'eslint:recommended', 'plugin:node/recommended', - 'plugin:@typescript-eslint/recommended' + 'plugin:@typescript-eslint/recommended', + 'plugin:regexp/recommended' ], - plugins: ['import'], + plugins: ['import', 'regexp'], parser: '@typescript-eslint/parser', parserOptions: { sourceType: 'module', @@ -97,7 +98,9 @@ module.exports = defineConfig({ memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], allowSeparatedGroups: false } - ] + ], + + 'regexp/no-contradiction-with-assertion': 'error' }, overrides: [ { diff --git a/package.json b/package.json index 06dff5675efbfe..e04e96d5b9b13e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "eslint-define-config": "^1.11.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-regexp": "^1.10.0", "execa": "^6.1.0", "fast-glob": "^3.2.12", "fs-extra": "^10.1.0", diff --git a/packages/create-vite/src/index.ts b/packages/create-vite/src/index.ts index d6407b8b3b4c17..ce800f3eb23a24 100755 --- a/packages/create-vite/src/index.ts +++ b/packages/create-vite/src/index.ts @@ -388,7 +388,7 @@ function copy(src: string, dest: string) { } function isValidPackageName(projectName: string) { - return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test( + return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test( projectName ) } @@ -399,7 +399,7 @@ function toValidPackageName(projectName: string) { .toLowerCase() .replace(/\s+/g, '-') .replace(/^[._]/, '') - .replace(/[^a-z0-9-~]+/g, '-') + .replace(/[^a-z\d\-~]+/g, '-') } function copyDir(srcDir: string, destDir: string) { diff --git a/packages/plugin-react/src/fast-refresh.ts b/packages/plugin-react/src/fast-refresh.ts index b0b38a8cafb94e..1461b4d785fe7b 100644 --- a/packages/plugin-react/src/fast-refresh.ts +++ b/packages/plugin-react/src/fast-refresh.ts @@ -56,7 +56,7 @@ if (import.meta.hot) { RefreshRuntime.register(type, __SOURCE__ + " " + id) }; window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; -}`.replace(/[\n]+/gm, '') +}`.replace(/\n+/g, '') const timeout = ` if (!window.__vite_plugin_react_timeout) { diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index fe7b209c44ed12..ad54bb909a593b 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -110,10 +110,10 @@ export default function viteReact(opts: Options = {}): PluginOption[] { // - import * as React from 'react'; // - import React from 'react'; // - import React, {useEffect} from 'react'; - const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/ + const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/ // Any extension, including compound ones like '.bs.js' - const fileExtensionRE = /\.[^\/\s\?]+$/ + const fileExtensionRE = /\.[^/\s?]+$/ const viteBabel: Plugin = { name: 'vite:react-babel', @@ -202,7 +202,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { filepath.match(fileExtensionRE) || [] - if (/\.(mjs|[tj]sx?)$/.test(extension)) { + if (/\.(?:mjs|[tj]sx?)$/.test(extension)) { const isJSX = extension.endsWith('x') const isNodeModules = id.includes('/node_modules/') const isProjectFile = diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts index 0db6b23f936280..5b2d9595de7fcf 100644 --- a/packages/plugin-vue/src/handleHotUpdate.ts +++ b/packages/plugin-vue/src/handleHotUpdate.ts @@ -11,7 +11,7 @@ import type { ResolvedOptions } from '.' const debug = _debug('vite:hmr') -const directRequestRE = /(\?|&)direct\b/ +const directRequestRE = /(?:\?|&)direct\b/ /** * Vite-specific HMR handling @@ -148,7 +148,7 @@ export async function handleHotUpdate( affectedModules.add(mainModule) } else if (mainModule && !affectedModules.has(mainModule)) { const styleImporters = [...mainModule.importers].filter((m) => - /\.css($|\?)/.test(m.url) + /\.css(?:$|\?)/.test(m.url) ) styleImporters.forEach((m) => affectedModules.add(m)) } diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 5bfdea72654678..e90bdd7150bced 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -465,8 +465,8 @@ export async function resolveConfig( ) const clientAlias = [ - { find: /^[\/]?@vite\/env/, replacement: () => ENV_ENTRY }, - { find: /^[\/]?@vite\/client/, replacement: () => CLIENT_ENTRY } + { find: /^\/?@vite\/env/, replacement: () => ENV_ENTRY }, + { find: /^\/?@vite\/client/, replacement: () => CLIENT_ENTRY } ] // resolve alias with internal client alias diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index e091afcebc928b..83f97586e6ea6d 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -46,9 +46,9 @@ export const DEFAULT_CONFIG_FILES = [ export const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/ -export const OPTIMIZABLE_ENTRY_RE = /\.(?:[cm]?[jt]s)$/ +export const OPTIMIZABLE_ENTRY_RE = /\.[cm]?[jt]s$/ -export const SPECIAL_QUERY_RE = /[\?&](?:worker|sharedworker|raw|url)\b/ +export const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/ /** * Prefix for resolved fs paths, since windows paths may not be valid as URLs. @@ -129,7 +129,7 @@ export const DEFAULT_ASSETS_RE = new RegExp( `\\.(` + KNOWN_ASSET_TYPES.join('|') + `)(\\?.*)?$` ) -export const DEP_VERSION_RE = /[\?&](v=[\w\.-]+)\b/ +export const DEP_VERSION_RE = /[?&](v=[\w.-]+)\b/ export const loopbackHosts = new Set([ 'localhost', diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index f3b300a5225bd8..3370cec68a6383 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -265,7 +265,7 @@ export function esbuildCjsExternalPlugin(externals: string[]): Plugin { name: 'cjs-external', setup(build) { const escape = (text: string) => - `^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$` + `^${text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}$` const filter = new RegExp(externals.map(escape).join('|')) build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({ diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 7764705d76694c..662f6050a470a0 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -40,7 +40,7 @@ const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/ // since even missed imports can be caught at runtime, and false positives will // simply be ignored. export const importsRE = - /(? @@ -149,13 +149,13 @@ function globEntries(pattern: string | string[], config: ResolvedConfig) { } const scriptModuleRE = - /(]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims -export const scriptRE = /(]*>|>))(.*?)<\/script>/gims + /(]+type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gis +export const scriptRE = /(]*>|>))(.*?)<\/script>/gis export const commentRE = //gs -const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im -const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im -const langRE = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im -const contextRE = /\bcontext\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im +const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i +const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i +const langRE = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i +const contextRE = /\bcontext\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i function esbuildScanPlugin( config: ResolvedConfig, diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index ac06868bebfead..1e8b6303488fc3 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -21,7 +21,7 @@ import { FS_PREFIX } from '../constants' export const assetUrlRE = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/g -const rawRE = /(\?|&)raw(?:&|$)/ +const rawRE = /(?:\?|&)raw(?:&|$)/ const urlRE = /(\?|&)url(?:&|$)/ const assetCache = new WeakMap>() @@ -164,7 +164,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin { return } - id = id.replace(urlRE, '$1').replace(/[\?&]$/, '') + id = id.replace(urlRE, '$1').replace(/[?&]$/, '') const url = await fileToUrl(id, config, this) return `export default ${JSON.stringify(url)}` }, diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index c21c1a0c29e4f8..6be48f5a6f102d 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -38,7 +38,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ) { let s: MagicString | undefined const assetImportMetaUrlRE = - /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*,?\s*\)/g + /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/g const cleanString = stripLiteral(code) let match: RegExpExecArray | null diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 01875173151fa2..35f2e01a6701ad 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -104,13 +104,14 @@ export interface CSSModulesOptions { const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)($|\\?)` const cssLangRE = new RegExp(cssLangs) +// eslint-disable-next-line regexp/no-unused-capturing-group const cssModuleRE = new RegExp(`\\.module${cssLangs}`) -const directRequestRE = /(\?|&)direct\b/ -const htmlProxyRE = /(\?|&)html-proxy\b/ +const directRequestRE = /(?:\?|&)direct\b/ +const htmlProxyRE = /(?:\?|&)html-proxy\b/ const commonjsProxyRE = /\?commonjs-proxy/ -const inlineRE = /(\?|&)inline\b/ -const inlineCSSRE = /(\?|&)inline-css\b/ -const usedRE = /(\?|&)used\b/ +const inlineRE = /(?:\?|&)inline\b/ +const inlineCSSRE = /(?:\?|&)inline-css\b/ +const usedRE = /(?:\?|&)used\b/ const varRE = /^var\(/i const cssBundleName = 'style.css' @@ -1163,11 +1164,13 @@ type CssUrlReplacer = ( ) => string | Promise // https://drafts.csswg.org/css-syntax-3/#identifier-code-point export const cssUrlRE = - /(?<=^|[^\w\-\u0080-\uffff])url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ + /(?<=^|[^\w\-\u0080-\uffff])url\((\s*('[^']+'|"[^"]+")\s*|[^'")]+)\)/ export const cssDataUriRE = - /(?<=^|[^\w\-\u0080-\uffff])data-uri\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ + /(?<=^|[^\w\-\u0080-\uffff])data-uri\((\s*('[^']+'|"[^"]+")\s*|[^'")]+)\)/ export const importCssRE = /@import ('[^']+\.css'|"[^"]+\.css"|[^'")]+\.css)/ -const cssImageSetRE = /(?<=image-set\()((?:[\w\-]+\([^\)]*\)|[^)])*)(?=\))/ +// Assuming a function name won't be longer than 256 chars +// eslint-disable-next-line regexp/no-unused-capturing-group -- doesn't detect asyncReplace usage +const cssImageSetRE = /(?<=image-set\()((?:[\w\-]{1,256}\([^)]*\)|[^)])*)(?=\))/ const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{ replacer: CssUrlReplacer @@ -1223,7 +1226,7 @@ function rewriteCssUrls( ): Promise { return asyncReplace(css, cssUrlRE, async (match) => { const [matched, rawUrl] = match - return await doUrlReplace(rawUrl, matched, replacer) + return await doUrlReplace(rawUrl.trim(), matched, replacer) }) } @@ -1233,7 +1236,7 @@ function rewriteCssDataUris( ): Promise { return asyncReplace(css, cssDataUriRE, async (match) => { const [matched, rawUrl] = match - return await doUrlReplace(rawUrl, matched, replacer, 'data-uri') + return await doUrlReplace(rawUrl.trim(), matched, replacer, 'data-uri') }) } @@ -1250,7 +1253,7 @@ function rewriteImportCss( // TODO: image and cross-fade could contain a "url" that needs to be processed // https://drafts.csswg.org/css-images-4/#image-notation // https://drafts.csswg.org/css-images-4/#cross-fade-function -const cssNotProcessedRE = /(gradient|element|cross-fade|image)\(/ +const cssNotProcessedRE = /(?:gradient|element|cross-fade|image)\(/ async function rewriteCssImageSet( css: string, @@ -1379,7 +1382,7 @@ export async function hoistAtRules(css: string): Promise { // to top when multiple files are concatenated. // match until semicolon that's not in quotes const atImportRE = - /@import\s*(?:url\([^\)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'|[^;]*).*?;/gm + /@import(?:\s*(?:url\([^)]*\)|"(?:[^"]|(?<=\\)")*"|'(?:[^']|(?<=\\)')*').*?|[^;]*);/g while ((match = atImportRE.exec(cleanCss))) { s.remove(match.index, match.index + match[0].length) // Use `appendLeft` instead of `prepend` to preserve original @import order @@ -1389,7 +1392,7 @@ export async function hoistAtRules(css: string): Promise { // #6333 // CSS @charset must be the top-first in the file, hoist the first to top const atCharsetRE = - /@charset\s*(?:"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'|[^;]*).*?;/gm + /@charset(?:\s*(?:"(?:[^"]|(?<=\\)")*"|'(?:[^']|(?<=\\)')*').*?|[^;]*);/g let foundCharset = false while ((match = atCharsetRE.exec(cleanCss))) { s.remove(match.index, match.index + match[0].length) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index e683ba4d0175db..47394b7f14e635 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -5,7 +5,7 @@ import { transformStableResult } from '../utils' import { isCSSRequest } from './css' import { isHTMLRequest } from './html' -const nonJsRe = /\.(json)($|\?)/ +const nonJsRe = /\.json(?:$|\?)/ const isNonJsRequest = (request: string): boolean => nonJsRe.test(request) export function definePlugin(config: ResolvedConfig): Plugin { diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index 137911377d15be..129ec41abca7e0 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -27,9 +27,9 @@ import { searchForWorkspaceRoot } from '..' const debug = createDebugger('vite:esbuild') const INJECT_HELPERS_IIFE_RE = - /^(.*)((?:const|var) [^\s]+=function\([^)]*?\){"use strict";)/s + /^(.*?)((?:const|var) \S+=function\([^)]*\)\{"use strict";)/s const INJECT_HELPERS_UMD_RE = - /^(.*)(\(function\([^)]*?\){.+amd.+function\([^)]*?\){"use strict";)/s + /^(.*?)(\(function\([^)]*\)\{.+amd.+function\([^)]*\)\{"use strict";)/s let server: ViteDevServer diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 5a9ac5b7b805bb..88f215c310ae59 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -43,12 +43,13 @@ const htmlProxyRE = /\?html-proxy=?(?:&inline-css)?&index=(\d+)\.(js|css)$/ const inlineCSSRE = /__VITE_INLINE_CSS__([a-z\d]{8}_\d+)__/g // Do not allow preceding '.', but do allow preceding '...' for spread operations const inlineImportRE = - /(?]*type\s*=\s*["']?importmap["']?[^>]*>.*?<\/script>/is -const moduleScriptRE = /[ \t]*]*type\s*=\s*["']?module["']?[^>]*>/is + /[ \t]*]*type\s*=\s*(?:"importmap"|'importmap'|importmap)[^>]*>.*?<\/script>/is +const moduleScriptRE = + /[ \t]*]*type\s*=\s*(?:"module"|'module'|module)[^>]*>/i export const isHTMLProxy = (id: string): boolean => htmlProxyRE.test(id) @@ -196,7 +197,7 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): { return { src, sourceCodeLocation, isModule, isAsync } } -const attrValueStartRE = /=[\s\t\n\r]*(.)/ +const attrValueStartRE = /=\s*(.)/ export function overwriteAttrValue( s: MagicString, diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index d15496d6d34c17..9dd0b028226396 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -71,12 +71,12 @@ const debug = createDebugger('vite:import-analysis') const clientDir = normalizePath(CLIENT_DIR) -const skipRE = /\.(map|json)($|\?)/ +const skipRE = /\.(?:map|json)(?:$|\?)/ export const canSkipImportAnalysis = (id: string): boolean => skipRE.test(id) || isDirectCSSRequest(id) -const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/ -const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/ +const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/ +const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/ export function isExplicitImportRequired(url: string): boolean { return !isJSRequest(cleanUrl(url)) && !isCSSRequest(url) @@ -347,7 +347,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // query can break 3rd party plugin's extension checks. if ( (isRelative || isSelfImport) && - !/[\?&]import=?\b/.test(url) && + !/[?&]import=?\b/.test(url) && !url.match(DEP_VERSION_RE) ) { const versionMatch = importer.match(DEP_VERSION_RE) @@ -583,7 +583,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '') .trim() if ( - !/^('.*'|".*"|`.*`)$/.test(url) || + !/^(?:'.*'|".*"|`.*`)$/.test(url) || isExplicitImportRequired(url.slice(1, -1)) ) { needQueryInjectHelper = true diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index d1eaa6437a6962..c8be0da8a95b32 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -38,8 +38,8 @@ const preloadMarkerWithQuote = `"${preloadMarker}"` as const const dynamicImportPrefixRE = /import\s*\(/ // TODO: abstract -const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/ -const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/ +const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/ +const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/ function toRelativePath(filename: string, importer: string) { const relPath = path.relative(path.dirname(importer), filename) diff --git a/packages/vite/src/node/plugins/json.ts b/packages/vite/src/node/plugins/json.ts index 216679a22148cd..ed02f7d3137e98 100644 --- a/packages/vite/src/node/plugins/json.ts +++ b/packages/vite/src/node/plugins/json.ts @@ -26,9 +26,9 @@ export interface JsonOptions { } // Custom json filter for vite -const jsonExtRE = /\.json($|\?)(?!commonjs-(proxy|external))/ +const jsonExtRE = /\.json(?:$|\?)(?!commonjs-(?:proxy|external))/ -const jsonLangs = `\\.(json|json5)($|\\?)` +const jsonLangs = `\\.(?:json|json5)(?:$|\\?)` const jsonLangRE = new RegExp(jsonLangs) export const isJSONRequest = (request: string): boolean => jsonLangRE.test(request) @@ -71,7 +71,7 @@ export function jsonPlugin( map: { mappings: '' } } } catch (e) { - const errorMessageList = /[\d]+/.exec(e.message) + const errorMessageList = /\d+/.exec(e.message) const position = errorMessageList && parseInt(errorMessageList[0], 10) const msg = position ? `, invalid JSON syntax found at line ${position}` diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index cbe3ff26f3c5a7..0c4aa2bdd7d49b 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -55,7 +55,7 @@ export const browserExternalId = '__vite-browser-external' // special id for packages that are optional peer deps export const optionalPeerDepId = '__vite-optional-peer-dep' -const nodeModulesInPathRE = /(^|\/)node_modules\// +const nodeModulesInPathRE = /(?:^|\/)node_modules\// const isDebug = process.env.DEBUG const debug = createDebugger('vite:resolve-details', { diff --git a/packages/vite/src/node/plugins/splitVendorChunk.ts b/packages/vite/src/node/plugins/splitVendorChunk.ts index 335a11bd6c3473..fcfed8b06ea91b 100644 --- a/packages/vite/src/node/plugins/splitVendorChunk.ts +++ b/packages/vite/src/node/plugins/splitVendorChunk.ts @@ -8,7 +8,7 @@ import type { UserConfig } from '../../node' import type { Plugin } from '../plugin' // This file will be built for both ESM and CJS. Avoid relying on other modules as possible. -const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)` +const cssLangs = `\\.(?:css|less|sass|scss|styl|stylus|pcss|postcss)(?:$|\\?)` const cssLangRE = new RegExp(cssLangs) export const isCSSRequest = (request: string): boolean => cssLangRE.test(request) diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index a2c4e22038b672..61b9b4750ac06f 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -41,7 +41,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType { // need to find in comment code const workerOptString = raw .substring(commaIndex + 1, endIndex) - .replace(/}[^]*,/g, '}') // strip trailing comma for parsing + .replace(/\}[\s\S]*,/g, '}') // strip trailing comma for parsing const hasViteIgnore = ignoreFlagRE.test(workerOptString) if (hasViteIgnore) { @@ -91,11 +91,11 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const cleanString = stripLiteral(code) const workerImportMetaUrlRE = - /\bnew\s+(Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/g + /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/g let match: RegExpExecArray | null while ((match = workerImportMetaUrlRE.exec(cleanString))) { - const { 0: allExp, 2: exp, 3: emptyUrl, index } = match + const { 0: allExp, 1: exp, 2: emptyUrl, index } = match const urlIndex = allExp.indexOf(exp) + index const urlStart = cleanString.indexOf(emptyUrl, index) diff --git a/packages/vite/src/node/server/sourcemap.ts b/packages/vite/src/node/server/sourcemap.ts index a6f344ea56292c..774d74dc8bb398 100644 --- a/packages/vite/src/node/server/sourcemap.ts +++ b/packages/vite/src/node/server/sourcemap.ts @@ -12,7 +12,7 @@ const debug = createDebugger('vite:sourcemap', { // Virtual modules should be prefixed with a null byte to avoid a // false positive "missing source" warning. We also check for certain // prefixes used for special handling in esbuildDepPlugin. -const virtualSourceRE = /^(\0|dep:|browser-external:)/ +const virtualSourceRE = /^(?:\0|dep:|browser-external:)/ interface SourceMapLike { sources: string[] diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 7c48c84db48743..9281c8ebebc9b8 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -83,7 +83,7 @@ export function cjsSsrResolveExternals( } const CJS_CONTENT_RE = - /\bmodule\.exports\b|\bexports[.\[]|\brequire\s*\(|\bObject\.(defineProperty|defineProperties|assign)\s*\(\s*exports\b/ + /\bmodule\.exports\b|\bexports[.[]|\brequire\s*\(|\bObject\.(?:defineProperty|defineProperties|assign)\s*\(\s*exports\b/ // TODO: use import() const _require = createRequire(import.meta.url) diff --git a/packages/vite/src/node/ssr/ssrStacktrace.ts b/packages/vite/src/node/ssr/ssrStacktrace.ts index 5ffd7d2e186545..2d71fada82065f 100644 --- a/packages/vite/src/node/ssr/ssrStacktrace.ts +++ b/packages/vite/src/node/ssr/ssrStacktrace.ts @@ -20,7 +20,7 @@ export function ssrRewriteStacktrace( .split('\n') .map((line) => { return line.replace( - /^ {4}at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?)\)?/, + /^ {4}at (?:(\S.*?)\s\()?(.+?):(\d+)(?::(\d+))?\)?/, (input, varName, url, line, column) => { if (!url) return input @@ -42,11 +42,12 @@ export function ssrRewriteStacktrace( return input } + const trimedVarName = varName.trim() const source = `${pos.source}:${pos.line}:${pos.column}` - if (!varName || varName === 'eval') { + if (!trimedVarName || trimedVarName === 'eval') { return ` at ${source}` } else { - return ` at ${varName} (${source})` + return ` at ${trimedVarName} (${source})` } } ) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index f38cd9ba41dd92..8123ec08c68dea 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -76,8 +76,8 @@ export function unwrapId(id: string): string { export const flattenId = (id: string): string => id - .replace(/[\/:]/g, '_') - .replace(/[\.]/g, '__') + .replace(/[/:]/g, '_') + .replace(/\./g, '__') .replace(/(\s*>\s*)/g, '___') export const normalizeId = (id: string): string => @@ -276,7 +276,7 @@ export const isDataUrl = (url: string): boolean => dataUrlRE.test(url) export const virtualModuleRE = /^virtual-module:.*/ export const virtualModulePrefix = 'virtual-module:' -const knownJsSrcRE = /\.((j|t)sx?|m[jt]s|vue|marko|svelte|astro|imba)($|\?)/ +const knownJsSrcRE = /\.(?:[jt]sx?|m[jt]s|vue|marko|svelte|astro|imba)(?:$|\?)/ export const isJSRequest = (url: string): boolean => { url = cleanUrl(url) if (knownJsSrcRE.test(url)) { @@ -288,8 +288,8 @@ export const isJSRequest = (url: string): boolean => { return false } -const knownTsRE = /\.(ts|mts|cts|tsx)$/ -const knownTsOutputRE = /\.(js|mjs|cjs|jsx)$/ +const knownTsRE = /\.(?:ts|mts|cts|tsx)$/ +const knownTsOutputRE = /\.(?:js|mjs|cjs|jsx)$/ export const isTsRequest = (url: string): boolean => knownTsRE.test(url) export const isPossibleTsOutput = (url: string): boolean => knownTsOutputRE.test(cleanUrl(url)) @@ -311,7 +311,7 @@ const internalPrefixes = [ ENV_PUBLIC_PATH ] const InternalPrefixRE = new RegExp(`^(?:${internalPrefixes.join('|')})`) -const trailingSeparatorRE = /[\?&]$/ +const trailingSeparatorRE = /[?&]$/ export const isImportRequest = (url: string): boolean => importQueryRE.test(url) export const isInternalRequest = (url: string): boolean => InternalPrefixRE.test(url) @@ -684,7 +684,7 @@ function splitSrcSet(srcs: string) { const parts: string[] = [] // There could be a ',' inside of url(data:...), linear-gradient(...) or "data:..." const cleanedSrcs = srcs.replace( - /(?:url|image|gradient|cross-fade)\([^\)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'/g, + /(?:url|image|gradient|cross-fade)\([^)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'/g, blankReplacer ) let startIndex = 0 @@ -908,9 +908,9 @@ export function toUpperCaseDriveLetter(pathName: string): string { } // Taken from https://stackoverflow.com/a/36328890 -export const multilineCommentsRE = /\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//gm +export const multilineCommentsRE = /\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g export const singlelineCommentsRE = /\/\/.*/g -export const requestQuerySplitRE = /\?(?!.*[\/|\}])/ +export const requestQuerySplitRE = /\?(?!.*[/|}])/ // @ts-expect-error export const usingDynamicImport = typeof jest === 'undefined' diff --git a/playground/cli-module/__tests__/serve.ts b/playground/cli-module/__tests__/serve.ts index 40921d17a255b4..545d3732844b6b 100644 --- a/playground/cli-module/__tests__/serve.ts +++ b/playground/cli-module/__tests__/serve.ts @@ -120,7 +120,7 @@ async function startedOnPort(serverProcess, port, timeout) { // hack, console output may contain color code gibberish // skip gibberish between localhost: and port number const match = str.match( - /(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/ + /(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):).*(\d{4})/ ) if (match) { const startedPort = parseInt(match[2], 10) diff --git a/playground/cli/__tests__/serve.ts b/playground/cli/__tests__/serve.ts index f0d34219be68fa..ec4f80a79d4593 100644 --- a/playground/cli/__tests__/serve.ts +++ b/playground/cli/__tests__/serve.ts @@ -120,7 +120,7 @@ async function startedOnPort(serverProcess, port, timeout) { // hack, console output may contain color code gibberish // skip gibberish between localhost: and port number const match = str.match( - /(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/ + /(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):).*(\d{4})/ ) if (match) { const startedPort = parseInt(match[2], 10) diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts index e2f41d893021c3..75be2303f9ddc6 100644 --- a/playground/css/__tests__/css.spec.ts +++ b/playground/css/__tests__/css.spec.ts @@ -18,7 +18,7 @@ import { // in later assertions to ensure CSS HMR doesn't reload the page test('imported css', async () => { const css = await page.textContent('.imported-css') - expect(css).toMatch(/\.imported ?{/) + expect(css).toMatch(/\.imported ?\{/) if (isBuild) { expect(css.trim()).not.toContain('\n') // check minified } diff --git a/playground/lib/__tests__/lib.spec.ts b/playground/lib/__tests__/lib.spec.ts index 0f9b837aab2b3f..b46ed96c69ee57 100644 --- a/playground/lib/__tests__/lib.spec.ts +++ b/playground/lib/__tests__/lib.spec.ts @@ -24,7 +24,7 @@ describe.runIf(isBuild)('build', () => { expect(await page.textContent('.iife')).toBe('It works') const code = readFile('dist/my-lib-custom-filename.iife.js') // esbuild helpers are injected inside of the IIFE wrapper - expect(code).toMatch(/^var MyLib=function\(\){"use strict";/) + expect(code).toMatch(/^var MyLib=function\(\)\{"use strict";/) }) test('Library mode does not include `preload`', async () => { diff --git a/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts b/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts index 37f9290250249e..3ccc857c1f8a5b 100644 --- a/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts +++ b/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts @@ -14,7 +14,7 @@ describe.runIf(isBuild)('build', () => { test('should throw an error on build', () => { expect(serveError).toBeTruthy() expect(serveError.message).toMatch( - /^parsing .* failed: SyntaxError: Unexpected token } in JSON at position \d+$/ + /^parsing .* failed: SyntaxError: Unexpected token \} in JSON at position \d+$/ ) clearServeError() // got expected error, null it here so testsuite does not fail from rethrow in afterAll }) @@ -46,7 +46,7 @@ describe.runIf(isServe)('server', () => { }) // use regex with variable filename and position values because they are different on win expect(message).toMatch( - /^parsing .* failed: SyntaxError: Unexpected token } in JSON at position \d+$/ + /^parsing .* failed: SyntaxError: Unexpected token \} in JSON at position \d+$/ ) }) diff --git a/playground/vue-server-origin/__tests__/vue-server-origin.spec.ts b/playground/vue-server-origin/__tests__/vue-server-origin.spec.ts index 777ee09bf33855..ef118ef712722a 100644 --- a/playground/vue-server-origin/__tests__/vue-server-origin.spec.ts +++ b/playground/vue-server-origin/__tests__/vue-server-origin.spec.ts @@ -3,7 +3,7 @@ import { isBuild, page } from '~utils' test('should render', async () => { const expected = isBuild - ? /assets\/asset\.[0-9a-f]+\.png/ + ? /assets\/asset\.[\da-f]+\.png/ : 'http://localhost/server-origin/test/assets/asset.png' expect(await page.getAttribute('img', 'src')).toMatch(expected) diff --git a/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts b/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts index ef5bea0090cb63..e1187f374673e0 100644 --- a/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts +++ b/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts @@ -114,7 +114,7 @@ describe.runIf(isBuild)('build', () => { }) function getSourceMapUrl(code: string): string { - const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const regex = /\/\/[#@]\ssource(?:Mapping)?URL=\s*(\S+)/ const results = regex.exec(code) if (results && results.length >= 2) { diff --git a/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts b/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts index 7d3ed6d266ed19..1e14f4374eec09 100644 --- a/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts +++ b/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts @@ -97,7 +97,7 @@ describe.runIf(isBuild)('build', () => { }) function getSourceMapUrl(code: string): string { - const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const regex = /\/\/[#@]\ssource(?:Mapping)?URL=\s*(\S+)/ const results = regex.exec(code) if (results && results.length >= 2) { diff --git a/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts b/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts index 2ba5d31d44fc1c..78a7417ece379a 100644 --- a/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts +++ b/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts @@ -115,7 +115,7 @@ describe.runIf(isBuild)('build', () => { }) function getSourceMapUrl(code: string): string { - const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const regex = /\/\/[#@]\ssource(?:Mapping)?URL=\s*(\S+)/ const results = regex.exec(code) if (results && results.length >= 2) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2c7d1dfb614f1..e95d077af08539 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ importers: eslint-define-config: ^1.11.0 eslint-plugin-import: ^2.26.0 eslint-plugin-node: ^11.1.0 + eslint-plugin-regexp: ^1.10.0 execa: ^6.1.0 fast-glob: ^3.2.12 fs-extra: ^10.1.0 @@ -96,6 +97,7 @@ importers: eslint-define-config: 1.11.0 eslint-plugin-import: 2.26.0_zql2fvxzk4tmut73fmbaoimchq eslint-plugin-node: 11.1.0_eslint@8.27.0 + eslint-plugin-regexp: 1.10.0_eslint@8.27.0 execa: 6.1.0 fast-glob: 3.2.12 fs-extra: 10.1.0 @@ -3520,6 +3522,11 @@ packages: engines: {node: ^12.20.0 || >=14} dev: true + /comment-parser/1.3.1: + resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} + engines: {node: '>= 12.0.0'} + dev: true + /commenting/1.1.0: resolution: {integrity: sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==} dev: true @@ -4936,6 +4943,23 @@ packages: semver: 6.3.0 dev: true + /eslint-plugin-regexp/1.10.0_eslint@8.27.0: + resolution: {integrity: sha512-9HPSmKlxc19csfpCBEPRMuGme767uypOzWxP9zplRXC7RnBluB1Xk8ggcuB4ZV0ZcpJA3znCKLeyUrRyk2YlQg==} + engines: {node: ^12 || >=14} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + comment-parser: 1.3.1 + eslint: 8.27.0 + eslint-utils: 3.0.0_eslint@8.27.0 + grapheme-splitter: 1.0.4 + jsdoctypeparser: 9.0.0 + refa: 0.9.1 + regexp-ast-analysis: 0.5.1 + regexpp: 3.2.0 + scslre: 0.1.6 + dev: true + /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -6029,6 +6053,12 @@ packages: argparse: 2.0.1 dev: true + /jsdoctypeparser/9.0.0: + resolution: {integrity: sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==} + engines: {node: '>=10'} + hasBin: true + dev: true + /jsesc/2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -7681,9 +7711,29 @@ packages: redis-errors: 1.2.0 dev: true + /refa/0.9.1: + resolution: {integrity: sha512-egU8LgFq2VXlAfUi8Jcbr5X38wEOadMFf8tCbshgcpVCYlE7k84pJOSlnvXF+muDB4igkdVMq7Z/kiNPqDT9TA==} + dependencies: + regexpp: 3.2.0 + dev: true + /regenerator-runtime/0.13.10: resolution: {integrity: sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==} + /regexp-ast-analysis/0.2.4: + resolution: {integrity: sha512-8L7kOZQaKPxKKAwGuUZxTQtlO3WZ+tiXy4s6G6PKL6trbOXcZoumwC3AOHHFtI/xoSbNxt7jgLvCnP1UADLWqg==} + dependencies: + refa: 0.9.1 + regexpp: 3.2.0 + dev: true + + /regexp-ast-analysis/0.5.1: + resolution: {integrity: sha512-Ca/g9gaTNuMewLuu+mBIq4vCrGRSO8AE9bP32NMQjJ/wBTdWq0g96qLkBb0NbGwEbp7S/q+NQF3o7veeuRfg0g==} + dependencies: + refa: 0.9.1 + regexpp: 3.2.0 + dev: true + /regexp.prototype.flags/1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} engines: {node: '>= 0.4'} @@ -7850,6 +7900,14 @@ packages: loose-envify: 1.4.0 dev: false + /scslre/0.1.6: + resolution: {integrity: sha512-JORxVRlQTfjvlOAaiQKebgFElyAm5/W8b50lgaZ0OkEnKnagJW2ufDh3xRfU75UD9z3FGIu1gL1IyR3Poa6Qmw==} + dependencies: + refa: 0.9.1 + regexp-ast-analysis: 0.2.4 + regexpp: 3.2.0 + dev: true + /scule/0.3.2: resolution: {integrity: sha512-zIvPdjOH8fv8CgrPT5eqtxHQXmPNnV/vHJYffZhE43KZkvULvpCTvOt1HPlFaCZx287INL9qaqrZg34e8NgI4g==} dev: true diff --git a/scripts/rollupLicensePlugin.mjs b/scripts/rollupLicensePlugin.mjs index 1a3e242eae0160..fedbd34b4fd660 100644 --- a/scripts/rollupLicensePlugin.mjs +++ b/scripts/rollupLicensePlugin.mjs @@ -90,7 +90,7 @@ function licensePlugin(licenseFilePath, licenseTitle, packageName) { '\n' + licenseText .trim() - .replace(/(\r\n|\r)/gm, '\n') + .replace(/(\r\n|\r)/g, '\n') .split('\n') .map((line) => `> ${line}`) .join('\n') + diff --git a/scripts/verifyCommit.ts b/scripts/verifyCommit.ts index 848295ddf72925..4d149d975b80ed 100644 --- a/scripts/verifyCommit.ts +++ b/scripts/verifyCommit.ts @@ -9,7 +9,7 @@ const msg = readFileSync(msgPath, 'utf-8').trim() const releaseRE = /^v\d/ const commitRE = - /^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/ + /^(?:revert: )?(?:feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(?:\(.+\))?: .{1,50}/ if (!releaseRE.test(msg) && !commitRE.test(msg)) { console.log()