Skip to content

Commit 6480ca9

Browse files
Implement printType helper to print the type of the passed expression as a warning (#115)
1 parent b749113 commit 6480ca9

File tree

13 files changed

+95
-11
lines changed

13 files changed

+95
-11
lines changed

readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ Check that `value` is marked a [`@deprecated`](https://jsdoc.app/tags-deprecated
189189

190190
Check that `value` is not marked a [`@deprecated`](https://jsdoc.app/tags-deprecated.html).
191191

192+
### printType(value)
193+
194+
Print the type of `value` as a warning.
195+
196+
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.
197+
192198

193199
## Programmatic API
194200

source/lib/assertions/assert.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,13 @@ export const expectDeprecated = (expression: any) => { // tslint:disable-line:n
6868
export const expectNotDeprecated = (expression: any) => { // tslint:disable-line:no-unused
6969
// Do nothing, the TypeScript compiler handles this for us
7070
};
71+
72+
/**
73+
* Will print a warning with the type of the expression passed as argument.
74+
*
75+
* @param expression - Expression whose type should be printed as a warning.
76+
*/
77+
// @ts-ignore
78+
export const printType = (expression: any) => { // tslint:disable-line:no-unused
79+
// Do nothing, the TypeScript compiler handles this for us
80+
};

source/lib/assertions/handlers/identicality.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const isIdentical = (checker: TypeChecker, nodes: Set<CallExpression>): D
5555
* Verifies that the argument of the assertion is not identical to the generic type of the assertion.
5656
*
5757
* @param checker - The TypeScript type checker.
58-
* @param nodes - The `expectType` AST nodes.
58+
* @param nodes - The `expectNotType` AST nodes.
5959
* @return List of custom diagnostics.
6060
*/
6161
export const isNotIdentical = (checker: TypeChecker, nodes: Set<CallExpression>): Diagnostic[] => {

source/lib/assertions/handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export {Handler} from './handler';
44
export {isIdentical, isNotIdentical} from './identicality';
55
export {isNotAssignable} from './assignability';
66
export {expectDeprecated, expectNotDeprecated} from './expect-deprecated';
7+
export {prinTypeWarning} from './informational';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {CallExpression, TypeChecker} from '@tsd/typescript';
2+
import {Diagnostic} from '../../interfaces';
3+
import {makeDiagnostic} from '../../utils';
4+
5+
/**
6+
* Emits a warning diagnostic for every call experession encountered containing the type of the first argument.
7+
*
8+
* @param checker - The TypeScript type checker.
9+
* @param nodes - The `printType` AST nodes.
10+
* @return List of warning diagnostics containing the type of the first argument.
11+
*/
12+
export const prinTypeWarning = (checker: TypeChecker, nodes: Set<CallExpression>): Diagnostic[] => {
13+
const diagnostics: Diagnostic[] = [];
14+
15+
if (!nodes) {
16+
return diagnostics;
17+
}
18+
19+
for (const node of nodes) {
20+
const argumentType = checker.getTypeAtLocation(node.arguments[0]);
21+
const argumentExpression = node.arguments[0].getText();
22+
23+
diagnostics.push(makeDiagnostic(node, `Type for expression \`${argumentExpression}\` is: \`${checker.typeToString(argumentType)}\``, 'warning'));
24+
}
25+
26+
return diagnostics;
27+
};

source/lib/assertions/index.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import {CallExpression, TypeChecker} from '@tsd/typescript';
22
import {Diagnostic} from '../interfaces';
3-
import {Handler, isIdentical, isNotIdentical, isNotAssignable, expectDeprecated, expectNotDeprecated} from './handlers';
3+
import {
4+
Handler,
5+
isIdentical,
6+
isNotIdentical,
7+
isNotAssignable,
8+
expectDeprecated,
9+
expectNotDeprecated,
10+
prinTypeWarning,
11+
} from './handlers';
412

513
export enum Assertion {
614
EXPECT_TYPE = 'expectType',
@@ -9,7 +17,8 @@ export enum Assertion {
917
EXPECT_ASSIGNABLE = 'expectAssignable',
1018
EXPECT_NOT_ASSIGNABLE = 'expectNotAssignable',
1119
EXPECT_DEPRECATED = 'expectDeprecated',
12-
EXPECT_NOT_DEPRECATED = 'expectNotDeprecated'
20+
EXPECT_NOT_DEPRECATED = 'expectNotDeprecated',
21+
PRINT_TYPE = 'printType',
1322
}
1423

1524
// List of diagnostic handlers attached to the assertion
@@ -18,7 +27,8 @@ const assertionHandlers = new Map<Assertion, Handler>([
1827
[Assertion.EXPECT_NOT_TYPE, isNotIdentical],
1928
[Assertion.EXPECT_NOT_ASSIGNABLE, isNotAssignable],
2029
[Assertion.EXPECT_DEPRECATED, expectDeprecated],
21-
[Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated]
30+
[Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated],
31+
[Assertion.PRINT_TYPE, prinTypeWarning]
2232
]);
2333

2434
/**

source/lib/parser.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import {Program, Node, CallExpression, forEachChild, isCallExpression, Identifie
22
import {Assertion} from './assertions';
33
import {Location, Diagnostic} from './interfaces';
44

5-
// TODO: Use Object.values() when targetting Node.js >= 8
6-
const assertionTypes = new Set<string>(Object.keys(Assertion).map(key => Assertion[key]));
5+
const assertionFnNames = new Set<string>(Object.values(Assertion));
76

87
/**
98
* Extract all assertions.
@@ -18,11 +17,11 @@ export const extractAssertions = (program: Program): Map<Assertion, Set<CallExpr
1817
*/
1918
function walkNodes(node: Node) {
2019
if (isCallExpression(node)) {
21-
const text = (node.expression as Identifier).getText();
20+
const identifier = (node.expression as Identifier).getText();
2221

2322
// Check if the call type is a valid assertion
24-
if (assertionTypes.has(text)) {
25-
const assertion = text as Assertion;
23+
if (assertionFnNames.has(identifier)) {
24+
const assertion = identifier as Assertion;
2625

2726
const nodes = assertions.get(assertion) || new Set<CallExpression>();
2827

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function (foo: number): number | null;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports.default = foo => {
2+
return foo > 0 ? foo : null;
3+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {printType} from '../../..';
2+
import aboveZero from '.';
3+
4+
printType(aboveZero);
5+
printType(null);
6+
printType(undefined);
7+
printType(null as any);
8+
printType(null as never);
9+
printType(null as unknown);
10+
printType('foo');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "foo"
3+
}

source/test/fixtures/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type Expectation = [
99
message: string,
1010
];
1111

12-
type ExpectationWithFilename = [
12+
type ExpectationWithFileName = [
1313
line: number,
1414
column: number,
1515
severity: 'error' | 'warning',
@@ -54,7 +54,7 @@ export const verifyWithFileName = (
5454
t: ExecutionContext,
5555
cwd: string,
5656
diagnostics: Diagnostic[],
57-
expectations: ExpectationWithFilename[]
57+
expectations: ExpectationWithFileName[]
5858
) => {
5959
const diagnosticObjs = diagnostics.map(({line, column, severity, message, fileName}) => ({
6060
line,

source/test/test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,17 @@ test('allow specifying `rootDir` option in `tsconfig.json`', async t => {
416416

417417
verify(t, diagnostics, []);
418418
});
419+
420+
test('prints the types of expressions passed to `printType` helper', async t => {
421+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/print-type')});
422+
423+
verify(t, diagnostics, [
424+
[4, 0, 'warning', 'Type for expression `aboveZero` is: `(foo: number) => number | null`'],
425+
[5, 0, 'warning', 'Type for expression `null` is: `null`'],
426+
[6, 0, 'warning', 'Type for expression `undefined` is: `undefined`'],
427+
[7, 0, 'warning', 'Type for expression `null as any` is: `any`'],
428+
[8, 0, 'warning', 'Type for expression `null as never` is: `never`'],
429+
[9, 0, 'warning', 'Type for expression `null as unknown` is: `unknown`'],
430+
[10, 0, 'warning', 'Type for expression `\'foo\'` is: `"foo"`'],
431+
]);
432+
});

0 commit comments

Comments
 (0)