Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(expectError): report missing diagnostic codes #178

Merged
merged 3 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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