Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rosetta): crashes on behavioral interfaces #1169

Merged
merged 2 commits into from
Jan 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/jsii-rosetta/lib/commands/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,25 @@ async function translateAll(snippets: IterableIterator<TypeScriptSnippet>, inclu
export function singleThreadedTranslateAll(snippets: IterableIterator<TypeScriptSnippet>, includeCompilerDiagnostics: boolean): TranslateAllResult {
const translatedSnippets = new Array<TranslatedSnippet>();

const failures = new Array<ts.Diagnostic>();

const translator = new Translator(includeCompilerDiagnostics);
for (const block of snippets) {
translatedSnippets.push(translator.translate(block));
try {
translatedSnippets.push(translator.translate(block));
} catch(e) {
failures.push({
category: ts.DiagnosticCategory.Error,
code: 999,
messageText: `rosetta: error translating snippet: ${e}\n${block.completeSource}`,
file: undefined,
start: undefined,
length: undefined,
});
}
}

return { translatedSnippets, diagnostics: translator.diagnostics };
return { translatedSnippets, diagnostics: [...translator.diagnostics, ...failures] };
}

/**
Expand Down
31 changes: 26 additions & 5 deletions packages/jsii-rosetta/lib/languages/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ interface CSharpLanguageContext {
*/
readonly inStructInterface: boolean;

/**
* So we know how to render property signatures
*/
readonly inRegularInterface: boolean;

/**
* So we know how to render property assignments
*/
Expand Down Expand Up @@ -53,9 +58,12 @@ interface CSharpLanguageContext {
type CSharpRenderer = AstRenderer<CSharpLanguageContext>;

export class CSharpVisitor extends DefaultVisitor<CSharpLanguageContext> {
public readonly language = 'csharp';

public readonly defaultContext = {
propertyOrMethod: false,
inStructInterface: false,
inRegularInterface: false,
inKeyValueList: false,
stringAsIdentifier: false,
identifierAsString: false,
Expand Down Expand Up @@ -136,6 +144,17 @@ export class CSharpVisitor extends DefaultVisitor<CSharpLanguageContext> {
return this.functionLike(node, renderer);
}

public methodSignature(node: ts.MethodSignature, renderer: CSharpRenderer): OTree {
return new OTree([
this.renderTypeNode(node.type, false, renderer),
' ',
renderer.updateContext({ propertyOrMethod: true }).convert(node.name),
'(',
new OTree([], renderer.convertAll(node.parameters), { separator: ', ' }),
');'
], [], { canBreakLine: true });
}

// tslint:disable-next-line:max-line-length
public functionLike(node: ts.FunctionLikeDeclarationBase, renderer: CSharpRenderer, opts: { isConstructor?: boolean } = {}): OTree {
const methodName = opts.isConstructor ? renderer.currentContext.currentClassName || 'MyClass' : renderer.updateContext({ propertyOrMethod: true }).convert(node.name);
Expand Down Expand Up @@ -248,13 +267,15 @@ export class CSharpVisitor extends DefaultVisitor<CSharpLanguageContext> {
}

public propertySignature(node: ts.PropertySignature, renderer: CSharpRenderer): OTree {
const canSet = renderer.currentContext.inStructInterface || !isReadOnly(node);

return new OTree([
visibility(node),
' ',
!renderer.currentContext.inRegularInterface ? `${visibility(node)} ` : NO_SYNTAX,
this.renderTypeNode(node.type, node.questionToken !== undefined, renderer),
' ',
renderer.updateContext({ propertyOrMethod: true }).convert(node.name),
';',
' ',
canSet ? '{ get; set; }' : '{ get; }',
], [], { canBreakLine: true });
}

Expand Down Expand Up @@ -308,7 +329,7 @@ export class CSharpVisitor extends DefaultVisitor<CSharpLanguageContext> {
...this.classHeritage(node, renderer),
'\n{',
],
renderer.convertAll(node.members),
renderer.updateContext({ inRegularInterface: true }).convertAll(node.members),
{
indent: 4,
canBreakLine: true,
Expand Down Expand Up @@ -503,7 +524,7 @@ function typeNameFromType(type: ts.Type, fallback: string): string {
}

function csharpTypeName(jsTypeName: string | undefined): string {
if (jsTypeName === undefined) { return '???'; }
if (jsTypeName === undefined) { return 'void'; }
switch (jsTypeName) {
case 'number': return 'int';
case 'any': return 'object';
Expand Down
8 changes: 7 additions & 1 deletion packages/jsii-rosetta/lib/languages/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { ImportStatement } from '../typescript/imports';
import { isStructInterface, isStructType } from '../jsii/jsii-utils';
import { mapElementType, typeWithoutUndefinedUnion } from '../typescript/types';
import { voidExpressionString } from '../typescript/ast-utils';
import { TargetLanguage } from '.';

/**
* A basic visitor that applies for most curly-braces-based languages
*/
export abstract class DefaultVisitor<C> implements AstHandler<C> {
public abstract readonly defaultContext: C;
public abstract readonly language: TargetLanguage;

public abstract mergeContext(old: C, update: C): C;

Expand Down Expand Up @@ -240,6 +242,10 @@ export abstract class DefaultVisitor<C> implements AstHandler<C> {
return this.notImplemented(node, context);
}

public methodSignature(node: ts.MethodSignature, context: AstRenderer<C>): OTree {
return this.notImplemented(node, context);
}

public asExpression(node: ts.AsExpression, context: AstRenderer<C>): OTree {
return this.notImplemented(node, context);
}
Expand Down Expand Up @@ -284,7 +290,7 @@ export abstract class DefaultVisitor<C> implements AstHandler<C> {
}

private notImplemented(node: ts.Node, context: AstRenderer<C>) {
context.reportUnsupported(node);
context.reportUnsupported(node, this.language);
return nimpl(node, context);
}
}
Expand Down
90 changes: 69 additions & 21 deletions packages/jsii-rosetta/lib/languages/java.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as ts from 'typescript';
import { DefaultVisitor } from './default';
import { AstRenderer } from '../renderer';
import { OTree } from '../o-tree';
import { OTree, NO_SYNTAX } from '../o-tree';
import { builtInTypeName, mapElementType, typeWithoutUndefinedUnion } from '../typescript/types';
import { isReadOnly, matchAst, nodeOfType, quoteStringLiteral, visibility } from '../typescript/ast-utils';
import { ImportStatement } from '../typescript/imports';
Expand Down Expand Up @@ -72,11 +72,17 @@ interface InsideTypeDeclaration {
* Needed to correctly generate the constructor.
*/
readonly typeName: ts.Node | undefined;

/**
* Is this an interface (true) or a class (unset/false)
*/
readonly isInterface?: boolean;
}

type JavaRenderer = AstRenderer<JavaContext>;

export class JavaVisitor extends DefaultVisitor<JavaContext> {
public readonly language = 'java';
public readonly defaultContext = {};

public mergeContext(old: JavaContext, update: Partial<JavaContext>): JavaContext {
Expand Down Expand Up @@ -110,11 +116,34 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
return this.renderClassDeclaration(node, renderer);
}

public regularInterfaceDeclaration(node: ts.InterfaceDeclaration, renderer: JavaRenderer): OTree {
return new OTree(
[
'public ',
'interface ',
renderer.convert(node.name),
...this.typeHeritage(node, renderer.updateContext({ ignorePropertyPrefix: true })),
' {',
],
renderer
.updateContext({ insideTypeDeclaration: { typeName: node.name, isInterface: true } })
.convertAll(node.members),
{
indent: 4,
canBreakLine: true,
suffix: '\n}',
},
);
}

public propertySignature(node: ts.PropertySignature, renderer: JavaRenderer): OTree {
const propertyType = this.renderTypeNode(node.type, renderer, 'Object');
const propertyName = renderer.convert(node.name);

const field = new OTree(
const isClass = !renderer.currentContext.insideTypeDeclaration?.isInterface;
const blockSep = isClass ? ' ' : ';';

const field = isClass ? new OTree(
[],
[
'private ',
Expand All @@ -126,16 +155,16 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
{
canBreakLine: true,
},
);
) : NO_SYNTAX;

const getter = new OTree(
[],
[
'public ',
isClass ? 'public ' : NO_SYNTAX,
propertyType,
' ',
`get${capitalize(renderer.textOf(node.name))}() `,
this.renderBlock([
`get${capitalize(renderer.textOf(node.name))}()${blockSep}`,
isClass ? this.renderBlock([
new OTree(
[
'\n',
Expand All @@ -146,26 +175,28 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
';',
],
),
]),
]) : NO_SYNTAX,
],
{
canBreakLine: true,
},
);

const setter = new OTree(
const hasSetter = isClass || !isReadOnly(node);

const setter = hasSetter ? new OTree(
[],
[
'public ',
isClass ? 'public ' : NO_SYNTAX,
renderer.convert(renderer.currentContext.insideTypeDeclaration!.typeName),
' ',
propertyName, // don't prefix the setter with `set` - makes it more aligned with JSII builders
'(',
propertyType,
' ',
propertyName,
') ',
this.renderBlock([
`)${blockSep}`,
isClass ? this.renderBlock([
new OTree(
[
'\n',
Expand All @@ -186,12 +217,12 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
'return this;',
],
),
]),
]) : NO_SYNTAX,
],
{
canBreakLine: true,
},
);
) : NO_SYNTAX;

return new OTree([], [field, getter, setter], { canBreakLine: true, separator: '\n' });
}
Expand Down Expand Up @@ -234,6 +265,17 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
this.renderTypeNode(node.type, renderer, 'void'));
}

public methodSignature(node: ts.MethodSignature, renderer: JavaRenderer): OTree {
return new OTree([
this.renderTypeNode(node.type, renderer, 'void'),
' ',
renderer.convert(node.name),
'(',
new OTree([], renderer.convertAll(node.parameters), { separator: ', ' }),
');',
], [], { canBreakLine: true });
}

public parameterDeclaration(node: ts.ParameterDeclaration, renderer: JavaRenderer): OTree {
return new OTree([
this.renderTypeNode(node.type, renderer),
Expand Down Expand Up @@ -472,8 +514,14 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {

public propertyAssignment(node: ts.PropertyAssignment, renderer: JavaRenderer): OTree {
return renderer.currentContext.inKeyValueList
? this.singlePropertyInJavaScriptObjectLiteralToJavaMap(node, renderer)
: this.singlePropertyInJavaScriptObjectLiteralToFluentSetters(node, renderer);
? this.singlePropertyInJavaScriptObjectLiteralToJavaMap(node.name, node.initializer, renderer)
: this.singlePropertyInJavaScriptObjectLiteralToFluentSetters(node.name, node.initializer, renderer);
}

public shorthandPropertyAssignment(node: ts.ShorthandPropertyAssignment, renderer: JavaRenderer): OTree {
return renderer.currentContext.inKeyValueList
? this.singlePropertyInJavaScriptObjectLiteralToJavaMap(node.name, node.name, renderer)
: this.singlePropertyInJavaScriptObjectLiteralToFluentSetters(node.name, node.name, renderer);
}

public propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: JavaRenderer): OTree {
Expand Down Expand Up @@ -530,28 +578,28 @@ export class JavaVisitor extends DefaultVisitor<JavaContext> {
);
}

private singlePropertyInJavaScriptObjectLiteralToJavaMap(node: ts.PropertyAssignment, renderer: JavaRenderer): OTree {
private singlePropertyInJavaScriptObjectLiteralToJavaMap(name: ts.Node, initializer: ts.Node, renderer: JavaRenderer): OTree {
return new OTree(
[],
[
renderer.updateContext({ identifierAsString: true }).convert(node.name),
renderer.updateContext({ identifierAsString: true }).convert(name),
', ',
renderer.updateContext({ inKeyValueList: false }).convert(node.initializer),
renderer.updateContext({ inKeyValueList: false }).convert(initializer),
],
{
canBreakLine: true,
},
);
}

private singlePropertyInJavaScriptObjectLiteralToFluentSetters(node: ts.PropertyAssignment, renderer: JavaRenderer): OTree {
private singlePropertyInJavaScriptObjectLiteralToFluentSetters(name: ts.Node, initializer: ts.Node, renderer: JavaRenderer): OTree {
return new OTree(
[],
[
'.',
renderer.updateContext({ stringLiteralAsIdentifier: true }).convert(node.name),
renderer.updateContext({ stringLiteralAsIdentifier: true }).convert(name),
'(',
renderer.updateContext({ inNewExprWithObjectLiteralAsLastArg: false }).convert(node.initializer),
renderer.updateContext({ inNewExprWithObjectLiteralAsLastArg: false }).convert(initializer),
')',
],
{
Expand Down
6 changes: 6 additions & 0 deletions packages/jsii-rosetta/lib/languages/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export interface PythonVisitorOptions {
}

export class PythonVisitor extends DefaultVisitor<PythonLanguageContext> {
public readonly language = 'python';
public readonly defaultContext = {};

public constructor(private readonly options: PythonVisitorOptions = {}) {
Expand Down Expand Up @@ -442,6 +443,11 @@ export class PythonVisitor extends DefaultVisitor<PythonLanguageContext> {
return NO_SYNTAX;
}

public methodSignature(_node: ts.MethodSignature, _context: PythonVisitorContext): OTree {
// Does not represent in Python
return NO_SYNTAX;
}

public asExpression(node: ts.AsExpression, context: PythonVisitorContext): OTree {
return context.convert(node.expression);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/jsii-rosetta/lib/languages/visualize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ export class VisualizeAstVisitor implements AstHandler<void> {
return this.defaultNode('propertySignature', node, context);
}

public methodSignature(node: ts.MethodSignature, context: AstRenderer<void>): OTree {
return this.defaultNode('methodSignature', node, context);
}

public asExpression(node: ts.AsExpression, context: AstRenderer<void>): OTree {
return this.defaultNode('asExpression', node, context);
}
Expand Down
Loading