From fb4cc679f34f204285f0bbb17d3793e73addad3d Mon Sep 17 00:00:00 2001 From: Brody McKee Date: Sat, 9 Mar 2024 00:50:42 +1100 Subject: [PATCH] feat: add support for expectTypeOf to expect-expect (#386) --- README.md | 16 ++++++++++++++++ docs/rules/expect-expect.md | 4 ++++ src/rules/expect-expect.ts | 11 +++++++---- src/utils/parsePluginSettings.ts | 20 ++++++++++++++++++++ tests/expect-expect.test.ts | 21 +++++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/utils/parsePluginSettings.ts diff --git a/README.md b/README.md index f2bb38f..6b54f6a 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,22 @@ export default [ ]; ``` +#### Enabling with type-testing + +Vitest ships with an optional [type-testing feature](https://vitest.dev/guide/testing-types), which is disabled by default. + +If you're using this feature, you should also enabled `typecheck` in the settings for this plugin. This ensures that rules like [expect-expect](docs/rules/expect-expect.md) account for type-related assertions in tests. + +```json +{ + "extends": ["plugin:vitest/recommended"], + "settings" :{ + "vitest": { + "typecheck": true, + } + } +} +``` ### Rules diff --git a/docs/rules/expect-expect.md b/docs/rules/expect-expect.md index e093ee5..ee50787 100644 --- a/docs/rules/expect-expect.md +++ b/docs/rules/expect-expect.md @@ -28,6 +28,10 @@ test('myLogic', () => { }) ``` +## Type-testing + +If you're using Vitest's [type-testing feature](https://vitest.dev/guide/testing-types) and have tests that only contain `expectTypeOf`, you will need to enable `typecheck` in this plugin's settings: [Enabling type-testing](../../README.md#enabling-with-type-testing). + ## Options ### `assertFunctionNames` diff --git a/src/rules/expect-expect.ts b/src/rules/expect-expect.ts index f5e2752..4dfac58 100644 --- a/src/rules/expect-expect.ts +++ b/src/rules/expect-expect.ts @@ -1,5 +1,6 @@ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils' import { createEslintRule, getNodeName, isSupportedAccessor } from '../utils' +import { parsePluginSettings } from '../utils/parsePluginSettings' import { getTestCallExpressionsFromDeclaredVariables, isTypeOfVitestFnCall } from '../utils/parseVitestFnCall' export const RULE_NAME = 'expect-expect' @@ -62,6 +63,9 @@ export default createEslintRule({ defaultOptions: [{ assertFunctionNames: ['expect'], additionalTestBlockFunctions: [] }], create(context, [{ assertFunctionNames = ['expect'], additionalTestBlockFunctions = [] }]) { const unchecked: TSESTree.CallExpression[] = [] + const settings = parsePluginSettings(context.settings) + + if (settings.typecheck) assertFunctionNames.push('expectTypeOf') function checkCallExpression(nodes: TSESTree.Node[]) { for (const node of nodes) { @@ -82,12 +86,11 @@ export default createEslintRule({ } return { - CallExpression(node) { - if (node?.callee?.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === 'skip') + CallExpression(node) { + if (node?.callee?.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === 'skip') return - - const name = getNodeName(node) ?? '' + const name = getNodeName(node) ?? '' if (isTypeOfVitestFnCall(node, context, ['test']) || additionalTestBlockFunctions.includes(name)) { if (node.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, 'todo')) return diff --git a/src/utils/parsePluginSettings.ts b/src/utils/parsePluginSettings.ts new file mode 100644 index 0000000..a835ee5 --- /dev/null +++ b/src/utils/parsePluginSettings.ts @@ -0,0 +1,20 @@ +import type { SharedConfigurationSettings } from '@typescript-eslint/utils/dist/ts-eslint' + +interface PluginSettings { + typecheck: boolean; +} + +const DEFAULTS: PluginSettings = { + typecheck: false +} + +export function parsePluginSettings(settings: SharedConfigurationSettings): PluginSettings { + const pluginSettings = typeof settings.vitest !== 'object' || settings.vitest === null + ? {} + : settings.vitest + + return { + ...DEFAULTS, + ...pluginSettings + } +} diff --git a/tests/expect-expect.test.ts b/tests/expect-expect.test.ts index 2f362a2..9cbfb24 100644 --- a/tests/expect-expect.test.ts +++ b/tests/expect-expect.test.ts @@ -155,6 +155,14 @@ ruleTester.run(RULE_NAME, rule, { `, options: [{ assertFunctionNames: ['tester.foo.bar.expect'] }], parserOptions: { sourceType: 'module' } + }, + { + code: ` + it("should pass with 'typecheck' enabled", () => { + expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>() + }); + `, + settings: { vitest: { typecheck: true } } } ], invalid: [ @@ -304,6 +312,19 @@ ruleTester.run(RULE_NAME, rule, { type: AST_NODE_TYPES.Identifier } ] + }, + { + code: ` + it("should fail without 'typecheck' enabled", () => { + expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>() + }); + `, + errors: [ + { + messageId: 'noAssertions', + type: AST_NODE_TYPES.Identifier + } + ] } ] })