diff --git a/eslint.config.js b/eslint.config.js index dad58754fa86f8..226323ecc756a0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,4 +1,5 @@ // @ts-check +import e18e from '@e18e/eslint-plugin' import eslint from '@eslint/js' import pluginImportX from 'eslint-plugin-import-x' import pluginN from 'eslint-plugin-n' @@ -53,6 +54,7 @@ export default defineConfig( plugins: { n: pluginN, 'import-x': pluginImportX, + e18e, }, rules: { 'n/no-exports-assign': 'error', @@ -154,6 +156,12 @@ export default defineConfig( 'regexp/prefer-regexp-test': 'error', // in some cases using explicit letter-casing is more performant than the `i` flag 'regexp/use-ignore-case': 'off', + 'e18e/prefer-array-at': 'error', + 'e18e/prefer-array-fill': 'error', + 'e18e/prefer-includes': 'error', + 'e18e/prefer-array-to-reversed': 'error', + 'e18e/prefer-object-has-own': 'error', + 'e18e/prefer-nullish-coalescing': 'error', }, }, { diff --git a/package.json b/package.json index ea55d9251fafac..c21cddf248df6e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "merge-changelog": "tsx scripts/mergeChangelog.ts" }, "devDependencies": { + "@e18e/eslint-plugin": "^0.8.0", "@eslint/js": "^9.39.5", "@type-challenges/utils": "^0.1.1", "@types/babel__core": "^7.20.5", diff --git a/packages/vite/rolldown.config.ts b/packages/vite/rolldown.config.ts index 73245d0d761cc1..a3762b08e710e6 100644 --- a/packages/vite/rolldown.config.ts +++ b/packages/vite/rolldown.config.ts @@ -212,9 +212,10 @@ function externalizeDepsInWatchPlugin(): Plugin { options.external ||= [] if (!Array.isArray(options.external)) throw new Error('external must be an array') - options.external = options.external.concat( - Object.keys(pkg.devDependencies), - ) + options.external = [ + ...options.external, + ...Object.keys(pkg.devDependencies), + ] } }, } diff --git a/packages/vite/rolldown.dts.config.ts b/packages/vite/rolldown.dts.config.ts index 730cd2887213de..4bc7f35636b47c 100644 --- a/packages/vite/rolldown.dts.config.ts +++ b/packages/vite/rolldown.dts.config.ts @@ -417,7 +417,7 @@ function escapeRegex(str: string): string { } function unique(arr: T[]): T[] { - return Array.from(new Set(arr)) + return [...new Set(arr)] } const postcssLoadConfigDeepImport = diff --git a/packages/vite/rollupLicensePlugin.ts b/packages/vite/rollupLicensePlugin.ts index da0472da777b1e..19c962ba81709f 100644 --- a/packages/vite/rollupLicensePlugin.ts +++ b/packages/vite/rollupLicensePlugin.ts @@ -170,7 +170,7 @@ function getDependencyInformation(dep: Dependency): DependencyInfo { } } if (names.size > 0) { - info.names = Array.from(names).join(', ') + info.names = [...names].join(', ') } if (repository) { diff --git a/packages/vite/src/client/bundledDevHmrClient.ts b/packages/vite/src/client/bundledDevHmrClient.ts index a8cc428234a818..60d11e3addfa5e 100644 --- a/packages/vite/src/client/bundledDevHmrClient.ts +++ b/packages/vite/src/client/bundledDevHmrClient.ts @@ -209,7 +209,7 @@ export class BundledDevHMRClient extends HMRClient { // Combined : [E, B, C, ACCEPTED, D, E] const importChain = [ importer, - ...[...currentChain].reverse(), + ...currentChain.toReversed(), ...nodeChain.slice(importerIndex, -1).reverse(), ] this.logger.debug( @@ -223,7 +223,7 @@ export class BundledDevHMRClient extends HMRClient { const result = this.isNodeWithinCircularImports( importer, nodeChain, - currentChain.concat(importer), + [...currentChain, importer], traversedModules, ) if (result) return result diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index b975b0a436c0f9..1ff49a6fc3a35a 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -171,7 +171,7 @@ export class ModuleRunner { const meta = mod.meta! const moduleId = meta.id - const importee = callstack[callstack.length - 1] + const importee = callstack.at(-1) if (importee) mod.importers.add(importee) diff --git a/packages/vite/src/module-runner/utils.ts b/packages/vite/src/module-runner/utils.ts index accf117f060f6d..1fbfa95e992954 100644 --- a/packages/vite/src/module-runner/utils.ts +++ b/packages/vite/src/module-runner/utils.ts @@ -50,7 +50,7 @@ export function posixPathToFileHref(posixPath: string): string { if ( (filePathLast === CHAR_FORWARD_SLASH || (isWindows && filePathLast === CHAR_BACKWARD_SLASH)) && - resolved[resolved.length - 1] !== '/' + resolved.at(-1) !== '/' ) resolved += '/' diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 5269ef9b828db6..dd4e4d6681df14 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -101,7 +101,7 @@ export const stopProfiler = ( const filterDuplicateOptions = (options: T) => { for (const [key, value] of Object.entries(options)) { if (Array.isArray(value)) { - options[key as keyof T] = value[value.length - 1] + options[key as keyof T] = value.at(-1) } } } diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 4cf7452ad9a4be..d93f191948ac15 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -328,8 +328,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin[] { if (!url) { const rawUrl = code.slice(start, end) if ( - (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`) || - (rawUrl[0] === '`' && rawUrl[rawUrl.length - 1] === '`') + (rawUrl[0] === `"` && rawUrl.at(-1) === `"`) || + (rawUrl[0] === '`' && rawUrl.at(-1) === '`') ) url = rawUrl.slice(1, -1) } @@ -429,8 +429,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin[] { if (!url) { const rawUrl = code.slice(start, end) if ( - (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`) || - (rawUrl[0] === '`' && rawUrl[rawUrl.length - 1] === '`') + (rawUrl[0] === `"` && rawUrl.at(-1) === `"`) || + (rawUrl[0] === '`' && rawUrl.at(-1) === '`') ) url = rawUrl.slice(1, -1) } diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index caa21b81a528ae..36f857bfbed846 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -262,7 +262,7 @@ export async function parseImportGlob( // skip invalid js code return [] } - const matches = Array.from(cleanCode.matchAll(importGlobRE)) + const matches = [...cleanCode.matchAll(importGlobRE)] const tasks = matches.map(async (match, index) => { const start = match.index! diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 7bd7282aed712b..5d9c11b92435c2 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -270,7 +270,7 @@ export function oxcResolvePlugin( mainFields: options.skipMainField ? options.mainFields - : options.mainFields.concat(['main']), + : [...options.mainFields, 'main'], conditions: options.conditions, externalConditions: options.externalConditions, extensions: options.extensions, diff --git a/packages/vite/src/node/server/bundledDev.ts b/packages/vite/src/node/server/bundledDev.ts index e195cb0c6cf545..3519f188c1044f 100644 --- a/packages/vite/src/node/server/bundledDev.ts +++ b/packages/vite/src/node/server/bundledDev.ts @@ -528,7 +528,7 @@ class Clients { } getAll(): NormalizedHotChannelClient[] { - return Array.from(this.idToClient.values()) + return [...this.idToClient.values()] } delete(client: NormalizedHotChannelClient): string | undefined { diff --git a/packages/vite/src/node/server/environments/runnableEnvironment.ts b/packages/vite/src/node/server/environments/runnableEnvironment.ts index e9acc5d672c1c1..aeee3302c89766 100644 --- a/packages/vite/src/node/server/environments/runnableEnvironment.ts +++ b/packages/vite/src/node/server/environments/runnableEnvironment.ts @@ -12,13 +12,8 @@ export function createRunnableDevEnvironment( config: ResolvedConfig, context: RunnableDevEnvironmentContext = {}, ): RunnableDevEnvironment { - if (context.transport == null) { - context.transport = createServerHotChannel() - } - if (context.hot == null) { - context.hot = true - } - + context.transport ??= createServerHotChannel() + context.hot ??= true return new RunnableDevEnvironment(name, config, context) } diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index cb141922cf63f8..5d5eed16112cd1 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -859,7 +859,7 @@ function propagateUpdate( } for (const importer of node.importers) { - const subChain = currentChain.concat(importer) + const subChain = [...currentChain, importer] if (importer.acceptedHmrDeps.has(node)) { // acceptedHmrDeps has value only for js and css @@ -947,7 +947,7 @@ function isNodeWithinCircularImports( // Combined : [E, B, C, ACCEPTED, D, E] const importChain = [ importer, - ...[...currentChain].reverse(), + ...currentChain.toReversed(), ...nodeChain.slice(importerIndex, -1).reverse(), ] debugHmr( @@ -963,7 +963,7 @@ function isNodeWithinCircularImports( const result = isNodeWithinCircularImports( importer, nodeChain, - currentChain.concat(importer), + [...currentChain, importer], traversedModules, ) if (result) return result diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 6b2ef51875cbb6..58ea8f82e9403a 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -307,9 +307,9 @@ const devHtmlHook: IndexHtmlTransformHook = async ( } else if (isModule && node.childNodes.length) { addInlineModule(node, 'js') } else if (node.childNodes.length) { - const scriptNode = node.childNodes[ - node.childNodes.length - 1 - ] as DefaultTreeAdapterMap['textNode'] + const scriptNode = node.childNodes.at( + -1, + ) as DefaultTreeAdapterMap['textNode'] for (const { url, start, diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index 5828eaf38ff296..0b4a8da79ef3ac 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -178,7 +178,7 @@ export function serveStaticMiddleware( const resolvedPathname = redirectedPathname || pathname let fileUrl = path.resolve(dir, removeLeadingSlash(resolvedPathname)) - if (resolvedPathname.endsWith('/') && fileUrl[fileUrl.length - 1] !== '/') { + if (resolvedPathname.endsWith('/') && fileUrl.at(-1) !== '/') { fileUrl = withTrailingSlash(fileUrl) } if (redirectedPathname) { diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index 18351356348057..0438c667e54cb7 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -636,7 +636,7 @@ class EnvironmentPluginContainer { async close(): Promise { if (this._closed) return this._closed = true - await Promise.allSettled(Array.from(this._processesing)) + await Promise.allSettled([...this._processesing]) const config = this.environment.getTopLevelConfig() let buildEndError: Error | undefined try { diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index a98e3fe7a06296..37d038e78bbb4d 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -461,7 +461,7 @@ export function createWebSocketServer( [isWebSocketServer]: true, get clients() { - return new Set(Array.from(wss.clients).map(getSocketClient)) + return new Set([...wss.clients].map(getSocketClient)) }, } } diff --git a/packages/vite/src/node/shortcuts.ts b/packages/vite/src/node/shortcuts.ts index 9100f24fd1b61c..876012adc5c8b9 100644 --- a/packages/vite/src/node/shortcuts.ts +++ b/packages/vite/src/node/shortcuts.ts @@ -67,11 +67,10 @@ export function bindCLIShortcuts( ) } - const shortcuts = customShortcuts.concat( - (isDev - ? BASE_DEV_SHORTCUTS - : BASE_PREVIEW_SHORTCUTS) as CLIShortcut[], - ) + const shortcuts = [ + ...customShortcuts, + isDev ? BASE_DEV_SHORTCUTS : BASE_PREVIEW_SHORTCUTS, + ] as CLIShortcut[] let actionRunning = false diff --git a/packages/vite/src/node/ssr/fetchModule.ts b/packages/vite/src/node/ssr/fetchModule.ts index f4063d461041ba..8efc98f1056d22 100644 --- a/packages/vite/src/node/ssr/fetchModule.ts +++ b/packages/vite/src/node/ssr/fetchModule.ts @@ -141,9 +141,10 @@ function inlineSourceMap( code = code.replace(OTHER_SOURCE_MAP_REGEXP, '') const sourceMap = startOffset - ? Object.assign({}, map, { + ? { + ...map, mappings: ';'.repeat(startOffset) + map.mappings, - }) + } : map result.code = `${code.trimEnd()}\n//# sourceURL=${ mod.id diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 2207e00f13a426..c84d3308662117 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -296,7 +296,7 @@ export const isJSRequest = (url: string): boolean => { if (knownJsSrcRE.test(url)) { return true } - if (!path.extname(url) && url[url.length - 1] !== '/') { + if (!path.extname(url) && url.at(-1) !== '/') { return true } return false @@ -515,7 +515,7 @@ export function numberToPos(source: string, offset: number | Pos): Pos { const lines = source.slice(0, offset).split(splitRE) return { line: lines.length, - column: lines[lines.length - 1].length, + column: lines.at(-1)!.length, } } @@ -946,7 +946,7 @@ export function combineSourcemaps( } export function unique(arr: T[]): T[] { - return Array.from(new Set(arr)) + return [...new Set(arr)] } /** diff --git a/packages/vite/src/shared/ssrTransform.ts b/packages/vite/src/shared/ssrTransform.ts index 9ce6fe9b4ee9da..2bffcce23fb948 100644 --- a/packages/vite/src/shared/ssrTransform.ts +++ b/packages/vite/src/shared/ssrTransform.ts @@ -35,7 +35,7 @@ export function analyzeImportedModDifference( if (metadata?.importedNames?.length) { const missingBindings = metadata.importedNames.filter((s) => !(s in mod)) if (missingBindings.length) { - const lastBinding = missingBindings[missingBindings.length - 1] + const lastBinding = missingBindings.at(-1) // For invalid named exports only, similar to how Node.js errors for top-level imports. // But since we transform as dynamic imports, we need to emulate the error manually. diff --git a/packages/vite/src/shared/utils.ts b/packages/vite/src/shared/utils.ts index 856aa86f02fcbc..bfa47cbd2c87a7 100644 --- a/packages/vite/src/shared/utils.ts +++ b/packages/vite/src/shared/utils.ts @@ -46,7 +46,7 @@ export function isPrimitive(value: unknown): boolean { } export function withTrailingSlash(path: string): string { - if (path[path.length - 1] !== '/') { + if (path.at(-1) !== '/') { return `${path}/` } return path diff --git a/playground/css/lightningcss-plugins.js b/playground/css/lightningcss-plugins.js index be455a90a27504..699e56c3d02780 100644 --- a/playground/css/lightningcss-plugins.js +++ b/playground/css/lightningcss-plugins.js @@ -138,8 +138,7 @@ export function nestedLikePlugin() { selector[0].type === 'nesting' && selector[1].type === 'type' ) { - const lastParentSelectorComponent = - parentSelector[parentSelector.length - 1] + const lastParentSelectorComponent = parentSelector.at(-1) if ('name' in lastParentSelectorComponent) { const newSelector = [ ...parentSelector.slice(0, -1), diff --git a/playground/optimize-deps/dep-cjs-with-external-deps/index.js b/playground/optimize-deps/dep-cjs-with-external-deps/index.js index cf2a3b9f5f8206..fdb71df77363eb 100644 --- a/playground/optimize-deps/dep-cjs-with-external-deps/index.js +++ b/playground/optimize-deps/dep-cjs-with-external-deps/index.js @@ -10,7 +10,7 @@ if (process.env.NODE_ENV === 'production') { } } const external = require('@vitejs/test-dep-esm-external') -// eslint-disable-next-line no-prototype-builtins -const externalResult = external.hasOwnProperty('foo') ? 'ok' : 'error' + +const externalResult = Object.hasOwn(external, 'foo') ? 'ok' : 'error' const externalDummyNodeBuiltinResult = `${externalDummyNodeBuiltin()} ${externalDummyNodeBuiltin.bar}` module.exports = { externalResult, externalDummyNodeBuiltinResult } diff --git a/playground/ssr/src/circular-dep-init/module-b.js b/playground/ssr/src/circular-dep-init/module-b.js index cb16d7e9be4a30..d12c3b5914467e 100644 --- a/playground/ssr/src/circular-dep-init/module-b.js +++ b/playground/ssr/src/circular-dep-init/module-b.js @@ -1,7 +1,7 @@ import { valueA } from './circular-dep-init' export const valueB = 'circ-dep-init-b' -export const valueAB = valueA.concat(` ${valueB}`) +export const valueAB = `${valueA} ${valueB}` export function getValueAB() { return valueAB diff --git a/playground/test-utils.ts b/playground/test-utils.ts index 67e7395e43efb2..9bc4a5a2c7f7cf 100644 --- a/playground/test-utils.ts +++ b/playground/test-utils.ts @@ -393,7 +393,7 @@ export function extractSourcemap( read?: (filename: string) => Promise, ): any { const lines = content.trim().split('\n') - const lastLine = lines[lines.length - 1] + const lastLine = lines.at(-1) if (read) { const result = fromMapFileComment(lastLine, async (url) => { if (url.startsWith('data:')) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 385614e87d3ad9..b5bb19f43755f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: .: devDependencies: + '@e18e/eslint-plugin': + specifier: ^0.8.0 + version: 0.8.0(eslint@9.39.5) '@eslint/js': specifier: ^9.39.5 version: 9.39.5 @@ -2667,6 +2670,17 @@ packages: '@docsearch/sidepanel-js@4.7.0': resolution: {integrity: sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA==} + '@e18e/eslint-plugin@0.8.0': + resolution: {integrity: sha512-js/TeM+XJyoJ2Zk4if5uTEchv3MEbqugc9gLgq8YdB6Dy+LvwGACpQiAwcul0r4LUl0TvhuzAKUeIUPtGp2cew==} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + oxlint: ^1.72.0 + peerDependenciesMeta: + eslint: + optional: true + oxlint: + optional: true + '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} @@ -6746,6 +6760,9 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + module-replacements@3.2.0: + resolution: {integrity: sha512-zaDcWtW+kLmYGWCMWAyQ8Je333BYmsBMB2X6q5MKBCI697l7OawNaC5TQqfgPVxhdXBA1AjumQjPMcHyJe1f2A==} + moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} @@ -9117,6 +9134,14 @@ snapshots: '@docsearch/sidepanel-js@4.7.0': {} + '@e18e/eslint-plugin@0.8.0(eslint@9.39.5)': + dependencies: + empathic: 2.0.1 + module-replacements: 3.2.0 + semver: 7.8.5 + optionalDependencies: + eslint: 9.39.5(jiti@2.7.0)(ms@2.1.3) + '@emnapi/core@1.11.2': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -12786,6 +12811,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.4 + module-replacements@3.2.0: {} + moment@2.30.1: {} mri@1.2.0: {}