From 17e80170e350e5178c165eeb7d5e941520e6808f Mon Sep 17 00:00:00 2001 From: Dimitri B Date: Tue, 1 Jun 2021 20:06:44 +0200 Subject: [PATCH 1/2] Implement `printType` helper to print the type of the passed expression as a warning Fixes #90 --- readme.md | 5 ++++ source/lib/assertions/assert.ts | 10 +++++++ .../lib/assertions/handlers/identicality.ts | 2 +- source/lib/assertions/handlers/index.ts | 1 + .../lib/assertions/handlers/informational.ts | 27 +++++++++++++++++++ source/lib/assertions/index.ts | 16 ++++++++--- source/lib/parser.ts | 9 +++---- source/test/fixtures/print-type/index.d.ts | 1 + source/test/fixtures/print-type/index.js | 3 +++ .../test/fixtures/print-type/index.test-d.ts | 10 +++++++ source/test/fixtures/print-type/package.json | 3 +++ source/test/fixtures/utils.ts | 4 +-- source/test/test.ts | 14 ++++++++++ 13 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 source/lib/assertions/handlers/informational.ts create mode 100644 source/test/fixtures/print-type/index.d.ts create mode 100644 source/test/fixtures/print-type/index.js create mode 100644 source/test/fixtures/print-type/index.test-d.ts create mode 100644 source/test/fixtures/print-type/package.json diff --git a/readme.md b/readme.md index a53338a3..7b58ba7f 100644 --- a/readme.md +++ b/readme.md @@ -189,6 +189,11 @@ 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 it 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 b6f967bd..1a118a9a 100644 --- a/source/test/test.ts +++ b/source/test/test.ts @@ -390,3 +390,17 @@ test('errors in libs from node_modules are not reported', async t => { [3, 18, 'error', 'Cannot find name \'Bar\'.'] ]); }); + +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"`'], + ]); +}); From d9ac3b26760f7bd9440a26511583c13b7fd5137b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 2 Jun 2021 20:26:33 +0700 Subject: [PATCH 2/2] Update readme.md --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 7b58ba7f..bcf5fc39 100644 --- a/readme.md +++ b/readme.md @@ -191,8 +191,9 @@ Check that `value` is not marked a [`@deprecated`](https://jsdoc.app/tags-deprec ### 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 it out by hand. +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