From 181012e0213aa53defcdfe6b11a7d43495d25781 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 10 Oct 2018 09:22:08 +0200 Subject: [PATCH] fix(jsii): support public autoproperties in private constructor (#256) Includes a fix in the .NET generator to add support for private constructors. --- packages/jsii-calc/lib/compliance.ts | 8 + packages/jsii-calc/test/assembly.jsii | 37 +++- .../Class/ClassGeneratorTests.cs | 30 ++++ .../Class/ClassGenerator.cs | 68 ++++---- .../Amazon.JSII.Generator/MethodExtensions.cs | 10 +- .../ComplianceTests.cs | 164 ++++++++++-------- .../.jsii | 37 +++- ...rivateConstructorAndAutomaticProperties.cs | 30 ++++ .../amazon/jsii/tests/calculator/$Module.java | 1 + ...vateConstructorAndAutomaticProperties.java | 27 +++ .../expected.jsii-calc/sphinx/jsii-calc.rst | 46 +++++ packages/jsii-runtime/package-lock.json | 28 ++- packages/jsii/lib/assembler.ts | 17 +- 13 files changed, 381 insertions(+), 122 deletions(-) create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/ClassWithPrivateConstructorAndAutomaticProperties.cs create mode 100644 packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/ClassWithPrivateConstructorAndAutomaticProperties.java diff --git a/packages/jsii-calc/lib/compliance.ts b/packages/jsii-calc/lib/compliance.ts index c0e3f1451f..6da86e64fe 100644 --- a/packages/jsii-calc/lib/compliance.ts +++ b/packages/jsii-calc/lib/compliance.ts @@ -914,3 +914,11 @@ export class DoNotOverridePrivates { this.privateProperty = newValue; } } + +/** + * Class that implements interface properties automatically, but using a private constructor + */ +export class ClassWithPrivateConstructorAndAutomaticProperties implements IInterfaceWithProperties { + private constructor(public readonly readOnlyString: string, public readWriteString: string) { + } +} diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii index 3ba8c034b9..652b664801 100644 --- a/packages/jsii-calc/test/assembly.jsii +++ b/packages/jsii-calc/test/assembly.jsii @@ -1035,6 +1035,41 @@ } ] }, + "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": { + "assembly": "jsii-calc", + "docs": { + "comment": "Class that implements interface properties automatically, but using a private constructor" + }, + "fqn": "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties", + "interfaces": [ + { + "fqn": "jsii-calc.IInterfaceWithProperties" + } + ], + "kind": "class", + "name": "ClassWithPrivateConstructorAndAutomaticProperties", + "properties": [ + { + "immutable": true, + "name": "readOnlyString", + "overrides": { + "fqn": "jsii-calc.IInterfaceWithProperties" + }, + "type": { + "primitive": "string" + } + }, + { + "name": "readWriteString", + "overrides": { + "fqn": "jsii-calc.IInterfaceWithProperties" + }, + "type": { + "primitive": "string" + } + } + ] + }, "jsii-calc.DefaultedConstructorArgument": { "assembly": "jsii-calc", "fqn": "jsii-calc.DefaultedConstructorArgument", @@ -3297,5 +3332,5 @@ } }, "version": "0.7.6", - "fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM=" + "fingerprint": "eFasWxN7YC37iWdz+dDbEFTSQCzyangrqP5Nu02rzpw=" } diff --git a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs index f430a9b358..5c44436163 100644 --- a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs +++ b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs @@ -57,6 +57,36 @@ protected MyClass(DeputyProps props): base(props) Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + [Fact(DisplayName = Prefix + nameof(AllowsPrivateConstructor))] + public void AllowsPrivateConstructor() + { + ClassType classType = new ClassType + ( + fullyQualifiedName: "myFqn", + assembly: "myPackage", + name: "myClass", + isAbstract: false + ); + + string actual = Render(classType); + string expected = + @"namespace MyNamespace +{ + [JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")] + public class MyClass : DeputyBase + { + protected MyClass(ByRefValue reference): base(reference) + { + } + + protected MyClass(DeputyProps props): base(props) + { + } + } +}"; + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } + [Fact(DisplayName = Prefix + nameof(IncludesAbstractKeyword))] public void IncludesAbstractKeyword() { diff --git a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/Class/ClassGenerator.cs b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/Class/ClassGenerator.cs index 32233e20b6..266bb71aa1 100644 --- a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/Class/ClassGenerator.cs +++ b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/Class/ClassGenerator.cs @@ -1,9 +1,9 @@ -using Amazon.JSII.JsonModel.Spec; +using System.Collections.Generic; +using System.Linq; +using Amazon.JSII.JsonModel.Spec; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Amazon.JSII.Generator.Class @@ -97,37 +97,41 @@ IEnumerable CreateConstructors() { SyntaxToken typeName = Symbols.GetNameSyntaxToken(Type); - yield return SF.ConstructorDeclaration - ( - SF.List(), - SF.TokenList(SF.Token( - Type.IsAbstract || Type.Initializer.IsProtected ? - SyntaxKind.ProtectedKeyword : - SyntaxKind.PublicKeyword - )), - typeName, - Type.Initializer.GetParameterListSyntax(Namespaces, Symbols), - SF.ConstructorInitializer + if (Type.Initializer != null) + { + yield return SF.ConstructorDeclaration ( - SyntaxKind.BaseConstructorInitializer, - SF.ArgumentList( - SF.SeparatedList(new[] { - SF.Argument( - SF.ObjectCreationExpression( - SF.Token(SyntaxKind.NewKeyword), - SF.ParseTypeName("DeputyProps"), - SF.ArgumentList(SF.SeparatedList( - new[] { GetBaseArgument() } - )), - null + SF.List(), + SF.TokenList(SF.Token( + Type.IsAbstract || Type.Initializer.IsProtected + ? SyntaxKind.ProtectedKeyword + : SyntaxKind.PublicKeyword + )), + typeName, + Type.Initializer.GetParameterListSyntax(Namespaces, Symbols), + SF.ConstructorInitializer + ( + SyntaxKind.BaseConstructorInitializer, + SF.ArgumentList( + SF.SeparatedList(new[] + { + SF.Argument( + SF.ObjectCreationExpression( + SF.Token(SyntaxKind.NewKeyword), + SF.ParseTypeName("DeputyProps"), + SF.ArgumentList(SF.SeparatedList( + new[] {GetBaseArgument()} + )), + null + ) ) - ) - }) - ) - ), - SF.Block(), - null - ); + }) + ) + ), + SF.Block(), + null + ); + } yield return SF.ConstructorDeclaration ( diff --git a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/MethodExtensions.cs b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/MethodExtensions.cs index 69d683a3ba..db411d9bc0 100644 --- a/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/MethodExtensions.cs +++ b/packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/MethodExtensions.cs @@ -1,10 +1,10 @@ -using Amazon.JSII.JsonModel.Spec; +using System; +using System.Collections.Generic; +using System.Linq; +using Amazon.JSII.JsonModel.Spec; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Amazon.JSII.Generator @@ -44,7 +44,7 @@ IEnumerable GetParameters() public static SyntaxToken GetParametersJsonSyntaxToken(this Method method) { // Strip docs before serializing. - Parameter[] parameters = (method.Parameters ?? Enumerable.Empty()) + Parameter[] parameters = (method?.Parameters ?? Enumerable.Empty()) .Select(p => new Parameter(p.Name, p.Type)) .ToArray(); diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs index a9dca0351f..37eda66883 100644 --- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs +++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Amazon.JSII.Runtime.Deputy; using Amazon.JSII.Tests.CalculatorNamespace; using Amazon.JSII.Tests.CalculatorNamespace.composition.CompositeOperation; @@ -47,7 +48,7 @@ public void PrimitiveTypes() // number types.NumberProperty = 1234; - Assert.Equal((double)1234, types.NumberProperty); + Assert.Equal((double) 1234, types.NumberProperty); // date types.DateProperty = UnixEpoch.AddMilliseconds(123); @@ -55,7 +56,7 @@ public void PrimitiveTypes() // json types.JsonProperty = JObject.Parse(@"{ ""Foo"": 123 }"); - Assert.Equal((double)123, types.JsonProperty["Foo"].Value()); + Assert.Equal((double) 123, types.JsonProperty["Foo"].Value()); } [Fact(DisplayName = Prefix + nameof(Dates))] @@ -78,14 +79,14 @@ public void CollectionTypes() AllTypes types = new AllTypes(); // array - types.ArrayProperty = new[] { "Hello", "World" }; + types.ArrayProperty = new[] {"Hello", "World"}; Assert.Equal("World", types.ArrayProperty[1]); // map IDictionary map = new Dictionary(); map["Foo"] = 123; types.MapProperty = map; - Assert.Equal((double)123, types.MapProperty["Foo"]); + Assert.Equal((double) 123, types.MapProperty["Foo"]); } [Fact(DisplayName = Prefix + nameof(DynamicTypes))] @@ -95,7 +96,7 @@ public void DynamicTypes() // boolean types.AnyProperty = false; - Assert.False((bool)types.AnyProperty); + Assert.False((bool) types.AnyProperty); // string types.AnyProperty = "String"; @@ -103,7 +104,7 @@ public void DynamicTypes() // number types.AnyProperty = 12; - Assert.Equal((double)12, types.AnyProperty); + Assert.Equal((double) 12, types.AnyProperty); // date types.AnyProperty = UnixEpoch.AddSeconds(1234); @@ -116,37 +117,37 @@ public void DynamicTypes() new JObject(new JProperty("World", 123)) ) )); - var @object = (IDictionary)types.AnyProperty; - var array = (object[])@object["Goo"]; - var innerObject = (IDictionary)array[1]; - Assert.Equal((double)123, innerObject["World"]); + var @object = (IDictionary) types.AnyProperty; + var array = (object[]) @object["Goo"]; + var innerObject = (IDictionary) array[1]; + Assert.Equal((double) 123, innerObject["World"]); // array - types.AnyProperty = new[] { "Hello", "World" }; - Assert.Equal("Hello", ((object[])types.AnyProperty)[0]); - Assert.Equal("World", ((object[])types.AnyProperty)[1]); + types.AnyProperty = new[] {"Hello", "World"}; + Assert.Equal("Hello", ((object[]) types.AnyProperty)[0]); + Assert.Equal("World", ((object[]) types.AnyProperty)[1]); // array of any - types.AnyArrayProperty = new object[] { "Hybrid", new Number(12), 123, false }; - Assert.Equal((double)123, types.AnyArrayProperty[2]); + types.AnyArrayProperty = new object[] {"Hybrid", new Number(12), 123, false}; + Assert.Equal((double) 123, types.AnyArrayProperty[2]); // map IDictionary map = new Dictionary(); map["MapKey"] = "MapValue"; types.AnyProperty = map; - Assert.Equal("MapValue", ((IDictionary)types.AnyProperty)["MapKey"]); + Assert.Equal("MapValue", ((IDictionary) types.AnyProperty)["MapKey"]); // map of any map["Goo"] = 19289812; types.AnyMapProperty = map; - Assert.Equal((double)19289812, types.AnyMapProperty["Goo"]); + Assert.Equal((double) 19289812, types.AnyMapProperty["Goo"]); // classes Multiply mult = new Multiply(new Number(10), new Number(20)); types.AnyProperty = mult; Assert.Same(types.AnyProperty, mult); Assert.IsType(types.AnyProperty); - Assert.Equal((double)200, ((Multiply)types.AnyProperty).Value); + Assert.Equal((double) 200, ((Multiply) types.AnyProperty).Value); } [Fact(DisplayName = Prefix + nameof(UnionTypes))] @@ -156,13 +157,13 @@ public void UnionTypes() // single valued property types.UnionProperty = 1234; - Assert.Equal((double)1234, types.UnionProperty); + Assert.Equal((double) 1234, types.UnionProperty); types.UnionProperty = "Hello"; Assert.Equal("Hello", types.UnionProperty); types.UnionProperty = new Multiply(new Number(2), new Number(12)); - Assert.Equal((double)24, ((Multiply)types.UnionProperty).Value); + Assert.Equal((double) 24, ((Multiply) types.UnionProperty).Value); // NOTE: union collections are untyped in C# (System.Object) @@ -199,13 +200,13 @@ public void CreateObjectAndCtorOverloads() public void GetSetPrimitiveProperties() { Number number = new Number(20); - Assert.Equal((double)20, number.Value); - Assert.Equal((double)40, number.DoubleValue); - Assert.Equal((double)-30, new Negate(new Add(new Number(20), new Number(10))).Value); - Assert.Equal((double)20, new Multiply(new Add(new Number(5), new Number(5)), new Number(2)).Value); - Assert.Equal((double)3 * 3 * 3 * 3, new Power(new Number(3), new Number(4)).Value); - Assert.Equal((double)999, new Power(new Number(999), new Number(1)).Value); - Assert.Equal((double)1, new Power(new Number(999), new Number(0)).Value); + Assert.Equal((double) 20, number.Value); + Assert.Equal((double) 40, number.DoubleValue); + Assert.Equal((double) -30, new Negate(new Add(new Number(20), new Number(10))).Value); + Assert.Equal((double) 20, new Multiply(new Add(new Number(5), new Number(5)), new Number(2)).Value); + Assert.Equal((double) 3 * 3 * 3 * 3, new Power(new Number(3), new Number(4)).Value); + Assert.Equal((double) 999, new Power(new Number(999), new Number(1)).Value); + Assert.Equal((double) 1, new Power(new Number(999), new Number(0)).Value); } [Fact(DisplayName = Prefix + nameof(CallMethods))] @@ -215,16 +216,16 @@ public void CallMethods() Calculator calc = new Calculator(new CalculatorProps()); calc.Add(10); - Assert.Equal((double)10, calc.Value); + Assert.Equal((double) 10, calc.Value); calc.Mul(2); - Assert.Equal((double)20, calc.Value); + Assert.Equal((double) 20, calc.Value); calc.Pow(5); - Assert.Equal((double)20 * 20 * 20 * 20 * 20, calc.Value); + Assert.Equal((double) 20 * 20 * 20 * 20 * 20, calc.Value); calc.Neg(); - Assert.Equal((double)-3200000, calc.Value); + Assert.Equal((double) -3200000, calc.Value); } [Fact(DisplayName = Prefix + nameof(UnmarkshallIntoAbstractType))] @@ -235,7 +236,7 @@ public void UnmarkshallIntoAbstractType() calc.Add(120); Value_ value = calc.Curr; - Assert.Equal((double)120, value.Value); + Assert.Equal((double) 120, value.Value); } [Fact(DisplayName = Prefix + nameof(GetAndSetNotPrimitiveProperties))] @@ -247,7 +248,7 @@ public void GetAndSetNotPrimitiveProperties() calc.Add(3200000); calc.Neg(); calc.Curr = new Multiply(new Number(2), calc.Curr); - Assert.Equal((double)-6400000, calc.Value); + Assert.Equal((double) -6400000, calc.Value); } [Fact(DisplayName = Prefix + nameof(GetAndSetEnumValues))] @@ -289,10 +290,10 @@ public void UndefinedAndNull() public void Arrays() { Sum sum = new Sum(); - sum.Parts = new Value_[] { new Number(5), new Number(10), new Multiply(new Number(2), new Number(3)) }; - Assert.Equal((double)10 + 5 + (2 * 3), sum.Value); - Assert.Equal((double)5, sum.Parts[0].Value); - Assert.Equal((double)6, sum.Parts[2].Value); + sum.Parts = new Value_[] {new Number(5), new Number(10), new Multiply(new Number(2), new Number(3))}; + Assert.Equal((double) 10 + 5 + (2 * 3), sum.Value); + Assert.Equal((double) 5, sum.Parts[0].Value); + Assert.Equal((double) 6, sum.Parts[2].Value); Assert.Equal("(((0 + 5) + 10) + (2 * 3))", sum.ToString()); } @@ -308,7 +309,7 @@ public void Maps() Assert.Collection( calc.OperationsMap["add"], val => { }, - val => Assert.Equal((double)30, val.Value) + val => Assert.Equal((double) 30, val.Value) ); Assert.Collection( calc.OperationsMap["mul"], @@ -338,13 +339,13 @@ public void Exceptions() }); calc.Add(3); - Assert.Equal((double)23, calc.Value); + Assert.Equal((double) 23, calc.Value); Assert.Throws(() => calc.Add(10)); calc.MaxValue = 40; calc.Add(10); - Assert.Equal((double)33, calc.Value); + Assert.Equal((double) 33, calc.Value); } [Fact(DisplayName = Prefix + nameof(UnionProperties))] @@ -355,7 +356,7 @@ public void UnionProperties() calc.UnionProperty = new Multiply(new Number(9), new Number(3)); Assert.IsType(calc.UnionProperty); - Assert.Equal((double)9 * 3, calc.ReadUnionValue()); + Assert.Equal((double) 9 * 3, calc.ReadUnionValue()); calc.UnionProperty = new Power(new Number(10), new Number(3)); Assert.IsType(calc.UnionProperty); @@ -369,7 +370,7 @@ public void SubClassing() calc.Curr = new AddTen(33); calc.Neg(); - Assert.Equal((double)-43, calc.Value); + Assert.Equal((double) -43, calc.Value); } [Fact(DisplayName = Prefix + nameof(TestJSObjectLiteralToNative))] @@ -379,7 +380,7 @@ public void TestJSObjectLiteralToNative() JSObjectLiteralToNativeClass obj2 = obj.ReturnLiteral(); Assert.Equal("Hello", obj2.PropA); - Assert.Equal((double)102, obj2.PropB); + Assert.Equal((double) 102, obj2.PropB); } [Fact(DisplayName = Prefix + nameof(TestFluentApiWithDerivedClasses), Skip = "There is no fluent API for C#")] @@ -423,37 +424,37 @@ public override double OverrideMe(double mult) public void AsyncOverrides_CallAsyncMethod() { AsyncVirtualMethods obj = new AsyncVirtualMethods(); - Assert.Equal((double)128, obj.CallMe()); - Assert.Equal((double)528, obj.OverrideMe(44)); + Assert.Equal((double) 128, obj.CallMe()); + Assert.Equal((double) 528, obj.OverrideMe(44)); } [Fact(DisplayName = Prefix + nameof(AsyncOverrides_OverrideAsyncMethod))] public void AsyncOverrides_OverrideAsyncMethod() { OverrideAsyncMethods obj = new OverrideAsyncMethods(); - Assert.Equal((double)4452, obj.CallMe()); + Assert.Equal((double) 4452, obj.CallMe()); } [Fact(DisplayName = Prefix + nameof(AsyncOverrides_OverrideAsyncMethodByParentClass))] public void AsyncOverrides_OverrideAsyncMethodByParentClass() { OverrideAsyncMethodsByBaseClass obj = new OverrideAsyncMethodsByBaseClass(); - Assert.Equal((double)4452, obj.CallMe()); + Assert.Equal((double) 4452, obj.CallMe()); } [Fact(DisplayName = Prefix + nameof(AsyncOverrides_OverrideCallsSuper))] public void AsyncOverrides_OverrideCallsSuper() { OverrideCallsSuper obj = new OverrideCallsSuper(); - Assert.Equal((double)1441, obj.OverrideMe(12)); - Assert.Equal((double)1209, obj.CallMe()); + Assert.Equal((double) 1441, obj.OverrideMe(12)); + Assert.Equal((double) 1209, obj.CallMe()); } [Fact(DisplayName = Prefix + nameof(AsyncOverrides_TwoOverrides))] public void AsyncOverrides_TwoOverrides() { TwoOverrides obj = new TwoOverrides(); - Assert.Equal((double)684, obj.CallMe()); + Assert.Equal((double) 684, obj.CallMe()); } [Fact(DisplayName = Prefix + nameof(AsyncOverrides_OverrideThrows))] @@ -573,28 +574,28 @@ public void InterfaceBuilder() public void SyncOverrides_SyncOverrides() { SyncOverrides obj = new SyncOverrides(); - Assert.Equal((double)10 * 5, obj.CallerIsMethod()); + Assert.Equal((double) 10 * 5, obj.CallerIsMethod()); // affect the result obj.Multiplier = 5; - Assert.Equal((double)10 * 5 * 5, obj.CallerIsMethod()); + Assert.Equal((double) 10 * 5 * 5, obj.CallerIsMethod()); // verify callbacks are invoked from a property - Assert.Equal((double)10 * 5 * 5, obj.CallerIsProperty); + Assert.Equal((double) 10 * 5 * 5, obj.CallerIsProperty); // and from an async method obj.Multiplier = 3; - Assert.Equal((double)10 * 5 * 3, obj.CallerIsAsync()); + Assert.Equal((double) 10 * 5 * 3, obj.CallerIsAsync()); } [Fact(DisplayName = Prefix + nameof(SyncOverrides_CallsSuper))] public void SyncOverrides_CallsSuper() { SyncOverrides obj = new SyncOverrides(); - Assert.Equal((double)10 * 5, obj.CallerIsProperty); + Assert.Equal((double) 10 * 5, obj.CallerIsProperty); obj.ReturnSuper = true; // js code returns n * 2 - Assert.Equal((double)10 * 2, obj.CallerIsProperty); + Assert.Equal((double) 10 * 2, obj.CallerIsProperty); } [Fact(DisplayName = Prefix + nameof(SyncOverrides_CallsDoubleAsyncMethodFails))] @@ -644,11 +645,11 @@ public void TestInterfaces() // friendlyRandomGenerator = multiply; // <-- shouldn't compile Assert.Equal("Hello, I am a binary operation. What's your name?", friendly.Hello()); Assert.Equal("Goodbye from Multiply!", friendlier.Goodbye()); - Assert.Equal((double)89, randomNumberGenerator.Next()); + Assert.Equal((double) 89, randomNumberGenerator.Next()); friendlyRandomGenerator = new DoubleTrouble(); Assert.Equal("world", friendlyRandomGenerator.Hello()); - Assert.Equal((double)12, friendlyRandomGenerator.Next()); + Assert.Equal((double) 12, friendlyRandomGenerator.Next()); Polymorphism poly = new Polymorphism(); Assert.Equal("oh, Hello, I am a binary operation. What's your name?", poly.SayHello(friendly)); @@ -681,16 +682,16 @@ public void TestNativeObjectsWithInterfaces() NumberGenerator generatorBoundToPSubclassedObject = new NumberGenerator(subclassedNative); Assert.Same(subclassedNative, generatorBoundToPSubclassedObject.Generator); generatorBoundToPSubclassedObject.IsSameGenerator(subclassedNative); - Assert.Equal((double)10000, generatorBoundToPSubclassedObject.NextTimes100()); + Assert.Equal((double) 10000, generatorBoundToPSubclassedObject.NextTimes100()); // when we invoke nextTimes100 again, it will use the objref and call into the same object. - Assert.Equal((double)20000, generatorBoundToPSubclassedObject.NextTimes100()); + Assert.Equal((double) 20000, generatorBoundToPSubclassedObject.NextTimes100()); NumberGenerator generatorBoundToPureNative = new NumberGenerator(pureNative); Assert.Same(pureNative, generatorBoundToPureNative.Generator); generatorBoundToPureNative.IsSameGenerator(pureNative); - Assert.Equal((double)100000, generatorBoundToPureNative.NextTimes100()); - Assert.Equal((double)200000, generatorBoundToPureNative.NextTimes100()); + Assert.Equal((double) 100000, generatorBoundToPureNative.NextTimes100()); + Assert.Equal((double) 200000, generatorBoundToPureNative.NextTimes100()); } [Fact(DisplayName = Prefix + nameof(TestLiteralInterface))] @@ -702,7 +703,7 @@ public void TestLiteralInterface() IIFriendlyRandomGenerator gen = obj.GiveMeFriendlyGenerator(); Assert.Equal("giveMeFriendlyGenerator", gen.Hello()); - Assert.Equal((double)42, gen.Next()); + Assert.Equal((double) 42, gen.Next()); } [Fact(DisplayName = Prefix + nameof(Structs_StepBuilders), Skip = "There is no fluent API for C#")] @@ -724,7 +725,7 @@ public void Structs_SerializeToJsii() { Astring = "FirstString", Anumber = 999, - FirstOptional = new[] { "First", "Optional" } + FirstOptional = new[] {"First", "Optional"} }; DoubleTrouble doubleTrouble = new DoubleTrouble(); @@ -736,7 +737,7 @@ public void Structs_SerializeToJsii() AnotherRequired = DateTime.Now, Astring = "String", Anumber = 1234, - FirstOptional = new[] { "one", "two" } + FirstOptional = new[] {"one", "two"} }; GiveMeStructs gms = new GiveMeStructs(); @@ -781,7 +782,7 @@ public void ReservedKeywordsAreSlugifiedInMethodNames() } [Fact(DisplayName = Prefix + nameof(NodeStandardLibrary))] - public void NodeStandardLibrary() + public void NodeStandardLibrary() { NodeStandardLibrary obj = new NodeStandardLibrary(); Assert.Equal("Hello, resource!", obj.FsReadFile()); @@ -790,7 +791,7 @@ public void NodeStandardLibrary() Assert.Equal("6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50", obj.CryptoSha256()); } - + [Fact(DisplayName = Prefix + nameof(ReturnAbstract))] public void ReturnAbstract() { @@ -807,6 +808,26 @@ public void ReturnAbstract() Assert.Equal("hello-abstract-property", obj.ReturnAbstractFromProperty.AbstractProperty); } + [Fact(DisplayName = Prefix + nameof(TestClassWithPrivateConstructorAndAutomaticProperties))] + public void TestClassWithPrivateConstructorAndAutomaticProperties() + { + var testType = typeof(ClassWithPrivateConstructorAndAutomaticProperties); + + // No public constructors + Assert.Equal(0, testType.GetConstructors().Select(c => c.IsPublic).Count()); + + var readOnlyProperty = + testType.GetProperty(nameof(ClassWithPrivateConstructorAndAutomaticProperties.ReadOnlyString)); + + Assert.True(readOnlyProperty.CanRead); + Assert.False(readOnlyProperty.CanWrite); + + var readWriteProperty = + testType.GetProperty(nameof(ClassWithPrivateConstructorAndAutomaticProperties.ReadWriteString)); + + Assert.True(readWriteProperty.CanRead); + Assert.True(readWriteProperty.CanWrite); + } class MulTen : Multiply { @@ -850,7 +871,7 @@ class OverrideCallsSuper : AsyncVirtualMethods public override double OverrideMe(double mult) { double superRet = base.OverrideMe(mult); - return ((int)superRet) * 10 + 1; + return ((int) superRet) * 10 + 1; } } @@ -882,7 +903,7 @@ public override double VirtualMethod(double n) return obj.CallMe(); } - return 5 * ((int)n) * Multiplier; + return 5 * ((int) n) * Multiplier; } public int Multiplier { get; set; } = 1; @@ -891,7 +912,8 @@ public override double VirtualMethod(double n) public bool CallAsync { get; set; } = false; - public override string TheProperty { + public override string TheProperty + { get => "I am an override!"; set => AnotherTheProperty = value; } @@ -943,4 +965,4 @@ public double Next() } } } -} +} \ No newline at end of file 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 3ba8c034b9..652b664801 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 @@ -1035,6 +1035,41 @@ } ] }, + "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": { + "assembly": "jsii-calc", + "docs": { + "comment": "Class that implements interface properties automatically, but using a private constructor" + }, + "fqn": "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties", + "interfaces": [ + { + "fqn": "jsii-calc.IInterfaceWithProperties" + } + ], + "kind": "class", + "name": "ClassWithPrivateConstructorAndAutomaticProperties", + "properties": [ + { + "immutable": true, + "name": "readOnlyString", + "overrides": { + "fqn": "jsii-calc.IInterfaceWithProperties" + }, + "type": { + "primitive": "string" + } + }, + { + "name": "readWriteString", + "overrides": { + "fqn": "jsii-calc.IInterfaceWithProperties" + }, + "type": { + "primitive": "string" + } + } + ] + }, "jsii-calc.DefaultedConstructorArgument": { "assembly": "jsii-calc", "fqn": "jsii-calc.DefaultedConstructorArgument", @@ -3297,5 +3332,5 @@ } }, "version": "0.7.6", - "fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM=" + "fingerprint": "eFasWxN7YC37iWdz+dDbEFTSQCzyangrqP5Nu02rzpw=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/ClassWithPrivateConstructorAndAutomaticProperties.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/ClassWithPrivateConstructorAndAutomaticProperties.cs new file mode 100644 index 0000000000..f02767771f --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/ClassWithPrivateConstructorAndAutomaticProperties.cs @@ -0,0 +1,30 @@ +using Amazon.JSII.Runtime.Deputy; + +namespace Amazon.JSII.Tests.CalculatorNamespace +{ + /// Class that implements interface properties automatically, but using a private constructor + [JsiiClass(typeof(ClassWithPrivateConstructorAndAutomaticProperties), "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties", "[]")] + public class ClassWithPrivateConstructorAndAutomaticProperties : DeputyBase, IIInterfaceWithProperties + { + protected ClassWithPrivateConstructorAndAutomaticProperties(ByRefValue reference): base(reference) + { + } + + protected ClassWithPrivateConstructorAndAutomaticProperties(DeputyProps props): base(props) + { + } + + [JsiiProperty("readOnlyString", "{\"primitive\":\"string\"}")] + public virtual string ReadOnlyString + { + get => GetInstanceProperty(); + } + + [JsiiProperty("readWriteString", "{\"primitive\":\"string\"}")] + public virtual string ReadWriteString + { + get => GetInstanceProperty(); + set => SetInstanceProperty(value); + } + } +} \ 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 f4fe19a282..bde4a47c4b 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 @@ -30,6 +30,7 @@ protected Class resolveClass(final String fqn) throws ClassNotFoundException case "jsii-calc.Calculator": return software.amazon.jsii.tests.calculator.Calculator.class; case "jsii-calc.CalculatorProps": return software.amazon.jsii.tests.calculator.CalculatorProps.class; case "jsii-calc.ClassWithMutableObjectLiteralProperty": return software.amazon.jsii.tests.calculator.ClassWithMutableObjectLiteralProperty.class; + case "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": return software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties.class; case "jsii-calc.DefaultedConstructorArgument": return software.amazon.jsii.tests.calculator.DefaultedConstructorArgument.class; case "jsii-calc.DerivedClassHasNoProperties.Base": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Base.class; case "jsii-calc.DerivedClassHasNoProperties.Derived": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Derived.class; diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/ClassWithPrivateConstructorAndAutomaticProperties.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/ClassWithPrivateConstructorAndAutomaticProperties.java new file mode 100644 index 0000000000..05df547c9f --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/ClassWithPrivateConstructorAndAutomaticProperties.java @@ -0,0 +1,27 @@ +package software.amazon.jsii.tests.calculator; + +/** + * Class that implements interface properties automatically, but using a private constructor + */ +@javax.annotation.Generated(value = "jsii-pacmak") +@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties") +public class ClassWithPrivateConstructorAndAutomaticProperties extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.IInterfaceWithProperties { + protected ClassWithPrivateConstructorAndAutomaticProperties(final software.amazon.jsii.JsiiObject.InitializationMode mode) { + super(mode); + } + + @Override + public java.lang.String getReadOnlyString() { + return this.jsiiGet("readOnlyString", java.lang.String.class); + } + + @Override + public java.lang.String getReadWriteString() { + return this.jsiiGet("readWriteString", java.lang.String.class); + } + + @Override + public void setReadWriteString(final java.lang.String value) { + this.jsiiSet("readWriteString", java.util.Objects.requireNonNull(value, "readWriteString is required")); + } +} 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 b45e6582bd..21ffb028aa 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 @@ -1006,6 +1006,52 @@ ClassWithMutableObjectLiteralProperty :type: :py:class:`~jsii-calc.MutableObjectLiteral`\ +ClassWithPrivateConstructorAndAutomaticProperties +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. py:class:: ClassWithPrivateConstructorAndAutomaticProperties + + **Language-specific names:** + + .. tabs:: + + .. code-tab:: c# + + using Amazon.JSII.Tests.CalculatorNamespace; + + .. code-tab:: java + + import software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties; + + .. code-tab:: javascript + + const { ClassWithPrivateConstructorAndAutomaticProperties } = require('jsii-calc'); + + .. code-tab:: typescript + + import { ClassWithPrivateConstructorAndAutomaticProperties } from 'jsii-calc'; + + + + Class that implements interface properties automatically, but using a private constructor + + + :implements: :py:class:`~jsii-calc.IInterfaceWithProperties`\ + + .. py:attribute:: readOnlyString + + *Implements* :py:meth:`jsii-calc.IInterfaceWithProperties.readOnlyString` + + :type: string *(readonly)* + + + .. py:attribute:: readWriteString + + *Implements* :py:meth:`jsii-calc.IInterfaceWithProperties.readWriteString` + + :type: string + + DefaultedConstructorArgument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/jsii-runtime/package-lock.json b/packages/jsii-runtime/package-lock.json index 623f260f1f..0ac9ef848a 100644 --- a/packages/jsii-runtime/package-lock.json +++ b/packages/jsii-runtime/package-lock.json @@ -4695,11 +4695,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4712,15 +4714,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4823,7 +4828,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4833,6 +4839,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4845,17 +4852,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4872,6 +4882,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4944,7 +4955,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4954,6 +4966,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5059,6 +5072,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 38b666d03c..e12355e137 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -385,23 +385,30 @@ export class Assembler implements Emitter { const constructor = type.symbol.members && type.symbol.members.get(ts.InternalSymbolName.Constructor); const ctorDeclaration = constructor && (constructor.declarations[0] as ts.ConstructorDeclaration); if (constructor && ctorDeclaration) { + const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration); + // tslint:disable-next-line:no-bitwise if ((ts.getCombinedModifierFlags(ctorDeclaration) & ts.ModifierFlags.Private) === 0) { - const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration); jsiiType.initializer = { initializer: true }; if (signature) { for (const param of signature.getParameters()) { jsiiType.initializer.parameters = jsiiType.initializer.parameters || []; jsiiType.initializer.parameters.push(await this._toParameter(param)); - if (ts.isParameterPropertyDeclaration(param.valueDeclaration)) { - await this._visitProperty(param, jsiiType); - } jsiiType.initializer.variadic = jsiiType.initializer.parameters && jsiiType.initializer.parameters.find(p => !!p.variadic) != null; } } this._visitDocumentation(constructor, jsiiType.initializer); } + + // Proces constructor-based property declarations even if constructor is private + if (signature) { + for (const param of signature.getParameters()) { + if (ts.isParameterPropertyDeclaration(param.valueDeclaration)) { + await this._visitProperty(param, jsiiType); + } + } + } } else if (jsiiType.base) { this._defer(() => { const baseType = this._dereference(jsiiType.base!, type.symbol.valueDeclaration); @@ -451,7 +458,7 @@ export class Assembler implements Emitter { * @returns ``documentable`` */ private _visitDocumentation(symbol: ts.Symbol | ts.Signature, documentable: T): T { - const comment = ts.displayPartsToString(symbol.getDocumentationComment(this._typeChecker)); + const comment = ts.displayPartsToString(symbol.getDocumentationComment(this._typeChecker)).trim(); if (comment) { if (LOG.isTraceEnabled()) { LOG.trace(`Found documentation comment: ${colors.yellow(comment)}`);