From 3ab6fe1a8722e704ce9ff323ab28b9d21cf859ef Mon Sep 17 00:00:00 2001 From: arodionov Date: Fri, 18 Oct 2024 11:40:23 +0200 Subject: [PATCH 1/2] Implemented qualifiedName visitor Most of the tests are skipped because enum, class, namespace constructions not supported yet. Also modified testHarness runner to be able to join to remotely running rewrite-remote test engine --- openrewrite/src/javascript/parser.ts | 9 +++- .../javascript/parser/qualifiedName.test.ts | 50 +++++++++++++++++++ openrewrite/test/javascript/testHarness.ts | 25 +++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 openrewrite/test/javascript/parser/qualifiedName.test.ts diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index 576d38be..dd2475e4 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -532,7 +532,14 @@ export class JavaScriptParserVisitor { } visitQualifiedName(node: ts.QualifiedName) { - return this.visitUnknown(node); + return new J.FieldAccess( + randomId(), + this.prefix(node), + Markers.EMPTY, + this.visit(node.left), + new JLeftPadded(this.suffix(node.left), this.convert(node.right), Markers.EMPTY), + this.mapType(node) + ); } visitComputedPropertyName(node: ts.ComputedPropertyName) { diff --git a/openrewrite/test/javascript/parser/qualifiedName.test.ts b/openrewrite/test/javascript/parser/qualifiedName.test.ts new file mode 100644 index 00000000..08167ab6 --- /dev/null +++ b/openrewrite/test/javascript/parser/qualifiedName.test.ts @@ -0,0 +1,50 @@ +import {connect, disconnect, rewriteRun, typeScript} from '../testHarness'; + +describe('empty mapping', () => { + beforeAll(() => connect()); + afterAll(() => disconnect()); + + test('globalThis qualified name', () => { + rewriteRun( + //language=typescript + typeScript('const value: globalThis.Number = 1') + ); + }); + + test.skip('nested class qualified name', () => { + rewriteRun( + //language=typescript + typeScript(` + class OuterClass { + public static InnerClass = class extends Number { }; + } + const a: typeof OuterClass.InnerClass.prototype = 1; + `) + ); + }); + + test.skip('namespace qualified name', () => { + rewriteRun( + //language=typescript + typeScript(` + namespace TestNamespace { + export class Test {} + }; + const value: TestNamespace.Test = null; + `) + ); + }); + + test.skip('enum qualified name', () => { + rewriteRun( + //language=typescript + typeScript(` + enum Test { + A + }; + + const val: Test.A = Test.A; + `) + ); + }); +}); diff --git a/openrewrite/test/javascript/testHarness.ts b/openrewrite/test/javascript/testHarness.ts index c3470b9e..d031c3c6 100644 --- a/openrewrite/test/javascript/testHarness.ts +++ b/openrewrite/test/javascript/testHarness.ts @@ -81,9 +81,12 @@ export async function connect(): Promise { return new Promise((resolve, reject) => { const pathToJar = path.resolve(__dirname, '../../../rewrite-test-engine-remote/build/libs/rewrite-test-engine-remote-fat-jar.jar'); console.log(pathToJar); - const randomPort = getRandomInt(50000, 60000); + client = new net.Socket(); + client.connect(65432, 'localhost'); - getNextPort(randomPort).then(port => { + client.once('error', (err) => { + const randomPort = getRandomInt(50000, 60000); + getNextPort(randomPort).then(port => { javaTestEngine = spawn('java', ['-jar', pathToJar, port.toString()]); const errorfn = (data: string) => { console.error(`stderr: ${data}`); @@ -110,7 +113,15 @@ export async function connect(): Promise { javaTestEngine.stdout.once('data', startfn); javaTestEngine.stderr.on('data', errorfn); + }); + }); + client.once('connect', () => { + remoting = new RemotingContext(); + remoting.connect(client); + PrinterFactory.current = new RemotePrinterFactory(remoting.client!); + resolve(remoting); }); + }); } @@ -123,10 +134,12 @@ export async function disconnect(): Promise { remoting.close(); } - javaTestEngine.once('close', (code: string) => { - resolve() - }); - javaTestEngine.kill("SIGKILL"); + if (javaTestEngine) { + javaTestEngine.once('close', (code: string) => { + resolve() + }); + javaTestEngine.kill("SIGKILL"); + } } else { resolve(); } From 1896694e4fa28ae4c0e6f5ec9c673cb88d16774a Mon Sep 17 00:00:00 2001 From: arodionov Date: Fri, 18 Oct 2024 15:13:03 +0200 Subject: [PATCH 2/2] Implemented type arguments support for qualified names. --- openrewrite/src/javascript/parser.ts | 42 +++++++++++++++++-- .../javascript/parser/qualifiedName.test.ts | 14 +++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index dd2475e4..dd055b46 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -42,7 +42,7 @@ export class JavaScriptParser extends Parser { const result: SourceFile[] = []; for (const filePath of program.getRootFileNames()) { const sourceFile = program.getSourceFile(filePath)!; - const input = new ParserInput(filePath, null, false, () => Buffer.from(ts.sys.readFile(filePath)!)); + const input = new ParserInput(filePath, null, false, () => Buffer.from(ts.sys.readFile(filePath)!)); try { const parsed = new JavaScriptParserVisitor(this, sourceFile, typeChecker).visit(sourceFile) as SourceFile; result.push(parsed); @@ -532,7 +532,7 @@ export class JavaScriptParserVisitor { } visitQualifiedName(node: ts.QualifiedName) { - return new J.FieldAccess( + const fieldAccess = new J.FieldAccess( randomId(), this.prefix(node), Markers.EMPTY, @@ -540,6 +540,20 @@ export class JavaScriptParserVisitor { new JLeftPadded(this.suffix(node.left), this.convert(node.right), Markers.EMPTY), 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(parent.typeArguments), + this.mapType(parent) + ) + } else { + return fieldAccess; + } } visitComputedPropertyName(node: ts.ComputedPropertyName) { @@ -688,6 +702,10 @@ 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 this.visit(node.typeName); @@ -831,7 +849,7 @@ export class JavaScriptParserVisitor { const statements: JRightPadded[] = this.rightPaddedSeparatedList( [...statementList.getChildren(this.sourceFile)], ts.SyntaxKind.CommaToken, - (nodes, i) => i == nodes.length -2 && nodes[i + 1].kind == ts.SyntaxKind.CommaToken ? Markers.build([new TrailingComma(randomId(), this.prefix(nodes[i + 1]))]) : Markers.EMPTY + (nodes, i) => i == nodes.length - 2 && nodes[i + 1].kind == ts.SyntaxKind.CommaToken ? Markers.build([new TrailingComma(randomId(), this.prefix(nodes[i + 1]))]) : Markers.EMPTY ); return new J.Block( @@ -1929,6 +1947,24 @@ export class JavaScriptParserVisitor { return this.mapToContainer(nodes, this.trailingComma(nodes)); } + private mapTypeArguments(nodes: readonly ts.Node[]): JContainer { + if (nodes.length === 0) { + return JContainer.empty(); + } + + const args = nodes.map(node => + this.rightPadded( + this.visit(node), + this.suffix(node), + Markers.EMPTY + )) + return new JContainer( + this.prefix(nodes[0]), + args, + Markers.EMPTY + ); + } + private trailingComma = (nodes: readonly ts.Node[]) => (ns: readonly ts.Node[], i: number) => { const last = i === ns.length - 2; return last ? Markers.build([new TrailingComma(randomId(), this.prefix(nodes[2], false))]) : Markers.EMPTY; diff --git a/openrewrite/test/javascript/parser/qualifiedName.test.ts b/openrewrite/test/javascript/parser/qualifiedName.test.ts index 08167ab6..8ec05b41 100644 --- a/openrewrite/test/javascript/parser/qualifiedName.test.ts +++ b/openrewrite/test/javascript/parser/qualifiedName.test.ts @@ -11,6 +11,20 @@ describe('empty mapping', () => { ); }); + test('globalThis qualified name with generic', () => { + rewriteRun( + //language=typescript + typeScript('const value: globalThis.Promise< string > = null') + ); + }); + + test('globalThis qualified name with comments', () => { + rewriteRun( + //language=typescript + typeScript('const value /*a123*/ : globalThis. globalThis . /*asda*/ globalThis.Promise = null;') + ); + }); + test.skip('nested class qualified name', () => { rewriteRun( //language=typescript