diff --git a/packages/jsii-rosetta/lib/jsii/jsii-types.ts b/packages/jsii-rosetta/lib/jsii/jsii-types.ts index 36302bdb7d..320c843dfa 100644 --- a/packages/jsii-rosetta/lib/jsii/jsii-types.ts +++ b/packages/jsii-rosetta/lib/jsii/jsii-types.ts @@ -7,8 +7,7 @@ import { hasAnyFlag, analyzeStructType, JsiiSymbol } from './jsii-utils'; export type JsiiType = | { kind: 'unknown' } | { kind: 'error'; message: string } - | { kind: 'map'; elementType: JsiiType } - | { kind: 'list'; elementType: JsiiType } + | { kind: 'map' | 'list'; elementType: JsiiType; elementTypeSymbol: ts.Symbol | undefined } | { kind: 'namedType'; name: string } | { kind: 'builtIn'; builtIn: BuiltInType }; @@ -28,6 +27,7 @@ export function determineJsiiType(typeChecker: ts.TypeChecker, type: ts.Type): J elementType: mapValuesType.elementType ? determineJsiiType(typeChecker, mapValuesType.elementType) : { kind: 'builtIn', builtIn: 'any' }, + elementTypeSymbol: mapValuesType.elementType?.symbol, }; } @@ -38,12 +38,14 @@ export function determineJsiiType(typeChecker: ts.TypeChecker, type: ts.Type): J return { kind: 'list', elementType: determineJsiiType(typeChecker, typeRef.typeArguments[0]), + elementTypeSymbol: typeRef.typeArguments[0].symbol, }; } return { kind: 'list', elementType: { kind: 'builtIn', builtIn: 'any' }, + elementTypeSymbol: undefined, }; } @@ -66,6 +68,11 @@ export function determineJsiiType(typeChecker: ts.TypeChecker, type: ts.Type): J message: `Type unions or intersections are not supported in examples, got: ${typeChecker.typeToString(type)}`, }; } + + if ((type.flags & (ts.TypeFlags.Void | ts.TypeFlags.VoidLike)) !== 0) { + return { kind: 'builtIn', builtIn: 'void' }; + } + return { kind: 'unknown' }; } diff --git a/packages/jsii-rosetta/lib/languages/csharp.ts b/packages/jsii-rosetta/lib/languages/csharp.ts index b31daf9d39..505843d97a 100644 --- a/packages/jsii-rosetta/lib/languages/csharp.ts +++ b/packages/jsii-rosetta/lib/languages/csharp.ts @@ -669,6 +669,8 @@ export class CSharpVisitor extends DefaultVisitor { return 'string'; case 'any': return 'object'; + case 'void': + return 'void'; } } } diff --git a/packages/jsii-rosetta/lib/languages/default.ts b/packages/jsii-rosetta/lib/languages/default.ts index bc82fde164..bef56096fe 100644 --- a/packages/jsii-rosetta/lib/languages/default.ts +++ b/packages/jsii-rosetta/lib/languages/default.ts @@ -45,6 +45,10 @@ export abstract class DefaultVisitor implements AstHandler { return new OTree([JSON.stringify(node.text)]); } + public numericLiteral(node: ts.NumericLiteral, _children: AstRenderer): OTree { + return new OTree([node.text]); + } + public identifier(node: ts.Identifier, _children: AstRenderer): OTree { return new OTree([node.text]); } diff --git a/packages/jsii-rosetta/lib/languages/go.ts b/packages/jsii-rosetta/lib/languages/go.ts new file mode 100644 index 0000000000..bb0b80743b --- /dev/null +++ b/packages/jsii-rosetta/lib/languages/go.ts @@ -0,0 +1,1052 @@ +// import { JsiiSymbol, simpleName, namespaceName } from '../jsii/jsii-utils'; +// import { jsiiTargetParameter } from '../jsii/packages'; +import { AssertionError } from 'assert'; +import * as ts from 'typescript'; + +import { analyzeObjectLiteral, determineJsiiType, JsiiType, ObjectLiteralStruct } from '../jsii/jsii-types'; +import { OTree } from '../o-tree'; +import { AstRenderer } from '../renderer'; +import { isExported, isPublic, isPrivate, isReadOnly, isStatic } from '../typescript/ast-utils'; +import { ImportStatement } from '../typescript/imports'; +import { + determineReturnType, + inferMapElementType, + inferredTypeOfExpression, + typeOfExpression, +} from '../typescript/types'; +import { DefaultVisitor } from './default'; +import { TargetLanguage } from './target-language'; + +interface GoLanguageContext { + /** + * Free floating symbols are made importable across packages by naming with a capital in Go. + */ + isExported: boolean; + + /** + * Whether type should be converted a pointer type + */ + isPtr: boolean; + + /** + * Whether this is the R-Value in an assignment expression to a pointer value. + */ + isPtrAssignmentRValue: boolean; + + /** + * Whether the current element is a parameter delcaration name. + */ + isParameterName: boolean; + + /** + * Whether context is within a struct declaration + */ + isStruct: boolean; + + /** + * Whether the context is within an interface delcaration. + */ + isInterface: boolean; + + /** + * Whether properties are being intialized within a `map` type + */ + inMapLiteral: boolean; + + /** + * Wheter to wrap a literal in a pointer constructor ie: jsii.String. + */ + wrapPtr: boolean; +} + +enum DeclarationType { + STRUCT, + INTERFACE, + FUNCTION, + BUILTIN, + UNKNOWN, +} + +type GoRenderer = AstRenderer; + +interface FormattedId { + readonly type: DeclarationType; + readonly formatted: string; +} +export class GoVisitor extends DefaultVisitor { + /** + * Translation version + * + * Bump this when you change something in the implementation to invalidate + * existing cached translations. + */ + public static readonly VERSION = '1'; + + public readonly indentChar = '\t'; + + public readonly language = TargetLanguage.GO; + + private readonly idMap = new Map(); + + public readonly defaultContext: GoLanguageContext = { + isExported: false, + isPtr: false, + isPtrAssignmentRValue: false, + isStruct: false, + isInterface: false, + isParameterName: false, + inMapLiteral: false, + wrapPtr: false, + }; + + protected argumentList(args: readonly ts.Node[] | undefined, renderer: GoRenderer): OTree { + return new OTree([], args ? renderer.convertAll(args) : [], { + separator: ', ', + }); + } + + public block(node: ts.Block, renderer: GoRenderer): OTree { + return new OTree(['{'], renderer.convertAll(node.statements), { + indent: 1, + suffix: renderer.mirrorNewlineBefore(node.statements[0], '}'), + }); + } + + public expressionStatement(node: ts.ExpressionStatement, renderer: GoRenderer): OTree { + const inner = renderer.convert(node.expression); + if (inner.isEmpty) { + return inner; + } + return new OTree([inner], [], { canBreakLine: true }); + } + + public functionDeclaration(node: ts.FunctionDeclaration, renderer: GoRenderer): OTree { + const funcName = renderer.updateContext({ isExported: isExported(node) }).convert(node.name); + const returnType = determineReturnType(renderer.typeChecker, node); + const goType = this.renderType(node.type ?? node, returnType?.symbol, returnType, true, '', renderer); + + const body = node.body?.statements ? renderer.convertAll(node.body.statements) : []; + return new OTree( + [ + 'func ', + funcName, + '(', + new OTree([], renderer.updateContext({ isPtr: true }).convertAll(node.parameters), { + separator: ', ', + }), + ')', + goType ? ' ' : '', + goType, + ], + [ + new OTree( + [' {'], + [this.defaultArgValues(node.parameters, renderer.updateContext({ wrapPtr: true })), ...body], + { + indent: 1, + suffix: '\n}', + }, + ), + ], + { + canBreakLine: true, + }, + ); + } + + public identifier(node: ts.Identifier | ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, renderer: GoRenderer) { + const symbol = renderer.typeChecker.getSymbolAtLocation(node); + return new OTree([this.goName(node.text, renderer, symbol)]); + } + + public newExpression(node: ts.NewExpression, renderer: GoRenderer): OTree { + const { classNamespace, className } = determineClassName.call(this, node.expression); + return new OTree( + [ + ...(classNamespace ? [classNamespace, '.'] : []), + 'New', // Should this be "new" if the class is unexported? + className, + '(', + ], + renderer.updateContext({ wrapPtr: true }).convertAll(node.arguments ?? []), + { canBreakLine: true, separator: ', ', suffix: ')' }, + ); + + function determineClassName(this: GoVisitor, expr: ts.Expression): { classNamespace?: OTree; className: string } { + if (ts.isIdentifier(expr)) { + return { className: ucFirst(expr.text) }; + } + if (ts.isPropertyAccessExpression(expr)) { + if (ts.isIdentifier(expr.expression)) { + return { + className: ucFirst(expr.name.text), + classNamespace: renderer.updateContext({ isExported: false }).convert(expr.expression), + }; + } + renderer.reportUnsupported(expr.expression, TargetLanguage.GO); + return { + className: ucFirst(expr.name.text), + classNamespace: new OTree(['#error#']), + }; + } + renderer.reportUnsupported(expr, TargetLanguage.GO); + return { className: expr.getText(expr.getSourceFile()) }; + } + } + + public arrayLiteralExpression(node: ts.ArrayLiteralExpression, renderer: AstRenderer): OTree { + const arrayType = + inferredTypeOfExpression(renderer.typeChecker, node) ?? renderer.typeChecker.getTypeAtLocation(node); + const [elementType] = renderer.typeChecker.getTypeArguments(arrayType as ts.TypeReference); + const typeName = elementType + ? this.renderType(node, elementType.symbol, elementType, true, 'interface{}', renderer) + : 'interface{}'; + + return new OTree(['[]', typeName, '{'], renderer.convertAll(node.elements), { + separator: ',', + trailingSeparator: true, + suffix: '}', + indent: 1, + }); + } + + public objectLiteralExpression(node: ts.ObjectLiteralExpression, renderer: GoRenderer): OTree { + const lit = analyzeObjectLiteral(renderer.typeChecker, node); + + switch (lit.kind) { + case 'unknown': + return this.unknownTypeObjectLiteralExpression(node, renderer); + case 'struct': + case 'local-struct': + return this.knownStructObjectLiteralExpression(node, lit, renderer); + case 'map': + return this.keyValueObjectLiteralExpression(node, renderer); + } + } + + public propertyAssignment(node: ts.PropertyAssignment, renderer: GoRenderer): OTree { + const key = + ts.isStringLiteralLike(node.name) || ts.isIdentifier(node.name) + ? renderer.currentContext.inMapLiteral + ? JSON.stringify(node.name.text) + : this.goName(node.name.text, renderer, renderer.typeChecker.getSymbolAtLocation(node.name)) + : renderer.convert(node.name); + // Struct member values are always pointers... + return new OTree( + [ + key, + ': ', + renderer + .updateContext({ + wrapPtr: renderer.currentContext.isStruct || renderer.currentContext.inMapLiteral, + isPtr: renderer.currentContext.isStruct, + }) + .convert(node.initializer), + ], + [], + { + canBreakLine: true, + }, + ); + } + + public shorthandPropertyAssignment( + node: ts.ShorthandPropertyAssignment, + renderer: AstRenderer, + ): OTree { + const key = + ts.isStringLiteralLike(node.name) || ts.isIdentifier(node.name) + ? renderer.currentContext.inMapLiteral + ? JSON.stringify(node.name.text) + : this.goName(node.name.text, renderer, renderer.typeChecker.getSymbolAtLocation(node.name)) + : renderer.convert(node.name); + + const rawValue = renderer.updateContext({ wrapPtr: true, isStruct: false }).convert(node.name); + const value = isPointerValue(renderer.typeChecker, node.name) + ? rawValue + : wrapPtrExpression(renderer.typeChecker, node.name, rawValue); + + return new OTree([key, ': ', value], [], { canBreakLine: true }); + } + + public templateExpression(node: ts.TemplateExpression, renderer: AstRenderer): OTree { + let template = ''; + const parameters = new Array(); + + if (node.head.rawText) { + template += node.head.rawText; + } + + for (const span of node.templateSpans) { + template += '%v'; + parameters.push(renderer.convert(span.expression)); + if (span.literal.rawText) { + template += span.literal.rawText; + } + } + + if (parameters.length === 0) { + return new OTree([JSON.stringify(template)]); + } + + return new OTree( + ['fmt.Sprintf('], + [ + JSON.stringify(template), + ...parameters.reduce((list, element) => list.concat(', ', element), new Array()), + ], + { + canBreakLine: true, + suffix: ')', + }, + ); + } + + public token(node: ts.Token, renderer: GoRenderer): OTree { + switch (node.kind) { + case ts.SyntaxKind.FalseKeyword: + case ts.SyntaxKind.TrueKeyword: + if (renderer.currentContext.wrapPtr) { + return new OTree(['jsii.Boolean(', node.getText(), ')']); + } + return new OTree([node.getText()]); + + case ts.SyntaxKind.NullKeyword: + case ts.SyntaxKind.UndefinedKeyword: + return new OTree(['nil']); + default: + return super.token(node, renderer); + } + } + + public unknownTypeObjectLiteralExpression(node: ts.ObjectLiteralExpression, renderer: GoRenderer): OTree { + return this.keyValueObjectLiteralExpression(node, renderer); + } + + public keyValueObjectLiteralExpression(node: ts.ObjectLiteralExpression, renderer: GoRenderer): OTree { + const valueType = inferMapElementType(node.properties, renderer.typeChecker); + + return new OTree( + [`map[string]`, this.renderType(node, valueType?.symbol, valueType, true, `interface{}`, renderer), `{`], + renderer.updateContext({ inMapLiteral: true, wrapPtr: true }).convertAll(node.properties), + { + suffix: '}', + separator: ',', + trailingSeparator: true, + indent: 1, + }, + ); + } + + public knownStructObjectLiteralExpression( + node: ts.ObjectLiteralExpression, + structType: ObjectLiteralStruct, + renderer: GoRenderer, + ): OTree { + return new OTree( + [ + '&', + this.goName(structType.type.symbol.name, renderer.updateContext({ isPtr: false }), structType.type.symbol), + '{', + ], + renderer.updateContext({ isStruct: true }).convertAll(node.properties), + { + suffix: '}', + separator: ',', + trailingSeparator: true, + indent: 1, + }, + ); + } + + public asExpression(node: ts.AsExpression, renderer: AstRenderer): OTree { + const jsiiType = determineJsiiType(renderer.typeChecker, renderer.typeChecker.getTypeFromTypeNode(node.type)); + switch (jsiiType.kind) { + case 'builtIn': + switch (jsiiType.builtIn) { + case 'boolean': + return new OTree(['bool(', renderer.convert(node.expression), ')'], [], { canBreakLine: true }); + case 'number': + return new OTree(['f64(', renderer.convert(node.expression), ')'], [], { canBreakLine: true }); + case 'string': + return new OTree(['string(', renderer.convert(node.expression), ')'], [], { canBreakLine: true }); + case 'any': + case 'void': + // Just return the value as-is... Everything is compatible with `interface{}`. + return renderer.convert(node.expression); + } + // To make linter understand there is no fall-through here... + throw new AssertionError({ message: 'unreachable' }); + default: + return new OTree( + [renderer.convert(node.expression), '.(', this.renderTypeNode(node.type, false, renderer), ')'], + [], + { canBreakLine: true }, + ); + } + } + + public parameterDeclaration(node: ts.ParameterDeclaration, renderer: GoRenderer): OTree { + const nodeName = renderer.updateContext({ isParameterName: true, isPtr: false }).convert(node.name); + const nodeType = node.dotDotDotToken ? (node.type as ts.ArrayTypeNode | undefined)?.elementType : node.type; + const typeNode = this.renderTypeNode(nodeType, true, renderer); + return new OTree([...(node.dotDotDotToken ? ['...'] : []), nodeName, ' ', typeNode]); + } + + public printStatement(args: ts.NodeArray, renderer: GoRenderer): OTree { + const renderedArgs = this.argumentList(args, renderer); + return new OTree(['fmt.Println(', renderedArgs, ')']); + } + + public propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: GoRenderer): OTree { + const expressionType = typeOfExpression(renderer.typeChecker, node.expression); + const valueSymbol = renderer.typeChecker.getSymbolAtLocation(node.name); + + const isClassStaticMember = + expressionType?.symbol?.valueDeclaration != null && + ts.isClassDeclaration(expressionType.symbol.valueDeclaration) && + valueSymbol?.valueDeclaration != null && + ts.isPropertyDeclaration(valueSymbol.valueDeclaration) && + isStatic(valueSymbol.valueDeclaration); + + // When the expression has an unknown type (unresolved symbol), and has an upper-case first + // letter, we assume it's a type name... In such cases, what comes after can be considered a + // static member access. Note that the expression might be further qualified, so we check using + // a regex that checks for the last "."-delimited segment if there's dots in there... + const expressionLooksLikeTypeReference = + expressionType.symbol == null && + /(?:\.|^)[A-Z][^.]*$/.exec(node.expression.getText(node.expression.getSourceFile())) != null; + + const isEnum = + expressionType?.symbol?.valueDeclaration != null && ts.isEnumDeclaration(expressionType.symbol.valueDeclaration); + + const delimiter = isEnum || isClassStaticMember || expressionLooksLikeTypeReference ? '_' : '.'; + + return new OTree([ + renderer.convert(node.expression), + delimiter, + renderer + .updateContext({ isExported: isClassStaticMember || expressionLooksLikeTypeReference || isEnum }) + .convert(node.name), + ...(isClassStaticMember + ? ['()'] + : // If the parent's not a call-like expression, and it's an inferred static property access, we need to put call + // parentheses at the end, as static properties are accessed via synthetic readers. + expressionLooksLikeTypeReference && findUp(node, ts.isCallLikeExpression) == null + ? ['()'] + : []), + ]); + } + + public methodSignature(node: ts.MethodSignature, renderer: AstRenderer): OTree { + const type = this.renderTypeNode(node.type, true, renderer); + return new OTree( + [ + renderer.updateContext({ isExported: renderer.currentContext.isExported && isPublic(node) }).convert(node.name), + '(', + ], + renderer.convertAll(node.parameters), + { suffix: `) ${type}`, canBreakLine: true }, + ); + } + + public propertyDeclaration(node: ts.PropertyDeclaration, renderer: AstRenderer): OTree { + return new OTree( + [ + renderer + .updateContext({ isExported: (renderer.currentContext.isExported && isPublic(node)) || isStatic(node) }) + .convert(node.name), + ' ', + this.renderTypeNode(node.type, true, renderer), + ], + [], + { canBreakLine: true }, + ); + } + + public propertySignature(node: ts.PropertySignature, renderer: GoRenderer): OTree { + if (renderer.currentContext.isInterface) { + const type = this.renderTypeNode(node.type, true, renderer); + const getter = new OTree([ + renderer.updateContext({ isExported: renderer.currentContext.isExported && isPublic(node) }).convert(node.name), + '() ', + type, + ]); + if (isReadOnly(node)) { + return getter; + } + const setter = new OTree([ + '\n', + renderer.currentContext.isExported && isPublic(node) ? 'Set' : 'set', + renderer.updateContext({ isExported: true }).convert(node.name), + '(value ', + type, + ')', + ]); + return new OTree([getter, setter]); + } + + return new OTree([ + '\n', + renderer.updateContext({ isExported: renderer.currentContext.isExported && isPublic(node) }).convert(node.name), + ' ', + this.renderTypeNode(node.type, renderer.currentContext.isPtr, renderer), + ]); + } + + public regularCallExpression(node: ts.CallExpression, renderer: GoRenderer): OTree { + return new OTree([ + renderer.convert(node.expression), + '(', + this.argumentList(node.arguments, renderer.updateContext({ wrapPtr: true })), + ')', + ]); + } + + public returnStatement(node: ts.ReturnStatement, renderer: AstRenderer): OTree { + return new OTree(['return ', renderer.updateContext({ wrapPtr: true }).convert(node.expression)], [], { + canBreakLine: true, + }); + } + + public binaryExpression(node: ts.BinaryExpression, renderer: AstRenderer): OTree { + if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) { + const symbol = symbolFor(renderer.typeChecker, node.left); + return new OTree([ + renderer.convert(node.left), + ' = ', + renderer + .updateContext({ + isPtrAssignmentRValue: + symbol?.valueDeclaration && + (ts.isParameter(symbol.valueDeclaration) || ts.isPropertyDeclaration(symbol.valueDeclaration)), + }) + .convert(node.right), + ]); + } + + const output = super.binaryExpression(node, renderer.updateContext({ wrapPtr: false, isPtr: false })); + if (!renderer.currentContext.wrapPtr) { + return output; + } + return wrapPtrExpression(renderer.typeChecker, node, output); + } + + public stringLiteral(node: ts.StringLiteral, renderer: GoRenderer): OTree { + const text = JSON.stringify(node.text); + + return new OTree([`${renderer.currentContext.wrapPtr ? jsiiStr(text) : text}`]); + } + + public numericLiteral(node: ts.NumericLiteral, renderer: GoRenderer): OTree { + const text = `${node.text}`; + + return new OTree([`${renderer.currentContext.wrapPtr ? jsiiNum(text) : text}`]); + } + + public classDeclaration(node: ts.ClassDeclaration, renderer: AstRenderer): OTree { + const className = node.name + ? renderer.updateContext({ isExported: isExported(node) }).convert(node.name) + : 'anonymous'; + + const extendsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword); + const base = extendsClause && this.renderTypeNode(extendsClause.types[0], false, renderer); + + const properties = node.members + .filter(ts.isPropertyDeclaration) + .map((prop) => renderer.updateContext({ isStruct: true, isPtr: true }).convert(prop)); + + const struct = new OTree(['type ', className, ' struct {'], [...(base ? ['\n', base] : []), ...properties], { + canBreakLine: true, + suffix: properties.length > 0 ? renderer.mirrorNewlineBefore(node.members[0], '}') : '\n}', + indent: 1, + }); + + const methods = [ + node.members.length > 0 + ? // Ensure there is a blank line between thre struct and the first member, but don't put two if there's already + // one as part of the first member's leading trivia. + new OTree(['\n\n'], [], { renderOnce: `ws-${node.members[0].getFullStart()}` }) + : '', + ...renderer.convertAll( + node.members.filter((member) => !ts.isPropertyDeclaration(member) || (isExported(node) && !isPrivate(member))), + ), + ]; + + return new OTree([struct], methods, { canBreakLine: true }); + } + + public structInterfaceDeclaration(node: ts.InterfaceDeclaration, renderer: GoRenderer): OTree { + const bases = + node.heritageClauses?.flatMap((hc) => hc.types).map((t) => this.renderTypeNode(t, false, renderer)) ?? []; + return new OTree( + ['type ', renderer.updateContext({ isStruct: true }).convert(node.name), ' struct {'], + [...bases, ...renderer.updateContext({ isStruct: true, isPtr: true }).convertAll(node.members)], + { indent: 1, canBreakLine: true, separator: '\n', trailingSeparator: true, suffix: '}' }, + ); + } + + public regularInterfaceDeclaration(node: ts.InterfaceDeclaration, renderer: AstRenderer): OTree { + if (node.members.length === 0) { + // Erase empty interfaces as they have no bearing in Go + return new OTree([]); + } + + const symbol = renderer.typeChecker.getSymbolAtLocation(node.name); + const name = this.goName(node.name.text, renderer.updateContext({ isExported: isExported(node) }), symbol); + return new OTree( + [`type ${name} interface {`], + renderer.updateContext({ isInterface: true, isExported: isExported(node) }).convertAll(node.members), + { indent: 1, canBreakLine: true, separator: '\n', trailingSeparator: true, suffix: '}' }, + ); + } + + public constructorDeclaration(node: ts.ConstructorDeclaration, renderer: AstRenderer): OTree { + const className = node.parent.name + ? this.goName( + node.parent.name.text, + renderer.updateContext({ isExported: isExported(node.parent) }), + renderer.typeChecker.getSymbolAtLocation(node.parent.name), + ) + : 'anonymous'; + + const defaultArgValues = this.defaultArgValues(node.parameters, renderer); + + return new OTree( + [ + 'func ', + isExported(node.parent) ? 'New' : 'new', + ucFirst(className), + '(', + new OTree([], renderer.convertAll(node.parameters), { separator: ', ' }), + ') *', + className, + ' {', + new OTree([], [defaultArgValues, '\nthis := &', className, '{}'], { + indent: 1, + }), + ], + node.body ? renderer.convertAll(node.body.statements) : [], + { canBreakLine: true, suffix: '\n\treturn this\n}', indent: 1 }, + ); + } + + public superCallExpression(node: ts.CallExpression, renderer: AstRenderer): OTree { + // We're on a `super` call, so we must be extending a base class. + const base = findUp(node, ts.isConstructorDeclaration)!.parent.heritageClauses!.find( + (clause) => clause.token === ts.SyntaxKind.ExtendsKeyword, + )!.types[0].expression; + const baseConstructor = ts.isPropertyAccessExpression(base) + ? new OTree([ + renderer.convert(base.expression), + '.New', + ucFirst(this.goName(base.name.text, renderer, renderer.typeChecker.getSymbolAtLocation(base.name))), + ]) + : ts.isIdentifier(base) + ? `new${ucFirst(this.goName(base.text, renderer, renderer.typeChecker.getSymbolAtLocation(base)))}` + : (function () { + renderer.reportUnsupported(node, TargetLanguage.GO); + return renderer.convert(base); + })(); + + return new OTree( + [ + baseConstructor, + '_Override(this, ', + this.argumentList(node.arguments, renderer.updateContext({ wrapPtr: true, isPtr: true })), + ')', + ], + [], + { + canBreakLine: true, + }, + ); + } + + public methodDeclaration(node: ts.MethodDeclaration, renderer: AstRenderer): OTree { + if (ts.isObjectLiteralExpression(node.parent)) { + return super.methodDeclaration(node, renderer); + } + + const className = node.parent.name + ? this.goName( + node.parent.name.text, + renderer.updateContext({ isExported: isExported(node.parent) }), + renderer.typeChecker.getSymbolAtLocation(node.parent.name), + ) + : 'anonymous'; + + const returnType = determineReturnType(renderer.typeChecker, node); + const goReturnType = + returnType && this.renderType(node.type ?? node, returnType.symbol, returnType, true, 'interface{}', renderer); + + return new OTree( + [ + 'func (this *', + className, + ') ', + renderer.updateContext({ isExported: renderer.currentContext.isExported && isPublic(node) }).convert(node.name), + '(', + new OTree([], renderer.convertAll(node.parameters), { separator: ', ' }), + ') ', + goReturnType, + goReturnType ? ' ' : '', + '{', + ], + [ + this.defaultArgValues(node.parameters, renderer), + ...(node.body ? renderer.convertAll(node.body.statements) : []), + ], + { canBreakLine: true, suffix: node.body && node.body.statements.length > 0 ? '\n}' : '}', indent: 1 }, + ); + } + + public ifStatement(node: ts.IfStatement, renderer: AstRenderer): OTree { + const [ifPrefix, ifSuffix, ifIndent] = ts.isBlock(node.thenStatement) ? [' '] : [' {\n', '\n}', 1]; + const ifStmt = new OTree( + ['if ', renderer.convert(node.expression)], + [ifPrefix, renderer.convert(node.thenStatement)], + { + canBreakLine: true, + suffix: ifSuffix, + indent: ifIndent, + }, + ); + if (!node.elseStatement) { + return ifStmt; + } + + const [elsePrefix, elseSuffix, elseIndent] = ts.isBlock(node.elseStatement) ? [' '] : [' {\n', '\n}', 1]; + const elseStmt = new OTree(['else'], [elsePrefix, renderer.convert(node.elseStatement)], { + canBreakLine: true, + suffix: elseSuffix, + indent: elseIndent, + }); + + return new OTree([], [ifStmt, elseStmt], { + separator: ' ', + canBreakLine: true, + }); + } + + public forOfStatement(node: ts.ForOfStatement, renderer: AstRenderer): OTree { + const [prefix, suffix, indent] = ts.isBlock(node.statement) ? [' '] : [' {\n', '\n}', 1]; + return new OTree( + ['for _, ', nameOf(node.initializer), ' := range ', renderer.convert(node.expression)], + [prefix, renderer.convert(node.statement)], + { canBreakLine: true, suffix, indent }, + ); + + function nameOf(decl: ts.ForInitializer | ts.Declaration): string | OTree { + if (ts.isVariableDeclarationList(decl)) { + if (decl.declarations.length !== 1) { + renderer.reportUnsupported(decl.declarations[1], TargetLanguage.GO); + } + return nameOf(decl.declarations[0]); + } + if (ts.isVariableDeclaration(decl)) { + return decl.name.getText(decl.name.getSourceFile()); + } + renderer.reportUnsupported(decl, TargetLanguage.GO); + return renderer.convert(decl); + } + } + + public importStatement(node: ImportStatement, renderer: AstRenderer): OTree { + const packageName = + node.moduleSymbol?.sourceAssembly?.packageJson.jsii?.targets?.go?.packageName ?? + this.goName(node.packageName, renderer, undefined); + const moduleName = node.moduleSymbol?.sourceAssembly?.packageJson.jsii?.targets?.go?.moduleName + ? `${node.moduleSymbol.sourceAssembly.packageJson.jsii.targets.go.moduleName}/${packageName}` + : `github.com/aws-samples/dummy/${packageName}`; + + if (node.imports.import === 'full') { + return new OTree(['import ', this.goName(node.imports.alias, renderer, undefined), ' "', moduleName, '"']); + } + + // We'll just create local type aliases for all imported types. This is not very go-idiomatic, but simplifies things elsewhere... + const elements = node.imports.elements + .filter((element) => element.importedSymbol?.symbolType === 'type') + .map( + (element) => + new OTree(['type ', element.alias ?? element.sourceName, ' ', packageName, '.', element.sourceName]), + ); + + const submodules = node.imports.elements + .filter((element) => element.importedSymbol?.symbolType === 'module') + .map( + (element) => + new OTree(['import ', element.alias ?? element.sourceName, ' "', moduleName, '/', element.sourceName, '"']), + ); + + if (elements.length === 0 && submodules.length === 0) { + // This is a blank import (for side-effects only) + return new OTree(['import _ "', moduleName, '"']); + } + + const mainImport = new OTree(['import ', packageName, ' "', moduleName, '"'], elements, { + canBreakLine: true, + separator: '\n', + }); + return new OTree([mainImport, ...submodules]); + } + + public variableDeclaration(node: ts.VariableDeclaration, renderer: AstRenderer): OTree { + if (!node.initializer) { + return new OTree([ + 'var ', + renderer.updateContext({ isExported: isExported(node) }).convert(node.name), + ' ', + this.renderTypeNode(node.type, false, renderer) || 'interface{}', + ]); + } + + return new OTree([ + renderer.updateContext({ isExported: false }).convert(node.name), + ' := ', + renderer.convert(node.initializer), + ]); + } + + private defaultArgValues(params: ts.NodeArray, renderer: GoRenderer) { + return new OTree( + params.reduce((accum: OTree[], param) => { + if (!param.initializer) { + return accum; + } + + const name = renderer.updateContext({ isPtr: true }).convert(param.name); + return [ + ...accum, + new OTree( + ['\n', 'if ', name, ' == nil {'], + ['\n', name, ' = ', renderer.updateContext({ wrapPtr: true }).convert(param.initializer)], + { + indent: 1, + suffix: '\n}', + }, + ), + ]; + }, []), + ); + } + + public mergeContext(old: GoLanguageContext, update: Partial): GoLanguageContext { + return Object.assign({}, old, update); + } + + private renderTypeNode(typeNode: ts.TypeNode | undefined, isPtr: boolean, renderer: GoRenderer): string { + if (!typeNode) { + return ''; + } + return this.renderType( + typeNode, + renderer.typeChecker.getTypeFromTypeNode(typeNode).symbol, + renderer.typeOfType(typeNode), + isPtr, + renderer.textOf(typeNode), + renderer, + ); + } + + private renderType( + typeNode: ts.Node, + typeSymbol: ts.Symbol | undefined, + type: ts.Type | undefined, + isPtr: boolean, + fallback: string, + renderer: GoRenderer, + ): string { + if (type === undefined) { + return fallback; + } + + const jsiiType = determineJsiiType(renderer.typeChecker, type); + + const doRender = (jsiiType: JsiiType, isPtr: boolean, typeSymbol: ts.Symbol | undefined): string => { + const prefix = isPtr ? '*' : ''; + switch (jsiiType.kind) { + case 'unknown': + return fallback; + case 'error': + renderer.report(typeNode, jsiiType.message); + return fallback; + case 'map': + return `map[string]${doRender(jsiiType.elementType, true, jsiiType.elementTypeSymbol)}`; + case 'list': + return `[]${doRender(jsiiType.elementType, true, jsiiType.elementTypeSymbol)}`; + case 'namedType': + return this.goName(jsiiType.name, renderer, typeSymbol); + case 'builtIn': + switch (jsiiType.builtIn) { + case 'boolean': + return `${prefix}bool`; + case 'number': + return `${prefix}f64`; + case 'string': + return `${prefix}string`; + case 'any': + return 'interface{}'; + case 'void': + return ''; + } + } + }; + + return doRender(jsiiType, isPtr, typeSymbol); + } + + /** + * Guess an item's go name based on it's TS name and context + */ + private goName(input: string, renderer: GoRenderer, symbol: ts.Symbol | undefined) { + let text = input.replace(/[^a-z0-9_]/gi, ''); + const prev = this.idMap.get(symbol ?? input) ?? this.idMap.get(input); + + if (prev) { + // If an identifier has been renamed go get it + text = prev.formatted; + } else if (renderer.currentContext.isExported && !renderer.currentContext.inMapLiteral) { + // Uppercase exported and public symbols/members + text = ucFirst(text); + } else if (!renderer.currentContext.inMapLiteral) { + // Lowercase unexported items that are capitalized in TS like structs/interfaces/classes + text = lcFirst(text); + } + + text = prefixReserved(text); + + if (text !== input && prev == null) { + this.idMap.set(symbol ?? input, { formatted: text, type: getDeclarationType(renderer.currentContext) }); + } + + if ( + // Non-pointer references to parameters need to be de-referenced + (!renderer.currentContext.isPtr && + !renderer.currentContext.isParameterName && + symbol?.valueDeclaration?.kind === ts.SyntaxKind.Parameter && + !renderer.currentContext.isPtrAssignmentRValue) || + // Pointer reference to non-interfaces are prefixed with * + (renderer.currentContext.isPtr && prev && prev?.type !== DeclarationType.INTERFACE) + ) { + return `*${text}`; + } + return text; + } +} + +/** + * Uppercase the first letter + */ +function ucFirst(x: string) { + return x.substring(0, 1).toUpperCase() + x.substring(1); +} + +/** + * Lowercase the first letter + */ +function lcFirst(x: string) { + return x.substring(0, 1).toLowerCase() + x.substring(1); +} + +function wrapPtrExpression(typeChecker: ts.TypeChecker, node: ts.Expression, unwrapped: OTree): OTree { + const type = typeOfExpression(typeChecker, node); + const jsiiType = determineJsiiType(typeChecker, type); + if (jsiiType.kind !== 'builtIn') { + return unwrapped; + } + switch (jsiiType.builtIn) { + case 'boolean': + return new OTree(['jsii.Boolean(', unwrapped, ')']); + case 'number': + return new OTree(['jsii.Number(', unwrapped, ')']); + case 'string': + return new OTree(['jsii.String(', unwrapped, ')']); + case 'any': + case 'void': + return unwrapped; + } +} + +/** + * Wrap a string literal in the jsii.String helper + */ +function jsiiStr(x: string) { + return `jsii.String(${x})`; +} + +/** + * Wrap a string literal in the jsii.String helper + */ +function jsiiNum(x: string) { + return `jsii.Number(${x})`; +} + +/** + * Prefix reserved word identifiers with _ + */ +function prefixReserved(x: string) { + if (['struct'].includes(x)) { + return `${x}_`; + } + return x; +} + +function getDeclarationType(ctx: GoLanguageContext) { + if (ctx.isStruct) { + return DeclarationType.STRUCT; + } + + return DeclarationType.UNKNOWN; +} + +function findUp(node: ts.Node, predicate: (node: ts.Node) => node is T): T | undefined { + if (predicate(node)) { + return node; + } + if (node.parent == null) { + return undefined; + } + return findUp(node.parent, predicate); +} + +function symbolFor(typeChecker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined { + if (ts.isIdentifier(node)) { + return typeChecker.getSymbolAtLocation(node); + } + if (ts.isPropertyAccessExpression(node)) { + return typeChecker.getSymbolAtLocation(node.name); + } + // I don't know 🤷🏻‍♂️ + return undefined; +} + +/** + * Checks whether the provided node corresponds to a pointer-value. + * + * NOTE: This currently only checkes for parameter declarations. This is + * presently used only to determine whether a variable reference needs to be + * wrapped or not (i.e: "jsii.String(varStr)"), and parameter references are the + * only "always pointer" values possible in that particular context. + * + * @param typeChecker a TypeChecker to use to resolve the node's symbol. + * @param node the node to be checked. + * + * @returns true if the node corresponds to a pointer-value. + */ +function isPointerValue(typeChecker: ts.TypeChecker, node: ts.Node): boolean { + const symbol = typeChecker.getSymbolAtLocation(node); + if (symbol == null) { + // Can't find symbol, assuming it's a pointer... + return true; + } + + const declaration = symbol.valueDeclaration; + if (declaration == null) { + // Doesn't have declaration, assuming it's a pointer... + return true; + } + + // Now check if this is known pointer kind or not.... + return ts.isParameter(node); +} diff --git a/packages/jsii-rosetta/lib/languages/index.ts b/packages/jsii-rosetta/lib/languages/index.ts index 28bcf72141..e993412745 100644 --- a/packages/jsii-rosetta/lib/languages/index.ts +++ b/packages/jsii-rosetta/lib/languages/index.ts @@ -1,5 +1,6 @@ import { AstHandler } from '../renderer'; import { CSharpVisitor } from './csharp'; +import { GoVisitor } from './go'; import { JavaVisitor } from './java'; import { PythonVisitor } from './python'; import { TargetLanguage } from './target-language'; @@ -24,4 +25,8 @@ export const TARGET_LANGUAGES: { [key in TargetLanguage]: VisitorFactory } = { version: JavaVisitor.VERSION, createVisitor: () => new JavaVisitor(), }, + go: { + version: GoVisitor.VERSION, + createVisitor: () => new GoVisitor(), + }, }; diff --git a/packages/jsii-rosetta/lib/languages/target-language.ts b/packages/jsii-rosetta/lib/languages/target-language.ts index 29c30e963d..b4a977150e 100644 --- a/packages/jsii-rosetta/lib/languages/target-language.ts +++ b/packages/jsii-rosetta/lib/languages/target-language.ts @@ -2,4 +2,5 @@ export enum TargetLanguage { PYTHON = 'python', CSHARP = 'csharp', JAVA = 'java', + GO = 'go', } diff --git a/packages/jsii-rosetta/lib/languages/visualize.ts b/packages/jsii-rosetta/lib/languages/visualize.ts index a58e345dc4..6bf4c2ac4c 100644 --- a/packages/jsii-rosetta/lib/languages/visualize.ts +++ b/packages/jsii-rosetta/lib/languages/visualize.ts @@ -38,6 +38,10 @@ export class VisualizeAstVisitor implements AstHandler { return this.defaultNode('stringLiteral', node, children); } + public numericLiteral(node: ts.NumericLiteral, children: AstRenderer): OTree { + return this.defaultNode('numericLiteral', node, children); + } + public identifier(node: ts.Identifier, children: AstRenderer): OTree { return this.defaultNode('identifier', node, children); } diff --git a/packages/jsii-rosetta/lib/o-tree.ts b/packages/jsii-rosetta/lib/o-tree.ts index 8f82e0f54a..681928e8ac 100644 --- a/packages/jsii-rosetta/lib/o-tree.ts +++ b/packages/jsii-rosetta/lib/o-tree.ts @@ -17,6 +17,14 @@ export interface OTreeOptions { */ separator?: string; + /** + * Whether trailing separators should be output. This imples children will be + * writen each on a new line. + * + * @default false + */ + trailingSeparator?: boolean; + /** * Suffix the token after outdenting * @@ -92,17 +100,30 @@ export class OTree implements OTree { const popIndent = sink.requestIndentChange(meVisible ? this.options.indent ?? 0 : 0); let mark = sink.mark(); + for (const child of this.children ?? []) { - if (this.options.separator && mark.wroteNonWhitespaceSinceMark) { - sink.write(this.options.separator); + if (this.options.separator) { + if (this.options.trailingSeparator) { + sink.ensureNewLine(); + } else if (mark.wroteNonWhitespaceSinceMark) { + sink.write(this.options.separator); + } } mark = sink.mark(); sink.write(child); + + if (this.options.separator && this.options.trailingSeparator) { + sink.write(this.options.separator.trimEnd()); + } } + popIndent(); if (this.options.suffix) { + if (this.options.separator && this.options.trailingSeparator) { + sink.ensureNewLine(); + } sink.renderingForSpan(this.span); sink.write(this.options.suffix); } @@ -126,9 +147,19 @@ export interface SinkMark { } export interface OTreeSinkOptions { + /** + * @default ' ' + */ + indentChar?: ' ' | '\t'; visibleSpans?: Spans; } +interface ConditionalNewLine { + readonly conditionalNewLine: { + readonly indent: number; + }; +} + /** * Output sink for OTree objects * @@ -139,13 +170,16 @@ export interface OTreeSinkOptions { * tree :). */ export class OTreeSink { + private readonly indentChar: ' ' | '\t'; private readonly indentLevels: number[] = [0]; - private readonly fragments = new Array(); + private readonly fragments = new Array(); private readonly singletonsRendered = new Set(); private pendingIndentChange = 0; private rendering = true; - public constructor(private readonly options: OTreeSinkOptions = {}) {} + public constructor(private readonly options: OTreeSinkOptions = {}) { + this.indentChar = options.indentChar ?? ' '; + } public tagOnce(key: string | undefined): boolean { if (key === undefined) { @@ -170,7 +204,7 @@ export class OTreeSink { return { get wroteNonWhitespaceSinceMark(): boolean { - return self.fragments.slice(markIndex).some((s) => /[^\s]/.exec(s) != null); + return self.fragments.slice(markIndex).some((s) => typeof s !== 'object' && /[^\s]/.exec(s) != null); }, }; } @@ -186,10 +220,20 @@ export class OTreeSink { if (containsNewline(text)) { this.applyPendingIndentChange(); } - this.append(text.replace(/\n/g, `\n${' '.repeat(this.currentIndent)}`)); + this.append(text.replace(/\n/g, `\n${this.indentChar.repeat(this.currentIndent)}`)); } } + /** + * Ensures the following tokens will be output on a new line (emits a new line + * and indent unless immediately preceded or followed by a newline, ignoring + * surrounding white space). + */ + public ensureNewLine(): void { + this.applyPendingIndentChange(); + this.fragments.push({ conditionalNewLine: { indent: this.currentIndent } }); + } + public renderingForSpan(span?: Span): boolean { if (span && this.options.visibleSpans) { this.rendering = this.options.visibleSpans.fullyContainsSpan(span); @@ -216,6 +260,48 @@ export class OTreeSink { public toString() { // Strip trailing whitespace from every line, and empty lines from the start and end return this.fragments + .map((item, index, fragments) => { + if (typeof item !== 'object') { + return item; + } + const ignore = ''; + + const leading = fragments.slice(0, index).reverse(); + for (const fragment of leading) { + if (typeof fragment === 'object') { + // We don't emit if there was already a conditional newline just before + return ignore; + } + // If there's a trailing newline, then we don't emit this one + if (/\n\s*$/m.exec(fragment)) { + return ignore; + } + // If it contained non-whitespace characters, we need to check trailing data... + if (/[^\s]/.exec(fragment)) { + break; + } + } + + const newlineAndIndent = `\n${this.indentChar.repeat(item.conditionalNewLine.indent)}`; + + const trailing = fragments.slice(index + 1); + for (const fragment of trailing) { + if (typeof fragment === 'object') { + // We're the first of a sequence, so we must emit (unless we returned earlier, of course) + return newlineAndIndent; + } + // If there's a leading newline, then we don't emit this one + if (/^\s*\n/m.exec(fragment)) { + return ignore; + } + // If it contained non-whitespace characters, we emit this one + if (/[^\s]/.exec(fragment)) { + return newlineAndIndent; + } + } + + return ignore; + }) .join('') .replace(/[ \t]+$/gm, '') .replace(/^\n+/, '') diff --git a/packages/jsii-rosetta/lib/renderer.ts b/packages/jsii-rosetta/lib/renderer.ts index cdca8ee037..84fdb08759 100644 --- a/packages/jsii-rosetta/lib/renderer.ts +++ b/packages/jsii-rosetta/lib/renderer.ts @@ -257,6 +257,9 @@ export class AstRenderer { if (ts.isStringLiteral(tree) || ts.isNoSubstitutionTemplateLiteral(tree)) { return visitor.stringLiteral(tree, this); } + if (ts.isNumericLiteral(tree)) { + return visitor.numericLiteral(tree, this); + } if (ts.isFunctionDeclaration(tree)) { return visitor.functionDeclaration(tree, this); } @@ -452,12 +455,14 @@ export class AstRenderer { */ export interface AstHandler { readonly defaultContext: C; + readonly indentChar?: ' ' | '\t'; mergeContext(old: C, update: Partial): C; sourceFile(node: ts.SourceFile, context: AstRenderer): OTree; commentRange(node: CommentSyntax, context: AstRenderer): OTree; importStatement(node: ImportStatement, context: AstRenderer): OTree; stringLiteral(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, children: AstRenderer): OTree; + numericLiteral(node: ts.NumericLiteral, children: AstRenderer): OTree; functionDeclaration(node: ts.FunctionDeclaration, children: AstRenderer): OTree; identifier(node: ts.Identifier, children: AstRenderer): OTree; block(node: ts.Block, children: AstRenderer): OTree; diff --git a/packages/jsii-rosetta/lib/translate.ts b/packages/jsii-rosetta/lib/translate.ts index d1f0456811..ee79c4fcbf 100644 --- a/packages/jsii-rosetta/lib/translate.ts +++ b/packages/jsii-rosetta/lib/translate.ts @@ -228,7 +228,7 @@ export class SnippetTranslator { ); const converted = converter.convert(this.compilation.rootFile); this.translateDiagnostics.push(...filterVisibleDiagnostics(converter.diagnostics, this.visibleSpans)); - return renderTree(converted, { visibleSpans: this.visibleSpans }); + return renderTree(converted, { indentChar: visitor.indentChar, visibleSpans: this.visibleSpans }); } public syntaxKindCounter(): Record { diff --git a/packages/jsii-rosetta/lib/typescript/ast-utils.ts b/packages/jsii-rosetta/lib/typescript/ast-utils.ts index ee10955943..ed58563f93 100644 --- a/packages/jsii-rosetta/lib/typescript/ast-utils.ts +++ b/packages/jsii-rosetta/lib/typescript/ast-utils.ts @@ -396,10 +396,24 @@ export function visibility(x: ts.PropertyLikeDeclaration | ts.FunctionLikeDeclar return 'public'; } -export function isReadOnly(x: ts.PropertyLikeDeclaration | ts.FunctionLikeDeclarationBase) { - const flags = ts.getCombinedModifierFlags(x); - return (flags & ts.ModifierFlags.Readonly) !== 0; +function hasFlag(flag: ts.ModifierFlags) { + return (x: T) => { + const flags = ts.getCombinedModifierFlags(x); + return (flags & flag) !== 0; + }; +} + +export const isReadOnly = hasFlag( + ts.ModifierFlags.Readonly, +); +export const isExported = hasFlag(ts.ModifierFlags.Export); +export const isPrivate = hasFlag(ts.ModifierFlags.Private); +export const isProtected = hasFlag(ts.ModifierFlags.Private); +export function isPublic(x: ts.Declaration) { + // In TypeScript, anything not explicitly marked private or protected is public. + return !isPrivate(x) && !isProtected(x); } +export const isStatic = hasFlag(ts.ModifierFlags.Static); /** * Return the super() call from a method body if found diff --git a/packages/jsii-rosetta/lib/typescript/types.ts b/packages/jsii-rosetta/lib/typescript/types.ts index 78932b4cc1..b5af10102a 100644 --- a/packages/jsii-rosetta/lib/typescript/types.ts +++ b/packages/jsii-rosetta/lib/typescript/types.ts @@ -16,7 +16,7 @@ export function firstTypeInUnion(typeChecker: ts.TypeChecker, type: ts.Type): ts return type.types[0]; } -export type BuiltInType = 'any' | 'boolean' | 'number' | 'string'; +export type BuiltInType = 'any' | 'boolean' | 'number' | 'string' | 'void'; export function builtInTypeName(type: ts.Type): BuiltInType | undefined { if (hasAnyFlag(type.flags, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { return 'any'; diff --git a/packages/jsii-rosetta/test/rosetta.test.ts b/packages/jsii-rosetta/test/rosetta.test.ts index 7d68c2eccd..f0d86d2ed9 100644 --- a/packages/jsii-rosetta/test/rosetta.test.ts +++ b/packages/jsii-rosetta/test/rosetta.test.ts @@ -59,6 +59,7 @@ test('Can use preloaded tablet', () => { python: 'Not Really Translated', csharp: 'Not Really Translated C#', java: 'Not Really Translated Java', + go: 'Not Really Translated Go', }), ); rosetta.addTablet(tablet); @@ -230,6 +231,7 @@ describe('with mocked filesystem', () => { python: 'My Stored Translation', csharp: 'My Stored Translation C#', java: 'My Stored Translation Java', + go: 'My Stored Translation Go', }), ); diff --git a/packages/jsii-rosetta/test/translations.test.ts b/packages/jsii-rosetta/test/translations.test.ts index 86b45e5aca..e5f82bfacf 100644 --- a/packages/jsii-rosetta/test/translations.test.ts +++ b/packages/jsii-rosetta/test/translations.test.ts @@ -18,6 +18,7 @@ import { testSnippetLocation } from './testutil'; // yarn test test/translations.test -t 'Translating .* to Python' // yarn test test/translations.test -t 'Translating .* to Java' // yarn test test/translations.test -t 'Translating .* to C#' +// yarn test test/translations.test -t 'Translating .* to Go' // // To narrow it down even more you can of course replace the '.*' regex with // whatever file indication you desire. @@ -46,6 +47,11 @@ export const SUPPORTED_LANGUAGES = new Array( extension: '.cs', visitorFactory: TARGET_LANGUAGES[TargetLanguage.CSHARP], }, + { + name: 'Go', + extension: '.go', + visitorFactory: TARGET_LANGUAGES[TargetLanguage.GO], + }, ); const translationsRoot = path.join(__dirname, 'translations'); @@ -87,7 +93,7 @@ for (const typeScriptTest of typeScriptTests) { const expected = fs.readFileSync(languageFile, { encoding: 'utf-8' }); try { const translation = translator.renderUsing(visitorFactory.createVisitor()); - expect(stripEmptyLines(translation)).toEqual(stripEmptyLines(stripCommonWhitespace(expected))); + expect(stripEmptyLines(translation)).toBe(stripEmptyLines(stripCommonWhitespace(expected))); } catch (e) { anyFailed = true; throw e; diff --git a/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.go b/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.go new file mode 100644 index 0000000000..d1809a37b1 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.go @@ -0,0 +1,6 @@ +func foo(x *string, y *string, z *string) { + if y == nil { + y = jsii.String("hello") + } + fmt.Println(*x, *y, *z) +} diff --git a/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.ts b/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.ts index 9dfcf68d23..c32a89e1d3 100644 --- a/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.ts +++ b/packages/jsii-rosetta/test/translations/calls/declaring_default_arguments.ts @@ -1,3 +1,3 @@ function foo(x: string | undefined, y: string = 'hello', z?: string) { console.log(x, y, z); -} \ No newline at end of file +} diff --git a/packages/jsii-rosetta/test/translations/calls/default_struct_fields.go b/packages/jsii-rosetta/test/translations/calls/default_struct_fields.go new file mode 100644 index 0000000000..ef34fa95b5 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/default_struct_fields.go @@ -0,0 +1,7 @@ +type struct_ struct { + x *string + y *string +} +func foo(s *struct_) { + fmt.Println(*s.x, *s.y) +} diff --git a/packages/jsii-rosetta/test/translations/calls/function_call.go b/packages/jsii-rosetta/test/translations/calls/function_call.go new file mode 100644 index 0000000000..f80bcd5d72 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/function_call.go @@ -0,0 +1 @@ +callSomeFunction(jsii.Number(1), jsii.Number(2), jsii.Number(3)) diff --git a/packages/jsii-rosetta/test/translations/calls/list_of_anonymous_structs.go b/packages/jsii-rosetta/test/translations/calls/list_of_anonymous_structs.go new file mode 100644 index 0000000000..ce64e3d82d --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/list_of_anonymous_structs.go @@ -0,0 +1,12 @@ +foo(map[string][]map[string]*f64{ + "list": []map[string]*f64{ + map[string]*f64{ + "a": jsii.Number(1), + "b": jsii.Number(2), + }, + map[string]*f64{ + "a": jsii.Number(3), + "b": jsii.Number(4), + }, + }, +}) diff --git a/packages/jsii-rosetta/test/translations/calls/literal_map_argument.go b/packages/jsii-rosetta/test/translations/calls/literal_map_argument.go new file mode 100644 index 0000000000..6ed0e7fc97 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/literal_map_argument.go @@ -0,0 +1,7 @@ +func foo(xs map[string]*string) { +} + +foo(map[string]*string{ + "foo": jsii.String("bar"), + "schmoo": jsii.String("schmar"), +}) diff --git a/packages/jsii-rosetta/test/translations/calls/method_call.go b/packages/jsii-rosetta/test/translations/calls/method_call.go new file mode 100644 index 0000000000..b7018981c5 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/method_call.go @@ -0,0 +1 @@ +someObject.callSomeFunction(jsii.Number(1), jsii.Number(2), jsii.Number(3)) diff --git a/packages/jsii-rosetta/test/translations/calls/self_method_call.go b/packages/jsii-rosetta/test/translations/calls/self_method_call.go new file mode 100644 index 0000000000..790425e500 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/self_method_call.go @@ -0,0 +1 @@ +this.callSomeFunction(jsii.Number(25)) diff --git a/packages/jsii-rosetta/test/translations/calls/shorthand_property.go b/packages/jsii-rosetta/test/translations/calls/shorthand_property.go new file mode 100644 index 0000000000..6653707006 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/shorthand_property.go @@ -0,0 +1,4 @@ +foo := "hello" +callFunction(map[string]*string{ + "foo": jsii.String(foo), +}) diff --git a/packages/jsii-rosetta/test/translations/calls/static_function_call.go b/packages/jsii-rosetta/test/translations/calls/static_function_call.go new file mode 100644 index 0000000000..2fa74b7518 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/static_function_call.go @@ -0,0 +1 @@ +someObject_CallSomeFunction(jsii.Number(1), jsii.Number(2), jsii.Number(3)) diff --git a/packages/jsii-rosetta/test/translations/calls/this_argument.go b/packages/jsii-rosetta/test/translations/calls/this_argument.go new file mode 100644 index 0000000000..d146e6ab11 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/this_argument.go @@ -0,0 +1 @@ +callSomeFunction(this, jsii.Number(25)) diff --git a/packages/jsii-rosetta/test/translations/calls/translate_object_literals_in_function_call.go b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_in_function_call.go new file mode 100644 index 0000000000..9b4dfc4783 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_in_function_call.go @@ -0,0 +1,4 @@ +foo(jsii.Number(25), map[string]interface{}{ + "foo": jsii.Number(3), + "banana": jsii.String("hello"), +}) diff --git a/packages/jsii-rosetta/test/translations/calls/translate_object_literals_only_one_level_deep.go b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_only_one_level_deep.go new file mode 100644 index 0000000000..92f38b3b5d --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_only_one_level_deep.go @@ -0,0 +1,7 @@ +foo(jsii.Number(25), map[string]interface{}{ + "foo": jsii.Number(3), + "deeper": map[string]*f64{ + "a": jsii.Number(1), + "b": jsii.Number(2), + }, +}) diff --git a/packages/jsii-rosetta/test/translations/calls/translate_object_literals_second_level_with_newlines.go b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_second_level_with_newlines.go new file mode 100644 index 0000000000..92f38b3b5d --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_second_level_with_newlines.go @@ -0,0 +1,7 @@ +foo(jsii.Number(25), map[string]interface{}{ + "foo": jsii.Number(3), + "deeper": map[string]*f64{ + "a": jsii.Number(1), + "b": jsii.Number(2), + }, +}) diff --git a/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_multiple_newlines.go b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_multiple_newlines.go new file mode 100644 index 0000000000..7d84cb9b32 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_multiple_newlines.go @@ -0,0 +1,5 @@ +foo(jsii.Number(25), map[string]interface{}{ + "foo": jsii.Number(3), + + "banana": jsii.String("hello"), +}) diff --git a/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_newlines.go b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_newlines.go new file mode 100644 index 0000000000..9b4dfc4783 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/translate_object_literals_with_newlines.go @@ -0,0 +1,4 @@ +foo(jsii.Number(25), map[string]interface{}{ + "foo": jsii.Number(3), + "banana": jsii.String("hello"), +}) diff --git a/packages/jsii-rosetta/test/translations/calls/will_type_deep_structs_directly_if_type_info_is_available.go b/packages/jsii-rosetta/test/translations/calls/will_type_deep_structs_directly_if_type_info_is_available.go new file mode 100644 index 0000000000..db753ca420 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/calls/will_type_deep_structs_directly_if_type_info_is_available.go @@ -0,0 +1,24 @@ +type baseDeeperStruct struct { + a *f64 +} + +type deeperStruct struct { + baseDeeperStruct + b *f64 +} + +type outerStruct struct { + foo *f64 + deeper *deeperStruct +} + +func foo(x *f64, outer *outerStruct) { +} + +foo(jsii.Number(25), &outerStruct{ + foo: jsii.Number(3), + deeper: &deeperStruct{ + a: jsii.Number(1), + b: jsii.Number(2), + }, +}) diff --git a/packages/jsii-rosetta/test/translations/classes/class_declaration_with_private_fields_and_constructor.go b/packages/jsii-rosetta/test/translations/classes/class_declaration_with_private_fields_and_constructor.go new file mode 100644 index 0000000000..a09e602d9b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_declaration_with_private_fields_and_constructor.go @@ -0,0 +1,9 @@ +type myClass struct { + x *string +} + +func newMyClass(y *string) *myClass { + this := &myClass{} + this.x = y + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_declaration_with_public_fields_and_constructor.go b/packages/jsii-rosetta/test/translations/classes/class_declaration_with_public_fields_and_constructor.go new file mode 100644 index 0000000000..a09e602d9b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_declaration_with_public_fields_and_constructor.go @@ -0,0 +1,9 @@ +type myClass struct { + x *string +} + +func newMyClass(y *string) *myClass { + this := &myClass{} + this.x = y + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_implementing_jsii_interface.go b/packages/jsii-rosetta/test/translations/classes/class_implementing_jsii_interface.go new file mode 100644 index 0000000000..c0b409f3a7 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_implementing_jsii_interface.go @@ -0,0 +1,6 @@ +type myClass struct { +} + +func (this *myClass) resolve() interface{} { + return jsii.Number(42) +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_different_name.go b/packages/jsii-rosetta/test/translations/classes/class_with_different_name.go new file mode 100644 index 0000000000..a076a6b5c4 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_different_name.go @@ -0,0 +1,7 @@ +type otherName struct { +} + +func newOtherName() *otherName { + this := &otherName{} + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_extends_and_implements.go b/packages/jsii-rosetta/test/translations/classes/class_with_extends_and_implements.go new file mode 100644 index 0000000000..33f1217700 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_extends_and_implements.go @@ -0,0 +1,3 @@ +type myClass struct { + SomeOtherClass +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_inheritance.go b/packages/jsii-rosetta/test/translations/classes/class_with_inheritance.go new file mode 100644 index 0000000000..0a25bf8f16 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_inheritance.go @@ -0,0 +1,3 @@ +type myClass struct { + cdk.SomeOtherClass +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_inheritance_and_super_class.go b/packages/jsii-rosetta/test/translations/classes/class_with_inheritance_and_super_class.go new file mode 100644 index 0000000000..b588825926 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_inheritance_and_super_class.go @@ -0,0 +1,9 @@ +type myClass struct { + cdk.SomeOtherClass +} + +func newMyClass(x *string, y *string) *myClass { + this := &myClass{} + cdk.NewSomeOtherClass_Override(this, x) + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_method.go b/packages/jsii-rosetta/test/translations/classes/class_with_method.go new file mode 100644 index 0000000000..05248af6a7 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_method.go @@ -0,0 +1,7 @@ +type myClass struct { + cdk.SomeOtherClass +} + +func (this *myClass) someMethod(x *string) { + fmt.Println(*x) +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.go b/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.go new file mode 100644 index 0000000000..7c6ba4fa2a --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.go @@ -0,0 +1,16 @@ +type myClassProps struct { + prop1 *string + prop2 *f64 +} + +type myClass struct { + cdk.SomeOtherClass +} + +func newMyClass(scope cdk.Construct, id *string, props myClassProps) *myClass { + this := &myClass{} + cdk.NewSomeOtherClass_Override(this, scope, id, props) + + fmt.Println(*props.prop1) + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.ts b/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.ts index adb8eee60c..46776b04ad 100644 --- a/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.ts +++ b/packages/jsii-rosetta/test/translations/classes/class_with_props_argument.ts @@ -9,4 +9,4 @@ class MyClass extends cdk.SomeOtherClass { console.log(props.prop1); } -} \ No newline at end of file +} diff --git a/packages/jsii-rosetta/test/translations/classes/constructor_with_optional_params.go b/packages/jsii-rosetta/test/translations/classes/constructor_with_optional_params.go new file mode 100644 index 0000000000..c8b12ce731 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/constructor_with_optional_params.go @@ -0,0 +1,10 @@ +type A struct { +} + +func NewA(a *string, b *f64) *A { + if b == nil { + b = jsii.Number(3) + } + this := &A{} + return this +} diff --git a/packages/jsii-rosetta/test/translations/classes/empty_class.go b/packages/jsii-rosetta/test/translations/classes/empty_class.go new file mode 100644 index 0000000000..72ba494869 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/empty_class.go @@ -0,0 +1,2 @@ +type empty_class struct { +} diff --git a/packages/jsii-rosetta/test/translations/classes/invisible_interfaces_do_not_affect_whitespace.go b/packages/jsii-rosetta/test/translations/classes/invisible_interfaces_do_not_affect_whitespace.go new file mode 100644 index 0000000000..9c8db230be --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/invisible_interfaces_do_not_affect_whitespace.go @@ -0,0 +1,8 @@ +type myClass1 struct { +} + +type thisWillNotBeRendered struct { +} + +type myClass2 struct { +} diff --git a/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_empty_members.go b/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_empty_members.go new file mode 100644 index 0000000000..a77b8fee99 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_empty_members.go @@ -0,0 +1,12 @@ +type myClass struct { +} + +func newMyClass(y *string) *myClass { + this := &myClass{} + this.x = *y + return this +} + +func (this *myClass) hello() {} + +func (this *myClass) bye() {} diff --git a/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_members.go b/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_members.go new file mode 100644 index 0000000000..0e5d076954 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/classes/whitespace_between_multiple_members.go @@ -0,0 +1,16 @@ +type myClass struct { +} + +func newMyClass(y *string) *myClass { + this := &myClass{} + this.x = *y + return this +} + +func (this *myClass) hello() { + fmt.Println(this.x) +} + +func (this *myClass) bye() { + fmt.Println("bye") +} diff --git a/packages/jsii-rosetta/test/translations/comments/empty_lines_in_comments.go b/packages/jsii-rosetta/test/translations/comments/empty_lines_in_comments.go new file mode 100644 index 0000000000..74b488b590 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/comments/empty_lines_in_comments.go @@ -0,0 +1,4 @@ +// Here's a comment +// +// Second line +someCall() diff --git a/packages/jsii-rosetta/test/translations/comments/interleave_multiline_comments_with_function_call.go b/packages/jsii-rosetta/test/translations/comments/interleave_multiline_comments_with_function_call.go new file mode 100644 index 0000000000..94e95c4b01 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/comments/interleave_multiline_comments_with_function_call.go @@ -0,0 +1,7 @@ +someFunction(arg1, map[string]*string{ + /* A comment before arg2 */ + "arg2": jsii.String("string"), + + /* A comment before arg3 */ + "arg3": jsii.String("boo"), +}) diff --git a/packages/jsii-rosetta/test/translations/comments/interleave_single_line_comments_with_function_call.go b/packages/jsii-rosetta/test/translations/comments/interleave_single_line_comments_with_function_call.go new file mode 100644 index 0000000000..13801f3f3b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/comments/interleave_single_line_comments_with_function_call.go @@ -0,0 +1,7 @@ +someFunction(arg1, map[string]*string{ + // A comment before arg2 + "arg2": jsii.String("string"), + + // A comment before arg3 + "arg3": jsii.String("boo"), +}) diff --git a/packages/jsii-rosetta/test/translations/comments/no_duplication_of_comments.go b/packages/jsii-rosetta/test/translations/comments/no_duplication_of_comments.go new file mode 100644 index 0000000000..b8e366c5e3 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/comments/no_duplication_of_comments.go @@ -0,0 +1,2 @@ +// Here's a comment +object.member.functionCall(NewClass(), jsii.String("argument")) diff --git a/packages/jsii-rosetta/test/translations/expressions/array_index.go b/packages/jsii-rosetta/test/translations/expressions/array_index.go new file mode 100644 index 0000000000..15f9c58171 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/array_index.go @@ -0,0 +1,3 @@ +var array []*string + +fmt.Println(array[3]) diff --git a/packages/jsii-rosetta/test/translations/expressions/as_expression.go b/packages/jsii-rosetta/test/translations/expressions/as_expression.go new file mode 100644 index 0000000000..e91991ed2b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/as_expression.go @@ -0,0 +1 @@ +fmt.Println(f64(3)) diff --git a/packages/jsii-rosetta/test/translations/expressions/backtick_string_w_o_substitutions.go b/packages/jsii-rosetta/test/translations/expressions/backtick_string_w_o_substitutions.go new file mode 100644 index 0000000000..178b109254 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/backtick_string_w_o_substitutions.go @@ -0,0 +1 @@ +x := "some string" diff --git a/packages/jsii-rosetta/test/translations/expressions/computed_key.go b/packages/jsii-rosetta/test/translations/expressions/computed_key.go new file mode 100644 index 0000000000..e4daa4a249 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/computed_key.go @@ -0,0 +1,8 @@ +y := "WHY?" + +x := map[string]*string{ + fmt.Sprintf("key-%v", y): jsii.String("value"), +} +z := map[string]*bool{ + y: jsii.Boolean(true), +} diff --git a/packages/jsii-rosetta/test/translations/expressions/double_quoted_dict_keys.go b/packages/jsii-rosetta/test/translations/expressions/double_quoted_dict_keys.go new file mode 100644 index 0000000000..0d90bda32a --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/double_quoted_dict_keys.go @@ -0,0 +1,3 @@ +x := map[string]*string{ + "key": jsii.String("value"), +} diff --git a/packages/jsii-rosetta/test/translations/expressions/ellipsis_at_a_random_place.go b/packages/jsii-rosetta/test/translations/expressions/ellipsis_at_a_random_place.go new file mode 100644 index 0000000000..4b7e236c8b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/ellipsis_at_a_random_place.go @@ -0,0 +1 @@ +callThisFunction(foo, ...) diff --git a/packages/jsii-rosetta/test/translations/expressions/enum_access.go b/packages/jsii-rosetta/test/translations/expressions/enum_access.go new file mode 100644 index 0000000000..916795d58b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/enum_access.go @@ -0,0 +1 @@ +fmt.Println(enumType_ENUM_VALUE_A) diff --git a/packages/jsii-rosetta/test/translations/expressions/enum_like_access.go b/packages/jsii-rosetta/test/translations/expressions/enum_like_access.go new file mode 100644 index 0000000000..8d71dd657d --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/enum_like_access.go @@ -0,0 +1 @@ +fmt.Println(enumType_ENUM_VALUE_A()) diff --git a/packages/jsii-rosetta/test/translations/expressions/non_null_expression.go b/packages/jsii-rosetta/test/translations/expressions/non_null_expression.go new file mode 100644 index 0000000000..29cbc07c3c --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/non_null_expression.go @@ -0,0 +1 @@ +x := someObject.someAttribute diff --git a/packages/jsii-rosetta/test/translations/expressions/string_interpolation.go b/packages/jsii-rosetta/test/translations/expressions/string_interpolation.go new file mode 100644 index 0000000000..6a043f864e --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/string_interpolation.go @@ -0,0 +1,6 @@ +x := "world" +y := "well" +fmt.Println(fmt.Sprintf("Hello, %v, it works %v!", x, y)) + +// And now a multi-line expression +fmt.Println(fmt.Sprintf("\nHello, %v.\n\nIt works %v!\n", x, y)) diff --git a/packages/jsii-rosetta/test/translations/expressions/string_literal.go b/packages/jsii-rosetta/test/translations/expressions/string_literal.go new file mode 100644 index 0000000000..4ff71f0a21 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/string_literal.go @@ -0,0 +1 @@ +literal := "\nThis si a multiline string literal.\n\n\"It's cool!\".\n\nYEAH BABY!!\n\nLitteral \\n right here (not a newline!)\n" diff --git a/packages/jsii-rosetta/test/translations/expressions/struct_assignment.go b/packages/jsii-rosetta/test/translations/expressions/struct_assignment.go new file mode 100644 index 0000000000..4d33c88c93 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/struct_assignment.go @@ -0,0 +1,7 @@ +type test struct { + key *string +} + +x := &test{ + key: jsii.String("value"), +} diff --git a/packages/jsii-rosetta/test/translations/expressions/unary_and_binary_operators.go b/packages/jsii-rosetta/test/translations/expressions/unary_and_binary_operators.go new file mode 100644 index 0000000000..c253f46f6e --- /dev/null +++ b/packages/jsii-rosetta/test/translations/expressions/unary_and_binary_operators.go @@ -0,0 +1,3 @@ +fmt.Println(-3) +fmt.Println(!false) +fmt.Println(a == b) diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_block_level_statements_using_void_directive.go b/packages/jsii-rosetta/test/translations/hiding/hide_block_level_statements_using_void_directive.go new file mode 100644 index 0000000000..938976459b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_block_level_statements_using_void_directive.go @@ -0,0 +1,5 @@ +if true { + fmt.Println("everything is well") +} + +onlyToEndOfBlock() diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_expression_with_explicit_ellipsis.go b/packages/jsii-rosetta/test/translations/hiding/hide_expression_with_explicit_ellipsis.go new file mode 100644 index 0000000000..3669c7f18a --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_expression_with_explicit_ellipsis.go @@ -0,0 +1 @@ +foo(jsii.Number(3), ...) diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_halfway_into_class_using_comments.go b/packages/jsii-rosetta/test/translations/hiding/hide_halfway_into_class_using_comments.go new file mode 100644 index 0000000000..47badc62f0 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_halfway_into_class_using_comments.go @@ -0,0 +1,3 @@ +prepare() + +fmt.Println(this, "it seems to work") diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_parameter_sequence.go b/packages/jsii-rosetta/test/translations/hiding/hide_parameter_sequence.go new file mode 100644 index 0000000000..39e87f0f68 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_parameter_sequence.go @@ -0,0 +1 @@ +foo(jsii.Number(3), jsii.Number(8)) diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_statements_with_explicit_ellipsis.go b/packages/jsii-rosetta/test/translations/hiding/hide_statements_with_explicit_ellipsis.go new file mode 100644 index 0000000000..573506acb4 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_statements_with_explicit_ellipsis.go @@ -0,0 +1,3 @@ +before() +// ... +after() diff --git a/packages/jsii-rosetta/test/translations/hiding/hide_top_level_statements_using_void_directive.go b/packages/jsii-rosetta/test/translations/hiding/hide_top_level_statements_using_void_directive.go new file mode 100644 index 0000000000..0235f9c22b --- /dev/null +++ b/packages/jsii-rosetta/test/translations/hiding/hide_top_level_statements_using_void_directive.go @@ -0,0 +1 @@ +foo(jsii.Number(3)) diff --git a/packages/jsii-rosetta/test/translations/identifiers/keyword.go b/packages/jsii-rosetta/test/translations/identifiers/keyword.go new file mode 100644 index 0000000000..ae93a6b066 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/identifiers/keyword.go @@ -0,0 +1,4 @@ +import lambda "github.com/aws-samples/dummy/scopeawslambda" +lambda.NewClassFromLambda(map[string]*string{ + "key": jsii.String("lambda.amazonaws.com"), +}) diff --git a/packages/jsii-rosetta/test/translations/imports/import_require.go b/packages/jsii-rosetta/test/translations/imports/import_require.go new file mode 100644 index 0000000000..bd49c61d0f --- /dev/null +++ b/packages/jsii-rosetta/test/translations/imports/import_require.go @@ -0,0 +1,2 @@ +import mod "github.com/aws-samples/dummy/scopesomemodule" +mod.NewClassFromModule() diff --git a/packages/jsii-rosetta/test/translations/imports/import_star_as.go b/packages/jsii-rosetta/test/translations/imports/import_star_as.go new file mode 100644 index 0000000000..bd49c61d0f --- /dev/null +++ b/packages/jsii-rosetta/test/translations/imports/import_star_as.go @@ -0,0 +1,2 @@ +import mod "github.com/aws-samples/dummy/scopesomemodule" +mod.NewClassFromModule() diff --git a/packages/jsii-rosetta/test/translations/interfaces/interface_with_method.go b/packages/jsii-rosetta/test/translations/interfaces/interface_with_method.go new file mode 100644 index 0000000000..ee948fc654 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/interfaces/interface_with_method.go @@ -0,0 +1,3 @@ +type iThing interface { + doAThing() +} diff --git a/packages/jsii-rosetta/test/translations/interfaces/interface_with_props.go b/packages/jsii-rosetta/test/translations/interfaces/interface_with_props.go new file mode 100644 index 0000000000..7c3dce8ddb --- /dev/null +++ b/packages/jsii-rosetta/test/translations/interfaces/interface_with_props.go @@ -0,0 +1,3 @@ +type iThing interface { + thingArn() *string +} diff --git a/packages/jsii-rosetta/test/translations/misc/booleans_render_to_right_primitives.go b/packages/jsii-rosetta/test/translations/misc/booleans_render_to_right_primitives.go new file mode 100644 index 0000000000..7ab966d826 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/misc/booleans_render_to_right_primitives.go @@ -0,0 +1 @@ +callFunction(jsii.Boolean(true), jsii.Boolean(false)) diff --git a/packages/jsii-rosetta/test/translations/statements/block_without_braces.go b/packages/jsii-rosetta/test/translations/statements/block_without_braces.go new file mode 100644 index 0000000000..3f5cb9e094 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/block_without_braces.go @@ -0,0 +1,3 @@ +if x == 3 { + fmt.Println("hello") +} diff --git a/packages/jsii-rosetta/test/translations/statements/declare_var.go b/packages/jsii-rosetta/test/translations/statements/declare_var.go new file mode 100644 index 0000000000..b23e0a55c0 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/declare_var.go @@ -0,0 +1 @@ +var variable Type diff --git a/packages/jsii-rosetta/test/translations/statements/empty_control_block.go b/packages/jsii-rosetta/test/translations/statements/empty_control_block.go new file mode 100644 index 0000000000..3ae67d5215 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/empty_control_block.go @@ -0,0 +1 @@ +if x == 3 {} diff --git a/packages/jsii-rosetta/test/translations/statements/for_of_loop.go b/packages/jsii-rosetta/test/translations/statements/for_of_loop.go new file mode 100644 index 0000000000..8db0d3f2bd --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/for_of_loop.go @@ -0,0 +1,3 @@ +for _, x := range xs { + fmt.Println(x) +} diff --git a/packages/jsii-rosetta/test/translations/statements/if.go b/packages/jsii-rosetta/test/translations/statements/if.go new file mode 100644 index 0000000000..f9d5d292e7 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/if.go @@ -0,0 +1,3 @@ +if x == 3 { + fmt.Println("bye") +} diff --git a/packages/jsii-rosetta/test/translations/statements/if_then_else.go b/packages/jsii-rosetta/test/translations/statements/if_then_else.go new file mode 100644 index 0000000000..688c0e50c1 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/if_then_else.go @@ -0,0 +1,5 @@ +if x == 3 { + fmt.Println("bye") +} else { + fmt.Println("toodels") +} diff --git a/packages/jsii-rosetta/test/translations/statements/initialize_object_literal.go b/packages/jsii-rosetta/test/translations/statements/initialize_object_literal.go new file mode 100644 index 0000000000..6bb1a286d8 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/initialize_object_literal.go @@ -0,0 +1,8 @@ +expected := map[string]interface{}{ + "Foo": jsii.String("Bar"), + "Baz": jsii.Number(5), + "Qux": []*string{ + jsii.String("Waldo"), + jsii.String("Fred"), + }, +} diff --git a/packages/jsii-rosetta/test/translations/statements/multiline_if_then_else.go b/packages/jsii-rosetta/test/translations/statements/multiline_if_then_else.go new file mode 100644 index 0000000000..24bd5d7a14 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/multiline_if_then_else.go @@ -0,0 +1,6 @@ +if x == 3 { + x += 1 + fmt.Println("bye") +} else { + fmt.Println("toodels") +} diff --git a/packages/jsii-rosetta/test/translations/statements/statements_and_newlines.go b/packages/jsii-rosetta/test/translations/statements/statements_and_newlines.go new file mode 100644 index 0000000000..149cbc2fb1 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/statements_and_newlines.go @@ -0,0 +1,21 @@ +func doThing() *f64 { + x := 1 // x seems to be equal to 1 + return jsii.Number(x + 1) +} + +func doThing2(x *f64) *bool { + if *x == 1 { + return jsii.Boolean(true) + } + return jsii.Boolean(false) +} + +func doThing3() *f64 { + x := 1 + return jsii.Number(x + 1) +} + +func doThing4() { + x := 1 + x = 85 +} diff --git a/packages/jsii-rosetta/test/translations/statements/vararg_any_call.go b/packages/jsii-rosetta/test/translations/statements/vararg_any_call.go new file mode 100644 index 0000000000..82e686a321 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/vararg_any_call.go @@ -0,0 +1,13 @@ +func test(..._args interface{}) { +} + +test(map[string]interface{}{ + "Key": jsii.String("Value"), + "also": jsii.Number(1337), +}) + +test(map[string]*string{ + "Key": jsii.String("Value"), +}, map[string]*f64{ + "also": jsii.Number(1337), +}) diff --git a/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements.go b/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements.go new file mode 100644 index 0000000000..c852abe45e --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements.go @@ -0,0 +1,3 @@ +statementOne() + +statementTwo() diff --git a/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements_in_a_block.go b/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements_in_a_block.go new file mode 100644 index 0000000000..07f1ce9d66 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/statements/whitespace_between_statements_in_a_block.go @@ -0,0 +1,5 @@ +if condition { + statementOne() + + statementTwo() +} diff --git a/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.go b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.go new file mode 100644 index 0000000000..be53502f49 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.go @@ -0,0 +1,3 @@ +functionThatTakesAnAny(map[string]*f64{ + "argument": jsii.Number(5), +}) diff --git a/packages/jsii-rosetta/test/translations/structs/infer_struct_from_union.go b/packages/jsii-rosetta/test/translations/structs/infer_struct_from_union.go new file mode 100644 index 0000000000..890a35ba1c --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/infer_struct_from_union.go @@ -0,0 +1,8 @@ +takes(&myProps{ + struct_: &someStruct{ + enabled: jsii.Boolean(false), + option: jsii.String("option"), + }, +}) + + diff --git a/packages/jsii-rosetta/test/translations/structs/optional_known_struct.go b/packages/jsii-rosetta/test/translations/structs/optional_known_struct.go new file mode 100644 index 0000000000..d2260cdfa3 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/optional_known_struct.go @@ -0,0 +1,3 @@ +NewVpc(this, jsii.String("Something"), &vpcProps{ + argument: jsii.Number(5), +}) diff --git a/packages/jsii-rosetta/test/translations/structs/struct_starting_with_i.go b/packages/jsii-rosetta/test/translations/structs/struct_starting_with_i.go new file mode 100644 index 0000000000..9a74d11250 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/struct_starting_with_i.go @@ -0,0 +1,3 @@ +NewIntegration(this, jsii.String("Something"), &integrationOptions{ + argument: jsii.Number(5), +}) diff --git a/packages/jsii-rosetta/test/translations/structs/var_new_class_known_struct.go b/packages/jsii-rosetta/test/translations/structs/var_new_class_known_struct.go new file mode 100644 index 0000000000..b1fa2fbdf2 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/var_new_class_known_struct.go @@ -0,0 +1,3 @@ +vpc := NewVpc(this, jsii.String("Something"), &vpcProps{ + argument: jsii.Number(5), +}) diff --git a/packages/jsii-rosetta/test/translations/structs/var_new_class_unknown_struct.go b/packages/jsii-rosetta/test/translations/structs/var_new_class_unknown_struct.go new file mode 100644 index 0000000000..3f0a85cd52 --- /dev/null +++ b/packages/jsii-rosetta/test/translations/structs/var_new_class_unknown_struct.go @@ -0,0 +1,3 @@ +vpc := NewVpc(this, jsii.String("Something"), map[string]*f64{ + "argument": jsii.Number(5), +})