Skip to content

Commit

Permalink
Report missing diagnostic codes when using expectError (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy-mitchell committed Mar 10, 2023
1 parent 168de40 commit b4503d0
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 62 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion source/lib/assertions/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const expectNotAssignable = <T>(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.
*/
Expand Down
27 changes: 20 additions & 7 deletions source/lib/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ignoredDiagnostics = new Set<number>([
]);

// List of diagnostic codes which should be ignored inside `expectError` statements
const expectErrordiagnosticCodesToIgnore = new Set<DiagnosticCode>([
const expectErrorDiagnosticCodesToIgnore = new Set<DiagnosticCode>([
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
DiagnosticCode.PropertyDoesNotExistOnType,
DiagnosticCode.CannotAssignToReadOnlyProperty,
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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'
});
}
Expand Down
4 changes: 2 additions & 2 deletions source/lib/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const extractAssertions = (program: Program): Map<Assertion, Set<CallExpr
return assertions;
};

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

/**
* Loop over all the error assertion nodes and convert them to a location map.
Expand All @@ -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
Expand Down
63 changes: 63 additions & 0 deletions source/test/expect-error.ts
Original file line number Diff line number Diff line change
@@ -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.'],
]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare const one: {
(foo: string, bar: string): string;
(foo: number, bar: number): number;
};

export default one;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports.default = (foo, bar) => {
return foo + bar;
};
Original file line number Diff line number Diff line change
@@ -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'));
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "foo"
}
51 changes: 0 additions & 51 deletions source/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')});

Expand Down

0 comments on commit b4503d0

Please sign in to comment.