From 0fbf42b3f2d35a469e55da68d87b673233a8f7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Wed, 3 Apr 2019 11:23:36 +0200 Subject: [PATCH] feat(jsii): Erase un-exported base classes When an exported class extends an un-exported class, merge the declarations of the un-exported base into the exported one, and replace the base class relationship with the base's own base (recursively until an exported base is found, or no further base class is declared - in which case the exported class will not have a base class at all). Declarations from the erased base class(es) will be ignored if there is another declaration with the same name and type in the exported type (or another erased base class that is closer to the exported type in the inheritance chain). Fixes #417 --- packages/jsii-calc/lib/erasures.ts | 25 +++++++++ packages/jsii-calc/lib/index.ts | 1 + packages/jsii-calc/test/assembly.jsii | 70 +++++++++++++++++++++++- packages/jsii-kernel/test/test.kernel.ts | 5 ++ packages/jsii/lib/assembler.ts | 34 ++++++++++-- 5 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 packages/jsii-calc/lib/erasures.ts 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); }