diff --git a/openrewrite/package-lock.json b/openrewrite/package-lock.json index cecff14e..7e7943ca 100644 --- a/openrewrite/package-lock.json +++ b/openrewrite/package-lock.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@types/diff": "^5.2.2", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/uuid": "^10.0.0", "jest": "^29.7.0", "ts-jest": "^29.2.5", @@ -1227,9 +1227,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/openrewrite/package.json b/openrewrite/package.json index e992acd0..481832f5 100644 --- a/openrewrite/package.json +++ b/openrewrite/package.json @@ -28,7 +28,7 @@ }, "devDependencies": { "@types/diff": "^5.2.2", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/uuid": "^10.0.0", "jest": "^29.7.0", "ts-jest": "^29.2.5", diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index d17b5de1..e531a489 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -1,6 +1,16 @@ import * as ts from 'typescript'; import * as J from '../java/tree'; -import {Comment, Expression, JavaType, JContainer, JLeftPadded, JRightPadded, Space, TextComment} from '../java/tree'; +import { + Comment, + Expression, + JavaType, + JContainer, + JLeftPadded, + JRightPadded, + Space, + Statement, + TextComment +} from '../java/tree'; import * as JS from './tree'; import { ExecutionContext, @@ -135,13 +145,13 @@ export class JavaScriptParserVisitor { false, null, [], - this.semicolonPaddedStatementList(node), + this.semicolonPaddedStatementList(node.statements), this.prefix(node.endOfFileToken) ); } - private semicolonPaddedStatementList(node: ts.SourceFile) { - return this.rightPaddedList(node.statements, this.semicolonPrefix, n => { + private semicolonPaddedStatementList(statements: ts.NodeArray) { + return this.rightPaddedList([...statements], this.semicolonPrefix, n => { const last = n.getLastToken(); return last?.kind == ts.SyntaxKind.SemicolonToken ? Markers.build([new Semicolon(randomId())]) : Markers.EMPTY; }); @@ -166,22 +176,33 @@ export class JavaScriptParserVisitor { ); } - private mapModifiers(node: ts.VariableStatement) { - return [new J.Modifier( - randomId(), - Space.EMPTY, - Markers.EMPTY, - node.declarationList.getFirstToken()?.getText()!, - J.Modifier.Type.LanguageExtension, - [] - )]; + private mapModifiers(node: ts.VariableStatement | ts.ClassDeclaration | ts.PropertyDeclaration) { + if (ts.isVariableStatement(node)) { + return [new J.Modifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + node.declarationList.getFirstToken()?.getText()!, + J.Modifier.Type.LanguageExtension, + [] + )]; + } else if (ts.isClassDeclaration(node)) { + return []; // FIXME + } else if (ts.isPropertyDeclaration(node)) { + return []; // FIXME + } + throw new Error(`Cannot get modifiers from ${node}`); } private rightPadded(t: T, trailing: Space, markers?: Markers) { return new JRightPadded(t, trailing, markers ?? Markers.EMPTY); } - private rightPaddedList(nodes: ts.NodeArray, trailing: (node: N) => Space, markers?: (node: N) => Markers): JRightPadded[] { + // private rightPaddedList(nodes: ts.NodeArray, trailing: (node: N) => Space, markers?: (node: N) => Markers): JRightPadded[] { + // return nodes.map(n => this.rightPadded(this.convert(n), trailing(n), markers?.(n))); + // } + + private rightPaddedList(nodes: N[], trailing: (node: N) => Space, markers?: (node: N) => Markers): JRightPadded[] { return nodes.map(n => this.rightPadded(this.convert(n), trailing(n), markers?.(n))); } @@ -200,8 +221,28 @@ export class JavaScriptParserVisitor { visitClassDeclaration(node: ts.ClassDeclaration) { - // return new ClassDeclaration(randomId(), node.) - return this.visitUnknown(node); + return new J.ClassDeclaration( + randomId(), + this.prefix(node), + Markers.EMPTY, + this.mapDecorators(node), + this.mapModifiers(node), + new J.ClassDeclaration.Kind( + randomId(), + Space.EMPTY, // TODO verify + Markers.EMPTY, + [], + J.ClassDeclaration.Kind.Type.Class + ), + node.name ? this.convert(node.name) : this.mapIdentifier(node, ""), + this.mapTypeParameters(node), + null, // FIXME primary constructor + node.heritageClauses ? this.visit(node.heritageClauses[0]) : null, + node.heritageClauses ? this.visit(node.heritageClauses[1]) : null, + null, + this.convertBlock(node.getChildren().slice(-3)), + this.mapType(node) + ); } visitNumericLiteral(node: ts.NumericLiteral) { @@ -322,7 +363,28 @@ export class JavaScriptParserVisitor { } visitPropertyDeclaration(node: ts.PropertyDeclaration) { - return this.visitUnknown(node); + return new J.VariableDeclarations( + randomId(), + this.prefix(node), + Markers.EMPTY, + [], + this.mapModifiers(node), + node.type ? this.visit(node.type!) : null, + null, + [], + [this.rightPadded( + new J.VariableDeclarations.NamedVariable( + randomId(), + this.prefix(node), + Markers.EMPTY, + this.visit(node.name), + [], + node.initializer ? this.leftPadded(this.prefix(node.getChildAt(node.getChildCount() - 2)), this.visit(node.initializer)) : null, + this.mapVariableType(node) + ), + Space.EMPTY // FIXME check for semicolon + )] + ); } visitMethodSignature(node: ts.MethodSignature) { @@ -880,10 +942,10 @@ export class JavaScriptParserVisitor { null, null, [], - this.rightPaddedList(node.declarationList.declarations, n => { + this.rightPaddedList([...node.declarationList.declarations], n => { return Space.EMPTY; }) - ) + ); } visitExpressionStatement(node: ts.ExpressionStatement): JS.ExpressionStatement { @@ -1457,6 +1519,44 @@ export class JavaScriptParserVisitor { Markers.EMPTY ); } + + private mapDecorators(node: ts.ClassDeclaration) { + return []; // FIXME + } + + private mapTypeParameters(node: ts.ClassDeclaration): JContainer | null { + return null; // FIXME + } + + private convertBlock(nodes: ts.Node[]): J.Block { + if (nodes.length === 0) { + return new J.Block( + randomId(), + Space.EMPTY, + Markers.EMPTY, + this.rightPadded(false, Space.EMPTY), + [], + Space.EMPTY + ); + } + + const prefix = this.prefix(nodes[0]); + let statementList = nodes[1] as ts.SyntaxList; + + const statements: JRightPadded[] = this.rightPaddedList([...statementList.getChildren()], this.semicolonPrefix, n => { + const last = n.getLastToken(); + return last?.kind == ts.SyntaxKind.SemicolonToken ? Markers.build([new Semicolon(randomId())]) : Markers.EMPTY; + }); + + return new J.Block( + randomId(), + prefix, + Markers.EMPTY, + this.rightPadded(false, Space.EMPTY), + statements, + this.prefix(nodes[nodes.length - 1]) + ); + } } function prefixFromNode(node: ts.Node, sourceFile: ts.SourceFile): Space { diff --git a/openrewrite/test/javascript/parser/class.test.ts b/openrewrite/test/javascript/parser/class.test.ts new file mode 100644 index 00000000..5ca0c3eb --- /dev/null +++ b/openrewrite/test/javascript/parser/class.test.ts @@ -0,0 +1,20 @@ +import {connect, disconnect, rewriteRun, typeScript} from '../testHarness'; + +describe('class mapping', () => { + beforeAll(() => connect()); + afterAll(() => disconnect()); + + test('empty', () => { + rewriteRun( + //language=typescript + typeScript('class A {}') + ); + }); + + test('body', () => { + rewriteRun( + //language=typescript + typeScript('class A { foo: number; }') + ); + }); +});