Skip to content

Commit b4503d0

Browse files
Report missing diagnostic codes when using expectError (#178)
1 parent 168de40 commit b4503d0

File tree

10 files changed

+107
-62
lines changed

10 files changed

+107
-62
lines changed

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Asserts that the type of `expression` is not assignable to type `T`.
150150

151151
### expectError<T = any>(expression: T)
152152

153-
Asserts that `expression` throws an error.
153+
Asserts that `expression` throws an error. Will not ignore syntax errors.
154154

155155
### expectDeprecated(expression: any)
156156

source/lib/assertions/assert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const expectNotAssignable = <T>(expression: any) => {
4343
};
4444

4545
/**
46-
* Asserts that `expression` throws an error.
46+
* Asserts that `expression` throws an error. Will not ignore syntax errors.
4747
*
4848
* @param expression - Expression that should throw an error.
4949
*/

source/lib/compiler.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const ignoredDiagnostics = new Set<number>([
1515
]);
1616

1717
// List of diagnostic codes which should be ignored inside `expectError` statements
18-
const expectErrordiagnosticCodesToIgnore = new Set<DiagnosticCode>([
18+
const expectErrorDiagnosticCodesToIgnore = new Set<DiagnosticCode>([
1919
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
2020
DiagnosticCode.PropertyDoesNotExistOnType,
2121
DiagnosticCode.CannotAssignToReadOnlyProperty,
@@ -65,18 +65,27 @@ const ignoreDiagnostic = (
6565
return 'ignore';
6666
}
6767

68-
if (!expectErrordiagnosticCodesToIgnore.has(diagnostic.code)) {
69-
return 'preserve';
70-
}
71-
7268
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
7369
const diagnosticFileName = diagnostic.file!.fileName;
7470

75-
for (const [location] of expectedErrors) {
71+
for (const [location, error] of expectedErrors) {
7672
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
7773
const start = diagnostic.start!;
7874

75+
// Diagnostic is inside of `expectError` clause
7976
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
77+
// Ignore syntactical errors
78+
if (diagnostic.code < 2000) {
79+
expectedErrors.delete(location);
80+
return 'preserve';
81+
}
82+
83+
// Set diagnostic code on `ExpectedError` to log
84+
if (!expectErrorDiagnosticCodesToIgnore.has(diagnostic.code)) {
85+
error.code = diagnostic.code;
86+
return 'preserve';
87+
}
88+
8089
return location;
8190
}
8291
}
@@ -141,9 +150,13 @@ export const getDiagnostics = (context: Context): Diagnostic[] => {
141150
}
142151

143152
for (const [, diagnostic] of expectedErrors) {
153+
const message = diagnostic.code ?
154+
`Found an error that tsd does not currently support (\`ts${diagnostic.code}\`), consider creating an issue on GitHub.` :
155+
'Expected an error, but found none.';
156+
144157
diagnostics.push({
145158
...diagnostic,
146-
message: 'Expected an error, but found none.',
159+
message,
147160
severity: 'error'
148161
});
149162
}

source/lib/parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const extractAssertions = (program: Program): Map<Assertion, Set<CallExpr
6767
return assertions;
6868
};
6969

70-
export type ExpectedError = Pick<Diagnostic, 'fileName' | 'line' | 'column'>;
70+
export type ExpectedError = Pick<Diagnostic, 'fileName' | 'line' | 'column'> & {code?: number};
7171

7272
/**
7373
* Loop over all the error assertion nodes and convert them to a location map.
@@ -91,7 +91,7 @@ export const parseErrorAssertionToLocation = (
9191
const location = {
9292
fileName: node.getSourceFile().fileName,
9393
start: node.getStart(),
94-
end: node.getEnd()
94+
end: node.getEnd() + 1
9595
};
9696

9797
const pos = node

source/test/expect-error.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import path from 'path';
2+
import test from 'ava';
3+
import {verify} from './fixtures/utils';
4+
import tsd from '..';
5+
6+
test('expectError for classes', async t => {
7+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/classes')});
8+
9+
verify(t, diagnostics, []);
10+
});
11+
12+
test('expectError for functions', async t => {
13+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/functions')});
14+
15+
verify(t, diagnostics, [
16+
[5, 0, 'error', 'Expected an error, but found none.']
17+
]);
18+
});
19+
20+
test('expectError for generics', async t => {
21+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/generics')});
22+
23+
verify(t, diagnostics, []);
24+
});
25+
26+
test('expectError should not ignore syntactical errors', async t => {
27+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')});
28+
29+
verify(t, diagnostics, [
30+
[4, 29, 'error', '\')\' expected.'],
31+
[5, 22, 'error', '\',\' expected.'],
32+
]);
33+
});
34+
35+
test('expectError for values', async t => {
36+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values')});
37+
38+
verify(t, diagnostics, [
39+
[5, 0, 'error', 'Expected an error, but found none.']
40+
]);
41+
});
42+
43+
test('expectError for values (noImplicitAny disabled)', async t => {
44+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values-disabled-no-implicit-any')});
45+
46+
verify(t, diagnostics, []);
47+
});
48+
49+
test('expectError for values (exactOptionalPropertyTypes enabled)', async t => {
50+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/enabled-exact-optional-property-types')});
51+
52+
verify(t, diagnostics, []);
53+
});
54+
55+
test('expectError should report missing diagnostic codes', async t => {
56+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/missing-diagnostic-code')});
57+
58+
verify(t, diagnostics, [
59+
[8, 12, 'error', 'Cannot find name \'undeclared\'.'],
60+
[5, 0, 'error', 'Expected an error, but found none.'],
61+
[8, 0, 'error', 'Found an error that tsd does not currently support (`ts2304`), consider creating an issue on GitHub.'],
62+
]);
63+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare const one: {
2+
(foo: string, bar: string): string;
3+
(foo: number, bar: number): number;
4+
};
5+
6+
export default one;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports.default = (foo, bar) => {
2+
return foo + bar;
3+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {expectError} from '../../../..';
2+
import one from '.';
3+
4+
// 'Expected an error, but found none.'
5+
expectError(one('foo', 'bar'));
6+
7+
// 'Found an error that tsd does not currently support (`ts2304`), consider creating an issue on GitHub.'
8+
expectError(undeclared = one('foo', 'bar'));
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/test.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -264,57 +264,6 @@ test('support setting a custom test directory', async t => {
264264
]);
265265
});
266266

267-
test('expectError for classes', async t => {
268-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/classes')});
269-
270-
verify(t, diagnostics, []);
271-
});
272-
273-
test('expectError for functions', async t => {
274-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/functions')});
275-
276-
verify(t, diagnostics, [
277-
[5, 0, 'error', 'Expected an error, but found none.']
278-
]);
279-
});
280-
281-
test('expectError for generics', async t => {
282-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/generics')});
283-
284-
verify(t, diagnostics, []);
285-
});
286-
287-
test('expectError should not ignore syntactical errors', async t => {
288-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')});
289-
290-
verify(t, diagnostics, [
291-
[4, 29, 'error', '\')\' expected.'],
292-
[5, 22, 'error', '\',\' expected.'],
293-
[4, 0, 'error', 'Expected an error, but found none.'],
294-
[5, 0, 'error', 'Expected an error, but found none.']
295-
]);
296-
});
297-
298-
test('expectError for values', async t => {
299-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values')});
300-
301-
verify(t, diagnostics, [
302-
[5, 0, 'error', 'Expected an error, but found none.']
303-
]);
304-
});
305-
306-
test('expectError for values (noImplicitAny disabled)', async t => {
307-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values-disabled-no-implicit-any')});
308-
309-
verify(t, diagnostics, []);
310-
});
311-
312-
test('expectError for values (exactOptionalPropertyTypes enabled)', async t => {
313-
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/enabled-exact-optional-property-types')});
314-
315-
verify(t, diagnostics, []);
316-
});
317-
318267
test('missing import', async t => {
319268
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/missing-import')});
320269

0 commit comments

Comments
 (0)