Skip to content

Commit

Permalink
Add decorator-related diagnostic codes to expectError (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
tbroyer committed Jan 17, 2024
1 parent bb28db1 commit 8eeab3d
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 6 deletions.
22 changes: 16 additions & 6 deletions source/lib/compiler.ts
Expand Up @@ -45,6 +45,16 @@ const expectErrorDiagnosticCodesToIgnore = new Set<DiagnosticCode>([
DiagnosticCode.StringLiteralTypeIsNotAssignableToUnionTypeWithSuggestion,
DiagnosticCode.ObjectLiteralMayOnlySpecifyKnownProperties,
DiagnosticCode.ObjectLiteralMayOnlySpecifyKnownProperties2,
DiagnosticCode.UnableToResolveSignatureOfClassDecorator,
DiagnosticCode.UnableToResolveSignatureOfParameterDecorator,
DiagnosticCode.UnableToResolveSignatureOfPropertyDecorator,
DiagnosticCode.UnableToResolveSignatureOfMethodDecorator,
DiagnosticCode.DecoratorCanOnlyDecorateMethodImplementation,
DiagnosticCode.DecoratorFunctionReturnTypeNotAssignableToType,
DiagnosticCode.DecoratorFunctionReturnTypeExpectedToBeVoidOrAny,
DiagnosticCode.RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsY,
DiagnosticCode.RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsAtLeastY,
DiagnosticCode.AcceptsTooFewArgumentsToBeUsedAsDecoratorHere,
]);

type IgnoreDiagnosticResult = 'preserve' | 'ignore' | Location;
Expand Down Expand Up @@ -76,19 +86,19 @@ const ignoreDiagnostic = (

// Diagnostic is inside of `expectError` clause
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
if (expectErrorDiagnosticCodesToIgnore.has(diagnostic.code)) {
return location;
}

// 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;
error.code = diagnostic.code;
return 'preserve';
}
}

Expand Down
10 changes: 10 additions & 0 deletions source/lib/interfaces.ts
Expand Up @@ -23,7 +23,17 @@ export interface Context {
}

export enum DiagnosticCode {
UnableToResolveSignatureOfClassDecorator = 1238,
UnableToResolveSignatureOfParameterDecorator = 1239,
UnableToResolveSignatureOfPropertyDecorator = 1240,
UnableToResolveSignatureOfMethodDecorator = 1241,
DecoratorCanOnlyDecorateMethodImplementation = 1249,
DecoratorFunctionReturnTypeNotAssignableToType = 1270,
DecoratorFunctionReturnTypeExpectedToBeVoidOrAny = 1271,
RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsY = 1278,
RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsAtLeastY = 1279,
AwaitExpressionOnlyAllowedWithinAsyncFunction = 1308,
AcceptsTooFewArgumentsToBeUsedAsDecoratorHere = 1329,
TopLevelAwaitOnlyAllowedWhenModuleESNextOrSystem = 1378,
GenericTypeRequiresTypeArguments = 2314,
GenericTypeRequiresBetweenXAndYTypeArugments = 2707,
Expand Down
12 changes: 12 additions & 0 deletions source/test/expect-error.ts
Expand Up @@ -52,6 +52,18 @@ test('expectError for values (exactOptionalPropertyTypes enabled)', async t => {
verify(t, diagnostics, []);
});

test('expectError for decorators', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/decorators')});

verify(t, diagnostics, []);
});

test('expectError for experimental decorators', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/experimental-decorators')});

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')});

Expand Down
15 changes: 15 additions & 0 deletions source/test/fixtures/expect-error/decorators/index.d.ts
@@ -0,0 +1,15 @@
export declare class Base {
dummy(a: string, b: number): boolean;
}

export function classDec<T extends new (a: number, b: boolean) => Base>(value: T, context: ClassDecoratorContext<T>): T;
export function methodDec(value: (this: Base, a: string, b: number) => boolean, context: ClassMethodDecoratorContext<Base, (this:Base, a: string, b: number) => boolean>): (this: Base, a: string, b: number) => boolean
export function getterDec(value: (this: Base) => number, context: ClassGetterDecoratorContext<Base, number>): (this: Base) => number;
export function setterDec(value: (this: Base, value: number) => void, context: ClassSetterDecoratorContext<Base, number>): (this: Base, value: number) => void;
export function accessorDec(value: ClassAccessorDecoratorTarget<Base, string>, context: ClassAccessorDecoratorContext<Base, string>): ClassAccessorDecoratorResult<Base, string>;
export function fieldDec(value: undefined, context: ClassFieldDecoratorContext<Base, number>): (initialValue: number) => number;

export function tooFewArguments(value: Function): void;
export function badReturnType(value: ClassAccessorDecoratorTarget<Base, number>, context: ClassAccessorDecoratorContext<Base, number>): number;

export function factory(arg: number): (value: (a: number) => void, context: ClassMethodDecoratorContext<unknown, (a: number) => void>) => void;
6 changes: 6 additions & 0 deletions source/test/fixtures/expect-error/decorators/index.js
@@ -0,0 +1,6 @@
module.exports.classDec = (value, context) => {};
module.exports.methodDec = (value, context) => {};
module.exports.getterDec = (value, context) => {};
module.exports.setterDec = (value, context) => {};
module.exports.accessorDec = (value, context) => {};
module.exports.fieldDec = (value, context) => {};
77 changes: 77 additions & 0 deletions source/test/fixtures/expect-error/decorators/index.test-d.ts
@@ -0,0 +1,77 @@
import {expectError} from '../../../..';
import {Base, classDec, methodDec, getterDec, setterDec, accessorDec, fieldDec, tooFewArguments, badReturnType, factory} from '.';

expectError(@classDec class {}); // 1238, 1270
expectError(() => { // 1238, 1270
@classDec
abstract class Test extends Base {}
});

expectError(class extends Base { // 1241
@methodDec static foo(a: string, b: number) { return true; }
});
expectError(class extends Base { // 1241, 1270
@methodDec foo() {}
});
expectError(class { // 1241
@methodDec foo(a: string, b: number) { return true; }
});
expectError(class extends Base { // 1249
@methodDec override dummy(a: string, b: number): boolean
dummy(): void
dummy(a?: string, b?: number) : boolean|void {}
});

expectError(class extends Base { // 1241
@getterDec static get foo() { return 42; }
});
expectError(class extends Base { // 1241, 1270
@getterDec get foo() { return "bar"; }
});
expectError(class { // 1241
@getterDec get foo() { return 42; }
});

expectError(class extends Base { // 1241
@setterDec static set foo(value: number) {}
});
expectError(class extends Base { // 1241, 1270
@setterDec set foo(value: string) {}
});
expectError(class { // 1241
@setterDec set foo(value: number) {}
});

expectError(class extends Base { // 1240, 1270
@accessorDec static accessor foo = "bar";
});
expectError(class extends Base { // 1240, 1270
@accessorDec accessor foo = 42;
});
expectError(class { // 1240, 1270
@accessorDec accessor foo = "bar";
});

expectError(class extends Base { // 1240
@fieldDec static foo = 42;
});
expectError(class extends Base { // 1240, 1270
@fieldDec foo = "bar"
});
expectError(class { // 1240
@fieldDec foo = 42;
});

expectError(class {
@tooFewArguments foo() {}
});
expectError(class extends Base {
@badReturnType accessor foo = 42;
})

expectError(class {
@factory("bar") foo(a: number) {}
});
expectError(class {
@factory foo(a: number) {}
});
3 changes: 3 additions & 0 deletions source/test/fixtures/expect-error/decorators/package.json
@@ -0,0 +1,3 @@
{
"name": "foo"
}
@@ -0,0 +1,15 @@
export declare class Base {
dummy(a: string, b: number): boolean;
}

export function classDec<T extends new (a: number, b: boolean) => Base>(constructor: T): T;
export function methodDec(target: Base, propertyKey: string, descriptor: TypedPropertyDescriptor<(a: number, b: number) => boolean>): void
export function getterDec(target: Base, propertyKey: string, descriptor: TypedPropertyDescriptor<number>): void;
export function setterDec(target: Base, propertyKey: string, descriptor: TypedPropertyDescriptor<number>): void;
export function propertyDec(target: Base, propertyKey: string): void;
export function parameterDec(target: Base, propertyKey: string, parameterIndex: number): void;

export function tooFewArguments(target: Base): PropertyDescriptor;
export function badReturnType(target: Base, propertyKey: string, descriptor: PropertyDescriptor): number;

export function factory(arg: number): (target: Base, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
@@ -0,0 +1,6 @@
module.exports.classDec = (constructor) => {};
module.exports.methodDec = (target, propertyKey, descriptor) => {};
module.exports.getterDec = (target, propertyKey, descriptor) => {};
module.exports.setterDec = (target, propertyKey, descriptor) => {};
module.exports.accessorDec = (target, propertyKey, descriptor) => {};
module.exports.fieldDec = (target, propertyKey, descriptor) => {};
@@ -0,0 +1,111 @@
import {expectError} from '../../../..';
import {Base, classDec, methodDec, getterDec, setterDec, propertyDec, parameterDec, tooFewArguments, badReturnType, factory} from '.';

expectError(() => { // 1238, 1270
@classDec
class Test {}
});
expectError(() => { // 1238, 1270
@classDec
abstract class Test extends Base {}
});

expectError(() => { // 1241
class Test extends Base {
@methodDec static foo(a: string, b: number) { return true; }
}
});
expectError(() => { // 1241
class Test extends Base {
@methodDec foo() {}
}
});
expectError(() => { // 1241
class Test {
@methodDec foo(a: string, b: number) { return true; }
}
});
expectError(() => { // 1249
class Test extends Base {
@methodDec override dummy(a: string, b: number): boolean
dummy(): void
dummy(a?: string, b?: number) : boolean|void {}
}
});

expectError(() => { // 1241
class Test extends Base {
@getterDec static get foo() { return 42; }
}
});
expectError(() => { // 1241
class Test extends Base {
@getterDec get foo() { return "bar"; }
}
});
expectError(() => { // 1241
class Test {
@getterDec get foo() { return 42; }
}
});

expectError(() => { // 1241
class Test extends Base {
@setterDec static set foo(value: number) {}
}
});
expectError(() => { // 1241
class Test extends Base {
@setterDec static set foo(value: string) {}
}
});
expectError(() => { // 1241
class Test {
@setterDec set foo(value: number) {}
}
});

expectError(() => { // 1240
class Test extends Base {
@propertyDec static foo = 42;
}
});
expectError(() => { // 1240
class Test {
@propertyDec foo = 42;
}
});

expectError(() => { // 1239
class Test extends Base {
static foo(@parameterDec a: number) {}
}
});
expectError(() => { // 1241
class Test {
foo(@parameterDec a: number) {}
}
});


expectError(() => {
class Test {
@tooFewArguments foo() {}
}
});
expectError(() => { // 1271
class Test extends Base {
@badReturnType accessor foo = 42;
}
})

expectError(() => {
class Test {
@factory("bar") foo(a: number) {}
}
});
expectError(() => {
class Test {
@factory foo(a: number) {}
}
});
@@ -0,0 +1,8 @@
{
"name": "foo",
"tsd": {
"compilerOptions": {
"experimentalDecorators": true
}
}
}

0 comments on commit 8eeab3d

Please sign in to comment.