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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy