Skip to content

Commit

Permalink
Add support for expectNever
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastienGllmt committed Sep 12, 2022
1 parent d32f3a7 commit fb964be
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 4 deletions.
6 changes: 6 additions & 0 deletions readme.md
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
9 changes: 9 additions & 0 deletions source/lib/assertions/assert.ts
Expand Up @@ -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.
*
Expand Down
27 changes: 26 additions & 1 deletion 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';

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
@@ -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
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
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
@@ -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
@@ -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
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 fb964be

Please sign in to comment.