From b4503d0f15b64a3c9f6794418a4684d2bc2edc63 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 10 Mar 2023 01:30:05 -0600 Subject: [PATCH] Report missing diagnostic codes when using `expectError` (#178) --- readme.md | 2 +- source/lib/assertions/assert.ts | 2 +- source/lib/compiler.ts | 27 +++++--- source/lib/parser.ts | 4 +- source/test/expect-error.ts | 63 +++++++++++++++++++ .../missing-diagnostic-code/index.d.ts | 6 ++ .../missing-diagnostic-code/index.js | 3 + .../missing-diagnostic-code/index.test-d.ts | 8 +++ .../missing-diagnostic-code/package.json | 3 + source/test/test.ts | 51 --------------- 10 files changed, 107 insertions(+), 62 deletions(-) create mode 100644 source/test/expect-error.ts create mode 100644 source/test/fixtures/expect-error/missing-diagnostic-code/index.d.ts create mode 100644 source/test/fixtures/expect-error/missing-diagnostic-code/index.js create mode 100644 source/test/fixtures/expect-error/missing-diagnostic-code/index.test-d.ts create mode 100644 source/test/fixtures/expect-error/missing-diagnostic-code/package.json diff --git a/readme.md b/readme.md index 3e7c04c3..6f483fbd 100644 --- a/readme.md +++ b/readme.md @@ -150,7 +150,7 @@ Asserts that the type of `expression` is not assignable to type `T`. ### expectError<T = any>(expression: T) -Asserts that `expression` throws an error. +Asserts that `expression` throws an error. Will not ignore syntax errors. ### expectDeprecated(expression: any) diff --git a/source/lib/assertions/assert.ts b/source/lib/assertions/assert.ts index 88f4c863..36f2e8b1 100644 --- a/source/lib/assertions/assert.ts +++ b/source/lib/assertions/assert.ts @@ -43,7 +43,7 @@ export const expectNotAssignable = (expression: any) => { }; /** - * Asserts that `expression` throws an error. + * Asserts that `expression` throws an error. Will not ignore syntax errors. * * @param expression - Expression that should throw an error. */ diff --git a/source/lib/compiler.ts b/source/lib/compiler.ts index 58dc33bc..91f69ef1 100644 --- a/source/lib/compiler.ts +++ b/source/lib/compiler.ts @@ -15,7 +15,7 @@ const ignoredDiagnostics = new Set([ ]); // List of diagnostic codes which should be ignored inside `expectError` statements -const expectErrordiagnosticCodesToIgnore = new Set([ +const expectErrorDiagnosticCodesToIgnore = new Set([ DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType, DiagnosticCode.PropertyDoesNotExistOnType, DiagnosticCode.CannotAssignToReadOnlyProperty, @@ -65,18 +65,27 @@ const ignoreDiagnostic = ( return 'ignore'; } - if (!expectErrordiagnosticCodesToIgnore.has(diagnostic.code)) { - return 'preserve'; - } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const diagnosticFileName = diagnostic.file!.fileName; - for (const [location] of expectedErrors) { + for (const [location, error] of expectedErrors) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const start = diagnostic.start!; + // Diagnostic is inside of `expectError` clause if (diagnosticFileName === location.fileName && start > location.start && start < location.end) { + // Ignore syntactical errors + if (diagnostic.code < 2000) { + expectedErrors.delete(location); + return 'preserve'; + } + + // Set diagnostic code on `ExpectedError` to log + if (!expectErrorDiagnosticCodesToIgnore.has(diagnostic.code)) { + error.code = diagnostic.code; + return 'preserve'; + } + return location; } } @@ -141,9 +150,13 @@ export const getDiagnostics = (context: Context): Diagnostic[] => { } for (const [, diagnostic] of expectedErrors) { + const message = diagnostic.code ? + `Found an error that tsd does not currently support (\`ts${diagnostic.code}\`), consider creating an issue on GitHub.` : + 'Expected an error, but found none.'; + diagnostics.push({ ...diagnostic, - message: 'Expected an error, but found none.', + message, severity: 'error' }); } diff --git a/source/lib/parser.ts b/source/lib/parser.ts index 8ad952f4..56175c7d 100644 --- a/source/lib/parser.ts +++ b/source/lib/parser.ts @@ -67,7 +67,7 @@ export const extractAssertions = (program: Program): Map; +export type ExpectedError = Pick & {code?: number}; /** * Loop over all the error assertion nodes and convert them to a location map. @@ -91,7 +91,7 @@ export const parseErrorAssertionToLocation = ( const location = { fileName: node.getSourceFile().fileName, start: node.getStart(), - end: node.getEnd() + end: node.getEnd() + 1 }; const pos = node diff --git a/source/test/expect-error.ts b/source/test/expect-error.ts new file mode 100644 index 00000000..38b3e6d6 --- /dev/null +++ b/source/test/expect-error.ts @@ -0,0 +1,63 @@ +import path from 'path'; +import test from 'ava'; +import {verify} from './fixtures/utils'; +import tsd from '..'; + +test('expectError for classes', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/classes')}); + + verify(t, diagnostics, []); +}); + +test('expectError for functions', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/functions')}); + + verify(t, diagnostics, [ + [5, 0, 'error', 'Expected an error, but found none.'] + ]); +}); + +test('expectError for generics', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/generics')}); + + verify(t, diagnostics, []); +}); + +test('expectError should not ignore syntactical errors', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')}); + + verify(t, diagnostics, [ + [4, 29, 'error', '\')\' expected.'], + [5, 22, 'error', '\',\' expected.'], + ]); +}); + +test('expectError for values', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values')}); + + verify(t, diagnostics, [ + [5, 0, 'error', 'Expected an error, but found none.'] + ]); +}); + +test('expectError for values (noImplicitAny disabled)', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values-disabled-no-implicit-any')}); + + verify(t, diagnostics, []); +}); + +test('expectError for values (exactOptionalPropertyTypes enabled)', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/enabled-exact-optional-property-types')}); + + verify(t, diagnostics, []); +}); + +test('expectError should report missing diagnostic codes', async t => { + const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/missing-diagnostic-code')}); + + verify(t, diagnostics, [ + [8, 12, 'error', 'Cannot find name \'undeclared\'.'], + [5, 0, 'error', 'Expected an error, but found none.'], + [8, 0, 'error', 'Found an error that tsd does not currently support (`ts2304`), consider creating an issue on GitHub.'], + ]); +}); diff --git a/source/test/fixtures/expect-error/missing-diagnostic-code/index.d.ts b/source/test/fixtures/expect-error/missing-diagnostic-code/index.d.ts new file mode 100644 index 00000000..0616ebaa --- /dev/null +++ b/source/test/fixtures/expect-error/missing-diagnostic-code/index.d.ts @@ -0,0 +1,6 @@ +declare const one: { + (foo: string, bar: string): string; + (foo: number, bar: number): number; +}; + +export default one; diff --git a/source/test/fixtures/expect-error/missing-diagnostic-code/index.js b/source/test/fixtures/expect-error/missing-diagnostic-code/index.js new file mode 100644 index 00000000..f17717f5 --- /dev/null +++ b/source/test/fixtures/expect-error/missing-diagnostic-code/index.js @@ -0,0 +1,3 @@ +module.exports.default = (foo, bar) => { + return foo + bar; +}; diff --git a/source/test/fixtures/expect-error/missing-diagnostic-code/index.test-d.ts b/source/test/fixtures/expect-error/missing-diagnostic-code/index.test-d.ts new file mode 100644 index 00000000..5b6a7d59 --- /dev/null +++ b/source/test/fixtures/expect-error/missing-diagnostic-code/index.test-d.ts @@ -0,0 +1,8 @@ +import {expectError} from '../../../..'; +import one from '.'; + +// 'Expected an error, but found none.' +expectError(one('foo', 'bar')); + +// 'Found an error that tsd does not currently support (`ts2304`), consider creating an issue on GitHub.' +expectError(undeclared = one('foo', 'bar')); diff --git a/source/test/fixtures/expect-error/missing-diagnostic-code/package.json b/source/test/fixtures/expect-error/missing-diagnostic-code/package.json new file mode 100644 index 00000000..de6dc1db --- /dev/null +++ b/source/test/fixtures/expect-error/missing-diagnostic-code/package.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} diff --git a/source/test/test.ts b/source/test/test.ts index 8f51278f..67bba0ed 100644 --- a/source/test/test.ts +++ b/source/test/test.ts @@ -264,57 +264,6 @@ test('support setting a custom test directory', async t => { ]); }); -test('expectError for classes', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/classes')}); - - verify(t, diagnostics, []); -}); - -test('expectError for functions', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/functions')}); - - verify(t, diagnostics, [ - [5, 0, 'error', 'Expected an error, but found none.'] - ]); -}); - -test('expectError for generics', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/generics')}); - - verify(t, diagnostics, []); -}); - -test('expectError should not ignore syntactical errors', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')}); - - verify(t, diagnostics, [ - [4, 29, 'error', '\')\' expected.'], - [5, 22, 'error', '\',\' expected.'], - [4, 0, 'error', 'Expected an error, but found none.'], - [5, 0, 'error', 'Expected an error, but found none.'] - ]); -}); - -test('expectError for values', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values')}); - - verify(t, diagnostics, [ - [5, 0, 'error', 'Expected an error, but found none.'] - ]); -}); - -test('expectError for values (noImplicitAny disabled)', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values-disabled-no-implicit-any')}); - - verify(t, diagnostics, []); -}); - -test('expectError for values (exactOptionalPropertyTypes enabled)', async t => { - const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/enabled-exact-optional-property-types')}); - - verify(t, diagnostics, []); -}); - test('missing import', async t => { const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/missing-import')});