Skip to content

Commit 8c826c1

Browse files
RomainMullerElad Ben-Israel
authored andcommitted
fix(jsii): Validate overriding does not affect optionality (#549)
ptionality must remain unchancged when overriding methods and properties, such that types are pure with respects to Liskov's substitution.
1 parent 719be24 commit 8c826c1

7 files changed

Lines changed: 92 additions & 1 deletion

packages/jsii/lib/validator.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,12 @@ function _defaultValidations(): ValidationFunction[] {
265265
if (expParam.variadic !== actParam.variadic) {
266266
diagnostic(ts.DiagnosticCategory.Error,
267267
// tslint:disable-next-line:max-line-length
268-
`${label} changes the variadicity of parameter ${actParam.name} when ${action} (expected ${expParam.variadic}, found ${actParam.variadic})`);
268+
`${label} changes the variadicity of parameter ${actParam.name} when ${action} (expected ${!!expParam.variadic}, found ${!!actParam.variadic})`);
269+
}
270+
if (expParam.optional !== actParam.optional) {
271+
diagnostic(ts.DiagnosticCategory.Error,
272+
// tslint:disable-next-line: max-line-length
273+
`${label} changes the optionality of paramerter ${actParam.name} when ${action} (expected ${!!expParam.optional}, found ${!!actParam.optional})`);
269274
}
270275
}
271276
}
@@ -281,6 +286,10 @@ function _defaultValidations(): ValidationFunction[] {
281286
diagnostic(ts.DiagnosticCategory.Error,
282287
`${label} changes immutability of property when ${action}`);
283288
}
289+
if (expected.optional !== actual.optional) {
290+
diagnostic(ts.DiagnosticCategory.Error,
291+
`${label} changes optionality of property when ${action}`);
292+
}
284293
}
285294
}
286295
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///!MATCH_ERROR: jsii.Implementor#method changes the optionality of paramerter _optional when overriding jsii.AbstractClass (expected true, found false)
2+
3+
// Attempt to change optionality of method parameter
4+
export abstract class AbstractClass {
5+
public abstract method(required: string, optional?: number): void;
6+
}
7+
8+
export class Implementor extends AbstractClass {
9+
public method(_required: string, _optional: number): void {
10+
// ...
11+
}
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
///!MATCH_ERROR: jsii.Implementor#method changes the optionality of paramerter _optional when overriding jsii.ParentClass (expected true, found false)
2+
3+
// Attempt to change optionality of method parameter
4+
export class ParentClass {
5+
public method(_required: string, _optional?: number): void {
6+
// ...
7+
}
8+
}
9+
10+
export class Implementor extends ParentClass {
11+
public method(_required: string, _optional: number): void {
12+
// ...
13+
}
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///!MATCH_ERROR: jsii.Implementor#method changes the optionality of paramerter _optional when implementing jsii.IInterface (expected true, found false)
2+
3+
// Attempt to change optionality of method parameter
4+
export interface IInterface {
5+
method(required: string, optional?: number): void;
6+
}
7+
8+
export class Implementor implements IInterface {
9+
public method(_required: string, _optional: number): void {
10+
// ...
11+
}
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
///!MATCH_ERROR: jsii.Implementor#property changes optionality of property when overriding jsii.AbstractClass
2+
3+
// Attempt to change optionality of method parameter
4+
export abstract class AbstractClass {
5+
public abstract property?: string;
6+
}
7+
8+
export class Implementor extends AbstractClass {
9+
public property: string;
10+
11+
constructor() {
12+
super();
13+
this.property = 'Bazinga!';
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
///!MATCH_ERROR: jsii.Implementor#property changes optionality of property when overriding jsii.ParentClass
2+
3+
// Attempt to change optionality of method parameter
4+
export class ParentClass {
5+
public property?: string = undefined;
6+
}
7+
8+
export class Implementor extends ParentClass {
9+
public property: string;
10+
11+
constructor() {
12+
super();
13+
this.property = 'Bazinga!';
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
///!MATCH_ERROR: jsii.Implementor#property changes optionality of property when implementing jsii.IInterface
2+
3+
// Attempt to change optionality of method parameter
4+
export interface IInterface {
5+
property?: string;
6+
}
7+
8+
export class Implementor implements IInterface {
9+
public property: string;
10+
11+
constructor() {
12+
this.property = 'Bazinga!';
13+
}
14+
}

0 commit comments

Comments
 (0)