From d006f5c3a6b2330c334f2a4c16dba671ae83b878 Mon Sep 17 00:00:00 2001 From: Romain Marcadier-Muller Date: Wed, 3 Apr 2019 16:35:21 +0200 Subject: [PATCH] feat(jsii): Erase un-exported base classes instead of prohibiting those (#425) 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 + .../.jsii | 70 +++++++++++- .../CalculatorNamespace/JSII417Derived.cs | 38 +++++++ .../JSII417PublicBaseOfBase.cs | 38 +++++++ .../amazon/jsii/tests/calculator/$Module.java | 2 + .../jsii/tests/calculator/JSII417Derived.java | 25 +++++ .../calculator/JSII417PublicBaseOfBase.java | 25 +++++ .../python/src/jsii_calc/__init__.py | 39 ++++++- .../expected.jsii-calc/sphinx/jsii-calc.rst | 101 ++++++++++++++++++ .../jsii-reflect/test/classes.expected.txt | 2 + .../test/jsii-tree.test.all.expected.txt | 28 +++++ .../jsii-tree.test.inheritance.expected.txt | 3 + .../test/jsii-tree.test.members.expected.txt | 12 +++ .../test/jsii-tree.test.types.expected.txt | 2 + packages/jsii/lib/assembler.ts | 60 +++++++++-- .../jsii/test/negatives/neg.deref-error.ts | 9 -- .../test/negatives/neg.internal-base-class.ts | 10 -- 20 files changed, 534 insertions(+), 31 deletions(-) create mode 100644 packages/jsii-calc/lib/erasures.ts create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417Derived.cs create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417PublicBaseOfBase.cs create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417Derived.java create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417PublicBaseOfBase.java delete mode 100644 packages/jsii/test/negatives/neg.deref-error.ts delete mode 100644 packages/jsii/test/negatives/neg.internal-base-class.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 def34613d1..ff61744495 100644 --- a/packages/jsii-calc/lib/index.ts +++ b/packages/jsii-calc/lib/index.ts @@ -1,3 +1,4 @@ export * from './calculator'; export * from './compliance'; export * from './documented'; +export * from './erasures'; diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii index bca6e36ff5..29460bee26 100644 --- a/packages/jsii-calc/test/assembly.jsii +++ b/packages/jsii-calc/test/assembly.jsii @@ -2558,6 +2558,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", @@ -4732,5 +4800,5 @@ } }, "version": "0.8.2", - "fingerprint": "RIbNFrgvpaT1nTpWaVDxO0fWs/kZcVmMscsDGU/GXwE=" + "fingerprint": "5kMI3UzT9N2sovIndhAmSefEqtrUStnpb0hmWwfLjOs=" } 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-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii index bca6e36ff5..29460bee26 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii @@ -2558,6 +2558,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", @@ -4732,5 +4800,5 @@ } }, "version": "0.8.2", - "fingerprint": "RIbNFrgvpaT1nTpWaVDxO0fWs/kZcVmMscsDGU/GXwE=" + "fingerprint": "5kMI3UzT9N2sovIndhAmSefEqtrUStnpb0hmWwfLjOs=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417Derived.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417Derived.cs new file mode 100644 index 0000000000..f17487efe6 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417Derived.cs @@ -0,0 +1,38 @@ +using Amazon.JSII.Runtime.Deputy; + +namespace Amazon.JSII.Tests.CalculatorNamespace +{ + [JsiiClass(typeof(JSII417Derived), "jsii-calc.JSII417Derived", "[{\"name\":\"property\",\"type\":{\"primitive\":\"string\"}}]")] + public class JSII417Derived : JSII417PublicBaseOfBase + { + public JSII417Derived(string property): base(new DeputyProps(new object[]{property})) + { + } + + protected JSII417Derived(ByRefValue reference): base(reference) + { + } + + protected JSII417Derived(DeputyProps props): base(props) + { + } + + [JsiiProperty("property", "{\"primitive\":\"string\"}")] + protected virtual string Property + { + get => GetInstanceProperty(); + } + + [JsiiMethod("bar", null, "[]")] + public virtual void Bar() + { + InvokeInstanceVoidMethod(new object[]{}); + } + + [JsiiMethod("baz", null, "[]")] + public virtual void Baz() + { + InvokeInstanceVoidMethod(new object[]{}); + } + } +} \ No newline at end of file diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417PublicBaseOfBase.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417PublicBaseOfBase.cs new file mode 100644 index 0000000000..258b186702 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/JSII417PublicBaseOfBase.cs @@ -0,0 +1,38 @@ +using Amazon.JSII.Runtime.Deputy; + +namespace Amazon.JSII.Tests.CalculatorNamespace +{ + [JsiiClass(typeof(JSII417PublicBaseOfBase), "jsii-calc.JSII417PublicBaseOfBase", "[]")] + public class JSII417PublicBaseOfBase : DeputyBase + { + public JSII417PublicBaseOfBase(): base(new DeputyProps(new object[]{})) + { + } + + protected JSII417PublicBaseOfBase(ByRefValue reference): base(reference) + { + } + + protected JSII417PublicBaseOfBase(DeputyProps props): base(props) + { + } + + [JsiiProperty("hasRoot", "{\"primitive\":\"boolean\"}")] + public virtual bool HasRoot + { + get => GetInstanceProperty(); + } + + [JsiiMethod("makeInstance", "{\"fqn\":\"jsii-calc.JSII417PublicBaseOfBase\"}", "[]")] + public static JSII417PublicBaseOfBase MakeInstance() + { + return InvokeStaticMethod(typeof(JSII417PublicBaseOfBase), new object[]{}); + } + + [JsiiMethod("foo", null, "[]")] + public virtual void Foo() + { + InvokeInstanceVoidMethod(new object[]{}); + } + } +} \ No newline at end of file diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java index e74511530b..93243b9b76 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java +++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java @@ -80,6 +80,8 @@ protected Class resolveClass(final String fqn) throws ClassNotFoundException case "jsii-calc.InterfaceInNamespaceIncludesClasses.Foo": return software.amazon.jsii.tests.calculator.InterfaceInNamespaceIncludesClasses.Foo.class; case "jsii-calc.InterfaceInNamespaceIncludesClasses.Hello": return software.amazon.jsii.tests.calculator.InterfaceInNamespaceIncludesClasses.Hello.class; case "jsii-calc.InterfaceInNamespaceOnlyInterface.Hello": return software.amazon.jsii.tests.calculator.InterfaceInNamespaceOnlyInterface.Hello.class; + case "jsii-calc.JSII417Derived": return software.amazon.jsii.tests.calculator.JSII417Derived.class; + case "jsii-calc.JSII417PublicBaseOfBase": return software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase.class; case "jsii-calc.JSObjectLiteralForInterface": return software.amazon.jsii.tests.calculator.JSObjectLiteralForInterface.class; case "jsii-calc.JSObjectLiteralToNative": return software.amazon.jsii.tests.calculator.JSObjectLiteralToNative.class; case "jsii-calc.JSObjectLiteralToNativeClass": return software.amazon.jsii.tests.calculator.JSObjectLiteralToNativeClass.class; diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417Derived.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417Derived.java new file mode 100644 index 0000000000..e187620d45 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417Derived.java @@ -0,0 +1,25 @@ +package software.amazon.jsii.tests.calculator; + +@javax.annotation.Generated(value = "jsii-pacmak") +@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.JSII417Derived") +public class JSII417Derived extends software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase { + protected JSII417Derived(final software.amazon.jsii.JsiiObject.InitializationMode mode) { + super(mode); + } + public JSII417Derived(final java.lang.String property) { + super(software.amazon.jsii.JsiiObject.InitializationMode.Jsii); + software.amazon.jsii.JsiiEngine.getInstance().createNewObject(this, java.util.stream.Stream.of(java.util.Objects.requireNonNull(property, "property is required")).toArray()); + } + + public void bar() { + this.jsiiCall("bar", Void.class); + } + + public void baz() { + this.jsiiCall("baz", Void.class); + } + + protected java.lang.String getProperty() { + return this.jsiiGet("property", java.lang.String.class); + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417PublicBaseOfBase.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417PublicBaseOfBase.java new file mode 100644 index 0000000000..1cf5446ee8 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/JSII417PublicBaseOfBase.java @@ -0,0 +1,25 @@ +package software.amazon.jsii.tests.calculator; + +@javax.annotation.Generated(value = "jsii-pacmak") +@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.JSII417PublicBaseOfBase") +public class JSII417PublicBaseOfBase extends software.amazon.jsii.JsiiObject { + protected JSII417PublicBaseOfBase(final software.amazon.jsii.JsiiObject.InitializationMode mode) { + super(mode); + } + public JSII417PublicBaseOfBase() { + super(software.amazon.jsii.JsiiObject.InitializationMode.Jsii); + software.amazon.jsii.JsiiEngine.getInstance().createNewObject(this); + } + + public static software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase makeInstance() { + return software.amazon.jsii.JsiiObject.jsiiStaticCall(software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase.class, "makeInstance", software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase.class); + } + + public void foo() { + this.jsiiCall("foo", Void.class); + } + + public java.lang.Boolean getHasRoot() { + return this.jsiiGet("hasRoot", java.lang.Boolean.class); + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/python/src/jsii_calc/__init__.py b/packages/jsii-pacmak/test/expected.jsii-calc/python/src/jsii_calc/__init__.py index 3799aeeb42..a3a5377f51 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/python/src/jsii_calc/__init__.py +++ b/packages/jsii-pacmak/test/expected.jsii-calc/python/src/jsii_calc/__init__.py @@ -1257,6 +1257,43 @@ class Hello(jsii.compat.TypedDict): foo: jsii.Number +class JSII417PublicBaseOfBase(metaclass=jsii.JSIIMeta, jsii_type="jsii-calc.JSII417PublicBaseOfBase"): + def __init__(self) -> None: + jsii.create(JSII417PublicBaseOfBase, self, []) + + @jsii.member(jsii_name="makeInstance") + @classmethod + def make_instance(cls) -> "JSII417PublicBaseOfBase": + return jsii.sinvoke(cls, "makeInstance", []) + + @jsii.member(jsii_name="foo") + def foo(self) -> None: + return jsii.invoke(self, "foo", []) + + @property + @jsii.member(jsii_name="hasRoot") + def has_root(self) -> bool: + return jsii.get(self, "hasRoot") + + +class JSII417Derived(JSII417PublicBaseOfBase, metaclass=jsii.JSIIMeta, jsii_type="jsii-calc.JSII417Derived"): + def __init__(self, property: str) -> None: + jsii.create(JSII417Derived, self, [property]) + + @jsii.member(jsii_name="bar") + def bar(self) -> None: + return jsii.invoke(self, "bar", []) + + @jsii.member(jsii_name="baz") + def baz(self) -> None: + return jsii.invoke(self, "baz", []) + + @property + @jsii.member(jsii_name="property") + def _property(self) -> str: + return jsii.get(self, "property") + + class JSObjectLiteralForInterface(metaclass=jsii.JSIIMeta, jsii_type="jsii-calc.JSObjectLiteralForInterface"): def __init__(self) -> None: jsii.create(JSObjectLiteralForInterface, self, []) @@ -2454,6 +2491,6 @@ def parts(self, value: typing.List[scope.jsii_calc_lib.Value]): return jsii.set(self, "parts", value) -__all__ = ["AbstractClass", "AbstractClassBase", "AbstractClassReturner", "Add", "AllTypes", "AllTypesEnum", "AllowedMethodNames", "AsyncVirtualMethods", "AugmentableClass", "BinaryOperation", "Calculator", "CalculatorProps", "ClassThatImplementsTheInternalInterface", "ClassThatImplementsThePrivateInterface", "ClassWithMutableObjectLiteralProperty", "ClassWithPrivateConstructorAndAutomaticProperties", "ConstructorPassesThisOut", "Constructors", "ConsumersOfThisCrazyTypeSystem", "DefaultedConstructorArgument", "DerivedClassHasNoProperties", "DerivedStruct", "DoNotOverridePrivates", "DoNotRecognizeAnyAsOptional", "DocumentedClass", "DontComplainAboutVariadicAfterOptional", "DoubleTrouble", "EraseUndefinedHashValues", "EraseUndefinedHashValuesOptions", "ExportedBaseClass", "ExtendsInternalInterface", "ExtendsPrivateInterface", "GiveMeStructs", "Greetee", "GreetingAugmenter", "IAnotherPublicInterface", "IFriendlier", "IFriendlyRandomGenerator", "IInterfaceImplementedByAbstractClass", "IInterfaceThatShouldNotBeADataType", "IInterfaceWithInternal", "IInterfaceWithMethods", "IInterfaceWithOptionalMethodArguments", "IInterfaceWithProperties", "IInterfaceWithPropertiesExtension", "IMutableObjectLiteral", "INonInternalInterface", "IPrivatelyImplemented", "IPublicInterface", "IPublicInterface2", "IRandomNumberGenerator", "IReturnsNumber", "ImplementInternalInterface", "ImplementsInterfaceWithInternal", "ImplementsInterfaceWithInternalSubclass", "ImplementsPrivateInterface", "ImplictBaseOfBase", "InbetweenClass", "InterfaceInNamespaceIncludesClasses", "InterfaceInNamespaceOnlyInterface", "JSObjectLiteralForInterface", "JSObjectLiteralToNative", "JSObjectLiteralToNativeClass", "JavaReservedWords", "JsiiAgent", "LoadBalancedFargateServiceProps", "Multiply", "Negate", "NodeStandardLibrary", "NullShouldBeTreatedAsUndefined", "NullShouldBeTreatedAsUndefinedData", "NumberGenerator", "ObjectRefsInCollections", "Old", "OptionalConstructorArgument", "OptionalStruct", "OptionalStructConsumer", "OverrideReturnsObject", "PartiallyInitializedThisConsumer", "Polymorphism", "Power", "PublicClass", "PythonReservedWords", "ReferenceEnumFromScopedPackage", "ReturnsPrivateImplementationOfInterface", "RuntimeTypeChecking", "SingleInstanceTwoTypes", "Statics", "StringEnum", "StripInternal", "Sum", "SyncVirtualMethods", "Thrower", "UnaryOperation", "UnionProperties", "UseBundledDependency", "UseCalcBase", "UsesInterfaceWithProperties", "VariadicMethod", "VirtualMethodPlayground", "__jsii_assembly__", "composition"] +__all__ = ["AbstractClass", "AbstractClassBase", "AbstractClassReturner", "Add", "AllTypes", "AllTypesEnum", "AllowedMethodNames", "AsyncVirtualMethods", "AugmentableClass", "BinaryOperation", "Calculator", "CalculatorProps", "ClassThatImplementsTheInternalInterface", "ClassThatImplementsThePrivateInterface", "ClassWithMutableObjectLiteralProperty", "ClassWithPrivateConstructorAndAutomaticProperties", "ConstructorPassesThisOut", "Constructors", "ConsumersOfThisCrazyTypeSystem", "DefaultedConstructorArgument", "DerivedClassHasNoProperties", "DerivedStruct", "DoNotOverridePrivates", "DoNotRecognizeAnyAsOptional", "DocumentedClass", "DontComplainAboutVariadicAfterOptional", "DoubleTrouble", "EraseUndefinedHashValues", "EraseUndefinedHashValuesOptions", "ExportedBaseClass", "ExtendsInternalInterface", "ExtendsPrivateInterface", "GiveMeStructs", "Greetee", "GreetingAugmenter", "IAnotherPublicInterface", "IFriendlier", "IFriendlyRandomGenerator", "IInterfaceImplementedByAbstractClass", "IInterfaceThatShouldNotBeADataType", "IInterfaceWithInternal", "IInterfaceWithMethods", "IInterfaceWithOptionalMethodArguments", "IInterfaceWithProperties", "IInterfaceWithPropertiesExtension", "IMutableObjectLiteral", "INonInternalInterface", "IPrivatelyImplemented", "IPublicInterface", "IPublicInterface2", "IRandomNumberGenerator", "IReturnsNumber", "ImplementInternalInterface", "ImplementsInterfaceWithInternal", "ImplementsInterfaceWithInternalSubclass", "ImplementsPrivateInterface", "ImplictBaseOfBase", "InbetweenClass", "InterfaceInNamespaceIncludesClasses", "InterfaceInNamespaceOnlyInterface", "JSII417Derived", "JSII417PublicBaseOfBase", "JSObjectLiteralForInterface", "JSObjectLiteralToNative", "JSObjectLiteralToNativeClass", "JavaReservedWords", "JsiiAgent", "LoadBalancedFargateServiceProps", "Multiply", "Negate", "NodeStandardLibrary", "NullShouldBeTreatedAsUndefined", "NullShouldBeTreatedAsUndefinedData", "NumberGenerator", "ObjectRefsInCollections", "Old", "OptionalConstructorArgument", "OptionalStruct", "OptionalStructConsumer", "OverrideReturnsObject", "PartiallyInitializedThisConsumer", "Polymorphism", "Power", "PublicClass", "PythonReservedWords", "ReferenceEnumFromScopedPackage", "ReturnsPrivateImplementationOfInterface", "RuntimeTypeChecking", "SingleInstanceTwoTypes", "Statics", "StringEnum", "StripInternal", "Sum", "SyncVirtualMethods", "Thrower", "UnaryOperation", "UnionProperties", "UseBundledDependency", "UseCalcBase", "UsesInterfaceWithProperties", "VariadicMethod", "VirtualMethodPlayground", "__jsii_assembly__", "composition"] publication.publish() diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst b/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst index 0eb087ee74..2654e12ce3 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst +++ b/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst @@ -3279,6 +3279,107 @@ Hello (interface) .. py:currentmodule:: jsii-calc +JSII417Derived +^^^^^^^^^^^^^^ + +.. py:class:: JSII417Derived(property) + + **Language-specific names:** + + .. tabs:: + + .. code-tab:: c# + + using Amazon.JSII.Tests.CalculatorNamespace; + + .. code-tab:: java + + import software.amazon.jsii.tests.calculator.JSII417Derived; + + .. code-tab:: javascript + + const { JSII417Derived } = require('jsii-calc'); + + .. code-tab:: typescript + + import { JSII417Derived } from 'jsii-calc'; + + + + :extends: :py:class:`~jsii-calc.JSII417PublicBaseOfBase`\ + :param property: + :type property: string + + .. py:method:: bar() + + + + .. py:method:: baz() + + + + .. py:attribute:: property + + *Protected property* + + :type: string *(readonly)* + + + .. py:method:: foo() + + *Inherited from* :py:meth:`jsii-calc.JSII417PublicBaseOfBase ` + + + + .. py:attribute:: hasRoot + + *Inherited from* :py:attr:`jsii-calc.JSII417PublicBaseOfBase ` + + :type: boolean *(readonly)* + + +JSII417PublicBaseOfBase +^^^^^^^^^^^^^^^^^^^^^^^ + +.. py:class:: JSII417PublicBaseOfBase() + + **Language-specific names:** + + .. tabs:: + + .. code-tab:: c# + + using Amazon.JSII.Tests.CalculatorNamespace; + + .. code-tab:: java + + import software.amazon.jsii.tests.calculator.JSII417PublicBaseOfBase; + + .. code-tab:: javascript + + const { JSII417PublicBaseOfBase } = require('jsii-calc'); + + .. code-tab:: typescript + + import { JSII417PublicBaseOfBase } from 'jsii-calc'; + + + + + .. py:staticmethod:: makeInstance() -> jsii-calc.JSII417PublicBaseOfBase + + :rtype: :py:class:`~jsii-calc.JSII417PublicBaseOfBase`\ + + + .. py:method:: foo() + + + + .. py:attribute:: hasRoot + + :type: boolean *(readonly)* + + JSObjectLiteralForInterface ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/jsii-reflect/test/classes.expected.txt b/packages/jsii-reflect/test/classes.expected.txt index 7fd29ad13d..cb4fb67e59 100644 --- a/packages/jsii-reflect/test/classes.expected.txt +++ b/packages/jsii-reflect/test/classes.expected.txt @@ -35,6 +35,8 @@ ImplementsInterfaceWithInternal ImplementsInterfaceWithInternalSubclass ImplementsPrivateInterface InbetweenClass +JSII417Derived +JSII417PublicBaseOfBase JSObjectLiteralForInterface JSObjectLiteralToNative JSObjectLiteralToNativeClass diff --git a/packages/jsii-reflect/test/jsii-tree.test.all.expected.txt b/packages/jsii-reflect/test/jsii-tree.test.all.expected.txt index 7583982279..af0f6df45d 100644 --- a/packages/jsii-reflect/test/jsii-tree.test.all.expected.txt +++ b/packages/jsii-reflect/test/jsii-tree.test.all.expected.txt @@ -512,6 +512,34 @@ assemblies │ │ │ └── returns: void │ │ └─┬ bar property │ │ └── type: string? + │ ├─┬ class JSII417Derived + │ │ ├── base: JSII417PublicBaseOfBase + │ │ └─┬ members + │ │ ├─┬ (property) method + │ │ │ ├─┬ parameters + │ │ │ │ └─┬ property + │ │ │ │ └── type: string + │ │ │ └── returns: void + │ │ ├─┬ bar() method + │ │ │ └── returns: void + │ │ ├─┬ baz() method + │ │ │ └── returns: void + │ │ └─┬ property property + │ │ ├── immutable + │ │ ├── protected + │ │ └── type: string + │ ├─┬ class JSII417PublicBaseOfBase + │ │ └─┬ members + │ │ ├─┬ () method + │ │ │ └── returns: void + │ │ ├─┬ makeInstance() method + │ │ │ ├── static + │ │ │ └── returns: jsii-calc.JSII417PublicBaseOfBase + │ │ ├─┬ foo() method + │ │ │ └── returns: void + │ │ └─┬ hasRoot property + │ │ ├── immutable + │ │ └── type: boolean │ ├─┬ class JSObjectLiteralForInterface │ │ └─┬ members │ │ ├─┬ () method diff --git a/packages/jsii-reflect/test/jsii-tree.test.inheritance.expected.txt b/packages/jsii-reflect/test/jsii-tree.test.inheritance.expected.txt index d7b4958c78..7314fd81f9 100644 --- a/packages/jsii-reflect/test/jsii-tree.test.inheritance.expected.txt +++ b/packages/jsii-reflect/test/jsii-tree.test.inheritance.expected.txt @@ -51,6 +51,9 @@ assemblies │ │ ├── base: PublicClass │ │ └── interfaces: IPublicInterface2 │ ├── class Foo + │ ├─┬ class JSII417Derived + │ │ └── base: JSII417PublicBaseOfBase + │ ├── class JSII417PublicBaseOfBase │ ├── class JSObjectLiteralForInterface │ ├── class JSObjectLiteralToNative │ ├── class JSObjectLiteralToNativeClass diff --git a/packages/jsii-reflect/test/jsii-tree.test.members.expected.txt b/packages/jsii-reflect/test/jsii-tree.test.members.expected.txt index 651e46efbe..760adca226 100644 --- a/packages/jsii-reflect/test/jsii-tree.test.members.expected.txt +++ b/packages/jsii-reflect/test/jsii-tree.test.members.expected.txt @@ -210,6 +210,18 @@ assemblies │ │ └─┬ members │ │ ├── () method │ │ └── bar property + │ ├─┬ class JSII417Derived + │ │ └─┬ members + │ │ ├── (property) method + │ │ ├── bar() method + │ │ ├── baz() method + │ │ └── property property + │ ├─┬ class JSII417PublicBaseOfBase + │ │ └─┬ members + │ │ ├── () method + │ │ ├── makeInstance() method + │ │ ├── foo() method + │ │ └── hasRoot property │ ├─┬ class JSObjectLiteralForInterface │ │ └─┬ members │ │ ├── () method diff --git a/packages/jsii-reflect/test/jsii-tree.test.types.expected.txt b/packages/jsii-reflect/test/jsii-tree.test.types.expected.txt index ea44cf1652..6c70eb5d4e 100644 --- a/packages/jsii-reflect/test/jsii-tree.test.types.expected.txt +++ b/packages/jsii-reflect/test/jsii-tree.test.types.expected.txt @@ -36,6 +36,8 @@ assemblies │ ├── class ImplementsPrivateInterface │ ├── class InbetweenClass │ ├── class Foo + │ ├── class JSII417Derived + │ ├── class JSII417PublicBaseOfBase │ ├── class JSObjectLiteralForInterface │ ├── class JSObjectLiteralToNative │ ├── class JSObjectLiteralToNativeClass diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 2dd6e5bef2..ed951f5a80 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -420,12 +420,29 @@ 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; } + /* + * Crawl up the inheritance tree if the current base type is not exported, so we identify the type(s) to be + * erased, and identify the closest exported base class, should there be one. + */ + // tslint:disable-next-line: no-bitwise + while (base && this._isPrivateOrInternal(base.symbol)) { + 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) { + // There is no exported base class to be found, pretend this class has no base class. + continue; + } + const ref = await this._typeReference(base, type.symbol.valueDeclaration); if (!spec.isNamedTypeReference(ref)) { @@ -470,14 +487,21 @@ 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" + for (const base of erasedBases) { + 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; } @@ -504,7 +528,8 @@ export class Assembler implements Emitter { } } - const constructor = type.symbol.members && type.symbol.members.get(ts.InternalSymbolName.Constructor); + // Find the first defined constructor in this class, or it's erased bases + const constructor = [type, ...erasedBases].map(getConstructor).find(ctor => ctor != null); const ctorDeclaration = constructor && (constructor.declarations[0] as ts.ConstructorDeclaration); if (constructor && ctorDeclaration) { const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration); @@ -557,7 +582,7 @@ export class Assembler implements Emitter { const hasUnderscorePrefix = symbol.name !== '__constructor' && symbol.name.startsWith('_'); if (_isPrivate(symbol)) { - LOG.trace(`${symbol.name} is marked "private"`); + LOG.trace(`${colors.cyan(symbol.name)} is marked "private", or is an unexported type declaration`); return true; } @@ -569,12 +594,12 @@ export class Assembler implements Emitter { if (validateDeclaration) { if (!hasUnderscorePrefix) { this._diagnostic(validateDeclaration, ts.DiagnosticCategory.Error, - `${symbol.name}: the name of members marked as @internal must begin with an underscore`); + `${colors.cyan(symbol.name)}: the name of members marked as @internal must begin with an underscore`); } if (!hasInternalJsDocTag) { this._diagnostic(validateDeclaration, ts.DiagnosticCategory.Error, - `${symbol.name}: members with names that begin with an underscore must be marked as @internal via a JSDoc tag`); + `${colors.cyan(symbol.name)}: members with names that begin with an underscore must be marked as @internal via a JSDoc tag`); } } @@ -817,6 +842,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); } @@ -865,6 +894,10 @@ export class Assembler implements Emitter { property.docs = this._visitDocumentation(symbol); 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); } @@ -1167,10 +1200,14 @@ function _isExported(node: ts.Declaration): boolean { * @return `true` if the symbol should be hidden */ function _isPrivate(symbol: ts.Symbol): boolean { - + const TYPE_DECLARATION_KINDS = new Set([ + ts.SyntaxKind.ClassDeclaration, + ts.SyntaxKind.InterfaceDeclaration, + ts.SyntaxKind.EnumDeclaration, + ]); // if the symbol doesn't have a value declaration, we are assuming it's a type (enum/interface/class) // and check that it has an "export" modifier - if (!symbol.valueDeclaration) { + if (!symbol.valueDeclaration || TYPE_DECLARATION_KINDS.has(symbol.valueDeclaration.kind)) { let hasExport = false; for (const decl of symbol.declarations) { // tslint:disable-next-line:no-bitwise @@ -1346,3 +1383,8 @@ function interfaceMemberNames(jsiiType: spec.InterfaceType): string[] { function isInterfaceName(name: string) { return name.length >= 2 && name.charAt(0) === 'I' && name.charAt(1).toUpperCase() === name.charAt(1); } + +function getConstructor(type: ts.Type): ts.Symbol | undefined { + return type.symbol.members + && type.symbol.members.get(ts.InternalSymbolName.Constructor); +} diff --git a/packages/jsii/test/negatives/neg.deref-error.ts b/packages/jsii/test/negatives/neg.deref-error.ts deleted file mode 100644 index f0179642bd..0000000000 --- a/packages/jsii/test/negatives/neg.deref-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -///!MATCH_ERROR: Unable to resolve referenced type 'Base'. Type may be @internal or unexported - -class Base { - -} - -export class Derived extends Base { - -} \ No newline at end of file diff --git a/packages/jsii/test/negatives/neg.internal-base-class.ts b/packages/jsii/test/negatives/neg.internal-base-class.ts deleted file mode 100644 index 3ff0231c92..0000000000 --- a/packages/jsii/test/negatives/neg.internal-base-class.ts +++ /dev/null @@ -1,10 +0,0 @@ -///!MATCH_ERROR: Unable to resolve referenced type 'jsii.InternalBaseClass'. Type may be @internal or unexported - -/** @internal */ -export class InternalBaseClass { - -} - -export class MyClass extends InternalBaseClass { - -}