Skip to content

Commit 181012e

Browse files
authoredOct 10, 2018
fix(jsii): support public autoproperties in private constructor (#256)
Includes a fix in the .NET generator to add support for private constructors.
1 parent 660ae79 commit 181012e

File tree

13 files changed

+381
-122
lines changed

13 files changed

+381
-122
lines changed
 

‎packages/jsii-calc/lib/compliance.ts

+8
Original file line numberDiff line numberDiff line change
@@ -914,3 +914,11 @@ export class DoNotOverridePrivates {
914914
this.privateProperty = newValue;
915915
}
916916
}
917+
918+
/**
919+
* Class that implements interface properties automatically, but using a private constructor
920+
*/
921+
export class ClassWithPrivateConstructorAndAutomaticProperties implements IInterfaceWithProperties {
922+
private constructor(public readonly readOnlyString: string, public readWriteString: string) {
923+
}
924+
}

‎packages/jsii-calc/test/assembly.jsii

+36-1
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,41 @@
10351035
}
10361036
]
10371037
},
1038+
"jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": {
1039+
"assembly": "jsii-calc",
1040+
"docs": {
1041+
"comment": "Class that implements interface properties automatically, but using a private constructor"
1042+
},
1043+
"fqn": "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties",
1044+
"interfaces": [
1045+
{
1046+
"fqn": "jsii-calc.IInterfaceWithProperties"
1047+
}
1048+
],
1049+
"kind": "class",
1050+
"name": "ClassWithPrivateConstructorAndAutomaticProperties",
1051+
"properties": [
1052+
{
1053+
"immutable": true,
1054+
"name": "readOnlyString",
1055+
"overrides": {
1056+
"fqn": "jsii-calc.IInterfaceWithProperties"
1057+
},
1058+
"type": {
1059+
"primitive": "string"
1060+
}
1061+
},
1062+
{
1063+
"name": "readWriteString",
1064+
"overrides": {
1065+
"fqn": "jsii-calc.IInterfaceWithProperties"
1066+
},
1067+
"type": {
1068+
"primitive": "string"
1069+
}
1070+
}
1071+
]
1072+
},
10381073
"jsii-calc.DefaultedConstructorArgument": {
10391074
"assembly": "jsii-calc",
10401075
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -3297,5 +3332,5 @@
32973332
}
32983333
},
32993334
"version": "0.7.6",
3300-
"fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM="
3335+
"fingerprint": "eFasWxN7YC37iWdz+dDbEFTSQCzyangrqP5Nu02rzpw="
33013336
}

‎packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs

+30
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ protected MyClass(DeputyProps props): base(props)
5757
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
5858
}
5959

60+
[Fact(DisplayName = Prefix + nameof(AllowsPrivateConstructor))]
61+
public void AllowsPrivateConstructor()
62+
{
63+
ClassType classType = new ClassType
64+
(
65+
fullyQualifiedName: "myFqn",
66+
assembly: "myPackage",
67+
name: "myClass",
68+
isAbstract: false
69+
);
70+
71+
string actual = Render(classType);
72+
string expected =
73+
@"namespace MyNamespace
74+
{
75+
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
76+
public class MyClass : DeputyBase
77+
{
78+
protected MyClass(ByRefValue reference): base(reference)
79+
{
80+
}
81+
82+
protected MyClass(DeputyProps props): base(props)
83+
{
84+
}
85+
}
86+
}";
87+
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
88+
}
89+
6090
[Fact(DisplayName = Prefix + nameof(IncludesAbstractKeyword))]
6191
public void IncludesAbstractKeyword()
6292
{

‎packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/Class/ClassGenerator.cs

+36-32
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using Amazon.JSII.JsonModel.Spec;
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Amazon.JSII.JsonModel.Spec;
24
using Microsoft.CodeAnalysis;
35
using Microsoft.CodeAnalysis.CSharp;
46
using Microsoft.CodeAnalysis.CSharp.Syntax;
5-
using System.Collections.Generic;
6-
using System.Linq;
77
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
88

99
namespace Amazon.JSII.Generator.Class
@@ -97,37 +97,41 @@ IEnumerable<MemberDeclarationSyntax> CreateConstructors()
9797
{
9898
SyntaxToken typeName = Symbols.GetNameSyntaxToken(Type);
9999

100-
yield return SF.ConstructorDeclaration
101-
(
102-
SF.List<AttributeListSyntax>(),
103-
SF.TokenList(SF.Token(
104-
Type.IsAbstract || Type.Initializer.IsProtected ?
105-
SyntaxKind.ProtectedKeyword :
106-
SyntaxKind.PublicKeyword
107-
)),
108-
typeName,
109-
Type.Initializer.GetParameterListSyntax(Namespaces, Symbols),
110-
SF.ConstructorInitializer
100+
if (Type.Initializer != null)
101+
{
102+
yield return SF.ConstructorDeclaration
111103
(
112-
SyntaxKind.BaseConstructorInitializer,
113-
SF.ArgumentList(
114-
SF.SeparatedList(new[] {
115-
SF.Argument(
116-
SF.ObjectCreationExpression(
117-
SF.Token(SyntaxKind.NewKeyword),
118-
SF.ParseTypeName("DeputyProps"),
119-
SF.ArgumentList(SF.SeparatedList(
120-
new[] { GetBaseArgument() }
121-
)),
122-
null
104+
SF.List<AttributeListSyntax>(),
105+
SF.TokenList(SF.Token(
106+
Type.IsAbstract || Type.Initializer.IsProtected
107+
? SyntaxKind.ProtectedKeyword
108+
: SyntaxKind.PublicKeyword
109+
)),
110+
typeName,
111+
Type.Initializer.GetParameterListSyntax(Namespaces, Symbols),
112+
SF.ConstructorInitializer
113+
(
114+
SyntaxKind.BaseConstructorInitializer,
115+
SF.ArgumentList(
116+
SF.SeparatedList(new[]
117+
{
118+
SF.Argument(
119+
SF.ObjectCreationExpression(
120+
SF.Token(SyntaxKind.NewKeyword),
121+
SF.ParseTypeName("DeputyProps"),
122+
SF.ArgumentList(SF.SeparatedList(
123+
new[] {GetBaseArgument()}
124+
)),
125+
null
126+
)
123127
)
124-
)
125-
})
126-
)
127-
),
128-
SF.Block(),
129-
null
130-
);
128+
})
129+
)
130+
),
131+
SF.Block(),
132+
null
133+
);
134+
}
131135

132136
yield return SF.ConstructorDeclaration
133137
(

‎packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/MethodExtensions.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using Amazon.JSII.JsonModel.Spec;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Amazon.JSII.JsonModel.Spec;
25
using Microsoft.CodeAnalysis;
36
using Microsoft.CodeAnalysis.CSharp.Syntax;
47
using Newtonsoft.Json;
5-
using System;
6-
using System.Collections.Generic;
7-
using System.Linq;
88
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
99

1010
namespace Amazon.JSII.Generator
@@ -44,7 +44,7 @@ IEnumerable<ParameterSyntax> GetParameters()
4444
public static SyntaxToken GetParametersJsonSyntaxToken(this Method method)
4545
{
4646
// Strip docs before serializing.
47-
Parameter[] parameters = (method.Parameters ?? Enumerable.Empty<Parameter>())
47+
Parameter[] parameters = (method?.Parameters ?? Enumerable.Empty<Parameter>())
4848
.Select(p => new Parameter(p.Name, p.Type))
4949
.ToArray();
5050

‎packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

+93-71
Large diffs are not rendered by default.

‎packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii

+36-1
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,41 @@
10351035
}
10361036
]
10371037
},
1038+
"jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": {
1039+
"assembly": "jsii-calc",
1040+
"docs": {
1041+
"comment": "Class that implements interface properties automatically, but using a private constructor"
1042+
},
1043+
"fqn": "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties",
1044+
"interfaces": [
1045+
{
1046+
"fqn": "jsii-calc.IInterfaceWithProperties"
1047+
}
1048+
],
1049+
"kind": "class",
1050+
"name": "ClassWithPrivateConstructorAndAutomaticProperties",
1051+
"properties": [
1052+
{
1053+
"immutable": true,
1054+
"name": "readOnlyString",
1055+
"overrides": {
1056+
"fqn": "jsii-calc.IInterfaceWithProperties"
1057+
},
1058+
"type": {
1059+
"primitive": "string"
1060+
}
1061+
},
1062+
{
1063+
"name": "readWriteString",
1064+
"overrides": {
1065+
"fqn": "jsii-calc.IInterfaceWithProperties"
1066+
},
1067+
"type": {
1068+
"primitive": "string"
1069+
}
1070+
}
1071+
]
1072+
},
10381073
"jsii-calc.DefaultedConstructorArgument": {
10391074
"assembly": "jsii-calc",
10401075
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -3297,5 +3332,5 @@
32973332
}
32983333
},
32993334
"version": "0.7.6",
3300-
"fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM="
3335+
"fingerprint": "eFasWxN7YC37iWdz+dDbEFTSQCzyangrqP5Nu02rzpw="
33013336
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
/// <summary>Class that implements interface properties automatically, but using a private constructor</summary>
6+
[JsiiClass(typeof(ClassWithPrivateConstructorAndAutomaticProperties), "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties", "[]")]
7+
public class ClassWithPrivateConstructorAndAutomaticProperties : DeputyBase, IIInterfaceWithProperties
8+
{
9+
protected ClassWithPrivateConstructorAndAutomaticProperties(ByRefValue reference): base(reference)
10+
{
11+
}
12+
13+
protected ClassWithPrivateConstructorAndAutomaticProperties(DeputyProps props): base(props)
14+
{
15+
}
16+
17+
[JsiiProperty("readOnlyString", "{\"primitive\":\"string\"}")]
18+
public virtual string ReadOnlyString
19+
{
20+
get => GetInstanceProperty<string>();
21+
}
22+
23+
[JsiiProperty("readWriteString", "{\"primitive\":\"string\"}")]
24+
public virtual string ReadWriteString
25+
{
26+
get => GetInstanceProperty<string>();
27+
set => SetInstanceProperty(value);
28+
}
29+
}
30+
}

‎packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
3030
case "jsii-calc.Calculator": return software.amazon.jsii.tests.calculator.Calculator.class;
3131
case "jsii-calc.CalculatorProps": return software.amazon.jsii.tests.calculator.CalculatorProps.class;
3232
case "jsii-calc.ClassWithMutableObjectLiteralProperty": return software.amazon.jsii.tests.calculator.ClassWithMutableObjectLiteralProperty.class;
33+
case "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties": return software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties.class;
3334
case "jsii-calc.DefaultedConstructorArgument": return software.amazon.jsii.tests.calculator.DefaultedConstructorArgument.class;
3435
case "jsii-calc.DerivedClassHasNoProperties.Base": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Base.class;
3536
case "jsii-calc.DerivedClassHasNoProperties.Derived": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Derived.class;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package software.amazon.jsii.tests.calculator;
2+
3+
/**
4+
* Class that implements interface properties automatically, but using a private constructor
5+
*/
6+
@javax.annotation.Generated(value = "jsii-pacmak")
7+
@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.ClassWithPrivateConstructorAndAutomaticProperties")
8+
public class ClassWithPrivateConstructorAndAutomaticProperties extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.IInterfaceWithProperties {
9+
protected ClassWithPrivateConstructorAndAutomaticProperties(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
10+
super(mode);
11+
}
12+
13+
@Override
14+
public java.lang.String getReadOnlyString() {
15+
return this.jsiiGet("readOnlyString", java.lang.String.class);
16+
}
17+
18+
@Override
19+
public java.lang.String getReadWriteString() {
20+
return this.jsiiGet("readWriteString", java.lang.String.class);
21+
}
22+
23+
@Override
24+
public void setReadWriteString(final java.lang.String value) {
25+
this.jsiiSet("readWriteString", java.util.Objects.requireNonNull(value, "readWriteString is required"));
26+
}
27+
}

‎packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst

+46
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,52 @@ ClassWithMutableObjectLiteralProperty
10061006
:type: :py:class:`~jsii-calc.MutableObjectLiteral`\
10071007

10081008

1009+
ClassWithPrivateConstructorAndAutomaticProperties
1010+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1011+
1012+
.. py:class:: ClassWithPrivateConstructorAndAutomaticProperties
1013+
1014+
**Language-specific names:**
1015+
1016+
.. tabs::
1017+
1018+
.. code-tab:: c#
1019+
1020+
using Amazon.JSII.Tests.CalculatorNamespace;
1021+
1022+
.. code-tab:: java
1023+
1024+
import software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties;
1025+
1026+
.. code-tab:: javascript
1027+
1028+
const { ClassWithPrivateConstructorAndAutomaticProperties } = require('jsii-calc');
1029+
1030+
.. code-tab:: typescript
1031+
1032+
import { ClassWithPrivateConstructorAndAutomaticProperties } from 'jsii-calc';
1033+
1034+
1035+
1036+
Class that implements interface properties automatically, but using a private constructor
1037+
1038+
1039+
:implements: :py:class:`~jsii-calc.IInterfaceWithProperties`\
1040+
1041+
.. py:attribute:: readOnlyString
1042+
1043+
*Implements* :py:meth:`jsii-calc.IInterfaceWithProperties.readOnlyString`
1044+
1045+
:type: string *(readonly)*
1046+
1047+
1048+
.. py:attribute:: readWriteString
1049+
1050+
*Implements* :py:meth:`jsii-calc.IInterfaceWithProperties.readWriteString`
1051+
1052+
:type: string
1053+
1054+
10091055
DefaultedConstructorArgument
10101056
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10111057

‎packages/jsii-runtime/package-lock.json

+21-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/jsii/lib/assembler.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -385,23 +385,30 @@ export class Assembler implements Emitter {
385385
const constructor = type.symbol.members && type.symbol.members.get(ts.InternalSymbolName.Constructor);
386386
const ctorDeclaration = constructor && (constructor.declarations[0] as ts.ConstructorDeclaration);
387387
if (constructor && ctorDeclaration) {
388+
const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration);
389+
388390
// tslint:disable-next-line:no-bitwise
389391
if ((ts.getCombinedModifierFlags(ctorDeclaration) & ts.ModifierFlags.Private) === 0) {
390-
const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration);
391392
jsiiType.initializer = { initializer: true };
392393
if (signature) {
393394
for (const param of signature.getParameters()) {
394395
jsiiType.initializer.parameters = jsiiType.initializer.parameters || [];
395396
jsiiType.initializer.parameters.push(await this._toParameter(param));
396-
if (ts.isParameterPropertyDeclaration(param.valueDeclaration)) {
397-
await this._visitProperty(param, jsiiType);
398-
}
399397
jsiiType.initializer.variadic = jsiiType.initializer.parameters
400398
&& jsiiType.initializer.parameters.find(p => !!p.variadic) != null;
401399
}
402400
}
403401
this._visitDocumentation(constructor, jsiiType.initializer);
404402
}
403+
404+
// Proces constructor-based property declarations even if constructor is private
405+
if (signature) {
406+
for (const param of signature.getParameters()) {
407+
if (ts.isParameterPropertyDeclaration(param.valueDeclaration)) {
408+
await this._visitProperty(param, jsiiType);
409+
}
410+
}
411+
}
405412
} else if (jsiiType.base) {
406413
this._defer(() => {
407414
const baseType = this._dereference(jsiiType.base!, type.symbol.valueDeclaration);
@@ -451,7 +458,7 @@ export class Assembler implements Emitter {
451458
* @returns ``documentable``
452459
*/
453460
private _visitDocumentation<T extends spec.Documentable>(symbol: ts.Symbol | ts.Signature, documentable: T): T {
454-
const comment = ts.displayPartsToString(symbol.getDocumentationComment(this._typeChecker));
461+
const comment = ts.displayPartsToString(symbol.getDocumentationComment(this._typeChecker)).trim();
455462
if (comment) {
456463
if (LOG.isTraceEnabled()) {
457464
LOG.trace(`Found documentation comment: ${colors.yellow(comment)}`);

0 commit comments

Comments
 (0)
Please sign in to comment.