diff --git a/eslint.config.mjs b/eslint.config.mjs index 425c0f00bc61..efdd4b10b045 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -39,6 +39,7 @@ const vitestFiles = [ 'packages/rule-tester/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/type-utils/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/typescript-eslint/tests/**/*.test.{ts,tsx,cts,mts}', + 'packages/typescript-estree/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/utils/tests/**/*.test?(-d).{ts,tsx,cts,mts}', 'packages/visitor-keys/tests/**/*.test.{ts,tsx,cts,mts}', ]; diff --git a/knip.ts b/knip.ts index 4f04d3824607..423ec5bb9f87 100644 --- a/knip.ts +++ b/knip.ts @@ -85,6 +85,11 @@ export default { 'packages/typescript-estree': { entry: ['src/use-at-your-own-risk.ts'], ignore: ['tests/fixtures/**', 'typings/typescript.d.ts'], + + vitest: { + config: ['vitest.config.mts'], + entry: ['tests/lib/**/*.{bench,test,test-d}.?(c|m)ts?(x)'], + }, }, 'packages/utils': { ignore: [ diff --git a/package.json b/package.json index ca4fd5dda007..c16d3cdca471 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "@types/natural-compare": "^1.4.3", "@types/node": "^20.12.5", "@types/semver": "^7.5.8", - "@types/tmp": "^0.2.6", "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "workspace:^", "@typescript-eslint/eslint-plugin-internal": "workspace:^", diff --git a/packages/typescript-estree/jest.config.js b/packages/typescript-estree/jest.config.js deleted file mode 100644 index 990924c7de23..000000000000 --- a/packages/typescript-estree/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; -// @ts-check - -/** @type {import('@jest/types').Config.InitialOptions} */ -module.exports = { - ...require('../../jest.config.base.js'), - testRegex: ['./tests/lib/.*\\.test\\.ts$'], - testPathIgnorePatterns: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE - ? ['/node_modules/', 'project-true'] - : [], -}; diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index ea8c61e666cd..538fb4c14e63 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -49,7 +49,7 @@ "postclean": "rimraf dist/ coverage/", "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", "lint": "npx nx lint", - "test": "jest --runInBand --verbose", + "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", "check-types": "npx nx typecheck" }, "dependencies": { @@ -63,13 +63,12 @@ "ts-api-utils": "^2.0.1" }, "devDependencies": { - "@jest/types": "29.6.3", + "@vitest/coverage-v8": "^3.1.1", "glob": "*", - "jest": "29.7.0", "prettier": "^3.2.5", "rimraf": "*", - "tmp": "*", - "typescript": "*" + "typescript": "*", + "vitest": "^3.1.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" diff --git a/packages/typescript-estree/project.json b/packages/typescript-estree/project.json index 231f8f93a011..f0c767ee7c9a 100644 --- a/packages/typescript-estree/project.json +++ b/packages/typescript-estree/project.json @@ -1,12 +1,16 @@ { "name": "typescript-estree", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "type": "library", - "implicitDependencies": ["types"], + "projectType": "library", + "root": "packages/typescript-estree", + "sourceRoot": "packages/typescript-estree/src", "targets": { "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vite:test" } } } diff --git a/packages/typescript-estree/tests/lib/__snapshots__/convert.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/convert.test.ts.snap index 12af596ec8d9..88b6ecabdead 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/convert.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/convert.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`convert deeplyCopy should convert array of nodes 1`] = ` +exports[`convert > deeplyCopy > should convert array of nodes 1`] = ` { "ambientModuleNames": undefined, "amdDependencies": [], @@ -200,7 +200,7 @@ exports[`convert deeplyCopy should convert array of nodes 1`] = ` } `; -exports[`convert deeplyCopy should convert node correctly 1`] = ` +exports[`convert > deeplyCopy > should convert node correctly 1`] = ` { "body": [ { @@ -498,7 +498,7 @@ exports[`convert deeplyCopy should convert node correctly 1`] = ` } `; -exports[`convert deeplyCopy should convert node with decorators correctly 1`] = ` +exports[`convert > deeplyCopy > should convert node with decorators correctly 1`] = ` { "decorators": [ { @@ -584,7 +584,7 @@ exports[`convert deeplyCopy should convert node with decorators correctly 1`] = } `; -exports[`convert deeplyCopy should convert node with type arguments correctly 1`] = ` +exports[`convert > deeplyCopy > should convert node with type arguments correctly 1`] = ` { "arguments": [], "emitNode": undefined, @@ -687,7 +687,7 @@ exports[`convert deeplyCopy should convert node with type arguments correctly 1` } `; -exports[`convert deeplyCopy should convert node with type parameters correctly 1`] = ` +exports[`convert > deeplyCopy > should convert node with type parameters correctly 1`] = ` { "emitNode": undefined, "id": 0, diff --git a/packages/typescript-estree/tests/lib/__snapshots__/describeFilePath.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/describeFilePath.test.ts.snap index 702bde4b5e74..17a9dcb261ba 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/describeFilePath.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/describeFilePath.test.ts.snap @@ -1,169 +1,169 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./repos/file.ts 1`] = `"/../file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./repos/file.ts 1`] = `"/../file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./repos/other/file.ts 1`] = `"/../other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./repos/other/file.ts 1`] = `"/../other/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./repos/repo/file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./repos/repo/file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ./repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ./repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /repos/repo/file.ts 1`] = `"/repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /repos/repo/file.ts 1`] = `"/repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath /repos/repo/nested/file.ts 1`] = `"/repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath /repos/repo/nested/file.ts 1`] = `"/repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/file.ts 1`] = `"~/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/file.ts 1`] = `"~/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/repos/repo/file.ts 1`] = `"~/repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/repos/repo/file.ts 1`] = `"~/repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ~/repos/repo/nested/file.ts 1`] = `"~/repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ~/repos/repo/nested/file.ts 1`] = `"~/repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath A:/file.ts 1`] = `"A:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath A:/file.ts 1`] = `"A:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath C:/file.ts 1`] = `"C:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath C:/file.ts 1`] = `"C:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath file.ts 1`] = `"file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath file.ts 1`] = `"file.ts"`; -exports[`describeFilePath tsconfigRootDir ./repos/repo filePath nested/file.ts 1`] = `"nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ./repos/repo > filePath nested/file.ts 1`] = `"nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./repos/file.ts 1`] = `"./repos/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./repos/file.ts 1`] = `"./repos/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./repos/other/file.ts 1`] = `"./repos/other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./repos/other/file.ts 1`] = `"./repos/other/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./repos/repo/file.ts 1`] = `"./repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./repos/repo/file.ts 1`] = `"./repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ./repos/repo/nested/file.ts 1`] = `"./repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ./repos/repo/nested/file.ts 1`] = `"./repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /repos/repo/file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /repos/repo/file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath /repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath /repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/file.ts 1`] = `"~/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/file.ts 1`] = `"~/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/repos/repo/file.ts 1`] = `"~/repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/repos/repo/file.ts 1`] = `"~/repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ~/repos/repo/nested/file.ts 1`] = `"~/repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ~/repos/repo/nested/file.ts 1`] = `"~/repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath A:/file.ts 1`] = `"A:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath A:/file.ts 1`] = `"A:/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath C:/file.ts 1`] = `"C:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath C:/file.ts 1`] = `"C:/file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath file.ts 1`] = `"file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath file.ts 1`] = `"file.ts"`; -exports[`describeFilePath tsconfigRootDir /repos/repo filePath nested/file.ts 1`] = `"nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir /repos/repo > filePath nested/file.ts 1`] = `"nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./elsewhere/repo/file.ts 1`] = `"./elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./elsewhere/repo/nested/file.ts 1`] = `"./elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./repos/file.ts 1`] = `"./repos/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./repos/file.ts 1`] = `"./repos/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./repos/other/file.ts 1`] = `"./repos/other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./repos/other/file.ts 1`] = `"./repos/other/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./repos/repo/file.ts 1`] = `"./repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./repos/repo/file.ts 1`] = `"./repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ./repos/repo/nested/file.ts 1`] = `"./repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ./repos/repo/nested/file.ts 1`] = `"./repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /elsewhere/repo/file.ts 1`] = `"/elsewhere/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /elsewhere/repo/nested/file.ts 1`] = `"/elsewhere/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /repos/repo/file.ts 1`] = `"/repos/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /repos/repo/file.ts 1`] = `"/repos/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath /repos/repo/nested/file.ts 1`] = `"/repos/repo/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath /repos/repo/nested/file.ts 1`] = `"/repos/repo/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/file.ts 1`] = `"~/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/file.ts 1`] = `"~/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/nested/file.ts 1`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/nested/file.ts 2`] = `"~/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/other/nested/path/to/file.ts 1`] = `"~/other/nested/path/to/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/other/repo/file.ts 1`] = `"~/other/repo/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/repos/file.ts 1`] = `"~/repos/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/repos/other/file.ts 1`] = `"~/repos/other/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/repos/other/nested/file.ts 1`] = `"~/repos/other/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/repos/repo/file.ts 1`] = `"/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/repos/repo/file.ts 1`] = `"/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ~/repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ~/repos/repo/nested/file.ts 1`] = `"/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath A:/file.ts 1`] = `"A:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath A:/file.ts 1`] = `"A:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath A:/nested/file.ts 1`] = `"A:/nested/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath ABC:/file.ts 1`] = `"ABC:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath C:/file.ts 1`] = `"C:/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath C:/file.ts 1`] = `"C:/file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath file.ts 1`] = `"file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath file.ts 1`] = `"file.ts"`; -exports[`describeFilePath tsconfigRootDir ~/repos/repo filePath nested/file.ts 1`] = `"nested/file.ts"`; +exports[`describeFilePath > tsconfigRootDir ~/repos/repo > filePath nested/file.ts 1`] = `"nested/file.ts"`; diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index c32074f04e33..f7b11ddb5251 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`parseAndGenerateServices isolated parsing should parse .js file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .js file - with JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -297,7 +297,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .js file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .js file - with JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -594,7 +594,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .js file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .js file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -781,7 +781,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho } `; -exports[`parseAndGenerateServices isolated parsing should parse .js file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .js file - without JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -968,7 +968,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho } `; -exports[`parseAndGenerateServices isolated parsing should parse .json file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .json file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -1191,7 +1191,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .json file - wit } `; -exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .jsx file - with JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -1488,7 +1488,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .jsx file - with JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -1785,7 +1785,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .jsx file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -1972,7 +1972,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .jsx file - without JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -2159,7 +2159,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .ts file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -2346,7 +2346,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho } `; -exports[`parseAndGenerateServices isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .ts file - without JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -2533,7 +2533,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho } `; -exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .tsx file - with JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -2830,7 +2830,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .tsx file - with JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -3127,7 +3127,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .tsx file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -3314,7 +3314,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .tsx file - without JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -3501,7 +3501,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .vue file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .vue file - with JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ @@ -3798,7 +3798,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .vue file - without JSX content - parserOptions.jsx = false 1`] = ` { "ast": { "body": [ @@ -3985,7 +3985,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with } `; -exports[`parseAndGenerateServices isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices > isolated parsing > should parse .vue file - without JSX content - parserOptions.jsx = true 1`] = ` { "ast": { "body": [ diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap index d7402d08234f..5272f64a124c 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`semanticInfo fixtures/export-file.src 1`] = ` +exports[`semanticInfo > fixtures/export-file.src 1`] = ` { "body": [ { @@ -300,7 +300,7 @@ exports[`semanticInfo fixtures/export-file.src 1`] = ` } `; -exports[`semanticInfo fixtures/import-file.src 1`] = ` +exports[`semanticInfo > fixtures/import-file.src 1`] = ` { "body": [ { @@ -789,7 +789,7 @@ exports[`semanticInfo fixtures/import-file.src 1`] = ` } `; -exports[`semanticInfo fixtures/isolated-file.src 1`] = ` +exports[`semanticInfo > fixtures/isolated-file.src 1`] = ` { "body": [ { @@ -1148,7 +1148,7 @@ exports[`semanticInfo fixtures/isolated-file.src 1`] = ` } `; -exports[`semanticInfo fixtures/non-existent-estree-nodes.src 1`] = ` +exports[`semanticInfo > fixtures/non-existent-estree-nodes.src 1`] = ` { "body": [ { diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts index 2d56d9fe3622..1473680fb8ec 100644 --- a/packages/typescript-estree/tests/lib/convert.test.ts +++ b/packages/typescript-estree/tests/lib/convert.test.ts @@ -10,7 +10,11 @@ import { Converter } from '../../src/convert'; describe('convert', () => { afterEach(() => { - jest.resetAllMocks(); + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); }); function convertCode(code: string): ts.SourceFile { @@ -238,21 +242,21 @@ describe('convert', () => { }); /* eslint-enable @typescript-eslint/dot-notation */ - it('should throw error on jsDoc node', () => { + describe('should throw error on jsDoc node', () => { const jsDocCode = [ 'const x: function(new: number, string);', 'const x: function(this: number, string);', 'var g: function(number, number): number;', - ]; + ] as const; - for (const code of jsDocCode) { + it.for(jsDocCode)('%s', (code, { expect }) => { const ast = convertCode(code); const instance = new Converter(ast); expect(() => instance.convertProgram()).toThrow( 'JSDoc types can only be used inside documentation comments.', ); - } + }); }); describe('allowInvalidAST', () => { @@ -319,9 +323,9 @@ describe('convert', () => { ); it('warns on a deprecated aliased property access when suppressDeprecatedPropertyWarnings is false', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const esTsEnumDeclaration = getEsTsEnumDeclaration({ suppressDeprecatedPropertyWarnings: false, }); @@ -329,16 +333,16 @@ describe('convert', () => { // eslint-disable-next-line @typescript-eslint/no-deprecated, @typescript-eslint/no-unused-expressions esTsEnumDeclaration.members; - expect(emitWarning).toHaveBeenCalledWith( + expect(emitWarning).toHaveBeenCalledExactlyOnceWith( `The 'members' property is deprecated on TSEnumDeclaration nodes. Use 'body.members' instead. See https://typescript-eslint.io/troubleshooting/faqs/general#the-key-property-is-deprecated-on-type-nodes-use-key-instead-warnings.`, 'DeprecationWarning', ); }); it('does not warn on a subsequent deprecated aliased property access when suppressDeprecatedPropertyWarnings is false', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const esTsEnumDeclaration = getEsTsEnumDeclaration({ suppressDeprecatedPropertyWarnings: false, }); @@ -348,13 +352,13 @@ describe('convert', () => { esTsEnumDeclaration.members; /* eslint-enable @typescript-eslint/no-deprecated, @typescript-eslint/no-unused-expressions */ - expect(emitWarning).toHaveBeenCalledTimes(1); + expect(emitWarning).toHaveBeenCalledOnce(); }); it('does not warn on a deprecated aliased property access when suppressDeprecatedPropertyWarnings is true', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const esTsEnumDeclaration = getEsTsEnumDeclaration({ suppressDeprecatedPropertyWarnings: true, }); @@ -383,9 +387,9 @@ describe('convert', () => { }); it('warns on a deprecated getter property access when suppressDeprecatedPropertyWarnings is false', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const tsMappedType = getEsTsMappedType({ suppressDeprecatedPropertyWarnings: false, }); @@ -393,16 +397,16 @@ describe('convert', () => { // eslint-disable-next-line @typescript-eslint/no-deprecated, @typescript-eslint/no-unused-expressions tsMappedType.typeParameter; - expect(emitWarning).toHaveBeenCalledWith( + expect(emitWarning).toHaveBeenCalledExactlyOnceWith( `The 'typeParameter' property is deprecated on TSMappedType nodes. Use 'constraint' and 'key' instead. See https://typescript-eslint.io/troubleshooting/faqs/general#the-key-property-is-deprecated-on-type-nodes-use-key-instead-warnings.`, 'DeprecationWarning', ); }); it('does not warn on a subsequent deprecated getter property access when suppressDeprecatedPropertyWarnings is false', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const tsMappedType = getEsTsMappedType({ suppressDeprecatedPropertyWarnings: false, }); @@ -412,13 +416,13 @@ describe('convert', () => { tsMappedType.typeParameter; /* eslint-enable @typescript-eslint/no-deprecated, @typescript-eslint/no-unused-expressions */ - expect(emitWarning).toHaveBeenCalledTimes(1); + expect(emitWarning).toHaveBeenCalledOnce(); }); it('does not warn on a deprecated getter property access when suppressDeprecatedPropertyWarnings is true', () => { - const emitWarning = jest + const emitWarning = vi .spyOn(process, 'emitWarning') - .mockImplementation(); + .mockImplementation(() => {}); const tsMappedType = getEsTsMappedType({ suppressDeprecatedPropertyWarnings: true, }); diff --git a/packages/typescript-estree/tests/lib/createParseSettings.test.ts b/packages/typescript-estree/tests/lib/createParseSettings.test.ts index 368a2f40d50d..ae7e2e28c606 100644 --- a/packages/typescript-estree/tests/lib/createParseSettings.test.ts +++ b/packages/typescript-estree/tests/lib/createParseSettings.test.ts @@ -2,14 +2,14 @@ import { createParseSettings } from '../../src/parseSettings/createParseSettings const projectService = { service: true }; -jest.mock('../../src/create-program/createProjectService', () => ({ +vi.mock('../../src/create-program/createProjectService.js', () => ({ createProjectService: (): typeof projectService => projectService, })); -describe('createParseSettings', () => { +describe(createParseSettings, () => { describe('projectService', () => { it('is created when options.projectService is enabled', () => { - process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE = 'false'; + vi.stubEnv('TYPESCRIPT_ESLINT_PROJECT_SERVICE', 'false'); const parseSettings = createParseSettings('', { projectService: true, @@ -19,7 +19,7 @@ describe('createParseSettings', () => { }); it('is created when options.projectService is undefined, options.project is true, and process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE is true', () => { - process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE = 'true'; + vi.stubEnv('TYPESCRIPT_ESLINT_PROJECT_SERVICE', 'true'); const parseSettings = createParseSettings('', { project: true, @@ -30,7 +30,7 @@ describe('createParseSettings', () => { }); it('is not created when options.projectService is undefined, options.project is falsy, and process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE is true', () => { - process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE = 'true'; + vi.stubEnv('TYPESCRIPT_ESLINT_PROJECT_SERVICE', 'true'); const parseSettings = createParseSettings('', { projectService: undefined, @@ -40,7 +40,7 @@ describe('createParseSettings', () => { }); it('is not created when options.projectService is false, options.project is true, and process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE is true', () => { - process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE = 'true'; + vi.stubEnv('TYPESCRIPT_ESLINT_PROJECT_SERVICE', 'true'); const parseSettings = createParseSettings('', { project: true, diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index fa9684e6237a..1fd7ba8771c0 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -1,53 +1,77 @@ import debug from 'debug'; import * as ts from 'typescript'; -const mockGetParsedConfigFile = jest.fn(); -const mockSetCompilerOptionsForInferredProjects = jest.fn(); -const mockSetHostConfiguration = jest.fn(); - -jest.mock('../../src/create-program/getParsedConfigFile', () => ({ - getParsedConfigFile: mockGetParsedConfigFile, -})); - -jest.mock('typescript/lib/tsserverlibrary', () => ({ - ...jest.requireActual('typescript/lib/tsserverlibrary'), - server: { - ...jest.requireActual('typescript/lib/tsserverlibrary').server, - ProjectService: class { - eventHandler: ts.server.ProjectServiceEventHandler | undefined; - host: ts.server.ServerHost; - logger: ts.server.Logger; - setCompilerOptionsForInferredProjects = - mockSetCompilerOptionsForInferredProjects; - setHostConfiguration = mockSetHostConfiguration; - constructor( - ...args: ConstructorParameters - ) { - this.eventHandler = args[0].eventHandler; - this.host = args[0].host; - this.logger = args[0].logger; - if (this.eventHandler) { - this.eventHandler({ - eventName: 'projectLoadingStart', - } as ts.server.ProjectLoadingStartEvent); - } - } - }, +import { createProjectService } from '../../src/create-program/createProjectService.js'; +import { getParsedConfigFile } from '../../src/create-program/getParsedConfigFile.js'; + +const mockGetParsedConfigFile = vi.mocked(getParsedConfigFile); + +vi.mock( + import('../../src/create-program/getParsedConfigFile.js'), + async importOriginal => { + const actual = await importOriginal(); + + return { + ...actual, + getParsedConfigFile: vi.fn(actual.getParsedConfigFile), + }; }, -})); +); + +vi.mock( + import('../../src/create-program/createProjectService.js'), + async importOriginal => { + const actual = await importOriginal(); -const { - createProjectService, - // eslint-disable-next-line @typescript-eslint/no-require-imports -} = require('../../src/create-program/createProjectService'); + vi.spyOn( + ts.server.ProjectService.prototype, + 'setCompilerOptionsForInferredProjects', + ); + + vi.spyOn(ts.server.ProjectService.prototype, 'setHostConfiguration'); + + return { + ...actual, + createProjectService: vi + .fn(actual.createProjectService) + .mockImplementation((...args) => { + const projectServiceSettings = actual.createProjectService(...args); + const service = + projectServiceSettings.service as typeof projectServiceSettings.service & { + eventHandler: ts.server.ProjectServiceEventHandler | undefined; + }; + + if (service.eventHandler) { + service.eventHandler({ + eventName: ts.server.ProjectLoadingStartEvent, + } as ts.server.ProjectLoadingStartEvent); + } + + return projectServiceSettings; + }), + }; + }, +); + +describe(createProjectService, () => { + const processStderrWriteSpy = vi + .spyOn(process.stderr, 'write') + .mockImplementation(() => true); -describe('createProjectService', () => { beforeEach(() => { - mockGetParsedConfigFile.mockReturnValue({ options: {} }); + mockGetParsedConfigFile.mockReturnValue({ + errors: [], + fileNames: [], + options: {}, + }); }); afterEach(() => { - jest.resetAllMocks(); + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); }); it('sets allowDefaultProject when options.allowDefaultProject is defined', () => { @@ -142,9 +166,14 @@ describe('createProjectService', () => { ); }); - it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', () => { - const compilerOptions = { strict: true }; - mockGetParsedConfigFile.mockReturnValue({ options: compilerOptions }); + it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', async () => { + const compilerOptions: ts.CompilerOptions = { strict: true }; + mockGetParsedConfigFile.mockReturnValueOnce({ + errors: [], + fileNames: [], + options: compilerOptions, + }); + const defaultProject = 'tsconfig.eslint.json'; const { service } = createProjectService( @@ -156,21 +185,25 @@ describe('createProjectService', () => { undefined, ); - expect(service.setCompilerOptionsForInferredProjects).toHaveBeenCalledWith( - compilerOptions, - ); - expect(mockGetParsedConfigFile).toHaveBeenCalledWith( - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('typescript/lib/tsserverlibrary'), + expect( + service.setCompilerOptionsForInferredProjects, + ).toHaveBeenCalledExactlyOnceWith(compilerOptions); + + expect(mockGetParsedConfigFile).toHaveBeenCalledExactlyOnceWith( + (await import('typescript/lib/tsserverlibrary.js')).default, defaultProject, undefined, ); }); - it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', () => { - const compilerOptions = { strict: true }; + it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', async () => { + const compilerOptions: ts.CompilerOptions = { strict: true }; const tsconfigRootDir = 'path/to/repo'; - mockGetParsedConfigFile.mockReturnValue({ options: compilerOptions }); + mockGetParsedConfigFile.mockReturnValueOnce({ + errors: [], + fileNames: [], + options: compilerOptions, + }); const { service } = createProjectService( { @@ -180,20 +213,18 @@ describe('createProjectService', () => { tsconfigRootDir, ); - expect(service.setCompilerOptionsForInferredProjects).toHaveBeenCalledWith( - compilerOptions, - ); - expect(mockGetParsedConfigFile).toHaveBeenCalledWith( - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('typescript/lib/tsserverlibrary'), + expect( + service.setCompilerOptionsForInferredProjects, + ).toHaveBeenCalledExactlyOnceWith(compilerOptions); + + expect(mockGetParsedConfigFile).toHaveBeenCalledExactlyOnceWith( + (await import('typescript/lib/tsserverlibrary.js')).default, 'tsconfig.json', tsconfigRootDir, ); }); it('uses the default projects error debugger for error messages when enabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); debug.enable('typescript-eslint:typescript-estree:tsserver:err'); const enabled = service.logger.loggingEnabled(); @@ -201,7 +232,7 @@ describe('createProjectService', () => { debug.disable(); expect(enabled).toBe(true); - expect(process.stderr.write).toHaveBeenCalledWith( + expect(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( expect.stringMatching( /^.*typescript-eslint:typescript-estree:tsserver:err foo\n$/, ), @@ -209,19 +240,15 @@ describe('createProjectService', () => { }); it('does not use the default projects error debugger for error messages when disabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); const enabled = service.logger.loggingEnabled(); service.logger.msg('foo', ts.server.Msg.Err); expect(enabled).toBe(false); - expect(process.stderr.write).toHaveBeenCalledTimes(0); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); }); it('uses the default projects info debugger for info messages when enabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); debug.enable('typescript-eslint:typescript-estree:tsserver:info'); const enabled = service.logger.loggingEnabled(); @@ -229,7 +256,7 @@ describe('createProjectService', () => { debug.disable(); expect(enabled).toBe(true); - expect(process.stderr.write).toHaveBeenCalledWith( + expect(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( expect.stringMatching( /^.*typescript-eslint:typescript-estree:tsserver:info foo\n$/, ), @@ -237,19 +264,15 @@ describe('createProjectService', () => { }); it('does not use the default projects info debugger for info messages when disabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); const enabled = service.logger.loggingEnabled(); service.logger.info('foo'); expect(enabled).toBe(false); - expect(process.stderr.write).toHaveBeenCalledTimes(0); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); }); it('uses the default projects perf debugger for perf messages when enabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); debug.enable('typescript-eslint:typescript-estree:tsserver:perf'); const enabled = service.logger.loggingEnabled(); @@ -257,7 +280,7 @@ describe('createProjectService', () => { debug.disable(); expect(enabled).toBe(true); - expect(process.stderr.write).toHaveBeenCalledWith( + expect(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( expect.stringMatching( /^.*typescript-eslint:typescript-estree:tsserver:perf foo\n$/, ), @@ -265,14 +288,12 @@ describe('createProjectService', () => { }); it('does not use the default projects perf debugger for perf messages when disabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - const { service } = createProjectService(undefined, undefined, undefined); const enabled = service.logger.loggingEnabled(); service.logger.perftrc('foo'); expect(enabled).toBe(false); - expect(process.stderr.write).toHaveBeenCalledTimes(0); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); }); it('enables all log levels for the default projects logger', () => { @@ -291,13 +312,11 @@ describe('createProjectService', () => { }); it('uses the default projects event debugger for event handling when enabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - debug.enable('typescript-eslint:typescript-estree:tsserver:event'); createProjectService(undefined, undefined, undefined); debug.disable(); - expect(process.stderr.write).toHaveBeenCalledWith( + expect(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( expect.stringMatching( /^.*typescript-eslint:typescript-estree:tsserver:event { eventName: 'projectLoadingStart' }\n$/, ), @@ -305,17 +324,15 @@ describe('createProjectService', () => { }); it('does not use the default projects event debugger for event handling when disabled', () => { - jest.spyOn(process.stderr, 'write').mockImplementation(); - createProjectService(undefined, undefined, undefined); - expect(process.stderr.write).toHaveBeenCalledTimes(0); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); }); it('provides a stub require to the host system when loadTypeScriptPlugins is falsy', () => { const { service } = createProjectService({}, undefined, undefined); - const required = service.host.require(); + const required = service.host.require?.('', ''); expect(required).toEqual({ error: { @@ -326,7 +343,7 @@ describe('createProjectService', () => { }); }); - it('does not provide a require to the host system when loadTypeScriptPlugins is truthy', () => { + it('does not provide a require to the host system when loadTypeScriptPlugins is truthy', async () => { const { service } = createProjectService( { loadTypeScriptPlugins: true, @@ -336,7 +353,11 @@ describe('createProjectService', () => { ); expect(service.host.require).toBe( - jest.requireActual('typescript/lib/tsserverlibrary').sys.require, + ( + await vi.importActual>>( + 'typescript/lib/tsserverlibrary.js', + ) + ).sys.require, ); }); @@ -349,7 +370,7 @@ describe('createProjectService', () => { undefined, ); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ preferences: { includePackageJsonAutoImports: 'off', }, diff --git a/packages/typescript-estree/tests/lib/describeFilePath.test.ts b/packages/typescript-estree/tests/lib/describeFilePath.test.ts index d9a9eaefd1cf..03ed7d036523 100644 --- a/packages/typescript-estree/tests/lib/describeFilePath.test.ts +++ b/packages/typescript-estree/tests/lib/describeFilePath.test.ts @@ -1,10 +1,10 @@ import { describeFilePath } from '../../src/create-program/describeFilePath'; -describe('describeFilePath', () => { - describe.each(['./repos/repo', '/repos/repo', '~/repos/repo'])( +describe(describeFilePath, () => { + describe.for(['./repos/repo', '/repos/repo', '~/repos/repo'] as const)( 'tsconfigRootDir %s', tsconfigRootDir => { - test.each([ + test.for([ './elsewhere/repo/file.ts', './elsewhere/repo/nested/file.ts', './repos/file.ts', @@ -33,7 +33,7 @@ describe('describeFilePath', () => { 'C:/file.ts', 'file.ts', 'nested/file.ts', - ])('filePath %s', filePath => { + ] as const)('filePath %s', (filePath, { expect }) => { expect( describeFilePath(filePath, tsconfigRootDir).replaceAll('\\', '/'), ).toMatchSnapshot(); diff --git a/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts b/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts index f8db03d1f365..797b50e14a03 100644 --- a/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts +++ b/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts @@ -3,7 +3,7 @@ import * as ts from 'typescript'; import { getParsedConfigFile } from '../../src/create-program/getParsedConfigFile'; -const mockGetParsedCommandLineOfConfigFile = jest.fn(); +const mockGetParsedCommandLineOfConfigFile = vi.fn(); const mockTsserver: typeof ts = { formatDiagnostics: ts.formatDiagnostics, @@ -12,9 +12,13 @@ const mockTsserver: typeof ts = { // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; -describe('getParsedConfigFile', () => { +describe(getParsedConfigFile, () => { afterEach(() => { - jest.resetAllMocks(); + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); }); it('throws an error when tsserver.sys is undefined', () => { @@ -27,7 +31,7 @@ describe('getParsedConfigFile', () => { it('uses the cwd as the default project directory', () => { getParsedConfigFile(mockTsserver, './tsconfig.json'); - expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledTimes(1); + expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledOnce(); const [_configFileName, _optionsToExtend, host] = mockGetParsedCommandLineOfConfigFile.mock.calls[0]; expect(host.getCurrentDirectory()).toBe(process.cwd()); @@ -39,7 +43,7 @@ describe('getParsedConfigFile', () => { './tsconfig.json', path.relative('./', path.dirname(__filename)), ); - expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledTimes(1); + expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledOnce(); const [_configFileName, _optionsToExtend, host] = mockGetParsedCommandLineOfConfigFile.mock.calls[0]; expect(host.getCurrentDirectory()).toBe(path.dirname(__filename)); @@ -47,7 +51,7 @@ describe('getParsedConfigFile', () => { it('resolves an absolute project directory when passed', () => { getParsedConfigFile(mockTsserver, './tsconfig.json', __dirname); - expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledTimes(1); + expect(mockGetParsedCommandLineOfConfigFile).toHaveBeenCalledOnce(); const [_configFileName, _optionsToExtend, host] = mockGetParsedCommandLineOfConfigFile.mock.calls[0]; expect(host.getCurrentDirectory()).toBe(__dirname); diff --git a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts index 8da31477fdf0..53cc0436de81 100644 --- a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts +++ b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts @@ -1,14 +1,20 @@ +import { existsSync } from 'node:fs'; import path from 'node:path'; import { ExpiringCache } from '../../src/parseSettings/ExpiringCache'; import { getProjectConfigFiles } from '../../src/parseSettings/getProjectConfigFiles'; -const mockExistsSync = jest.fn(); +const mockExistsSync = vi.mocked(existsSync); -jest.mock('node:fs', () => ({ - ...jest.requireActual('fs'), - existsSync: (filePath: string): boolean => mockExistsSync(filePath), -})); +vi.mock(import('node:fs'), async importOriginal => { + const actual = await importOriginal(); + + return { + ...actual, + default: actual.default, + existsSync: vi.fn(actual.existsSync), + }; +}); const parseSettings = { filePath: './repos/repo/packages/package/file.ts', @@ -16,12 +22,16 @@ const parseSettings = { tsconfigRootDir: './repos/repo', }; -beforeEach(() => { - parseSettings.tsconfigMatchCache.clear(); - jest.clearAllMocks(); -}); +describe(getProjectConfigFiles, () => { + beforeEach(() => { + parseSettings.tsconfigMatchCache.clear(); + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); + }); -describe('getProjectConfigFiles', () => { it('returns an array with just the project when given as a string', () => { const project = './tsconfig.eslint.json'; @@ -39,18 +49,18 @@ describe('getProjectConfigFiles', () => { }); describe('it does not enable type-aware linting when given as', () => { - for (const project of [undefined, null, false]) { - it(`${project}`, () => { - const actual = getProjectConfigFiles(parseSettings, project); + const testCases = [[undefined], [null], [false]] as const; - expect(actual).toBeNull(); - }); - } + it.for(testCases)('%o', ([project], { expect }) => { + const actual = getProjectConfigFiles(parseSettings, project); + + expect(actual).toBeNull(); + }); }); describe('when caching hits', () => { it('returns a local tsconfig.json without calling existsSync a second time', () => { - mockExistsSync.mockReturnValue(true); + mockExistsSync.mockReturnValueOnce(true); getProjectConfigFiles(parseSettings, true); const actual = getProjectConfigFiles(parseSettings, true); @@ -58,7 +68,7 @@ describe('getProjectConfigFiles', () => { expect(actual).toEqual([ path.normalize('repos/repo/packages/package/tsconfig.json'), ]); - expect(mockExistsSync).toHaveBeenCalledTimes(1); + expect(mockExistsSync).toHaveBeenCalledOnce(); }); it('returns a nearby parent tsconfig.json when it was previously cached by a different directory search', () => { @@ -128,7 +138,7 @@ describe('getProjectConfigFiles', () => { describe('when caching misses', () => { it('returns a local tsconfig.json when matched', () => { - mockExistsSync.mockReturnValue(true); + mockExistsSync.mockReturnValueOnce(true); const actual = getProjectConfigFiles(parseSettings, true); @@ -153,7 +163,7 @@ describe('getProjectConfigFiles', () => { expect(() => getProjectConfigFiles(parseSettings, true), ).toThrowErrorMatchingInlineSnapshot( - `"project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within './repos/repo'."`, + `[Error: project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within './repos/repo'.]`, ); }); @@ -163,7 +173,7 @@ describe('getProjectConfigFiles', () => { expect(() => getProjectConfigFiles({ ...parseSettings, tsconfigRootDir: '/' }, true), ).toThrowErrorMatchingInlineSnapshot( - `"project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within '/'."`, + `[Error: project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within '/'.]`, ); }); }); diff --git a/packages/typescript-estree/tests/lib/inferSingleRun.test.ts b/packages/typescript-estree/tests/lib/inferSingleRun.test.ts index 0febd1dbaee1..2fe53da89076 100644 --- a/packages/typescript-estree/tests/lib/inferSingleRun.test.ts +++ b/packages/typescript-estree/tests/lib/inferSingleRun.test.ts @@ -2,11 +2,11 @@ import path from 'node:path'; import { inferSingleRun } from '../../src/parseSettings/inferSingleRun'; -describe('inferSingleRun', () => { +describe(inferSingleRun, () => { beforeEach(() => { - process.argv = ['node', 'eslint']; - process.env.CI = undefined; - process.env.TSESTREE_SINGLE_RUN = undefined; + vi.stubGlobal('process', { ...process, argv: ['node', 'eslint'] }); + vi.stubEnv('CI', undefined); + vi.stubEnv('TSESTREE_SINGLE_RUN', undefined); }); it('returns false when options is undefined', () => { @@ -21,14 +21,14 @@ describe('inferSingleRun', () => { expect(actual).toBe(false); }); - it('returns false when options.program is defined', () => { + it('returns false when options.programs is defined', () => { const actual = inferSingleRun({ programs: [], project: true }); expect(actual).toBe(false); }); it("returns false when TSESTREE_SINGLE_RUN is 'false'", () => { - process.env.TSESTREE_SINGLE_RUN = 'false'; + vi.stubEnv('TSESTREE_SINGLE_RUN', 'false'); const actual = inferSingleRun({ project: true }); @@ -36,7 +36,7 @@ describe('inferSingleRun', () => { }); it("returns true when TSESTREE_SINGLE_RUN is 'true'", () => { - process.env.TSESTREE_SINGLE_RUN = 'true'; + vi.stubEnv('TSESTREE_SINGLE_RUN', 'true'); const actual = inferSingleRun({ project: true }); @@ -44,42 +44,48 @@ describe('inferSingleRun', () => { }); it("returns true when CI is 'true'", () => { - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); const actual = inferSingleRun({ project: true }); expect(actual).toBe(true); }); - it.each(['project', 'programs'])( - 'returns false when given %j is null', - key => { + it.for(['project', 'programs'] as const)( + 'returns false when given %s is null', + (key, { expect }) => { const actual = inferSingleRun({ [key]: null }); expect(actual).toBe(false); }, ); - it.each([ + it.for([ ['true', true], ['false', false], - ])('return %s when given TSESTREE_SINGLE_RUN is "%s"', (run, expected) => { - process.env.TSESTREE_SINGLE_RUN = run; + ] as const)( + 'return %s when given TSESTREE_SINGLE_RUN is "%s"', + ([run, expected], { expect }) => { + vi.stubEnv('TSESTREE_SINGLE_RUN', run); - const actual = inferSingleRun({ - programs: null, - project: './tsconfig.json', - }); + const actual = inferSingleRun({ + programs: null, + project: './tsconfig.json', + }); - expect(actual).toBe(expected); - }); + expect(actual).toBe(expected); + }, + ); - describe.each([ + describe.for([ 'node_modules/.bin/eslint', 'node_modules/eslint/bin/eslint.js', - ])('%s', pathName => { + ] as const)('%s', pathName => { it('returns false when singleRun is inferred from process.argv with --fix', () => { - process.argv = ['', path.normalize(pathName), '', '--fix']; + vi.stubGlobal('process', { + ...process, + argv: ['', path.normalize(pathName), '', '--fix'], + }); const actual = inferSingleRun({ programs: null, @@ -90,7 +96,10 @@ describe('inferSingleRun', () => { }); it('returns true when singleRun is inferred from process.argv without --fix', () => { - process.argv = ['', path.normalize(pathName), '']; + vi.stubGlobal('process', { + ...process, + argv: ['', path.normalize(pathName), ''], + }); const actual = inferSingleRun({ programs: null, @@ -102,7 +111,7 @@ describe('inferSingleRun', () => { }); it('returns true when singleRun is inferred from CI=true', () => { - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); const actual = inferSingleRun({ programs: null, @@ -113,7 +122,7 @@ describe('inferSingleRun', () => { }); it('returns true when singleRun can be inferred and options.extraFileExtensions is an empty array', () => { - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); const actual = inferSingleRun({ extraFileExtensions: [], @@ -124,7 +133,7 @@ describe('inferSingleRun', () => { }); it('returns false when singleRun can be inferred options.extraFileExtensions contains entries', () => { - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); const actual = inferSingleRun({ extraFileExtensions: ['.vue'], diff --git a/packages/typescript-estree/tests/lib/node-utils.test.ts b/packages/typescript-estree/tests/lib/node-utils.test.ts index a315d44de3ab..f1a1145685b7 100644 --- a/packages/typescript-estree/tests/lib/node-utils.test.ts +++ b/packages/typescript-estree/tests/lib/node-utils.test.ts @@ -1,6 +1,6 @@ import { unescapeStringLiteralText } from '../../src/node-utils'; -describe('unescapeStringLiteralText()', () => { +describe(unescapeStringLiteralText, () => { it('should not modify content', () => { let text = 'amp;'; expect(unescapeStringLiteralText(text)).toBe(text); diff --git a/packages/typescript-estree/tests/lib/parse.project-true.test.ts b/packages/typescript-estree/tests/lib/parse.project-true.test.ts index 35abf0baf929..2cf54a086868 100644 --- a/packages/typescript-estree/tests/lib/parse.project-true.test.ts +++ b/packages/typescript-estree/tests/lib/parse.project-true.test.ts @@ -2,19 +2,19 @@ import { join } from 'node:path'; import * as parser from '../../src'; -const PROJECT_DIR = join(__dirname, '../fixtures/projectTrue'); +const PROJECT_DIR = join(__dirname, '..', 'fixtures', 'projectTrue'); const config = { project: true, tsconfigRootDir: PROJECT_DIR, } satisfies Partial; -describe('parseAndGenerateServices', () => { +describe(parser.parseAndGenerateServices, () => { describe('when project is true', () => { it('finds a parent project when it exists in the project', () => { const result = parser.parseAndGenerateServices('const a = true', { ...config, - filePath: join(PROJECT_DIR, 'nested/deep/included.ts'), + filePath: join(PROJECT_DIR, 'nested', 'deep', 'included.ts'), }); expect(result).toEqual({ @@ -26,7 +26,7 @@ describe('parseAndGenerateServices', () => { it('finds a sibling project when it exists in the project', () => { const result = parser.parseAndGenerateServices('const a = true', { ...config, - filePath: join(PROJECT_DIR, 'nested/included.ts'), + filePath: join(PROJECT_DIR, 'nested', 'included.ts'), }); expect(result).toEqual({ diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index b701bf35d216..7e3a37b2aa5f 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -1,5 +1,4 @@ import type { CacheDurationSeconds } from '@typescript-eslint/types'; -import type * as typescriptModule from 'typescript'; import debug from 'debug'; import * as fastGlobModule from 'fast-glob'; @@ -11,17 +10,15 @@ import * as parser from '../../src'; import * as sharedParserUtilsModule from '../../src/create-program/shared'; import { clearGlobResolutionCache } from '../../src/parseSettings/resolveProjectList'; -const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); +const FIXTURES_DIR = join(__dirname, '..', 'fixtures', 'simpleProject'); -jest.mock('../../src/create-program/shared', () => { - const sharedActual = jest.requireActual( - '../../src/create-program/shared', - ); +vi.mock(import('../../src/create-program/shared.js'), async importOriginal => { + const sharedActual = await importOriginal(); return { ...sharedActual, __esModule: true, - createDefaultCompilerOptionsFromExtra: jest.fn( + createDefaultCompilerOptionsFromExtra: vi.fn( sharedActual.createDefaultCompilerOptionsFromExtra, ), }; @@ -29,10 +26,12 @@ jest.mock('../../src/create-program/shared', () => { // Tests in CI by default run with lowercase program file names, // resulting in path.relative results starting with many "../"s -jest.mock('typescript', () => { - const ts = jest.requireActual('typescript'); +vi.mock(import('typescript'), async importOriginal => { + const ts = await importOriginal(); + return { ...ts, + default: ts.default, sys: { ...ts.sys, useCaseSensitiveFileNames: true, @@ -40,20 +39,20 @@ jest.mock('typescript', () => { }; }); -jest.mock('fast-glob', () => { - const fastGlob = jest.requireActual('fast-glob'); +vi.mock('fast-glob', async importOriginal => { + const fastGlob = await importOriginal(); + return { ...fastGlob, - sync: jest.fn(fastGlob.sync), + default: fastGlob.default, + sync: vi.fn(fastGlob.sync), }; }); -const hrtimeSpy = jest.spyOn(process, 'hrtime'); - -const createDefaultCompilerOptionsFromExtra = jest.mocked( +const createDefaultCompilerOptionsFromExtra = vi.mocked( sharedParserUtilsModule.createDefaultCompilerOptionsFromExtra, ); -const fastGlobSyncMock = jest.mocked(fastGlobModule.sync); +const fastGlobSyncMock = vi.mocked(fastGlobModule.sync); /** * Aligns paths between environments, node for windows uses `\`, for linux and mac uses `/` @@ -63,12 +62,18 @@ function alignErrorPath(error: Error): never { throw error; } -beforeEach(() => { - jest.clearAllMocks(); - clearGlobResolutionCache(); -}); +describe(parser.parseAndGenerateServices, () => { + const hrtimeSpy = vi.spyOn(process, 'hrtime'); + + beforeEach(() => { + vi.clearAllMocks(); + clearGlobResolutionCache(); + }); + + afterAll(() => { + vi.restoreAllMocks(); + }); -describe('parseAndGenerateServices', () => { describe('preserveNodeMaps', () => { const code = 'var a = true'; const baseConfig: TSESTreeOptions = { @@ -198,7 +203,7 @@ describe('parseAndGenerateServices', () => { let result: | parser.ParseAndGenerateServicesResult | undefined; - // eslint-disable-next-line jest/valid-expect + // eslint-disable-next-line vitest/valid-expect const exp = expect(() => { result = parser.parseAndGenerateServices(code, { ...config, @@ -471,9 +476,10 @@ describe('parseAndGenerateServices', () => { }); }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - describe('invalid file error messages', () => { - const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); + describe.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'invalid file error messages', + () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '..', 'invalidFileErrors'); const code = 'var a = true'; const config: TSESTreeOptions = { comment: true, @@ -518,40 +524,40 @@ describe('parseAndGenerateServices', () => { it('errors for not included files', () => { expect(testParse('ts/notIncluded0j1.ts')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: /tsconfig.json - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); expect(testParse('ts/notIncluded02.tsx')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/ts/notIncluded02.tsx\` using \`parserOptions.project\`: /tsconfig.json - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/ts/notIncluded02.tsx\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); expect(testParse('js/notIncluded01.js')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/js/notIncluded01.js\` using \`parserOptions.project\`: /tsconfig.json - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/js/notIncluded01.js\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); expect(testParse('js/notIncluded02.jsx')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/js/notIncluded02.jsx\` using \`parserOptions.project\`: /tsconfig.json - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/js/notIncluded02.jsx\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); }); }); @@ -563,9 +569,9 @@ describe('parseAndGenerateServices', () => { it('the extension does not match', () => { expect(testParse('other/unknownFileType.unknown', [])) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json - The extension for the file (\`.unknown\`) is non-standard. You should add \`parserOptions.extraFileExtensions\` to your config." - `); + [Error: ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + The extension for the file (\`.unknown\`) is non-standard. You should add \`parserOptions.extraFileExtensions\` to your config.] + `); }); }); @@ -578,44 +584,44 @@ describe('parseAndGenerateServices', () => { it("the file isn't included", () => { expect(testParse('other/notIncluded.vue')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/other/notIncluded.vue\` using \`parserOptions.project\`: /tsconfig.json - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/other/notIncluded.vue\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); }); it('duplicate extension', () => { expect(testParse('ts/notIncluded.ts', ['.ts'])) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/ts/notIncluded.ts\` using \`parserOptions.project\`: /tsconfig.json - You unnecessarily included the extension \`.ts\` with the \`parserOptions.extraFileExtensions\` option. This extension is already handled by the parser by default. - However, that TSConfig does not include this file. Either: - - Change ESLint's list of included files to not include this file - - Change that TSConfig to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); + [Error: ESLint was configured to run on \`/ts/notIncluded.ts\` using \`parserOptions.project\`: /tsconfig.json + You unnecessarily included the extension \`.ts\` with the \`parserOptions.extraFileExtensions\` option. This extension is already handled by the parser by default. + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); }); }); it('invalid extension', () => { expect(testParse('other/unknownFileType.unknown', ['unknown'])) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json - Found unexpected extension \`unknown\` specified with the \`parserOptions.extraFileExtensions\` option. Did you mean \`.unknown\`? - The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." - `); + [Error: ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + Found unexpected extension \`unknown\` specified with the \`parserOptions.extraFileExtensions\` option. Did you mean \`.unknown\`? + The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`.] + `); }); it('the extension does not match', () => { expect(testParse('other/unknownFileType.unknown')) .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json - The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." - `); + [Error: ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`.] + `); }); }); @@ -658,56 +664,53 @@ describe('parseAndGenerateServices', () => { ); }); }); - }); + }, + ); - describe('invalid project error messages', () => { - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it('throws when none of multiple projects include the file', () => { - const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); - const code = 'var a = true'; - const config: TSESTreeOptions = { - comment: true, - disallowAutomaticSingleRunInference: true, - loc: true, - project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], - range: true, - tokens: true, - tsconfigRootDir: PROJECT_DIR, - }; - const testParse = (filePath: string) => (): void => { - try { - parser.parseAndGenerateServices(code, { - ...config, - filePath: join(PROJECT_DIR, filePath), - }); - } catch (error) { - alignErrorPath(error as Error); - } - }; + describe('invalid project error messages', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'throws when none of multiple projects include the file', + () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '..', 'invalidFileErrors'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + disallowAutomaticSingleRunInference: true, + loc: true, + project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], + range: true, + tokens: true, + tsconfigRootDir: PROJECT_DIR, + }; + const testParse = (filePath: string) => (): void => { + try { + parser.parseAndGenerateServices(code, { + ...config, + filePath: join(PROJECT_DIR, filePath), + }); + } catch (error) { + alignErrorPath(error as Error); + } + }; - expect(testParse('ts/notIncluded0j1.ts')) - .toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: - - /tsconfig.json - - /tsconfig.extra.json - However, none of those TSConfigs include this file. Either: - - Change ESLint's list of included files to not include this file - - Change one of those TSConfigs to include this file - - Create a new TSConfig that includes this file and include it in your parserOptions.project - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" - `); - }); - } - }); - } + expect(testParse('ts/notIncluded0j1.ts')) + .toThrowErrorMatchingInlineSnapshot(` + [Error: ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: + - /tsconfig.json + - /tsconfig.extra.json + However, none of those TSConfigs include this file. Either: + - Change ESLint's list of included files to not include this file + - Change one of those TSConfigs to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file] + `); + }, + ); + }); describe('debug options', () => { - const debugEnable = jest.fn(); - beforeEach(() => { - debugEnable.mockReset(); - debug.enable = debugEnable; - jest.spyOn(debug, 'enabled').mockImplementation(() => false); - }); + const debugEnable = vi.spyOn(debug, 'enable'); + vi.spyOn(debug, 'enabled').mockImplementation(() => false); it("shouldn't turn on debugger if no options were provided", () => { parser.parseAndGenerateServices('const x = 1;', { @@ -722,8 +725,9 @@ describe('parseAndGenerateServices', () => { debugLevel: ['eslint'], disallowAutomaticSingleRunInference: true, }); - expect(debugEnable).toHaveBeenCalledTimes(1); - expect(debugEnable).toHaveBeenCalledWith('eslint:*,-eslint:code-path'); + expect(debugEnable).toHaveBeenCalledExactlyOnceWith( + 'eslint:*,-eslint:code-path', + ); }); it('should turn on typescript-eslint debugger', () => { @@ -731,8 +735,9 @@ describe('parseAndGenerateServices', () => { debugLevel: ['typescript-eslint'], disallowAutomaticSingleRunInference: true, }); - expect(debugEnable).toHaveBeenCalledTimes(1); - expect(debugEnable).toHaveBeenCalledWith('typescript-eslint:*'); + expect(debugEnable).toHaveBeenCalledExactlyOnceWith( + 'typescript-eslint:*', + ); }); it('should turn on both eslint and typescript-eslint debugger', () => { @@ -740,14 +745,14 @@ describe('parseAndGenerateServices', () => { debugLevel: ['typescript-eslint', 'eslint'], disallowAutomaticSingleRunInference: true, }); - expect(debugEnable).toHaveBeenCalledTimes(1); - expect(debugEnable).toHaveBeenCalledWith( + expect(debugEnable).toHaveBeenCalledExactlyOnceWith( 'typescript-eslint:*,eslint:*,-eslint:code-path', ); }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it('should turn on typescript debugger', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'should turn on typescript debugger', + () => { expect(() => parser.parseAndGenerateServices('const x = 1;', { debugLevel: ['typescript'], @@ -757,23 +762,28 @@ describe('parseAndGenerateServices', () => { }), ) // should throw because the file and tsconfig don't exist .toThrow(); - expect(createDefaultCompilerOptionsFromExtra).toHaveBeenCalled(); - expect(createDefaultCompilerOptionsFromExtra).toHaveReturnedWith( + expect(createDefaultCompilerOptionsFromExtra).toHaveBeenCalledOnce(); + expect(createDefaultCompilerOptionsFromExtra).toHaveLastReturnedWith( expect.objectContaining({ extendedDiagnostics: true, }), ); - }); - } + }, + ); }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - describe('projectFolderIgnoreList', () => { + describe.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'projectFolderIgnoreList', + () => { beforeEach(() => { parser.clearCaches(); }); - const PROJECT_DIR = resolve(FIXTURES_DIR, '../projectFolderIgnoreList'); + const PROJECT_DIR = resolve( + FIXTURES_DIR, + '..', + 'projectFolderIgnoreList', + ); const code = 'var a = true'; const config: TSESTreeOptions = { comment: true, @@ -810,9 +820,12 @@ describe('parseAndGenerateServices', () => { // cspell:disable-next-line expect(testParse('includeme', ignore)).not.toThrow(); }); - }); + }, + ); - describe('cacheLifetime', () => { + describe.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'cacheLifetime', + () => { describe('glob', () => { const project = ['./**/tsconfig.json', './**/tsconfig.extra.json']; // fast-glob returns arbitrary order of results to improve performance. @@ -880,20 +893,23 @@ describe('parseAndGenerateServices', () => { expect(fastGlobSyncMock).toHaveBeenCalledTimes(expectFastGlobCalls); }); }); - }); + }, + ); - describe('project references', () => { + describe.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'project references', + () => { beforeEach(() => { parser.clearCaches(); }); - const PROJECT_DIR = resolve(FIXTURES_DIR, '../projectReferences'); + const PROJECT_DIR = resolve(FIXTURES_DIR, '..', 'projectReferences'); const code = 'var a = true'; const testParse = () => (): void => { parser.parseAndGenerateServices(code, { disallowAutomaticSingleRunInference: true, - filePath: join(PROJECT_DIR, './file.ts'), + filePath: join(PROJECT_DIR, 'file.ts'), project: './**/tsconfig.json', tsconfigRootDir: PROJECT_DIR, }); @@ -901,14 +917,14 @@ describe('parseAndGenerateServices', () => { it('throws a special-case error when project references are enabled in the only TSConfig and the file is not found', () => { expect(testParse()).toThrowErrorMatchingInlineSnapshot(` - "ESLint was configured to run on \`/file.ts\` using \`parserOptions.project\`: /tsconfig.json - That TSConfig uses project "references" and doesn't include \`/file.ts\` directly, which is not supported by \`parserOptions.project\`. - Either: - - Switch to \`parserOptions.projectService\` - - Use an ESLint-specific TSConfig - See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#are-typescript-project-references-supported" + [Error: ESLint was configured to run on \`/file.ts\` using \`parserOptions.project\`: /tsconfig.json + That TSConfig uses project "references" and doesn't include \`/file.ts\` directly, which is not supported by \`parserOptions.project\`. + Either: + - Switch to \`parserOptions.projectService\` + - Use an ESLint-specific TSConfig + See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#are-typescript-project-references-supported] `); }); - }); - } + }, + ); }); diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 6a3338cf9be0..d7283f412330 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -1,6 +1,6 @@ -import fs from 'node:fs'; +import fs from 'node:fs/promises'; +import * as os from 'node:os'; import path from 'node:path'; -import tmp from 'tmp'; import { clearCaches } from '../../src/clear-caches'; import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects'; @@ -19,8 +19,12 @@ const CONTENTS = { string: 'let a: "a" | "b";', }; +const homeOrTmpDir = os.tmpdir() || os.homedir(); + +const tmpDirsParentDirectory = path.join(homeOrTmpDir, 'typescript-estree'); + const cwdCopy = process.cwd(); -const tmpDirs = new Set(); +const tmpDirs = new Set(); afterEach(() => { // reset project tracking clearDefaultProjectMatchedFiles(); @@ -28,48 +32,77 @@ afterEach(() => { // stop watching the files and folders clearWatchCaches(); - // clean up the temporary files and folders - tmpDirs.forEach(t => t.removeCallback()); tmpDirs.clear(); // restore original cwd process.chdir(cwdCopy); }); -function writeTSConfig(dirName: string, config: Record): void { - fs.writeFileSync(path.join(dirName, 'tsconfig.json'), JSON.stringify(config)); +beforeAll(async () => { + await fs.mkdir(tmpDirsParentDirectory, { + recursive: true, + }); +}); + +afterAll(async () => { + // clean up the temporary files and folders + await fs.rm(tmpDirsParentDirectory, { recursive: true }); +}); + +async function writeTSConfig( + dirName: string, + config: Record, +): Promise { + await fs.writeFile( + path.join(dirName, 'tsconfig.json'), + JSON.stringify(config, null, 2), + { encoding: 'utf-8' }, + ); } -function writeFile(dirName: string, file: keyof typeof CONTENTS): void { - fs.writeFileSync(path.join(dirName, 'src', `${file}.ts`), CONTENTS[file]); +async function writeFile( + dirName: string, + file: keyof typeof CONTENTS, +): Promise { + await fs.writeFile( + path.join(dirName, 'src', `${file}.ts`), + CONTENTS[file], + 'utf-8', + ); } -function renameFile(dirName: string, src: 'bar', dest: 'baz/bar'): void { - fs.renameSync( +async function renameFile( + dirName: string, + src: 'bar', + dest: 'baz/bar', +): Promise { + await fs.rename( path.join(dirName, 'src', `${src}.ts`), path.join(dirName, 'src', `${dest}.ts`), ); } -function createTmpDir(): tmp.DirResult { - const tmpDir = tmp.dirSync({ - keep: false, - unsafeCleanup: true, +async function createTmpDir(): Promise { + const tmpDir = await fs.mkdtemp(`${tmpDirsParentDirectory}/`, { + encoding: 'utf-8', }); tmpDirs.add(tmpDir); return tmpDir; } -function setup(tsconfig: Record, writeBar = true): string { - const tmpDir = createTmpDir(); +async function setup( + tsconfig: Record, + writeBar = true, +): Promise { + const tmpDir = await createTmpDir(); - writeTSConfig(tmpDir.name, tsconfig); + await writeTSConfig(tmpDir, tsconfig); - fs.mkdirSync(path.join(tmpDir.name, 'src')); - fs.mkdirSync(path.join(tmpDir.name, 'src', 'baz')); - writeFile(tmpDir.name, 'foo'); + await fs.mkdir(path.join(tmpDir, 'src'), { recursive: true }); + await fs.mkdir(path.join(tmpDir, 'src', 'baz'), { recursive: true }); + await writeFile(tmpDir, 'foo'); if (writeBar) { - writeFile(tmpDir.name, 'bar'); + await writeFile(tmpDir, 'bar'); } - return tmpDir.name; + return tmpDir; } function parseFile( @@ -88,35 +121,33 @@ function parseFile( }); } -function existsSync(filename: keyof typeof CONTENTS, tmpDir = ''): boolean { - return fs.existsSync(path.join(tmpDir, 'src', `${filename}.ts`)); +async function exists( + filename: keyof typeof CONTENTS, + tmpDir = '', +): Promise { + return (await fs.lstat(path.join(tmpDir, 'src', `${filename}.ts`))).isFile(); } function baseTests( tsConfigExcludeBar: Record, tsConfigIncludeAll: Record, ): void { - // The project service creates a default project for files - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true') { - return; - } - - it('parses both files successfully when included', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll); + it('parses both files successfully when included', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); - it('parses included files, and throws on excluded files', () => { - const PROJECT_DIR = setup(tsConfigExcludeBar); + it('parses included files, and throws on excluded files', async () => { + const PROJECT_DIR = await setup(tsConfigExcludeBar); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); }); - it('allows parsing of new files', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll, false); + it('allows parsing of new files', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll, false); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); @@ -124,15 +155,15 @@ function baseTests( expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); + await writeFile(PROJECT_DIR, 'bar'); // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); - it('allows parsing of deeply nested new files', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll, false); + it('allows parsing of deeply nested new files', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll, false); const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup @@ -141,33 +172,35 @@ function baseTests( expect(() => parseFile(bazSlashBar, PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, bazSlashBar); + await writeFile(PROJECT_DIR, bazSlashBar); // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); }); - it('allows parsing of deeply nested new files in new folder', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll); + it('allows parsing of deeply nested new files in new folder', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); // Create deep folder structure after first parse (this is important step) // context: https://github.com/typescript-eslint/typescript-eslint/issues/1394 - fs.mkdirSync(path.join(PROJECT_DIR, 'src', 'bat')); - fs.mkdirSync(path.join(PROJECT_DIR, 'src', 'bat', 'baz')); + await fs.mkdir(path.join(PROJECT_DIR, 'src', 'bat'), { recursive: true }); + await fs.mkdir(path.join(PROJECT_DIR, 'src', 'bat', 'baz'), { + recursive: true, + }); const bazSlashBar = 'bat/baz/bar'; // write a new file and attempt to parse it - writeFile(PROJECT_DIR, bazSlashBar); + await writeFile(PROJECT_DIR, bazSlashBar); expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); }); - it('allows renaming of files', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll, true); + it('allows renaming of files', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll, true); const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup @@ -176,30 +209,30 @@ function baseTests( expect(() => parseFile(bazSlashBar, PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - renameFile(PROJECT_DIR, 'bar', bazSlashBar); + await renameFile(PROJECT_DIR, 'bar', bazSlashBar); // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); }); - it('reacts to changes in the tsconfig', () => { - const PROJECT_DIR = setup(tsConfigExcludeBar); + it('reacts to changes in the tsconfig', async () => { + const PROJECT_DIR = await setup(tsConfigExcludeBar); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); // change the config file so it now includes all files - writeTSConfig(PROJECT_DIR, tsConfigIncludeAll); + await writeTSConfig(PROJECT_DIR, tsConfigIncludeAll); clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); - it('should work with relative paths', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll, false); + it('should work with relative paths', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll, false); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR, true)).not.toThrow(); @@ -207,18 +240,18 @@ function baseTests( expect(() => parseFile('bar', PROJECT_DIR, true)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); + await writeFile(PROJECT_DIR, 'bar'); // make sure that file is correctly created - expect(existsSync('bar', PROJECT_DIR)).toBe(true); + await expect(exists('bar', PROJECT_DIR)).resolves.toBe(true); // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR, true)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR, true)).not.toThrow(); }); - it('should work with relative paths without tsconfig root', () => { - const PROJECT_DIR = setup(tsConfigIncludeAll, false); + it('should work with relative paths without tsconfig root', async () => { + const PROJECT_DIR = await setup(tsConfigIncludeAll, false); process.chdir(PROJECT_DIR); // parse once to: assert the config as correct, and to make sure the program is setup @@ -227,11 +260,11 @@ function baseTests( expect(() => parseFile('bar', PROJECT_DIR, true, true)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); + await writeFile(PROJECT_DIR, 'bar'); // make sure that file is correctly created - expect(existsSync('bar')).toBe(true); - expect(existsSync('bar', PROJECT_DIR)).toBe(true); + await expect(exists('bar')).resolves.toBe(true); + await expect(exists('bar', PROJECT_DIR)).resolves.toBe(true); // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR, true, true)).not.toThrow(); @@ -240,42 +273,49 @@ function baseTests( } describe('persistent parse', () => { - describe('includes not ending in a slash', () => { - const tsConfigExcludeBar = { - exclude: ['./src/bar.ts'], - include: ['src'], - }; - const tsConfigIncludeAll = { - exclude: [], - include: ['src'], - }; - - baseTests(tsConfigExcludeBar, tsConfigIncludeAll); - }); + describe.skipIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true')( + 'includes not ending in a slash', + () => { + const tsConfigExcludeBar = { + exclude: ['./src/bar.ts'], + include: ['src'], + }; + const tsConfigIncludeAll = { + exclude: [], + include: ['src'], + }; + + baseTests(tsConfigExcludeBar, tsConfigIncludeAll); + }, + ); /* If the includes ends in a slash, typescript will ask for watchers ending in a slash. These tests ensure the normalization of code works as expected in this case. */ - describe('includes ending in a slash', () => { - const tsConfigExcludeBar = { - exclude: ['./src/bar.ts'], - include: ['src/'], - }; - const tsConfigIncludeAll = { - exclude: [], - include: ['src/'], - }; - - baseTests(tsConfigExcludeBar, tsConfigIncludeAll); - }); + describe.skipIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true')( + 'includes ending in a slash', + () => { + const tsConfigExcludeBar = { + exclude: ['./src/bar.ts'], + include: ['src/'], + }; + const tsConfigIncludeAll = { + exclude: [], + include: ['src/'], + }; + + baseTests(tsConfigExcludeBar, tsConfigIncludeAll); + }, + ); /* If there is no includes, then typescript will ask for a slightly different set of watchers. */ - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - describe('tsconfig with no includes / files', () => { + describe.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'tsconfig with no includes / files', + () => { const tsConfigExcludeBar = { exclude: ['./src/bar.ts'], }; @@ -283,23 +323,23 @@ describe('persistent parse', () => { baseTests(tsConfigExcludeBar, tsConfigIncludeAll); - it('handles tsconfigs with no includes/excludes (single level)', () => { - const PROJECT_DIR = setup({}, false); + it('handles tsconfigs with no includes/excludes (single level)', async () => { + const PROJECT_DIR = await setup({}, false); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); + await writeFile(PROJECT_DIR, 'bar'); clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); - it('handles tsconfigs with no includes/excludes (nested)', () => { - const PROJECT_DIR = setup({}, false); + it('handles tsconfigs with no includes/excludes (nested)', async () => { + const PROJECT_DIR = await setup({}, false); const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup @@ -307,29 +347,32 @@ describe('persistent parse', () => { expect(() => parseFile(bazSlashBar, PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, bazSlashBar); + await writeFile(PROJECT_DIR, bazSlashBar); clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); }); - }); - } + }, + ); /* If there is no includes, then typescript will ask for a slightly different set of watchers. */ - describe('tsconfig with overlapping globs', () => { - const tsConfigExcludeBar = { - exclude: ['./src/bar.ts'], - include: ['./*', './**/*', './src/**/*'], - }; - const tsConfigIncludeAll = { - include: ['./*', './**/*', './src/**/*'], - }; - - baseTests(tsConfigExcludeBar, tsConfigIncludeAll); - }); + describe.skipIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true')( + 'tsconfig with overlapping globs', + () => { + const tsConfigExcludeBar = { + exclude: ['./src/bar.ts'], + include: ['./*', './**/*', './src/**/*'], + }; + const tsConfigIncludeAll = { + include: ['./*', './**/*', './src/**/*'], + }; + + baseTests(tsConfigExcludeBar, tsConfigIncludeAll); + }, + ); describe('tsconfig with module set', () => { const moduleTypes = [ @@ -341,24 +384,24 @@ describe('persistent parse', () => { 'ES6', 'ES2015', 'ESNext', - ]; - - for (const module of moduleTypes) { - describe(`module ${module}`, () => { - const tsConfigIncludeAll = { - compilerOptions: { module }, - include: ['./**/*'], - }; - - const testNames = ['object', 'number', 'string', 'foo'] as const; - for (const name of testNames) { - it(`first parse of ${name} should not throw`, () => { - const PROJECT_DIR = setup(tsConfigIncludeAll); - writeFile(PROJECT_DIR, name); - expect(() => parseFile(name, PROJECT_DIR)).not.toThrow(); - }); - } - }); - } + ] as const; + + const testNames = ['object', 'number', 'string', 'foo'] as const; + + describe.for(moduleTypes)('module %s', module => { + const tsConfigIncludeAll = { + compilerOptions: { module }, + include: ['./**/*'], + }; + + it.for(testNames)( + 'first parse of %s should not throw', + async (name, { expect }) => { + const PROJECT_DIR = await setup(tsConfigIncludeAll); + await writeFile(PROJECT_DIR, name); + expect(() => parseFile(name, PROJECT_DIR)).not.toThrow(); + }, + ); + }); }); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index 0df2e12b6a9b..4c1813f251fb 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -20,7 +20,7 @@ const mockProgram = { }, }; -jest.mock('../../src/ast-converter', () => { +vi.mock('../../src/ast-converter.js', () => { return { astConverter(): unknown { return { astMaps: {}, estree: {} }; @@ -32,10 +32,12 @@ interface MockProgramWithConfigFile { __FROM_CONFIG_FILE__?: string; } -jest.mock('../../src/create-program/shared.ts', () => { +vi.mock(import('../../src/create-program/shared.js'), async importOriginal => { + const actual = await importOriginal(); + return { - ...jest.requireActual('../../src/create-program/shared.ts'), - getAstFromProgram(program: MockProgramWithConfigFile): unknown { + ...actual, + getAstFromProgram: ((program: MockProgramWithConfigFile): unknown => { if ( program.__FROM_CONFIG_FILE__?.endsWith('non-matching-tsconfig.json') ) { @@ -44,39 +46,49 @@ jest.mock('../../src/create-program/shared.ts', () => { // Remove temporary tracking value for the config added by mock createProgramFromConfigFile() below delete program.__FROM_CONFIG_FILE__; return { ast: {}, program }; - }, + }) as unknown as typeof actual.getAstFromProgram, }; }); -jest.mock('../../src/create-program/useProvidedPrograms.ts', () => { - return { - ...jest.requireActual('../../src/create-program/useProvidedPrograms.ts'), - createProgramFromConfigFile: jest - .fn() - .mockImplementation((configFile): MockProgramWithConfigFile => { - return { - // So we can differentiate our mock return values based on which tsconfig this is - __FROM_CONFIG_FILE__: configFile, - ...mockProgram, - }; - }), - }; -}); +vi.mock( + import('../../src/create-program/useProvidedPrograms.js'), + async importOriginal => { + const actual = await importOriginal(); + + return { + ...actual, + createProgramFromConfigFile: vi.fn( + (configFile): MockProgramWithConfigFile => { + return { + // So we can differentiate our mock return values based on which tsconfig this is + __FROM_CONFIG_FILE__: configFile, + ...mockProgram, + }; + }, + ) as unknown as typeof actual.createProgramFromConfigFile, + }; + }, +); -jest.mock('../../src/create-program/getWatchProgramsForProjects', () => { - return { - ...jest.requireActual( - '../../src/create-program/getWatchProgramsForProjects', - ), - getWatchProgramsForProjects: jest.fn(() => [mockProgram]), - }; -}); +vi.mock( + import('../../src/create-program/getWatchProgramsForProjects.js'), + async importOriginal => { + const actual = await importOriginal(); + + return { + ...actual, + getWatchProgramsForProjects: vi.fn(() => [ + mockProgram, + ]) as unknown as typeof actual.getWatchProgramsForProjects, + }; + }, +); -const createProgramFromConfigFile = jest.mocked( +const createProgramFromConfigFile = vi.mocked( createProgramFromConfigFileOriginal, ); -const FIXTURES_DIR = './tests/fixtures/semanticInfo'; +const FIXTURES_DIR = path.join(__dirname, '..', 'fixtures', 'semanticInfo'); const testFiles = glob.sync(`**/*.src.ts`, { cwd: FIXTURES_DIR, }); @@ -89,11 +101,10 @@ const options = { filePath: testFiles[0], loggerFn: false, project: tsconfigs, - tsconfigRootDir: path.join(process.cwd(), FIXTURES_DIR), + tsconfigRootDir: FIXTURES_DIR, } as const; -const resolvedProject = (p: string): string => - path.resolve(path.join(process.cwd(), FIXTURES_DIR), p); +const resolvedProject = (p: string): string => path.resolve(FIXTURES_DIR, p); describe('semanticInfo - singleRun', () => { beforeEach(() => { @@ -107,8 +118,7 @@ describe('semanticInfo - singleRun', () => { it('should not create any programs ahead of time by default when there is no way to infer singleRun=true', () => { // For when these tests themselves are running in CI, we need to ignore that for this particular spec - const originalEnvCI = process.env.CI; - process.env.CI = 'false'; + vi.stubEnv('CI', 'false'); /** * At this point there is nothing to indicate it is a single run, so createProgramFromConfigFile should @@ -116,34 +126,25 @@ describe('semanticInfo - singleRun', () => { */ parseAndGenerateServices(code, options); expect(createProgramFromConfigFile).not.toHaveBeenCalled(); - - // Restore process data - process.env.CI = originalEnvCI; }); it('should not create any programs ahead of time when when TSESTREE_SINGLE_RUN=false, even if other inferrence criteria apply', () => { - const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; - process.env.TSESTREE_SINGLE_RUN = 'false'; + vi.stubEnv('TSESTREE_SINGLE_RUN', 'false'); // Normally CI=true would be used to infer singleRun=true, but TSESTREE_SINGLE_RUN is explicitly set to false - const originalEnvCI = process.env.CI; - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); parseAndGenerateServices(code, options); expect(createProgramFromConfigFile).not.toHaveBeenCalled(); - - // Restore process data - process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; - process.env.CI = originalEnvCI; }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it('should lazily create the required program out of the provided "parserOptions.project" one time when TSESTREE_SINGLE_RUN=true', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'should lazily create the required program out of the provided "parserOptions.project" one time when TSESTREE_SINGLE_RUN=true', + () => { /** * Single run because of explicit environment variable TSESTREE_SINGLE_RUN */ - const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; - process.env.TSESTREE_SINGLE_RUN = 'true'; + vi.stubEnv('TSESTREE_SINGLE_RUN', 'true'); const resultProgram = parseAndGenerateServices(code, options).services .program; @@ -164,18 +165,17 @@ describe('semanticInfo - singleRun', () => { 2, resolvedProject(tsconfigs[1]), ); + }, + ); - // Restore process data - process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; - }); - - it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from CI=true', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from CI=true', + () => { /** * Single run because of CI=true (we need to make sure we respect the original value * so that we won't interfere with our own usage of the variable) */ - const originalEnvCI = process.env.CI; - process.env.CI = 'true'; + vi.stubEnv('CI', 'true'); const resultProgram = parseAndGenerateServices(code, options).services .program; @@ -196,17 +196,19 @@ describe('semanticInfo - singleRun', () => { 2, resolvedProject(tsconfigs[1]), ); + }, + ); - // Restore process data - process.env.CI = originalEnvCI; - }); - - it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from process.argv', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from process.argv', + () => { /** * Single run because of process.argv */ - const originalProcessArgv = process.argv; - process.argv = ['', path.normalize('node_modules/.bin/eslint'), '']; + vi.stubGlobal('process', { + ...process, + argv: ['', path.normalize('node_modules/.bin/eslint'), ''], + }); const resultProgram = parseAndGenerateServices(code, options).services .program; @@ -227,17 +229,16 @@ describe('semanticInfo - singleRun', () => { 2, resolvedProject(tsconfigs[1]), ); + }, + ); - // Restore process data - process.argv = originalProcessArgv; - }); - - it('should stop iterating through and lazily creating programs for the given "parserOptions.project" once a matching one has been found', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'should stop iterating through and lazily creating programs for the given "parserOptions.project" once a matching one has been found', + () => { /** * Single run because of explicit environment variable TSESTREE_SINGLE_RUN */ - const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; - process.env.TSESTREE_SINGLE_RUN = 'true'; + vi.stubEnv('TSESTREE_SINGLE_RUN', 'true'); const optionsWithReversedTsconfigs = { ...options, @@ -253,16 +254,10 @@ describe('semanticInfo - singleRun', () => { // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... parseAndGenerateServices(code, options); - // ...by asserting this was only called only once - expect(createProgramFromConfigFile).toHaveBeenCalledTimes(1); - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 1, + expect(createProgramFromConfigFile).toHaveBeenCalledExactlyOnceWith( resolvedProject(tsconfigs[1]), ); - - // Restore process data - process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; - }); - } + }, + ); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 135b4b9afc0d..f7fc20703d38 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -1,5 +1,5 @@ import * as glob from 'glob'; -import * as fs from 'node:fs'; +import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import * as ts from 'typescript'; @@ -12,12 +12,12 @@ import { createProgramFromConfigFile as createProgram } from '../../src/create-p import { parseAndGenerateServices } from '../../src/parser'; import { expectToHaveParserServices } from '../test-utils/expectToHaveParserServices'; import { - createSnapshotTestBlock, + deeplyCopy, formatSnapshotName, parseCodeAndGenerateServices, } from '../test-utils/test-utils'; -const FIXTURES_DIR = './tests/fixtures/semanticInfo'; +const FIXTURES_DIR = path.join(__dirname, '..', 'fixtures', 'semanticInfo'); const testFiles = glob.sync(`**/*.src.ts`, { cwd: FIXTURES_DIR, }); @@ -34,34 +34,49 @@ function createOptions(fileName: string): { cwd?: string } & TSESTreeOptions { project: `./tsconfig.json`, range: true, tokens: true, - tsconfigRootDir: path.join(process.cwd(), FIXTURES_DIR), + tsconfigRootDir: FIXTURES_DIR, }; } // ensure tsconfig-parser watch caches are clean for each test -beforeEach(() => clearCaches()); +beforeEach(() => { + clearCaches(); +}); -describe('semanticInfo', () => { +describe('semanticInfo', async () => { beforeEach(() => { - process.env.TSESTREE_SINGLE_RUN = ''; + vi.stubEnv('TSESTREE_SINGLE_RUN', ''); }); // test all AST snapshots - testFiles.forEach(filename => { - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); - it( - formatSnapshotName(filename, FIXTURES_DIR, path.extname(filename)), - createSnapshotTestBlock( - code, - createOptions(filename), - /*generateServices*/ true, - ), - ); + const testCases = await Promise.all( + testFiles.map(async filename => { + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); + const snapshotName = formatSnapshotName( + filename, + FIXTURES_DIR, + path.extname(filename), + ); + + const { ast } = parseAndGenerateServices(code, createOptions(filename)); + + const result = deeplyCopy(ast); + + return [snapshotName, result] as const; + }), + ); + + it.for(testCases)('%s', ([, result], { expect }) => { + expect(result).toMatchSnapshot(); }); - it(`should cache the created ts.program`, () => { + it(`should cache the created ts.program`, async () => { const filename = testFiles[0]; - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); const options = createOptions(filename); const optionsProjectString = { ...options, @@ -74,9 +89,11 @@ describe('semanticInfo', () => { ); }); - it(`should handle "project": "./tsconfig.json" and "project": ["./tsconfig.json"] the same`, () => { + it(`should handle "project": "./tsconfig.json" and "project": ["./tsconfig.json"] the same`, async () => { const filename = testFiles[0]; - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); const options = createOptions(filename); const optionsProjectString = { ...options, @@ -100,9 +117,11 @@ describe('semanticInfo', () => { ); }); - it(`should resolve absolute and relative tsconfig paths the same`, () => { + it(`should resolve absolute and relative tsconfig paths the same`, async () => { const filename = testFiles[0]; - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); const options = createOptions(filename); const optionsAbsolutePath = { ...options, @@ -133,36 +152,42 @@ describe('semanticInfo', () => { }); // case-specific tests - it('isolated-file tests', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const parseResult = parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - createOptions(fileName), - ); + it.skipIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true')( + 'isolated-file tests', + async () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const parseResult = parseCodeAndGenerateServices( + await fs.readFile(fileName, { encoding: 'utf-8' }), + createOptions(fileName), + ); - testIsolatedFile(parseResult); - }); + testIsolatedFile(parseResult); + }, + ); - it('isolated-vue-file tests', () => { - const fileName = path.resolve(FIXTURES_DIR, 'extra-file-extension.vue'); - const parseResult = parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - { - ...createOptions(fileName), - extraFileExtensions: ['.vue'], - }, - ); + it.skipIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true')( + 'isolated-vue-file tests', + async () => { + const fileName = path.resolve(FIXTURES_DIR, 'extra-file-extension.vue'); + const parseResult = parseCodeAndGenerateServices( + await fs.readFile(fileName, { encoding: 'utf-8' }), + { + ...createOptions(fileName), + extraFileExtensions: ['.vue'], + }, + ); - testIsolatedFile(parseResult); - }); + testIsolatedFile(parseResult); + }, + ); - it('non-existent-estree-nodes tests', () => { + it('non-existent-estree-nodes tests', async () => { const fileName = path.resolve( FIXTURES_DIR, 'non-existent-estree-nodes.src.ts', ); const parseResult = parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), + await fs.readFile(fileName, { encoding: 'utf-8' }), createOptions(fileName), ); @@ -182,13 +207,13 @@ describe('semanticInfo', () => { const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap.get(computedPropertyString); expect(tsComputedPropertyString).toBeDefined(); - expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral); + expect(tsComputedPropertyString.kind).toBe(ts.SyntaxKind.StringLiteral); }); - it('imported-file tests', () => { + it('imported-file tests', async () => { const fileName = path.resolve(FIXTURES_DIR, 'import-file.src.ts'); const parseResult = parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), + await fs.readFile(fileName, { encoding: 'utf-8' }), createOptions(fileName), ); @@ -251,8 +276,9 @@ describe('semanticInfo', () => { expect(parseResult.services.program).toBeDefined(); }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it(`non-existent file should throw error when project provided`, () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + `non-existent file should throw error when project provided`, + () => { expect(() => parseCodeAndGenerateServices( `function M() { return Base }`, @@ -261,51 +287,50 @@ describe('semanticInfo', () => { ).toThrow( /ESLint was configured to run on `\/estree\.ts` using/, ); - }); - } + }, + ); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it('non-existent project file', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'non-existent project file', + async () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName); badConfig.project = './tsconfigs.json'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow(/Cannot read file .+tsconfigs\.json'/); - }); + const code = await fs.readFile(fileName, { encoding: 'utf-8' }); + expect(() => parseCodeAndGenerateServices(code, badConfig)).toThrow( + /Cannot read file .+tsconfigs\.json'/, + ); + }, + ); - it('fail to read project file', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'fail to read project file', + async () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName); badConfig.project = '.'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow( + const code = await fs.readFile(fileName, { encoding: 'utf-8' }); + expect(() => parseCodeAndGenerateServices(code, badConfig)).toThrow( // case insensitive because unix based systems are case insensitive /Cannot read file .+semanticInfo'/i, ); - }); + }, + ); - it('malformed project file', () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'malformed project file', + async () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName); badConfig.project = './badTSConfig/tsconfig.json'; + const code = await fs.readFile(fileName, { encoding: 'utf-8' }); expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), + parseCodeAndGenerateServices(code, badConfig), ).toThrowErrorMatchingInlineSnapshot( - `"Compiler option 'compileOnSave' requires a value of type boolean."`, + `[Error: Compiler option 'compileOnSave' requires a value of type boolean.]`, ); - }); - } + }, + ); it('empty programs array should throw', () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); @@ -316,12 +341,15 @@ describe('semanticInfo', () => { ); }); - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') { - it(`first matching provided program instance is returned in result`, () => { + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + `first matching provided program instance is returned in result`, + async () => { const filename = testFiles[0]; const program1 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); const program2 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); const options = createOptions(filename); const optionsProjectString = { ...options, @@ -330,10 +358,13 @@ describe('semanticInfo', () => { }; const parseResult = parseAndGenerateServices(code, optionsProjectString); expect(parseResult.services.program).toBe(program1); - }); + }, + ); - it('file not in single provided project instance in single-run mode should throw', () => { - process.env.TSESTREE_SINGLE_RUN = 'true'; + it.runIf(process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true')( + 'file not in single provided project instance in single-run mode should throw', + () => { + vi.stubEnv('TSESTREE_SINGLE_RUN', 'true'); const filename = 'non-existent-file.ts'; const options = createOptions(filename); const optionsWithProjectTrue = { @@ -348,8 +379,8 @@ describe('semanticInfo', () => { ? `${filename} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.` : `The file was not found in any of the provided project(s): ${filename}`, ); - }); - } + }, + ); it('file not in single provided program instance should throw', () => { const filename = 'non-existent-file.ts'; @@ -412,10 +443,6 @@ describe('semanticInfo', () => { function testIsolatedFile( parseResult: ParseAndGenerateServicesResult, ): void { - if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true') { - return; - } - // get type checker expectToHaveParserServices(parseResult.services); const checker = parseResult.services.program.getTypeChecker(); diff --git a/packages/typescript-estree/tests/lib/source-files.test.ts b/packages/typescript-estree/tests/lib/source-files.test.ts index 1986b25094e3..b0700fce804e 100644 --- a/packages/typescript-estree/tests/lib/source-files.test.ts +++ b/packages/typescript-estree/tests/lib/source-files.test.ts @@ -2,13 +2,15 @@ import * as ts from 'typescript'; import { getCodeText, isSourceFile } from '../../src/source-files'; -describe('isSourceFile', () => { - it.each([null, undefined, {}, { getFullText: (): string => '', text: '' }])( - `returns false when given %j`, - input => { - expect(isSourceFile(input)).toBe(false); - }, - ); +describe(isSourceFile, () => { + it.for([ + [null], + [undefined], + [{}], + [{ getFullText: (): string => '', text: '' }], + ] as const)('returns false when given %o', ([input], { expect }) => { + expect(isSourceFile(input)).toBe(false); + }); it('returns true when given a real source file', () => { const input = ts.createSourceFile('test.ts', '', ts.ScriptTarget.ESNext); @@ -18,7 +20,7 @@ describe('isSourceFile', () => { }); }); -describe('getCodeText', () => { +describe(getCodeText, () => { it('returns the code when code is provided as a string', () => { const code = '// Hello world'; diff --git a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts index ef319255b766..9aeafde8ebd2 100644 --- a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts @@ -9,30 +9,30 @@ import type { ParseSettings } from '../../src/parseSettings'; import { useProgramFromProjectService } from '../../src/useProgramFromProjectService'; -const mockCreateNoProgram = jest.fn(); +const mockCreateNoProgram = vi.fn(); -jest.mock('../../src/create-program/createSourceFile', () => ({ +vi.mock('../../src/create-program/createSourceFile', () => ({ get createNoProgram() { return mockCreateNoProgram; }, })); -const mockCreateProjectProgram = jest.fn(); +const mockCreateProjectProgram = vi.fn(); -jest.mock('../../src/create-program/createProjectProgram', () => ({ +vi.mock('../../src/create-program/createProjectProgram', () => ({ get createProjectProgram() { return mockCreateProjectProgram; }, })); -const mockGetProgram = jest.fn(); +const mockGetProgram = vi.fn(); const currentDirectory = '/repos/repo'; function createMockProjectService() { - const openClientFile = jest.fn(); - const setHostConfiguration = jest.fn(); - const reloadProjects = jest.fn(); + const openClientFile = vi.fn(); + const setHostConfiguration = vi.fn(); + const reloadProjects = vi.fn(); const service = { getDefaultProjectForFile: () => ({ getLanguageService: () => ({ @@ -74,7 +74,7 @@ const createProjectServiceSettings = < ...settings, }); -describe('useProgramFromProjectService', () => { +describe(useProgramFromProjectService, () => { it('creates a standalone AST with no program when hasFullTypeInformation is false and allowDefaultProject is falsy', () => { const { service } = createMockProjectService(); @@ -111,7 +111,7 @@ describe('useProgramFromProjectService', () => { new Set(), ); - expect(service.openClientFile).toHaveBeenCalledWith( + expect(service.openClientFile).toHaveBeenCalledExactlyOnceWith( path.normalize( `${currentDirectory}/path/PascalCaseDirectory/camelCaseFile.ts`, ), @@ -185,12 +185,12 @@ describe('useProgramFromProjectService', () => { ).toThrow( `${mockParseSettings.filePath} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.`, ); - expect(service.reloadProjects).toHaveBeenCalledTimes(1); + expect(service.reloadProjects).toHaveBeenCalledOnce(); }); it('returns a created program after reloading projects when hasFullTypeInformation is enabled, the file is only in the project service after reload, and the last reload was recent', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; service.openClientFile.mockReturnValueOnce({}).mockReturnValueOnce({ configFileName: 'tsconfig.json', @@ -211,12 +211,12 @@ describe('useProgramFromProjectService', () => { ); expect(actual).toBe(program); - expect(service.reloadProjects).toHaveBeenCalledTimes(1); + expect(service.reloadProjects).toHaveBeenCalledOnce(); }); it('throws an error when more than the maximum allowed file count is matched to the default project', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; mockGetProgram.mockReturnValueOnce(program); @@ -250,7 +250,7 @@ If you absolutely need more files included, set parserOptions.projectService.max it('truncates the files printed by the maximum allowed files error when they exceed the print limit', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; mockGetProgram.mockReturnValueOnce(program); @@ -324,7 +324,7 @@ If you absolutely need more files included, set parserOptions.projectService.max it('returns a created program when hasFullTypeInformation is disabled, the file is both in the project service and allowDefaultProject, and the service has a matching program', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; mockGetProgram.mockReturnValueOnce(program); @@ -348,7 +348,7 @@ If you absolutely need more files included, set parserOptions.projectService.max it('returns undefined when hasFullTypeInformation is disabled, the file is neither in the project service nor allowDefaultProject, and the service has a matching program', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; mockGetProgram.mockReturnValueOnce(program); @@ -370,7 +370,7 @@ If you absolutely need more files included, set parserOptions.projectService.max it('returns undefined when hasFullTypeInformation is disabled, the file is in the project service, the service has a matching program, and no out', () => { const { service } = createMockProjectService(); - const program = { getSourceFile: jest.fn() }; + const program = { getSourceFile: vi.fn() }; mockGetProgram.mockReturnValueOnce(program); @@ -444,7 +444,7 @@ If you absolutely need more files included, set parserOptions.projectService.max new Set(), ); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ extraFileExtensions: [ { extension: '.vue', @@ -472,8 +472,7 @@ If you absolutely need more files included, set parserOptions.projectService.max new Set(), ); - expect(service.setHostConfiguration).toHaveBeenCalledTimes(1); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ extraFileExtensions: [ { extension: '.vue', @@ -492,7 +491,7 @@ If you absolutely need more files included, set parserOptions.projectService.max false, new Set(), ); - expect(service.setHostConfiguration).toHaveBeenCalledTimes(1); + expect(service.setHostConfiguration).toHaveBeenCalledOnce(); }); it('calls setHostConfiguration on the service to use extraFileExtensions when changed', () => { @@ -512,8 +511,7 @@ If you absolutely need more files included, set parserOptions.projectService.max new Set(), ); - expect(service.setHostConfiguration).toHaveBeenCalledTimes(1); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ extraFileExtensions: [ { extension: '.vue', @@ -534,7 +532,7 @@ If you absolutely need more files included, set parserOptions.projectService.max ); expect(service.setHostConfiguration).toHaveBeenCalledTimes(2); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenLastCalledWith({ extraFileExtensions: [], }); }); @@ -556,8 +554,7 @@ If you absolutely need more files included, set parserOptions.projectService.max new Set(), ); - expect(service.setHostConfiguration).toHaveBeenCalledTimes(1); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ extraFileExtensions: [ { extension: '.vue', @@ -570,7 +567,7 @@ If you absolutely need more files included, set parserOptions.projectService.max useProgramFromProjectService(settings, mockParseSettings, false, new Set()); expect(service.setHostConfiguration).toHaveBeenCalledTimes(2); - expect(service.setHostConfiguration).toHaveBeenCalledWith({ + expect(service.setHostConfiguration).toHaveBeenLastCalledWith({ extraFileExtensions: [], }); }); diff --git a/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts b/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts index f0f99363e47b..9eccb60f8cc1 100644 --- a/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts +++ b/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts @@ -1,6 +1,6 @@ import { validateDefaultProjectForFilesGlob } from '../../src/create-program/validateDefaultProjectForFilesGlob'; -describe('validateDefaultProjectForFilesGlob', () => { +describe(validateDefaultProjectForFilesGlob, () => { it('does not throw when options.allowDefaultProject is an empty array', () => { expect(() => validateDefaultProjectForFilesGlob([])).not.toThrow(); }); diff --git a/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.test.ts b/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.test.ts index 8a70192aac72..1404d0dcd86a 100644 --- a/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.test.ts +++ b/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.test.ts @@ -2,30 +2,32 @@ import semver from 'semver'; import type * as Parser from '../../src/parser'; -jest.mock('semver'); - -const resetIsTTY = process.stdout.isTTY; +vi.mock('semver'); describe('Warn on unsupported TypeScript version', () => { let parser: typeof Parser; + const semverSatisfiesMock = vi.mocked(semver.satisfies); + beforeEach(async () => { - // @ts-expect-error -- We don't support ESM imports of local code yet. - parser = await import('../../src/parser'); + parser = await import('../../src/parser.js'); }); afterEach(() => { - jest.resetModules(); - jest.resetAllMocks(); - process.stdout.isTTY = resetIsTTY; + vi.resetModules(); + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); }); it('should warn the user if they are using an unsupported TypeScript version', () => { - (semver.satisfies as jest.Mock).mockReturnValue(false); - jest.spyOn(console, 'log').mockImplementation(); - process.stdout.isTTY = true; + semverSatisfiesMock.mockReturnValueOnce(false); + vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.stubGlobal('process', { ...process, stdout: { isTTY: true } }); parser.parse(''); - expect(console.log).toHaveBeenCalledWith( + expect(console.log).toHaveBeenCalledExactlyOnceWith( expect.stringContaining( 'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.', ), @@ -33,20 +35,20 @@ describe('Warn on unsupported TypeScript version', () => { }); it('should warn the user if they are running on a non TTY process and a custom loggerFn was passed', () => { - (semver.satisfies as jest.Mock).mockReturnValue(false); - const loggerFn = jest.fn(); - process.stdout.isTTY = false; + semverSatisfiesMock.mockReturnValueOnce(false); + const loggerFn = vi.fn(); + vi.stubGlobal('process', { ...process, stdout: { isTTY: false } }); parser.parse('', { loggerFn, }); - expect(loggerFn).toHaveBeenCalled(); + expect(loggerFn).toHaveBeenCalledOnce(); }); it('should not warn the user if they are running on a non TTY process and a custom loggerFn was not passed', () => { - (semver.satisfies as jest.Mock).mockReturnValue(false); - jest.spyOn(console, 'log').mockImplementation(); - process.stdout.isTTY = false; + semverSatisfiesMock.mockReturnValueOnce(false); + vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.stubGlobal('process', { ...process, stdout: { isTTY: false } }); parser.parse(''); expect(console.log).not.toHaveBeenCalled(); diff --git a/packages/typescript-estree/tests/lib/withoutProjectParserOptions.test.ts b/packages/typescript-estree/tests/lib/withoutProjectParserOptions.test.ts index 2b4a16fa78d8..264809166fbd 100644 --- a/packages/typescript-estree/tests/lib/withoutProjectParserOptions.test.ts +++ b/packages/typescript-estree/tests/lib/withoutProjectParserOptions.test.ts @@ -2,7 +2,7 @@ import type { TSESTreeOptions } from '../../src'; import { withoutProjectParserOptions } from '../../src'; -describe('withoutProjectParserOptions', () => { +describe(withoutProjectParserOptions, () => { it('removes only project parser options', () => { const options = { comment: true, diff --git a/packages/typescript-estree/tests/test-utils/test-utils.ts b/packages/typescript-estree/tests/test-utils/test-utils.ts index 3ea4e57ea175..14c19c9bf271 100644 --- a/packages/typescript-estree/tests/test-utils/test-utils.ts +++ b/packages/typescript-estree/tests/test-utils/test-utils.ts @@ -1,10 +1,9 @@ import type { ParseAndGenerateServicesResult, - TSESTree, TSESTreeOptions, } from '../../src'; -import { parseAndGenerateServices, parse as parserParse } from '../../src'; +import { parseAndGenerateServices } from '../../src'; export function parseCodeAndGenerateServices( code: string, @@ -13,46 +12,6 @@ export function parseCodeAndGenerateServices( return parseAndGenerateServices(code, config); } -/** - * Returns a function which can be used as the callback of a Jest test() block, - * and which performs an assertion on the snapshot for the given code and config. - * @param code The source code to parse - * @param config the parser configuration - * @param generateServices Flag determining whether to generate ast maps and program or not - * @returns callback for Jest it() block - */ -export function createSnapshotTestBlock( - code: string, - config: TSESTreeOptions, - generateServices?: true, -): jest.ProvidesCallback { - /** - * @returns the AST object - */ - function parse(): TSESTree.Program { - const ast = generateServices - ? parseAndGenerateServices(code, config).ast - : parserParse(code, config); - return deeplyCopy(ast); - } - - return (): void => { - try { - const result = parse(); - expect(result).toMatchSnapshot(); - } catch (error) { - /** - * If we are deliberately throwing because of encountering an unknown - * AST_NODE_TYPE, we rethrow to cause the test to fail - */ - if ((error as Error).message.includes('Unknown AST_NODE_TYPE')) { - throw error; - } - expect(parse).toThrowErrorMatchingSnapshot(); - } - }; -} - export function formatSnapshotName( filename: string, fixturesDir: string, diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index c055fde615f3..4ffc7ff2cc59 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -9,7 +9,7 @@ "types": ["node"] }, "include": ["src/**/*.ts", "typings"], - "exclude": ["jest.config.js", "src/**/*.spec.ts", "src/**/*.test.ts"], + "exclude": ["vitest.config.mts", "src/**/*.spec.ts", "src/**/*.test.ts"], "references": [ { "path": "../visitor-keys/tsconfig.build.json" diff --git a/packages/typescript-estree/tsconfig.spec.json b/packages/typescript-estree/tsconfig.spec.json index 5ab0499df71e..39d5f78b4b0b 100644 --- a/packages/typescript-estree/tsconfig.spec.json +++ b/packages/typescript-estree/tsconfig.spec.json @@ -4,10 +4,11 @@ "outDir": "../../dist/out-tsc/packages/typescript-estree", "module": "NodeNext", "resolveJsonModule": true, - "types": ["jest", "node"] + "types": ["node", "vitest/globals", "vitest/importMeta"] }, "include": [ - "jest.config.js", + "vitest.config.mts", + "package.json", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts", @@ -17,6 +18,9 @@ "references": [ { "path": "./tsconfig.build.json" + }, + { + "path": "../../tsconfig.spec.json" } ] } diff --git a/packages/typescript-estree/vitest.config.mts b/packages/typescript-estree/vitest.config.mts new file mode 100644 index 000000000000..82868b9d3fc0 --- /dev/null +++ b/packages/typescript-estree/vitest.config.mts @@ -0,0 +1,29 @@ +import * as path from 'node:path'; +import { defaultExclude, defineProject, mergeConfig } from 'vitest/config'; + +import { vitestBaseConfig } from '../../vitest.config.base.mjs'; +import packageJson from './package.json' with { type: 'json' }; + +const vitestConfig = mergeConfig( + vitestBaseConfig, + + defineProject({ + root: import.meta.dirname, + + test: { + dir: path.join(import.meta.dirname, 'tests', 'lib'), + + exclude: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE + ? [...defaultExclude, 'parse.project-true.test.ts'] + : [...defaultExclude], + + name: packageJson.name.replace('@typescript-eslint/', ''), + root: import.meta.dirname, + testTimeout: 15_000, + unstubEnvs: true, + unstubGlobals: true, + }, + }), +); + +export default vitestConfig; diff --git a/yarn.lock b/yarn.lock index 2d88c4602d34..8ae724026e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5925,13 +5925,6 @@ __metadata: languageName: node linkType: hard -"@types/tmp@npm:^0.2.6": - version: 0.2.6 - resolution: "@types/tmp@npm:0.2.6" - checksum: 0b24bb6040cc289440a609e10ec99a704978c890a5828ff151576489090b2257ce2e2570b0f320ace9c8099c3642ea6221fbdf6d8f2e22b7cd1f4fbf6e989e3e - languageName: node - linkType: hard - "@types/trusted-types@npm:^2.0.2": version: 2.0.2 resolution: "@types/trusted-types@npm:2.0.2" @@ -6213,7 +6206,6 @@ __metadata: "@types/natural-compare": ^1.4.3 "@types/node": ^20.12.5 "@types/semver": ^7.5.8 - "@types/tmp": ^0.2.6 "@types/yargs": ^17.0.32 "@typescript-eslint/eslint-plugin": "workspace:^" "@typescript-eslint/eslint-plugin-internal": "workspace:^" @@ -6266,21 +6258,20 @@ __metadata: version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: - "@jest/types": 29.6.3 "@typescript-eslint/types": 8.29.1 "@typescript-eslint/visitor-keys": 8.29.1 + "@vitest/coverage-v8": ^3.1.1 debug: ^4.3.4 fast-glob: ^3.3.2 glob: "*" is-glob: ^4.0.3 - jest: 29.7.0 minimatch: ^9.0.4 prettier: ^3.2.5 rimraf: "*" semver: ^7.6.0 - tmp: "*" ts-api-utils: ^2.0.1 typescript: "*" + vitest: ^3.1.1 peerDependencies: typescript: ">=4.8.4 <5.9.0" languageName: unknown