Skip to content

Commit

Permalink
Add expectNever assertion (#130)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
SebastienGllmt and sindresorhus authored Sep 13, 2022
1 parent d32f3a7 commit ae6189b
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 4 deletions.
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions source/lib/assertions/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ 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`.
*
* Useful for checking that all branches are covered.
*
* @param expression - Expression that should be `never`.
*/
export const expectNever = (expression: never): never => {
return expression;
};

/**
* Prints the type of `expression` as a warning.
*
Expand Down
27 changes: 26 additions & 1 deletion source/lib/assertions/handlers/identicality.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -82,3 +82,28 @@ export const isNotIdentical = (checker: TypeChecker, nodes: Set<CallExpression>)

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<CallExpression>): 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;
};
2 changes: 1 addition & 1 deletion source/lib/assertions/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -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';
3 changes: 3 additions & 0 deletions source/lib/assertions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
isNotAssignable,
expectDeprecated,
expectNotDeprecated,
isNever,
prinTypeWarning,
} from './handlers';

Expand All @@ -18,6 +19,7 @@ export enum Assertion {
EXPECT_NOT_ASSIGNABLE = 'expectNotAssignable',
EXPECT_DEPRECATED = 'expectDeprecated',
EXPECT_NOT_DEPRECATED = 'expectNotDeprecated',
EXPECT_NEVER = 'expectNever',
PRINT_TYPE = 'printType',
}

Expand All @@ -28,6 +30,7 @@ const assertionHandlers = new Map<Assertion, Handler>([
[Assertion.EXPECT_NOT_ASSIGNABLE, isNotAssignable],
[Assertion.EXPECT_DEPRECATED, expectDeprecated],
[Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated],
[Assertion.EXPECT_NEVER, isNever],
[Assertion.PRINT_TYPE, prinTypeWarning]
]);

Expand Down
2 changes: 2 additions & 0 deletions source/test/fixtures/identicality/identical/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ declare const concat: {
};

export default concat;

export const returnsNever: () => never;
2 changes: 2 additions & 0 deletions source/test/fixtures/identicality/identical/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module.exports.default = (foo, bar) => {
return foo + bar;
};

module.exports.returnsNever = () => {};
7 changes: 5 additions & 2 deletions source/test/fixtures/identicality/identical/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectType} from '../../../..';
import concat from '.';
import {expectType, expectNever} from '../../../..';
import concat, {returnsNever} from '.';

expectType<string>(concat('foo', 'bar'));
expectType<number>(concat(1, 2));
Expand All @@ -11,3 +11,6 @@ expectType<false>(concat(1, 2) as any);

expectType<string>('' as never);
expectType<any>('' as never);

expectNever(returnsNever());
expectNever(5 as number);
2 changes: 2 additions & 0 deletions source/test/identicality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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\'.'],
]);
});

Expand Down

0 comments on commit ae6189b

Please sign in to comment.