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

Implemented Enum declaration support #128

Merged
merged 2 commits into from
Oct 23, 2024
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
85 changes: 82 additions & 3 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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)) {
Expand Down Expand Up @@ -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<J.EnumValue>[] = [];
for (let i = 0; i < childCount; i++) {
if (children[i].kind === ts.SyntaxKind.EnumMember) {
const rp = new JRightPadded(
this.convert<J.EnumValue>(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) {
Expand Down Expand Up @@ -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) {
Expand Down
169 changes: 169 additions & 0 deletions openrewrite/test/javascript/parser/enum.test.ts
Original file line number Diff line number Diff line change
@@ -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,
}
`)
);
});
});
4 changes: 2 additions & 2 deletions openrewrite/test/javascript/parser/qualifiedName.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,25 @@ public J visit(@Nullable Tree tree, PrintOutputCapture<P> p) {
}
}

@Override
public J visitEnumValue(J.EnumValue enum_, PrintOutputCapture<P> 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);
p.append("=");
// there can be only one argument
Expression expression = initializer.getArguments().get(0);
visit(expression, p);
return enum_;
}

afterSyntax(enum_, p);
return enum_;
}

@Override
public J visitAnnotation(J.Annotation annotation, PrintOutputCapture<P> p) {
beforeSyntax(annotation, Space.Location.ANNOTATION_PREFIX, p);
Expand Down