Skip to content

Commit

Permalink
Merge tsdjs#130
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy-mitchell committed Sep 13, 2022
2 parents b53b8d5 + ae6189b commit 7cbd8b4
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 5 deletions.
8 changes: 7 additions & 1 deletion readme.md
Expand Up @@ -166,6 +166,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.

## Configuration

`tsd` is designed to be used with as little configuration as possible. However, if you need a bit more control, a project's `package.json` and the `tsd` CLI offer a limited set of configurations.
Expand Down Expand Up @@ -233,7 +239,7 @@ These options will be overridden if a `tsconfig.json` file is found in your proj

The `tsd` CLI is designed to test a whole project at once, and as such only offers a couple of flags for configuration.

#### --typings
Asserts that the type of `expression` is identical to type `T`.

Alias: `-t`

Expand Down
11 changes: 11 additions & 0 deletions source/lib/assertions/assert.ts
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
@@ -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 7cbd8b4

Please sign in to comment.