diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index f7e8df2a7a..d997ab95eb 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -624,6 +624,27 @@ export class Assembler implements Emitter { } }); + // Check that no interface declares a member that's already declared + // in a base type (not allowed in C#). + const memberNames = interfaceMemberNames(jsiiType); + const checkNoIntersection = (...bases: spec.Type[]) => { + for (const base of bases) { + if (!spec.isInterfaceType(base)) { continue; } + + const baseMembers = interfaceMemberNames(base); + for (const memberName of memberNames) { + if (baseMembers.includes(memberName)) { + this._diagnostic(type.symbol.declarations[0], + ts.DiagnosticCategory.Error, + `Interface declares same member as inherited interface: ${memberName}`); + } + } + // Recurse upwards + this._deferUntilTypesAvailable(fqn, base.interfaces || [], type.symbol.valueDeclaration, checkNoIntersection); + } + }; + this._deferUntilTypesAvailable(fqn, jsiiType.interfaces || [], type.symbol.valueDeclaration, checkNoIntersection); + return _sortMembers(this._visitDocumentation(type.symbol, jsiiType)); } @@ -1164,6 +1185,22 @@ function intersection(xs: Set, ys: Set): Set { return ret; } +/** + * Return all members names of a JSII interface type + * + * Returns empty string for a non-interface type. + */ +function interfaceMemberNames(jsiiType: spec.InterfaceType): string[] { + const ret = new Array(); + if (jsiiType.methods) { + ret.push(...jsiiType.methods.map(m => m.name).filter(x => x !== undefined) as string[]); + } + if (jsiiType.properties) { + ret.push(...jsiiType.properties.map(m => m.name)); + } + return ret; +} + /** * Whether or not the given name is conventionally an interface name * diff --git a/packages/jsii/test/negatives/neg.double-interface-members-deeper.ts b/packages/jsii/test/negatives/neg.double-interface-members-deeper.ts new file mode 100644 index 0000000000..bfebb2015f --- /dev/null +++ b/packages/jsii/test/negatives/neg.double-interface-members-deeper.ts @@ -0,0 +1,15 @@ +///!MATCH_ERROR: Interface declares same member as inherited interface: foo + +export interface IA { + foo(): void; +} + +export interface IB extends IA { + bar(): void; +} + +export interface IC extends IB { + foo(): void; +} + + diff --git a/packages/jsii/test/negatives/neg.double-interface-members-method.ts b/packages/jsii/test/negatives/neg.double-interface-members-method.ts new file mode 100644 index 0000000000..2d0dee3c44 --- /dev/null +++ b/packages/jsii/test/negatives/neg.double-interface-members-method.ts @@ -0,0 +1,9 @@ +///!MATCH_ERROR: Interface declares same member as inherited interface: foo + +export interface IA { + foo(): void; +} +export interface IB extends IA { + foo(): void; +} + diff --git a/packages/jsii/test/negatives/neg.double-interface-members.ts b/packages/jsii/test/negatives/neg.double-interface-members.ts new file mode 100644 index 0000000000..d15eedaabb --- /dev/null +++ b/packages/jsii/test/negatives/neg.double-interface-members.ts @@ -0,0 +1,8 @@ +///!MATCH_ERROR: Interface declares same member as inherited interface: foo + +export interface A { + foo: number; +} +export interface B extends A { + foo: number; +} diff --git a/packages/jsii/test/negatives/neg.implementation-changes-types.2.ts b/packages/jsii/test/negatives/neg.implementation-changes-types.2.ts index 0eb0f843c3..b154758791 100644 --- a/packages/jsii/test/negatives/neg.implementation-changes-types.2.ts +++ b/packages/jsii/test/negatives/neg.implementation-changes-types.2.ts @@ -9,6 +9,8 @@ export interface ISomething { returnSomething(): Superclass; } -export interface ISomethingElse extends ISomething { - returnSomething(): Subclass; +export class ISomethingElse implements ISomething { + public returnSomething(): Subclass { + return new Subclass(); + } }