Skip to content

Commit

Permalink
Map class and function decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
knutwannheden committed Oct 4, 2024
1 parent bceaf74 commit ee68354
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 16 deletions.
2 changes: 2 additions & 0 deletions openrewrite/src/java/tree/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export function getJavaType<T extends J>(expr: T): JavaType | null {
return expr.annotationType.type;
} else if (expr instanceof MethodDeclaration) {
return expr.methodType != null ? expr.methodType.returnType : null;
} else if (expr instanceof MethodInvocation) {
return expr.methodType == null ? null : expr.methodType.returnType;
}
throw new Error("Unsupported expression type: " + expr.constructor.name);
}
Expand Down
47 changes: 38 additions & 9 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class JavaScriptParserVisitor {
this.typeMapping = new JavaScriptTypeMapping(typeChecker);
}

visit(node: ts.Node): any {
visit = (node: ts.Node): any => {
const member = this[(visitMethodMap.get(node.kind) as keyof JavaScriptParserVisitor)];
if (typeof member === 'function') {
return member.bind(this)(node as any);
Expand All @@ -193,7 +193,7 @@ export class JavaScriptParserVisitor {
}
}

convert<T extends J.J>(node: ts.Node): T {
convert = <T extends J.J>(node: ts.Node): T => {
return this.visit(node) as T;
}

Expand Down Expand Up @@ -359,7 +359,7 @@ export class JavaScriptParserVisitor {
}

visitClassDeclaration(node: ts.ClassDeclaration) {
if (node.modifiers?.find(ts.isDecorator) || node.typeParameters) {
if (node.typeParameters) {
return this.visitUnknown(node);
}

Expand Down Expand Up @@ -571,7 +571,36 @@ export class JavaScriptParserVisitor {
}

visitDecorator(node: ts.Decorator) {
return this.visitUnknown(node);
let annotationType: J.NameTree;
let _arguments: JContainer<J.Expression> | null = null;

if (ts.isCallExpression(node.expression)) {
let callExpression: J.MethodInvocation = this.convert(node.expression);
annotationType = callExpression.select === null ? callExpression.name :
new J.FieldAccess(
randomId(),
callExpression.prefix,
Markers.EMPTY,
callExpression.select,
this.leftPadded(callExpression.padding.select!.after, callExpression.name),
callExpression.type
);
_arguments = callExpression.padding.arguments;
} else if (ts.isIdentifier(node.expression)) {
annotationType = this.convert(node.expression);
} else if (ts.isPropertyAccessExpression(node.expression)) {
annotationType = this.convert(node.expression);
} else {
return this.visitUnknown(node);
}

return new J.Annotation(
randomId(),
this.prefix(node),
Markers.EMPTY,
annotationType,
_arguments
);
}

visitPropertySignature(node: ts.PropertySignature) {
Expand Down Expand Up @@ -1403,18 +1432,18 @@ export class JavaScriptParserVisitor {
}

visitFunctionDeclaration(node: ts.FunctionDeclaration) {
if (node.modifiers?.find(ts.isDecorator) || node.typeParameters) {
if (node.typeParameters) {
return this.visitUnknown(node);
}

return new J.MethodDeclaration(
randomId(),
this.prefix(node),
Markers.EMPTY,
[], // FIXME decorators
this.mapDecorators(node),
[new J.Modifier(
randomId(),
Space.EMPTY,
this.prefix(this.findChildNode(node, ts.SyntaxKind.FunctionKeyword)!),
Markers.EMPTY,
node.getChildAt(1, this.sourceFile).kind == ts.SyntaxKind.AsteriskToken ? "function*" : "function",
J.Modifier.Type.LanguageExtension,
Expand Down Expand Up @@ -1940,8 +1969,8 @@ export class JavaScriptParserVisitor {
return args;
}

private mapDecorators(node: ts.ClassDeclaration) {
return []; // FIXME
private mapDecorators(node: ts.ClassDeclaration | ts.FunctionDeclaration): J.Annotation[] {
return node.modifiers?.filter(ts.isDecorator)?.map(this.convert<J.Annotation>) ?? [];
}

private mapTypeParameters(node: ts.ClassDeclaration): JContainer<J.TypeParameter> | null {
Expand Down
7 changes: 0 additions & 7 deletions openrewrite/test/javascript/parser/class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@ describe('class mapping', () => {
typeScript('class A {}')
);
});
test('decorator', () => {
rewriteRunWithOptions(
{expectUnknowns: true},
//language=typescript
typeScript('@foo class A {}')
);
});
test('type parameter', () => {
rewriteRunWithOptions(
{expectUnknowns: true},
Expand Down
61 changes: 61 additions & 0 deletions openrewrite/test/javascript/parser/decorator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {connect, disconnect, rewriteRun, typeScript} from '../testHarness';

describe('class decorator mapping', () => {
beforeAll(() => connect());
afterAll(() => disconnect());

test('unqualified', () => {
rewriteRun(
//language=typescript
typeScript('@foo class A {}')
);
});
test('unqualified parens', () => {
rewriteRun(
//language=typescript
typeScript('@foo( ) class A {}')
);
});
test('qualified', () => {
rewriteRun(
//language=typescript
typeScript('@foo . bar class A {}')
);
});
test('qualified parens', () => {
rewriteRun(
//language=typescript
typeScript('@foo . bar ( ) class A {}')
);
});
});

describe('function decorator mapping', () => {
beforeAll(() => connect());
afterAll(() => disconnect());

test('unqualified', () => {
rewriteRun(
//language=typescript
typeScript('@foo function f() {}')
);
});
test('unqualified parens', () => {
rewriteRun(
//language=typescript
typeScript('@foo( ) function f() {}')
);
});
test('qualified', () => {
rewriteRun(
//language=typescript
typeScript('@foo . bar function f() {}')
);
});
test('qualified parens', () => {
rewriteRun(
//language=typescript
typeScript('@foo . bar ( ) function f() {}')
);
});
});

0 comments on commit ee68354

Please sign in to comment.