diff --git a/packages/jsii-calc/lib/erasures.ts b/packages/jsii-calc/lib/erasures.ts new file mode 100644 index 0000000000..dd6459d1ed --- /dev/null +++ b/packages/jsii-calc/lib/erasures.ts @@ -0,0 +1,25 @@ +// +// Un-exported base classes are erased +// https://github.com/awslabs/jsii/issues/417 +// +class JSII417PrivateRoot { + public readonly hasRoot = true; +} +export class JSII417PublicBaseOfBase extends JSII417PrivateRoot { + public static makeInstance(): JSII417PublicBaseOfBase { + return new JSII417PrivateBase("TEST"); + } + public foo() { return; } +} +class JSII417PrivateBase extends JSII417PublicBaseOfBase { + constructor(protected readonly property: string) { + super(); + } + public bar() { return; } +} +export class JSII417Derived extends JSII417PrivateBase { + public bar() { + return super.bar(); + } + public baz() { return; } +} diff --git a/packages/jsii-calc/lib/index.ts b/packages/jsii-calc/lib/index.ts index 24cc3a95a8..c0b802da39 100644 --- a/packages/jsii-calc/lib/index.ts +++ b/packages/jsii-calc/lib/index.ts @@ -1,2 +1,3 @@ export * from './calculator'; export * from './compliance'; +export * from './erasures'; diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii index c0b561552c..4a4eb760f3 100644 --- a/packages/jsii-calc/test/assembly.jsii +++ b/packages/jsii-calc/test/assembly.jsii @@ -2484,6 +2484,74 @@ } ] }, + "jsii-calc.JSII417Derived": { + "assembly": "jsii-calc", + "base": { + "fqn": "jsii-calc.JSII417PublicBaseOfBase" + }, + "fqn": "jsii-calc.JSII417Derived", + "initializer": { + "initializer": true, + "parameters": [ + { + "name": "property", + "type": { + "primitive": "string" + } + } + ] + }, + "kind": "class", + "methods": [ + { + "name": "bar" + }, + { + "name": "baz" + } + ], + "name": "JSII417Derived", + "properties": [ + { + "immutable": true, + "name": "property", + "protected": true, + "type": { + "primitive": "string" + } + } + ] + }, + "jsii-calc.JSII417PublicBaseOfBase": { + "assembly": "jsii-calc", + "fqn": "jsii-calc.JSII417PublicBaseOfBase", + "initializer": { + "initializer": true + }, + "kind": "class", + "methods": [ + { + "name": "makeInstance", + "returns": { + "fqn": "jsii-calc.JSII417PublicBaseOfBase" + }, + "static": true + }, + { + "name": "foo" + } + ], + "name": "JSII417PublicBaseOfBase", + "properties": [ + { + "immutable": true, + "name": "hasRoot", + "type": { + "primitive": "boolean" + } + } + ] + }, "jsii-calc.JSObjectLiteralForInterface": { "assembly": "jsii-calc", "fqn": "jsii-calc.JSObjectLiteralForInterface", @@ -4633,5 +4701,5 @@ } }, "version": "0.8.2", - "fingerprint": "GihU8+thuZ1W9TNwDba1Ux44Rac3+kUHUCrGH73N0tw=" + "fingerprint": "upM77jvLtKXqaAYygq6yoGd2vc9Ae/Y7x70F/EiuKEY=" } diff --git a/packages/jsii-kernel/test/test.kernel.ts b/packages/jsii-kernel/test/test.kernel.ts index 8bd6b59c5b..74e5298eb8 100644 --- a/packages/jsii-kernel/test/test.kernel.ts +++ b/packages/jsii-kernel/test/test.kernel.ts @@ -1126,6 +1126,11 @@ defineTest('struct: non-empty object deserializes properly', async (test, sandbo test.equal('foo', field.value); }); +defineTest('erased base: can receive an instance of private type', async (test, sandbox) => { + const objref = sandbox.sinvoke({ fqn: 'jsii-calc.JSII417PublicBaseOfBase', method: 'makeInstance' }); + test.deepEqual(objref.result, { [api.TOKEN_REF]: 'jsii-calc.JSII417PublicBaseOfBase@10000' }); +}); + // ================================================================================================= const testNames: { [name: string]: boolean } = { }; diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 98e1f5600a..3673bea8d1 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -417,12 +417,24 @@ export class Assembler implements Emitter { if (_isAbstract(type.symbol, jsiiType)) { jsiiType.abstract = true; } - for (const base of (type.getBaseTypes() || [])) { + + const erasedBases = new Array(); + for (let base of (type.getBaseTypes() || [])) { if (jsiiType.base) { this._diagnostic(base.symbol.valueDeclaration, ts.DiagnosticCategory.Error, `Found multiple base types for ${jsiiType.fqn}`); continue; } + // tslint:disable-next-line: no-bitwise + while (base && (ts.getCombinedModifierFlags(base.symbol.valueDeclaration) & ts.ModifierFlags.Export) === 0) { + LOG.debug(`Base class of ${colors.green(jsiiType.fqn)} named ${colors.green(base.symbol.name)} is not exported, erasing it...`); + erasedBases.push(base); + base = (base.getBaseTypes() || [])[0]; + } + if (!base) { + continue; + } + const ref = await this._typeReference(base, type.symbol.valueDeclaration); if (!spec.isNamedTypeReference(ref)) { @@ -467,14 +479,18 @@ export class Assembler implements Emitter { throw new Error('Oh no'); } - for (const decl of type.symbol.declarations) { + const allDeclarations: Array<{ decl: ts.Declaration, type: ts.InterfaceType | ts.BaseType }> + = type.symbol.declarations.map(decl => ({ decl, type })); + // Considering erased bases' declarations, too, so they are "blended in" + erasedBases.forEach(base => allDeclarations.push(...base.symbol.declarations.map(decl => ({ decl, type: base })))); + for (const { decl, type: declaringType } of allDeclarations) { const classDecl = (decl as ts.ClassDeclaration | ts.InterfaceDeclaration); if (!classDecl.members) { continue; } for (const memberDecl of classDecl.members) { const member: ts.Symbol = (memberDecl as any).symbol; - if (!(type.symbol.getDeclarations() || []).find(d => d === memberDecl.parent)) { + if (!(declaringType.symbol.getDeclarations() || []).find(d => d === memberDecl.parent)) { continue; } @@ -501,7 +517,9 @@ export class Assembler implements Emitter { } } - const constructor = type.symbol.members && type.symbol.members.get(ts.InternalSymbolName.Constructor); + const constructor = (type.symbol.members && type.symbol.members.get(ts.InternalSymbolName.Constructor)) + // If no local constructor, look for one in the erased bases + || (erasedBases.map(base => base.symbol.members && base.symbol.members.get(ts.InternalSymbolName.Constructor)).find(ctor => ctor != null)); const ctorDeclaration = constructor && (constructor.declarations[0] as ts.ConstructorDeclaration); if (constructor && ctorDeclaration) { const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration); @@ -830,6 +848,10 @@ export class Assembler implements Emitter { } type.methods = type.methods || []; + if (type.methods.find(m => m.name === method.name && m.static === method.static) != null) { + LOG.trace(`Dropping re-declaration of ${colors.green(type.fqn)}#${colors.cyan(method.name!)}`); + return; + } type.methods.push(method); } @@ -878,6 +900,10 @@ export class Assembler implements Emitter { this._visitDocumentation(symbol, property); type.properties = type.properties || []; + if (type.properties.find(prop => prop.name === property.name && prop.static === property.static) != null) { + LOG.trace(`Dropping re-declaration of ${colors.green(type.fqn)}#${colors.cyan(property.name)}`); + return; + } type.properties.push(property); }