From 8218d37427c031ff0ce1a117ff3adab41a249732 Mon Sep 17 00:00:00 2001 From: Andrii Rodionov Date: Tue, 22 Oct 2024 12:51:18 +0200 Subject: [PATCH 1/2] Implemented Enum declaration support Implemented `visitEnumDeclaration` parser. Enum members initialization in `visitEnumMember` is not currently implemented. --- openrewrite/src/javascript/parser.ts | 85 ++++++++- .../test/javascript/parser/enum.test.ts | 169 ++++++++++++++++++ .../javascript/parser/qualifiedName.test.ts | 4 +- .../internal/JavaScriptPrinter.java | 18 ++ 4 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 openrewrite/test/javascript/parser/enum.test.ts diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index b8435d6..546de5e 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -246,7 +246,7 @@ export class JavaScriptParserVisitor { ); } - private mapModifiers(node: ts.VariableDeclarationList | ts.VariableStatement | ts.ClassDeclaration | ts.PropertyDeclaration | ts.FunctionDeclaration | ts.ParameterDeclaration | ts.MethodDeclaration) { + private mapModifiers(node: ts.VariableDeclarationList | ts.VariableStatement | ts.ClassDeclaration | ts.PropertyDeclaration | ts.FunctionDeclaration | ts.ParameterDeclaration | ts.MethodDeclaration | ts.EnumDeclaration) { if (ts.isVariableStatement(node)) { return [new J.Modifier( randomId(), @@ -258,6 +258,8 @@ export class JavaScriptParserVisitor { )]; } else if (ts.isClassDeclaration(node)) { return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : []; + } else if (ts.isEnumDeclaration(node)) { + return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : []; } else if (ts.isPropertyDeclaration(node)) { return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : []; } else if (ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isMethodDeclaration(node)) { @@ -1551,7 +1553,63 @@ export class JavaScriptParserVisitor { } visitEnumDeclaration(node: ts.EnumDeclaration) { - return this.visitUnknown(node); + return new J.ClassDeclaration( + randomId(), + this.prefix(node), + Markers.EMPTY, + [], // enum has no decorators + this.mapModifiers(node), + new J.ClassDeclaration.Kind( + randomId(), + node.modifiers ? this.suffix(node.modifiers[node.modifiers.length - 1]) : this.prefix(node), + Markers.EMPTY, + [], + J.ClassDeclaration.Kind.Type.Enum + ), + node.name ? this.convert(node.name) : this.mapIdentifier(node, ""), + null, // enum has no type parameters + null, // enum has no constructor + null, // enum can't extend smth. + null, // enum can't implement smth. + null, + new J.Block( + randomId(), + this.prefix(node.getChildren().find(v => v.kind === ts.SyntaxKind.OpenBraceToken)!), + Markers.EMPTY, + new JRightPadded(false, Space.EMPTY, Markers.EMPTY), + this.convertEnumBlock(node), + this.prefix(node.getLastToken()!) + ), + this.mapType(node) + ); + } + + // EnumMembers (got from EnumDeclaration#members) have no information about Commas between them, + // so we should parse the EnumDeclaration body as a SyntaxList + // The enum body has the following structure: [EnumMember, Coma, EnumMember, ...], + // and we should manually figure out Commas positions + convertEnumBlock(enumDeclaration: ts.EnumDeclaration) { + if (enumDeclaration.members.length == 0) { + return []; + } + + // if the enum is not empty and have the following representation [ ..., '{', EnumBody, '}'] + // we access the enum body in the following way + const node = enumDeclaration.getChildren()[enumDeclaration.getChildCount() - 2]; + const children = node.getChildren(); + const childCount = children.length; + const enumMembers: JRightPadded[] = []; + for (let i = 0; i < childCount; i++) { + if (children[i].kind === ts.SyntaxKind.EnumMember) { + const rp = new JRightPadded( + this.convert(children[i]), + i + 1 < childCount ? this.prefix(children[i+1]) : Space.EMPTY, + i + 1 < childCount ? Markers.build([new TrailingComma(randomId(), Space.EMPTY)]) : Markers.EMPTY + ); + enumMembers.push(rp); + } + } + return enumMembers; } visitModuleDeclaration(node: ts.ModuleDeclaration) { @@ -1743,7 +1801,28 @@ export class JavaScriptParserVisitor { } visitEnumMember(node: ts.EnumMember) { - return this.visitUnknown(node); + return new J.EnumValue( + randomId(), + this.prefix(node), + Markers.EMPTY, + [], + node.name ? this.convert(node.name) : this.mapIdentifier(node, ""), + node.initializer ? new J.NewClass( + randomId(), + this.suffix(node.name), + Markers.EMPTY, + null, + Space.EMPTY, + null, + new JContainer( + Space.EMPTY, + [this.rightPadded(this.visit(node.initializer), Space.EMPTY)], + Markers.EMPTY + ), + null, + this.mapMethodType(node) + ) : null + ) } visitBundle(node: ts.Bundle) { diff --git a/openrewrite/test/javascript/parser/enum.test.ts b/openrewrite/test/javascript/parser/enum.test.ts new file mode 100644 index 0000000..e047794 --- /dev/null +++ b/openrewrite/test/javascript/parser/enum.test.ts @@ -0,0 +1,169 @@ +import {connect, disconnect, rewriteRun, typeScript} from '../testHarness'; + +describe('empty mapping', () => { + beforeAll(() => connect()); + afterAll(() => disconnect()); + + test('enum declaration', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + }; + `) + ); + }); + + test('enum empty declaration with modifiers', () => { + rewriteRun( + //language=typescript + typeScript(` + declare const enum Test { + }; + `) + ); + }); + + test('enum member', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A + }; + `) + ); + }); + + test('enum member with coma', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A , + }; + `) + ); + }); + + test('enum members', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A, + B, + C + }; + `) + ); + }); + + test('enum with const modifier', () => { + rewriteRun( + //language=typescript + typeScript(` + const enum Test { + A, + B, + C, + }; + `) + ); + }); + + test('enum with declare modifier', () => { + rewriteRun( + //language=typescript + typeScript(` + declare enum Test { + A, + B, + C, + }; + `) + ); + }); + + test('enum with declare const modifier', () => { + rewriteRun( + //language=typescript + typeScript(` + declare const enum Test { + A, + B, + C, + }; + `) + ); + }); + + test('enum members with comments', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test /*xx*/ { + A /*aa*/, + /*bb*/ B /*cc*/, + C, /*dd*/ + }; + `) + ); + }); + + test('enum members with initializer', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A = "AA", + B = 10 + } + `) + ); + }); + + test('enum mixed members with initializer', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A = "AA", + B, + C = 10, + D = globalThis.NaN, + E = (2 + 2), + F, + } + `) + ); + }); + + test('enum members with initializer and comments', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + //A /*aaa*/ = /*bbb*/ "A" + A /*aaa*/ = /*bbb*/ "AA" , + B = 10 /*ccc*/ + /*ddd*/ 5 + } + `) + ); + }); + + test('enum complex members with initializer', () => { + rewriteRun( + //language=typescript + typeScript(` + const baseValue = 10; + + const enum MathConstants { + Pi = 3.14, + E = Math.E, + GoldenRatio = baseValue + 1.618, + } + `) + ); + }); +}); diff --git a/openrewrite/test/javascript/parser/qualifiedName.test.ts b/openrewrite/test/javascript/parser/qualifiedName.test.ts index 8ec05b4..fd296c5 100644 --- a/openrewrite/test/javascript/parser/qualifiedName.test.ts +++ b/openrewrite/test/javascript/parser/qualifiedName.test.ts @@ -49,12 +49,12 @@ describe('empty mapping', () => { ); }); - test.skip('enum qualified name', () => { + test('enum qualified name', () => { rewriteRun( //language=typescript typeScript(` enum Test { - A + A, B }; const val: Test.A = Test.A; diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java index 481cb23..c45776a 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java @@ -449,6 +449,24 @@ public J visit(@Nullable Tree tree, PrintOutputCapture

p) { } } + @Override + public J visitEnumValue(J.EnumValue enum_, PrintOutputCapture

p) { + beforeSyntax(enum_, Space.Location.ENUM_VALUE_PREFIX, p); + visit(enum_.getName(), p); + + J.NewClass initializer = enum_.getInitializer(); + if (initializer != null) { + visitSpace(initializer.getPrefix(), Space.Location.NEW_CLASS_PREFIX, p); + // there can be only one argument + Expression expression = initializer.getArguments().get(0); + visitLeftPadded("=", JLeftPadded.build(expression), JLeftPadded.Location.VARIABLE_INITIALIZER, p); + return enum_; + } + + afterSyntax(enum_, p); + return enum_; + } + @Override public J visitAnnotation(J.Annotation annotation, PrintOutputCapture

p) { beforeSyntax(annotation, Space.Location.ANNOTATION_PREFIX, p); From fb924e7eadad01e3c5551bc05667bea9a234bf2a Mon Sep 17 00:00:00 2001 From: OlegDokuka Date: Wed, 23 Oct 2024 14:23:38 +0200 Subject: [PATCH 2/2] Replaced leftPadded on direct append --- .../org/openrewrite/javascript/internal/JavaScriptPrinter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java index c45776a..7ed7272 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/internal/JavaScriptPrinter.java @@ -457,9 +457,10 @@ public J visitEnumValue(J.EnumValue enum_, PrintOutputCapture

p) { J.NewClass initializer = enum_.getInitializer(); if (initializer != null) { visitSpace(initializer.getPrefix(), Space.Location.NEW_CLASS_PREFIX, p); + p.append("="); // there can be only one argument Expression expression = initializer.getArguments().get(0); - visitLeftPadded("=", JLeftPadded.build(expression), JLeftPadded.Location.VARIABLE_INITIALIZER, p); + visit(expression, p); return enum_; }