From fb964be743bddad111d9bac1858abd4c06e9abfd Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Sat, 6 Nov 2021 22:17:35 +0900 Subject: [PATCH] Add support for expectNever --- readme.md | 6 +++++ source/lib/assertions/assert.ts | 9 +++++++ .../lib/assertions/handlers/identicality.ts | 27 ++++++++++++++++++- source/lib/assertions/handlers/index.ts | 2 +- source/lib/assertions/index.ts | 3 +++ .../identicality/identical/index.d.ts | 2 ++ .../fixtures/identicality/identical/index.js | 2 ++ .../identicality/identical/index.test-d.ts | 7 +++-- source/test/identicality.ts | 2 ++ 9 files changed, 56 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 4bfacb42..989d9f24 100644 --- a/readme.md +++ b/readme.md @@ -193,6 +193,12 @@ Prints the type of `expression` as a warning. Useful if you don't know the exact type of the expression passed to `printType()` or the type is too complex to write out by hand. +### expectNever(expression: never) + +Asserts that the type and return type of `expression` is `never`. + +Useful for checking that all branches are covered. + ## Programmatic API You can use the programmatic API to retrieve the diagnostics and do something with them. This can be useful to run the tests with AVA, Jest or any other testing framework. diff --git a/source/lib/assertions/assert.ts b/source/lib/assertions/assert.ts index 80e3d772..3943dfbd 100644 --- a/source/lib/assertions/assert.ts +++ b/source/lib/assertions/assert.ts @@ -72,6 +72,15 @@ export const expectNotDeprecated = (expression: any) => { // Do nothing, the TypeScript compiler handles this for us }; +/** + * Asserts that the type and return type of `expression` is `never`. + * + * @param expression - Expression that should be `never`. + */ +export const expectNever = (expression: never): never => { + return expression; +}; + /** * Prints the type of `expression` as a warning. * diff --git a/source/lib/assertions/handlers/identicality.ts b/source/lib/assertions/handlers/identicality.ts index 33c4e928..f375c134 100644 --- a/source/lib/assertions/handlers/identicality.ts +++ b/source/lib/assertions/handlers/identicality.ts @@ -1,4 +1,4 @@ -import {CallExpression, TypeChecker} from '@tsd/typescript'; +import {CallExpression, TypeChecker, TypeFlags} from '@tsd/typescript'; import {Diagnostic} from '../../interfaces'; import {makeDiagnostic} from '../../utils'; @@ -82,3 +82,28 @@ export const isNotIdentical = (checker: TypeChecker, nodes: Set) return diagnostics; }; + +/** + * Verifies that the argument of the assertion is `never` + * + * @param checker - The TypeScript type checker. + * @param nodes - The `expectNever` AST nodes. + * @return List of custom diagnostics. + */ +export const isNever = (checker: TypeChecker, nodes: Set): Diagnostic[] => { + const diagnostics: Diagnostic[] = []; + + if (!nodes) { + return diagnostics; + } + + for (const node of nodes) { + const argumentType = checker.getTypeAtLocation(node.arguments[0]); + + if (argumentType.flags !== TypeFlags.Never) { + diagnostics.push(makeDiagnostic(node, `Argument of type \`${checker.typeToString(argumentType)}\` is not \`never\`.`)); + } + } + + return diagnostics; +}; diff --git a/source/lib/assertions/handlers/index.ts b/source/lib/assertions/handlers/index.ts index 2646ac7f..01b3a47f 100644 --- a/source/lib/assertions/handlers/index.ts +++ b/source/lib/assertions/handlers/index.ts @@ -1,7 +1,7 @@ export {Handler} from './handler'; // Handlers -export {isIdentical, isNotIdentical} from './identicality'; +export {isIdentical, isNotIdentical, isNever} from './identicality'; export {isNotAssignable} from './assignability'; export {expectDeprecated, expectNotDeprecated} from './expect-deprecated'; export {prinTypeWarning} from './informational'; diff --git a/source/lib/assertions/index.ts b/source/lib/assertions/index.ts index bda816a4..a41b8024 100644 --- a/source/lib/assertions/index.ts +++ b/source/lib/assertions/index.ts @@ -7,6 +7,7 @@ import { isNotAssignable, expectDeprecated, expectNotDeprecated, + isNever, prinTypeWarning, } from './handlers'; @@ -18,6 +19,7 @@ export enum Assertion { EXPECT_NOT_ASSIGNABLE = 'expectNotAssignable', EXPECT_DEPRECATED = 'expectDeprecated', EXPECT_NOT_DEPRECATED = 'expectNotDeprecated', + EXPECT_NEVER = 'expectNever', PRINT_TYPE = 'printType', } @@ -28,6 +30,7 @@ const assertionHandlers = new Map([ [Assertion.EXPECT_NOT_ASSIGNABLE, isNotAssignable], [Assertion.EXPECT_DEPRECATED, expectDeprecated], [Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated], + [Assertion.EXPECT_NEVER, isNever], [Assertion.PRINT_TYPE, prinTypeWarning] ]); diff --git a/source/test/fixtures/identicality/identical/index.d.ts b/source/test/fixtures/identicality/identical/index.d.ts index 266914ab..e5e47847 100644 --- a/source/test/fixtures/identicality/identical/index.d.ts +++ b/source/test/fixtures/identicality/identical/index.d.ts @@ -4,3 +4,5 @@ declare const concat: { }; export default concat; + +export const returnsNever: () => never; diff --git a/source/test/fixtures/identicality/identical/index.js b/source/test/fixtures/identicality/identical/index.js index f17717f5..0162a427 100644 --- a/source/test/fixtures/identicality/identical/index.js +++ b/source/test/fixtures/identicality/identical/index.js @@ -1,3 +1,5 @@ module.exports.default = (foo, bar) => { return foo + bar; }; + +module.exports.returnsNever = () => {}; diff --git a/source/test/fixtures/identicality/identical/index.test-d.ts b/source/test/fixtures/identicality/identical/index.test-d.ts index 1af8c764..940734b1 100644 --- a/source/test/fixtures/identicality/identical/index.test-d.ts +++ b/source/test/fixtures/identicality/identical/index.test-d.ts @@ -1,5 +1,5 @@ -import {expectType} from '../../../..'; -import concat from '.'; +import {expectType, expectNever} from '../../../..'; +import concat, {returnsNever} from '.'; expectType(concat('foo', 'bar')); expectType(concat(1, 2)); @@ -11,3 +11,6 @@ expectType(concat(1, 2) as any); expectType('' as never); expectType('' as never); + +expectNever(returnsNever()); +expectNever(5 as number); diff --git a/source/test/identicality.ts b/source/test/identicality.ts index 6f7ec197..4d9abec4 100644 --- a/source/test/identicality.ts +++ b/source/test/identicality.ts @@ -12,6 +12,8 @@ test('identical', async t => { [10, 0, 'error', 'Parameter type `false` is not identical to argument type `any`.'], [12, 0, 'error', 'Parameter type `string` is declared too wide for argument type `never`.'], [13, 0, 'error', 'Parameter type `any` is declared too wide for argument type `never`.'], + [16, 0, 'error', 'Argument of type `number` is not `never`.'], + [16, 12, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'never\'.'], ]); });