Skip to content

Commit b2b2c89

Browse files
authored
fix(jsii): detect double interface member declarations (#360)
In C# it's prohibited to declare an interface member that also exists in an inherited interface. Have jsii detect this illegal pattern. Strictly speaking, this only true if we don't overload, but since we don't support overloading anyway we just check on the member names, not the types. Fixes #340.
1 parent 999f597 commit b2b2c89

File tree

5 files changed

+73
-2
lines changed

5 files changed

+73
-2
lines changed

packages/jsii/lib/assembler.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,27 @@ export class Assembler implements Emitter {
624624
}
625625
});
626626

627+
// Check that no interface declares a member that's already declared
628+
// in a base type (not allowed in C#).
629+
const memberNames = interfaceMemberNames(jsiiType);
630+
const checkNoIntersection = (...bases: spec.Type[]) => {
631+
for (const base of bases) {
632+
if (!spec.isInterfaceType(base)) { continue; }
633+
634+
const baseMembers = interfaceMemberNames(base);
635+
for (const memberName of memberNames) {
636+
if (baseMembers.includes(memberName)) {
637+
this._diagnostic(type.symbol.declarations[0],
638+
ts.DiagnosticCategory.Error,
639+
`Interface declares same member as inherited interface: ${memberName}`);
640+
}
641+
}
642+
// Recurse upwards
643+
this._deferUntilTypesAvailable(fqn, base.interfaces || [], type.symbol.valueDeclaration, checkNoIntersection);
644+
}
645+
};
646+
this._deferUntilTypesAvailable(fqn, jsiiType.interfaces || [], type.symbol.valueDeclaration, checkNoIntersection);
647+
627648
return _sortMembers(this._visitDocumentation(type.symbol, jsiiType));
628649
}
629650

@@ -1164,6 +1185,22 @@ function intersection<T>(xs: Set<T>, ys: Set<T>): Set<T> {
11641185
return ret;
11651186
}
11661187

1188+
/**
1189+
* Return all members names of a JSII interface type
1190+
*
1191+
* Returns empty string for a non-interface type.
1192+
*/
1193+
function interfaceMemberNames(jsiiType: spec.InterfaceType): string[] {
1194+
const ret = new Array<string>();
1195+
if (jsiiType.methods) {
1196+
ret.push(...jsiiType.methods.map(m => m.name).filter(x => x !== undefined) as string[]);
1197+
}
1198+
if (jsiiType.properties) {
1199+
ret.push(...jsiiType.properties.map(m => m.name));
1200+
}
1201+
return ret;
1202+
}
1203+
11671204
/**
11681205
* Whether or not the given name is conventionally an interface name
11691206
*
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
///!MATCH_ERROR: Interface declares same member as inherited interface: foo
2+
3+
export interface IA {
4+
foo(): void;
5+
}
6+
7+
export interface IB extends IA {
8+
bar(): void;
9+
}
10+
11+
export interface IC extends IB {
12+
foo(): void;
13+
}
14+
15+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
///!MATCH_ERROR: Interface declares same member as inherited interface: foo
2+
3+
export interface IA {
4+
foo(): void;
5+
}
6+
export interface IB extends IA {
7+
foo(): void;
8+
}
9+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
///!MATCH_ERROR: Interface declares same member as inherited interface: foo
2+
3+
export interface A {
4+
foo: number;
5+
}
6+
export interface B extends A {
7+
foo: number;
8+
}

packages/jsii/test/negatives/neg.implementation-changes-types.2.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export interface ISomething {
99
returnSomething(): Superclass;
1010
}
1111

12-
export interface ISomethingElse extends ISomething {
13-
returnSomething(): Subclass;
12+
export class ISomethingElse implements ISomething {
13+
public returnSomething(): Subclass {
14+
return new Subclass();
15+
}
1416
}

0 commit comments

Comments
 (0)