diff --git a/eslint.config.mjs b/eslint.config.mjs index 8dae7749..b73cc2b8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,9 @@ // @ts-check import base from '@metamask/eslint-config'; -import jest from '@metamask/eslint-config-jest'; import nodejs from '@metamask/eslint-config-nodejs'; import typescript from '@metamask/eslint-config-typescript'; +import vitest from '@metamask/eslint-config-vitest'; // eslint-disable-next-line import-x/no-unresolved import tseslint from 'typescript-eslint'; @@ -29,15 +29,7 @@ const config = tseslint.config( { files: ['**/*.test.mjs'], - extends: jest, - rules: { - 'no-shadow': [ - 'error', - { - allow: ['describe', 'it', 'expect'], - }, - ], - }, + extends: vitest, }, { diff --git a/package.json b/package.json index e9cbdb83..19b3ae1a 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,9 @@ "@lavamoat/allow-scripts": "^3.0.4", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^13.0.0", - "@metamask/eslint-config-jest": "workspace:^", "@metamask/eslint-config-nodejs": "^13.0.0", "@metamask/eslint-config-typescript": "workspace:^", + "@metamask/eslint-config-vitest": "workspace:^", "@metamask/utils": "^9.1.0", "@types/eslint__js": "^8.42.3", "@types/node": "^22.5.5", diff --git a/packages/vitest/CHANGELOG.md b/packages/vitest/CHANGELOG.md new file mode 100644 index 00000000..36a58ce9 --- /dev/null +++ b/packages/vitest/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/eslint-config/ diff --git a/packages/vitest/LICENSE b/packages/vitest/LICENSE new file mode 100644 index 00000000..37484ffd --- /dev/null +++ b/packages/vitest/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 MetaMask + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +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. diff --git a/packages/vitest/README.md b/packages/vitest/README.md new file mode 100644 index 00000000..5d7b6cd2 --- /dev/null +++ b/packages/vitest/README.md @@ -0,0 +1,42 @@ +# `@metamask/eslint-config-vitest` + +MetaMask's [Vitest](https://vitest.dev/) ESLint configuration. + +## Usage + +```bash +yarn add --dev \ + @metamask/eslint-config@^13.0.0 \ + @metamask/eslint-config-vitest@^0.0.0 \ + @vitest/eslint-plugin@^1.1.4 \ + eslint@^9.11.0 \ + eslint-config-prettier@^9.1.0 \ + eslint-plugin-import-x@^4.3.0 \ + eslint-plugin-jsdoc@^50.2.4 \ + eslint-plugin-prettier@^5.2.1 \ + eslint-plugin-promise@^7.1.0 \ + prettier@^3.3.3 +``` + +The order in which you extend ESLint rules matters. +The `@metamask/*` eslint configs should be added to the config array _last_, +with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any +order thereafter. + +```js +import base from '@metamask/eslint-config'; +import vitest from '@metamask/eslint-config-vitest'; + +const config = { + // Any custom shared config should be added here. + // ... + + // This should be added last unless you know what you're doing. + ...base, + ...vitest, + + { + // Your overrides here. + } +}; +``` diff --git a/packages/vitest/package.json b/packages/vitest/package.json new file mode 100644 index 00000000..a92e8dbf --- /dev/null +++ b/packages/vitest/package.json @@ -0,0 +1,64 @@ +{ + "name": "@metamask/eslint-config-vitest", + "version": "0.0.0", + "description": "Shareable MetaMask ESLint config for Vitest.", + "homepage": "https://github.com/MetaMask/eslint-config#readme", + "bugs": { + "url": "https://github.com/MetaMask/eslint-config/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/eslint-config.git" + }, + "license": "MIT", + "type": "module", + "exports": { + ".": { + "import": { + "types": "./src/index.d.mts", + "default": "./src/index.mjs" + } + } + }, + "main": "./src/index.mjs", + "types": "./src/index.d.mts", + "files": [ + "src/", + "!src/**/*.test.mjs" + ], + "scripts": { + "lint:changelog": "auto-changelog validate", + "publish": "npm publish", + "test": "eslint ." + }, + "dependencies": { + "@eslint/js": "^9.11.0", + "globals": "^15.9.0" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "@metamask/auto-changelog": "^3.4.4", + "@metamask/eslint-config": "^13.0.0", + "@vitest/eslint-plugin": "^1.1.4", + "eslint": "^9.11.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import-x": "^4.3.0", + "eslint-plugin-jsdoc": "^50.2.4", + "eslint-plugin-prettier": "^5.2.1", + "jest": "^29.7.0", + "prettier": "^3.3.3", + "vitest": "^2.1.1" + }, + "peerDependencies": { + "@metamask/eslint-config": "^13.0.0", + "@vitest/eslint-plugin": "^1.1.4", + "eslint": "^9.11.0" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/vitest/rules-snapshot.json b/packages/vitest/rules-snapshot.json new file mode 100644 index 00000000..b31a4ef9 --- /dev/null +++ b/packages/vitest/rules-snapshot.json @@ -0,0 +1,42 @@ +{ + "vitest/consistent-test-it": ["error", { "fn": "it" }], + "vitest/expect-expect": "error", + "vitest/no-alias-methods": "error", + "vitest/no-commented-out-tests": "error", + "vitest/no-conditional-expect": "error", + "vitest/no-conditional-in-test": "error", + "vitest/no-disabled-tests": "error", + "vitest/no-duplicate-hooks": "error", + "vitest/no-focused-tests": "error", + "vitest/no-identical-title": "error", + "vitest/no-import-node-test": "error", + "vitest/no-interpolation-in-snapshots": "error", + "vitest/no-mocks-import": "error", + "vitest/no-restricted-matchers": [ + "error", + { + "resolves": "Use `expect(await promise)` instead.", + "toBeFalsy": "Avoid `toBeFalsy`.", + "toBeTruthy": "Avoid `toBeTruthy`.", + "toMatchSnapshot": "Use `toMatchInlineSnapshot()` instead.", + "toThrowErrorMatchingSnapshot": "Use `toThrowErrorMatchingInlineSnapshot()` instead." + } + ], + "vitest/no-standalone-expect": "error", + "vitest/no-test-prefixes": "error", + "vitest/no-test-return-statement": "error", + "vitest/prefer-hooks-on-top": "error", + "vitest/prefer-lowercase-title": ["error", { "ignore": ["describe"] }], + "vitest/prefer-spy-on": "error", + "vitest/prefer-strict-equal": "error", + "vitest/prefer-to-be": "error", + "vitest/prefer-to-contain": "error", + "vitest/prefer-to-have-length": "error", + "vitest/prefer-todo": "error", + "vitest/require-local-test-context-for-concurrent-snapshots": "error", + "vitest/require-to-throw-message": "error", + "vitest/require-top-level-describe": "error", + "vitest/valid-describe-callback": "error", + "vitest/valid-expect": ["error", { "alwaysAwait": true }], + "vitest/valid-title": "error" +} diff --git a/packages/vitest/src/index.d.mts b/packages/vitest/src/index.d.mts new file mode 100644 index 00000000..71bd2ed3 --- /dev/null +++ b/packages/vitest/src/index.d.mts @@ -0,0 +1,6 @@ +declare module '@metamask/eslint-config-vitest' { + import type { Linter } from 'eslint'; + + const config: Linter.Config[]; + export default config; +} diff --git a/packages/vitest/src/index.mjs b/packages/vitest/src/index.mjs new file mode 100644 index 00000000..03b6c537 --- /dev/null +++ b/packages/vitest/src/index.mjs @@ -0,0 +1,73 @@ +import vitest from '@vitest/eslint-plugin'; + +/** + * @type {import('eslint').Linter.Config[]} + */ +const config = [ + vitest.configs.recommended, + + { + name: '@metamask/eslint-config-vitest', + + files: [ + '**/*.test.js', + '**/*.spec.js', + '**/*.test.mjs', + '**/*.spec.mjs', + '**/*.test.cjs', + '**/*.spec.cjs', + '**/*.test.ts', + '**/*.spec.ts', + '**/*.test.tsx', + '**/*.spec.tsx', + '**/*.test.mts', + '**/*.spec.mts', + '**/*.test.cts', + '**/*.spec.cts', + '**/*.test.mtsx', + '**/*.spec.mtsx', + '**/*.test.ctsx', + '**/*.spec.ctsx', + ], + + rules: { + 'vitest/consistent-test-it': ['error', { fn: 'it' }], + 'vitest/no-alias-methods': 'error', + 'vitest/no-commented-out-tests': 'error', + 'vitest/no-conditional-expect': 'error', + 'vitest/no-conditional-in-test': 'error', + 'vitest/no-disabled-tests': 'error', + 'vitest/no-duplicate-hooks': 'error', + 'vitest/no-focused-tests': 'error', + 'vitest/no-interpolation-in-snapshots': 'error', + 'vitest/no-mocks-import': 'error', + 'vitest/no-standalone-expect': 'error', + 'vitest/no-test-prefixes': 'error', + 'vitest/no-test-return-statement': 'error', + 'vitest/prefer-hooks-on-top': 'error', + 'vitest/prefer-lowercase-title': ['error', { ignore: ['describe'] }], + 'vitest/prefer-spy-on': 'error', + 'vitest/prefer-strict-equal': 'error', + 'vitest/prefer-to-be': 'error', + 'vitest/prefer-to-contain': 'error', + 'vitest/prefer-to-have-length': 'error', + 'vitest/prefer-todo': 'error', + 'vitest/require-to-throw-message': 'error', + 'vitest/require-top-level-describe': 'error', + 'vitest/valid-expect': ['error', { alwaysAwait: true }], + 'vitest/no-restricted-matchers': [ + 'error', + { + resolves: 'Use `expect(await promise)` instead.', + toBeFalsy: 'Avoid `toBeFalsy`.', + toBeTruthy: 'Avoid `toBeTruthy`.', + toMatchSnapshot: 'Use `toMatchInlineSnapshot()` instead.', + toThrowErrorMatchingSnapshot: + 'Use `toThrowErrorMatchingInlineSnapshot()` instead.', + }, + ], + }, + }, +]; + +export default config; diff --git a/packages/vitest/src/index.test.mjs b/packages/vitest/src/index.test.mjs new file mode 100644 index 00000000..908ffbef --- /dev/null +++ b/packages/vitest/src/index.test.mjs @@ -0,0 +1,18 @@ +import { ESLint } from 'eslint'; +import { describe, it, expect } from 'vitest'; + +import config from '.'; + +describe('index', () => { + it('is a valid ESLint config', async () => { + const api = new ESLint({ + baseConfig: config, + }); + + const result = await api.lintText(`export {};\n`); + + expect(result[0].messages).toStrictEqual([]); + expect(result[0].warningCount).toBe(0); + expect(result[0].errorCount).toBe(0); + }); +}); diff --git a/yarn.lock b/yarn.lock index f838965a..3d5aa686 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1106,7 +1106,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eslint-config-jest@workspace:^, @metamask/eslint-config-jest@workspace:packages/jest": +"@metamask/eslint-config-jest@workspace:packages/jest": version: 0.0.0-use.local resolution: "@metamask/eslint-config-jest@workspace:packages/jest" dependencies: @@ -1209,6 +1209,31 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eslint-config-vitest@workspace:^, @metamask/eslint-config-vitest@workspace:packages/vitest": + version: 0.0.0-use.local + resolution: "@metamask/eslint-config-vitest@workspace:packages/vitest" + dependencies: + "@eslint/js": ^9.11.0 + "@jest/globals": ^29.7.0 + "@metamask/auto-changelog": ^3.4.4 + "@metamask/eslint-config": ^13.0.0 + "@vitest/eslint-plugin": ^1.1.4 + eslint: ^9.11.0 + eslint-config-prettier: ^9.1.0 + eslint-plugin-import-x: ^4.3.0 + eslint-plugin-jsdoc: ^50.2.4 + eslint-plugin-prettier: ^5.2.1 + globals: ^15.9.0 + jest: ^29.7.0 + prettier: ^3.3.3 + vitest: ^2.1.1 + peerDependencies: + "@metamask/eslint-config": ^13.0.0 + "@vitest/eslint-plugin": ^1.1.4 + eslint: ^9.11.0 + languageName: unknown + linkType: soft + "@metamask/eslint-config@^13.0.0, @metamask/eslint-config@workspace:packages/base": version: 0.0.0-use.local resolution: "@metamask/eslint-config@workspace:packages/base" @@ -1877,6 +1902,25 @@ __metadata: languageName: node linkType: hard +"@vitest/eslint-plugin@npm:^1.1.4": + version: 1.1.4 + resolution: "@vitest/eslint-plugin@npm:1.1.4" + peerDependencies: + "@typescript-eslint/utils": ">= 8.0" + eslint: ">= 8.57.0" + typescript: ">= 5.0.0" + vitest: "*" + peerDependenciesMeta: + "@typescript-eslint/utils": + optional: true + typescript: + optional: true + vitest: + optional: true + checksum: 270ecd5e01ec42368cb05179ecf5d3a14350aa166143cea86416d1a03983568dff7d8437440fe51df60dadb8f06c4f4ad9d67404987fd22e76259ebc494e19d7 + languageName: node + linkType: hard + "@vitest/expect@npm:2.1.1": version: 2.1.1 resolution: "@vitest/expect@npm:2.1.1" @@ -5643,9 +5687,9 @@ __metadata: "@lavamoat/allow-scripts": ^3.0.4 "@metamask/auto-changelog": ^3.4.4 "@metamask/eslint-config": ^13.0.0 - "@metamask/eslint-config-jest": "workspace:^" "@metamask/eslint-config-nodejs": ^13.0.0 "@metamask/eslint-config-typescript": "workspace:^" + "@metamask/eslint-config-vitest": "workspace:^" "@metamask/utils": ^9.1.0 "@types/eslint__js": ^8.42.3 "@types/node": ^22.5.5