Skip to content
This repository was archived by the owner on May 12, 2025. It is now read-only.
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
80 changes: 52 additions & 28 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export class JavaScriptParserVisitor {
null,
new J.Block(
randomId(),
this.prefix(node.getChildren().find(v => v.kind === ts.SyntaxKind.OpenBraceToken)!),
this.prefix(this.findChildNode(node, ts.SyntaxKind.OpenBraceToken)!),
Markers.EMPTY,
new JRightPadded(false, Space.EMPTY, Markers.EMPTY),
node.members.map((ce : ts.ClassElement) => new JRightPadded(
Expand All @@ -420,7 +420,7 @@ export class JavaScriptParserVisitor {
);
}

private mapExtends(node: ts.ClassDeclaration): JLeftPadded<J.TypeTree> | null {
private mapExtends(node: ts.ClassDeclaration | ts.ClassExpression): JLeftPadded<J.TypeTree> | null {
if (node.heritageClauses == undefined || node.heritageClauses.length == 0) {
return null;
}
Expand Down Expand Up @@ -452,7 +452,7 @@ export class JavaScriptParserVisitor {
return null;
}

private mapImplements(node: ts.ClassDeclaration): JContainer<J.TypeTree> | null {
private mapImplements(node: ts.ClassDeclaration | ts.ClassExpression): JContainer<J.TypeTree> | null {
if (node.heritageClauses == undefined || node.heritageClauses.length == 0) {
return null;
}
Expand Down Expand Up @@ -590,28 +590,14 @@ export class JavaScriptParserVisitor {
}

visitQualifiedName(node: ts.QualifiedName) {
const fieldAccess = new J.FieldAccess(
return new J.FieldAccess(
randomId(),
this.prefix(node),
Markers.EMPTY,
this.visit(node.left),
new JLeftPadded<J.Identifier>(this.suffix(node.left), this.convert<J.Identifier>(node.right), Markers.EMPTY),
this.leftPadded(this.suffix(node.left), this.convert(node.right)),
this.mapType(node)
);

const parent = node.parent as ts.TypeReferenceNode;
if (parent.typeArguments) {
return new J.ParameterizedType(
randomId(),
this.prefix(parent),
Markers.EMPTY,
fieldAccess,
this.mapTypeArguments(this.suffix(parent.typeName), parent.typeArguments),
this.mapType(parent)
)
} else {
return fieldAccess;
}
}

visitComputedPropertyName(node: ts.ComputedPropertyName) {
Expand Down Expand Up @@ -1051,11 +1037,14 @@ export class JavaScriptParserVisitor {

visitTypeReference(node: ts.TypeReferenceNode) {
if (node.typeArguments) {
// Temporary check for supported constructions with type arguments
if (ts.isQualifiedName(node.typeName)) {
return this.visit(node.typeName);
}
return this.visitUnknown(node);
return new J.ParameterizedType(
randomId(),
this.prefix(node),
Markers.EMPTY,
this.visit(node.typeName),
this.mapTypeArguments(this.suffix(node.typeName), node.typeArguments),
this.mapType(node)
)
}
return this.visit(node.typeName);
}
Expand Down Expand Up @@ -1642,7 +1631,42 @@ export class JavaScriptParserVisitor {
}

visitClassExpression(node: ts.ClassExpression) {
return this.visitUnknown(node);
return new JS.StatementExpression(
randomId(),
new J.ClassDeclaration(
randomId(),
this.prefix(node),
Markers.EMPTY,
[], //this.mapDecorators(node),
[], //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.Class
),
node.name ? this.convert(node.name) : this.mapIdentifier(node, ""),
this.mapTypeParametersAsJContainer(node),
null, // FIXME primary constructor
this.mapExtends(node),
this.mapImplements(node),
null,
new J.Block(
randomId(),
this.prefix(this.findChildNode(node, ts.SyntaxKind.OpenBraceToken)!),
Markers.EMPTY,
this.rightPadded(false, Space.EMPTY),
node.members.map(ce => new JRightPadded(
this.convert(ce),
ce.getLastToken()?.kind === ts.SyntaxKind.SemicolonToken ? this.prefix(ce.getLastToken()!) : Space.EMPTY,
ce.getLastToken()?.kind === ts.SyntaxKind.SemicolonToken ? Markers.build([new Semicolon(randomId())]) : Markers.EMPTY
)),
this.prefix(node.getLastToken()!)
),
this.mapType(node)
)
)
}

visitOmittedExpression(node: ts.OmittedExpression) {
Expand Down Expand Up @@ -2024,9 +2048,9 @@ export class JavaScriptParserVisitor {
null,
new J.Block(
randomId(),
this.prefix(node.getChildren().find(v => v.kind === ts.SyntaxKind.OpenBraceToken)!),
this.prefix(this.findChildNode(node, ts.SyntaxKind.OpenBraceToken)!),
Markers.EMPTY,
new JRightPadded(false, Space.EMPTY, Markers.EMPTY),
this.rightPadded(false, Space.EMPTY),
node.members.map(te => new JRightPadded(
this.convert(te),
(te.getLastToken()?.kind === ts.SyntaxKind.SemicolonToken) || (te.getLastToken()?.kind === ts.SyntaxKind.CommaToken) ? this.prefix(te.getLastToken()!) : Space.EMPTY,
Expand Down Expand Up @@ -2737,7 +2761,7 @@ export class JavaScriptParserVisitor {
return node.modifiers?.filter(ts.isDecorator)?.map(this.convert<J.Annotation>) ?? [];
}

private mapTypeParametersAsJContainer(node: ts.ClassDeclaration | ts.InterfaceDeclaration): JContainer<J.TypeParameter> | null {
private mapTypeParametersAsJContainer(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.ClassExpression): JContainer<J.TypeParameter> | null {
return node.typeParameters
? JContainer.build(
this.suffix(this.findChildNode(node, ts.SyntaxKind.Identifier)!),
Expand Down
56 changes: 56 additions & 0 deletions openrewrite/test/javascript/parser/arrow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ describe('arrow mapping', () => {
);
});

test('function with empty body', () => {
rewriteRun(
//language=typescript
typeScript(`
const empty = (/*a*/) /*b*/ => {/*c*/};
`)
);
});

test('function with simple body and comments', () => {
rewriteRun(
//language=typescript
Expand Down Expand Up @@ -51,6 +60,53 @@ describe('arrow mapping', () => {
);
});

test('function with trailing comma', () => {
rewriteRun(
//language=typescript
typeScript(`
let sum = (x: number, y: number /*a*/, /*b*/) => x + y;
`)
);
});

test('function with async modifier', () => {
rewriteRun(
//language=typescript
typeScript(`
let sum = async (x: number, y: number ) => x + y;
`)
);
});

test('basic async arrow function', () => {
rewriteRun(
//language=typescript
typeScript(`
const fetchData = async (): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched successfully!");
}, 2000);
});
};

// Using the function
fetchData().then((message) => {
console.log(message); // Outputs: Data fetched successfully! (after 2 seconds)
});
`)
);
});

test('function with async modifier and comments', () => {
rewriteRun(
//language=typescript
typeScript(`
let sum = /*a*/async /*b*/ (/*c*/x: number, y: number /*d*/) /*e*/ => x + y;
`)
);
});

test('function with implicit return and comments', () => {
rewriteRun(
//language=typescript
Expand Down
83 changes: 67 additions & 16 deletions openrewrite/test/javascript/parser/class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,12 @@ describe('class mapping', () => {
b = 6;

// method 1
abs(): string{
abs(x): string{
return "1";
}

//method 2
max(): number {
max(x, y /*a*/, /*b*/): number {
return 2;
}
} /*asdasdas*/
Expand Down Expand Up @@ -179,7 +179,6 @@ describe('class mapping', () => {
);
});


test('class with simple ctor', () => {
rewriteRun(
//language=typescript
Expand Down Expand Up @@ -220,7 +219,70 @@ describe('class mapping', () => {
);
});

test.skip('anonymous class declaration', () => {
test('anonymous class expression', () => {
rewriteRun(
//language=typescript
typeScript(`
const MyClass = class {
constructor(public name: string) {
}
};
`)
);
});

test('anonymous class expression with comments', () => {
rewriteRun(
//language=typescript
typeScript(`
const MyClass = /*a*/class/*b*/ {/*c*/
constructor(public name: string) {
}
};
`)
);
});

test('named class expression', () => {
rewriteRun(
//language=typescript
typeScript(`
const Employee = class EmployeeClass {
constructor(public position: string, public salary: number, ) {
}
};
`)
);
});

test('class extends expression', () => {
rewriteRun(
//language=typescript
typeScript(`
class OuterClass extends (class extends Number { }) {
}
`)
);
});

test.skip('class expressions inline', () => {
rewriteRun(
//language=typescript
typeScript(`
function createInstance(ClassType: new () => any) {
return new ClassType();
}

const instance = createInstance(class {
sayHello() {
console.log("Hello from an inline class!");
}
});
`)
);
});

test.skip('inner class declaration with extends', () => {
rewriteRun(
//language=typescript
typeScript(`
Expand All @@ -230,18 +292,7 @@ describe('class mapping', () => {
const a: typeof OuterClass.InnerClass.prototype = 1;
`)
);
});

test.skip('nested class qualified name', () => {
rewriteRun(
//language=typescript
typeScript(`
class OuterClass extends (class extends Number { }) {
}
const a: typeof OuterClass.InnerClass.prototype = 1;
`)
);
});
});

test('class with optional properties, ctor and modifiers', () => {
rewriteRun(
Expand Down
11 changes: 11 additions & 0 deletions openrewrite/test/javascript/parser/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ describe('function mapping', () => {
);
});

test('function with type ref', () => {
rewriteRun(
//language=typescript
typeScript(`
function getLength(arr: Array<string>): number {
return arr.length;
}
`)
);
});

test.skip('function type with parameter', () => {
rewriteRun(
//language=typescript
Expand Down