diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 80c8089fd5a5b..228b830ea7db5 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -121,7 +121,7 @@ export default class CodeGenerator { this.docLink(spec.Documentation, `Properties for defining a \`${resourceContext.specName!.fqn}\``); this.code.openBlock(`export interface ${name.className}`); - const conversionTable = this.emitPropsTypeProperties(resourceContext, spec.Properties); + const conversionTable = this.emitPropsTypeProperties(resourceContext, spec.Properties, Container.Interface); this.code.closeBlock(); @@ -138,13 +138,24 @@ export default class CodeGenerator { * * Return a mapping of { originalName -> newName }. */ - private emitPropsTypeProperties(resource: genspec.CodeName, propertiesSpec: { [name: string]: schema.Property }): Dictionary { + private emitPropsTypeProperties( + resource: genspec.CodeName, + propertiesSpec: { [name: string]: schema.Property }, + container: Container): Dictionary { + const propertyMap: Dictionary = {}; Object.keys(propertiesSpec).sort(propertyComparator).forEach(propName => { + this.code.line(); const propSpec = propertiesSpec[propName]; const additionalDocs = resource.specName!.relativeName(propName).fqn; - const newName = this.emitProperty(resource, propName, propSpec, quoteCode(additionalDocs)); + const newName = this.emitProperty({ + context: resource, + propName, + spec: propSpec, + additionalDocs: quoteCode(additionalDocs), + container, + }); propertyMap[propName] = newName; }); return propertyMap; @@ -243,19 +254,11 @@ export default class CodeGenerator { attributes.push(refAttribute); } } - // set the TagType to help format tags later - const tagEnum = tagType(spec); - if (tagEnum !== `${TAG_TYPE}.NotTaggable`) { - this.code.line(); - this.code.line('/**'); - this.code.line(' * The `TagManager` handles setting, removing and formatting tags'); - this.code.line(' *'); - this.code.line(' * Tags should be managed either passing them as properties during'); - this.code.line(' * initiation or by calling methods on this object. If both techniques are'); - this.code.line(' * used only the tags from the TagManager will be used. `Tag` (aspect)'); - this.code.line(' * will use the manager.'); - this.code.line(' */'); - this.code.line(`public readonly tags: ${TAG_MANAGER};`); + + // set class properties to match CloudFormation Properties spec + + if (propsType) { + this.emitPropsTypeProperties(resourceName, spec.Properties!, Container.Class); } // @@ -308,6 +311,9 @@ export default class CodeGenerator { if (deprecated) { this.code.line(`this.node.addWarning('DEPRECATION: ${deprecation}');`); } + + // set the TagType to help format tags later + const tagEnum = tagType(spec); if (tagEnum !== `${TAG_TYPE}.NotTaggable`) { this.code.line('const tags = props === undefined ? undefined : props.tags;'); this.code.line(`this.tags = new ${TAG_MANAGER}(${tagEnum}, ${resourceTypeName}, tags);`); @@ -514,15 +520,26 @@ export default class CodeGenerator { this.code.closeBlock(); } - private emitProperty(context: genspec.CodeName, propName: string, spec: schema.Property, additionalDocs: string): string { - const question = spec.Required ? '' : '?'; - const javascriptPropertyName = genspec.cloudFormationToScriptName(propName); + private emitProperty(props: EmitPropertyProps): string { + const question = props.spec.Required ? '' : '?'; + const javascriptPropertyName = genspec.cloudFormationToScriptName(props.propName); - this.docLink(spec.Documentation, additionalDocs); - this.code.line(`readonly ${javascriptPropertyName}${question}: ${this.findNativeType(context, spec, propName)};`); + this.docLink(props.spec.Documentation, props.additionalDocs); + const line = `: ${this.findNativeType(props.context, props.spec, props.propName)};`; + if (props.container === Container.Interface) { + this.code.line(`readonly ${javascriptPropertyName}${question}${line}`); + } + if (props.container === Container.Class) { + if (props.propName === 'Tags' && schema.isTagProperty(props.spec)) { + this.code.line(`public readonly tags: ${TAG_MANAGER};`); + } else { + this.code.line(`public ${javascriptPropertyName}Prop${line}`); + } + } return javascriptPropertyName; } + private beginNamespace(type: genspec.CodeName) { if (type.namespace) { const parts = type.namespace.split('.'); @@ -555,7 +572,13 @@ export default class CodeGenerator { Object.keys(propTypeSpec.Properties).forEach(propName => { const propSpec = propTypeSpec.Properties[propName]; const additionalDocs = quoteCode(`${typeName.fqn}.${propName}`); - const newName = this.emitProperty(resourceContext, propName, propSpec, additionalDocs); + const newName = this.emitProperty({ + context: resourceContext, + propName, + spec: propSpec, + additionalDocs, + container: Container.Interface, + }); conversionTable[propName] = newName; }); } @@ -702,3 +725,33 @@ function tagType(resource: schema.ResourceType): string { } return `${TAG_TYPE}.NotTaggable`; } + +enum Container { + Interface = 'INTERFACE', + Class = 'CLASS', +} + +interface EmitPropertyProps { + context: genspec.CodeName; + propName: string; + spec: schema.Property; + additionalDocs: string; + container: Container; +} + +// remove export +// export class PropertyGenerator { +// +// private readonly resource: genspec.CodeName; +// private readonly spec: schema.ResourceType; +// +// constructor(resourceContext: genspec.CodeName, spec: schema.ResourceType) { +// this.resource = resourceContext; +// this.spec = spec; +// } +// +// // const conversionTable = this.emitPropsTypeProperties(resourceContext, spec.Properties); +// public hasProperties(): boolean { +// return this.spec.Properties ? Object.keys(this.spec.Properties).length > 0 : false; +// } +// }