diff --git a/readme.md b/readme.md index a53338a3..bcf5fc39 100644 --- a/readme.md +++ b/readme.md @@ -189,6 +189,12 @@ Check that `value` is marked a [`@deprecated`](https://jsdoc.app/tags-deprecated Check that `value` is not marked a [`@deprecated`](https://jsdoc.app/tags-deprecated.html). +### printType(value) + +Print the type of `value` 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. + ## Programmatic API diff --git a/source/lib/assertions/assert.ts b/source/lib/assertions/assert.ts index df48f090..6101bda8 100644 --- a/source/lib/assertions/assert.ts +++ b/source/lib/assertions/assert.ts @@ -68,3 +68,13 @@ export const expectDeprecated = (expression: any) => { // tslint:disable-line:n export const expectNotDeprecated = (expression: any) => { // tslint:disable-line:no-unused // Do nothing, the TypeScript compiler handles this for us }; + +/** + * Will print a warning with the type of the expression passed as argument. + * + * @param expression - Expression whose type should be printed as a warning. + */ +// @ts-ignore +export const printType = (expression: any) => { // tslint:disable-line:no-unused + // Do nothing, the TypeScript compiler handles this for us +}; diff --git a/source/lib/assertions/handlers/identicality.ts b/source/lib/assertions/handlers/identicality.ts index 31994a1f..dc677e81 100644 --- a/source/lib/assertions/handlers/identicality.ts +++ b/source/lib/assertions/handlers/identicality.ts @@ -55,7 +55,7 @@ export const isIdentical = (checker: TypeChecker, nodes: Set): D * Verifies that the argument of the assertion is not identical to the generic type of the assertion. * * @param checker - The TypeScript type checker. - * @param nodes - The `expectType` AST nodes. + * @param nodes - The `expectNotType` AST nodes. * @return List of custom diagnostics. */ export const isNotIdentical = (checker: TypeChecker, nodes: Set): Diagnostic[] => { diff --git a/source/lib/assertions/handlers/index.ts b/source/lib/assertions/handlers/index.ts index fe253d71..2646ac7f 100644 --- a/source/lib/assertions/handlers/index.ts +++ b/source/lib/assertions/handlers/index.ts @@ -4,3 +4,4 @@ export {Handler} from './handler'; export {isIdentical, isNotIdentical} from './identicality'; export {isNotAssignable} from './assignability'; export {expectDeprecated, expectNotDeprecated} from './expect-deprecated'; +export {prinTypeWarning} from './informational'; diff --git a/source/lib/assertions/handlers/informational.ts b/source/lib/assertions/handlers/informational.ts new file mode 100644 index 00000000..47283b20 --- /dev/null +++ b/source/lib/assertions/handlers/informational.ts @@ -0,0 +1,27 @@ +import {CallExpression, TypeChecker} from '@tsd/typescript'; +import {Diagnostic} from '../../interfaces'; +import {makeDiagnostic} from '../../utils'; + +/** + * Emits a warning diagnostic for every call experession encountered containing the type of the first argument. + * + * @param checker - The TypeScript type checker. + * @param nodes - The `printType` AST nodes. + * @return List of warning diagnostics containing the type of the first argument. + */ +export const prinTypeWarning = (checker: TypeChecker, nodes: Set): Diagnostic[] => { + const diagnostics: Diagnostic[] = []; + + if (!nodes) { + return diagnostics; + } + + for (const node of nodes) { + const argumentType = checker.getTypeAtLocation(node.arguments[0]); + const argumentExpression = node.arguments[0].getText(); + + diagnostics.push(makeDiagnostic(node, `Type for expression \`${argumentExpression}\` is: \`${checker.typeToString(argumentType)}\``, 'warning')); + } + + return diagnostics; +}; diff --git a/source/lib/assertions/index.ts b/source/lib/assertions/index.ts index 3c9d8239..bda816a4 100644 --- a/source/lib/assertions/index.ts +++ b/source/lib/assertions/index.ts @@ -1,6 +1,14 @@ import {CallExpression, TypeChecker} from '@tsd/typescript'; import {Diagnostic} from '../interfaces'; -import {Handler, isIdentical, isNotIdentical, isNotAssignable, expectDeprecated, expectNotDeprecated} from './handlers'; +import { + Handler, + isIdentical, + isNotIdentical, + isNotAssignable, + expectDeprecated, + expectNotDeprecated, + prinTypeWarning, +} from './handlers'; export enum Assertion { EXPECT_TYPE = 'expectType', @@ -9,7 +17,8 @@ export enum Assertion { EXPECT_ASSIGNABLE = 'expectAssignable', EXPECT_NOT_ASSIGNABLE = 'expectNotAssignable', EXPECT_DEPRECATED = 'expectDeprecated', - EXPECT_NOT_DEPRECATED = 'expectNotDeprecated' + EXPECT_NOT_DEPRECATED = 'expectNotDeprecated', + PRINT_TYPE = 'printType', } // List of diagnostic handlers attached to the assertion @@ -18,7 +27,8 @@ const assertionHandlers = new Map([ [Assertion.EXPECT_NOT_TYPE, isNotIdentical], [Assertion.EXPECT_NOT_ASSIGNABLE, isNotAssignable], [Assertion.EXPECT_DEPRECATED, expectDeprecated], - [Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated] + [Assertion.EXPECT_NOT_DEPRECATED, expectNotDeprecated], + [Assertion.PRINT_TYPE, prinTypeWarning] ]); /** diff --git a/source/lib/parser.ts b/source/lib/parser.ts index 24e4819c..6bc8cf05 100644 --- a/source/lib/parser.ts +++ b/source/lib/parser.ts @@ -2,8 +2,7 @@ import {Program, Node, CallExpression, forEachChild, isCallExpression, Identifie import {Assertion} from './assertions'; import {Location, Diagnostic} from './interfaces'; -// TODO: Use Object.values() when targetting Node.js >= 8 -const assertionTypes = new Set(Object.keys(Assertion).map(key => Assertion[key])); +const assertionFnNames = new Set(Object.values(Assertion)); /** * Extract all assertions. @@ -18,11 +17,11 @@ export const extractAssertions = (program: Program): Map(); diff --git a/source/test/fixtures/print-type/index.d.ts b/source/test/fixtures/print-type/index.d.ts new file mode 100644 index 00000000..6f3a1c51 --- /dev/null +++ b/source/test/fixtures/print-type/index.d.ts @@ -0,0 +1 @@ +export default function (foo: number): number | null; diff --git a/source/test/fixtures/print-type/index.js b/source/test/fixtures/print-type/index.js new file mode 100644 index 00000000..0d06f8dd --- /dev/null +++ b/source/test/fixtures/print-type/index.js @@ -0,0 +1,3 @@ +module.exports.default = foo => { + return foo > 0 ? foo : null; +}; diff --git a/source/test/fixtures/print-type/index.test-d.ts b/source/test/fixtures/print-type/index.test-d.ts new file mode 100644 index 00000000..9a17b216 --- /dev/null +++ b/source/test/fixtures/print-type/index.test-d.ts @@ -0,0 +1,10 @@ +import {printType} from '../../..'; +import aboveZero from '.'; + +printType(aboveZero); +printType(null); +printType(undefined); +printType(null as any); +printType(null as never); +printType(null as unknown); +printType('foo'); diff --git a/source/test/fixtures/print-type/package.json b/source/test/fixtures/print-type/package.json new file mode 100644 index 00000000..de6dc1db --- /dev/null +++ b/source/test/fixtures/print-type/package.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} diff --git a/source/test/fixtures/utils.ts b/source/test/fixtures/utils.ts index 57741854..02e0e860 100644 --- a/source/test/fixtures/utils.ts +++ b/source/test/fixtures/utils.ts @@ -9,7 +9,7 @@ type Expectation = [ message: string, ]; -type ExpectationWithFilename = [ +type ExpectationWithFileName = [ line: number, column: number, severity: 'error' | 'warning', @@ -54,7 +54,7 @@ export const verifyWithFileName = ( t: ExecutionContext, cwd: string, diagnostics: Diagnostic[], - expectations: ExpectationWithFilename[] + expectations: ExpectationWithFileName[] ) => { const diagnosticObjs = diagnostics.map(({line, column, severity, message, fileName}) => ({ line, diff --git a/source/test/test.ts b/source/test/test.ts index 7f1bd14e..0abfd794 100644 --- a/source/test/test.ts +++ b/source/test/test.ts @@ -416,3 +416,17 @@ test('allow specifying `rootDir` option in `tsconfig.json`', async t => { verify(t, diagnostics, []); }); + +test('prints the types of expressions passed to `printType` helper', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/print-type')}); + + verify(t, diagnostics, [ + [4, 0, 'warning', 'Type for expression `aboveZero` is: `(foo: number) => number | null`'], + [5, 0, 'warning', 'Type for expression `null` is: `null`'], + [6, 0, 'warning', 'Type for expression `undefined` is: `undefined`'], + [7, 0, 'warning', 'Type for expression `null as any` is: `any`'], + [8, 0, 'warning', 'Type for expression `null as never` is: `never`'], + [9, 0, 'warning', 'Type for expression `null as unknown` is: `unknown`'], + [10, 0, 'warning', 'Type for expression `\'foo\'` is: `"foo"`'], + ]); +});