From 905c4c4d62b6b07833b0f2874dc82039a01784ed Mon Sep 17 00:00:00 2001 From: Andrii Rodionov Date: Mon, 23 Dec 2024 11:54:13 +0100 Subject: [PATCH] Updates for ExpressionWithTypeArguments, TaggedTemplateExpression and TypeQuery Fixes for: - trailing comma - types in models --- openrewrite/src/javascript/parser.ts | 77 +++++++------------ openrewrite/src/javascript/remote/receiver.ts | 6 +- openrewrite/src/javascript/remote/sender.ts | 1 + .../src/javascript/tree/support_types.ts | 1 + openrewrite/src/javascript/tree/tree.ts | 51 ++++++++---- openrewrite/src/javascript/visitor.ts | 1 + .../test/javascript/parser/arrow.test.ts | 19 +++++ .../test/javascript/parser/call.test.ts | 10 +++ .../test/javascript/parser/enum.test.ts | 11 +++ .../test/javascript/parser/for.test.ts | 13 ++++ openrewrite/test/javascript/parser/if.test.ts | 37 ++++++++- .../test/javascript/parser/import.test.ts | 1 + .../test/javascript/parser/method.test.ts | 16 ++++ .../javascript/parser/propertyAccess.test.ts | 29 +++++++ .../parser/templateExpression.test.ts | 24 ++++++ .../test/javascript/parser/typeAlias.test.ts | 23 ++++++ .../javascript/parser/typeLiteral.test.ts | 11 +++ .../test/javascript/parser/typeQuery.test.ts | 10 +++ .../javascript/remote/JavaScriptReceiver.java | 2 + .../javascript/remote/JavaScriptSender.java | 1 + .../remote/JavaScriptValidator.java | 5 +- .../javascript/JavaScriptVisitor.java | 3 + .../internal/JavaScriptPrinter.java | 4 +- .../org/openrewrite/javascript/tree/JS.java | 57 +++++++++++++- .../javascript/tree/JsContainer.java | 3 +- .../javascript/tree/JsRightPadded.java | 3 +- .../openrewrite/javascript/tree/JsSpace.java | 4 +- 27 files changed, 345 insertions(+), 78 deletions(-) diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index 81e78b01..7d252269 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -263,12 +263,12 @@ export class JavaScriptParserVisitor { | ts.FunctionDeclaration | ts.ParameterDeclaration | ts.MethodDeclaration | ts.EnumDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.ConstructorDeclaration | ts.ModuleDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration | ts.ArrowFunction | ts.IndexSignatureDeclaration | ts.TypeAliasDeclaration | ts.ExportDeclaration | ts.ExportAssignment | ts.FunctionExpression - | ts.ConstructorTypeNode) { + | ts.ConstructorTypeNode | ts.TypeParameterDeclaration) { if (ts.isVariableStatement(node) || ts.isModuleDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isPropertySignature(node) || ts.isParameter(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isArrowFunction(node) || ts.isIndexSignatureDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isExportDeclaration(node) - || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isConstructorTypeNode(node)) { + || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isConstructorTypeNode(node) || ts.isTypeParameterDeclaration(node)) { return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : []; } else if (ts.isExportAssignment(node)) { @@ -648,7 +648,7 @@ export class JavaScriptParserVisitor { this.prefix(node), Markers.EMPTY, [], - [], + this.mapModifiers(node), this.visit(node.name), (node.constraint || node.default) ? new JContainer( @@ -771,17 +771,8 @@ export class JavaScriptParserVisitor { let _arguments: JContainer | null = null; if (ts.isCallExpression(node.expression)) { - let callExpression: J.MethodInvocation = this.convert(node.expression); - annotationType = callExpression.select === null ? callExpression.name : - new J.FieldAccess( - randomId(), - callExpression.prefix, - Markers.EMPTY, - callExpression.select, - this.leftPadded(callExpression.padding.select!.after, callExpression.name), - callExpression.type - ); - _arguments = callExpression.padding.arguments; + annotationType = this.convert(node.expression.expression); + _arguments = this.mapCommaSeparatedList(node.expression.getChildren(this.sourceFile).slice(-3)) } else if (ts.isIdentifier(node.expression)) { annotationType = this.convert(node.expression); } else if (ts.isPropertyAccessExpression(node.expression)) { @@ -1333,7 +1324,8 @@ export class JavaScriptParserVisitor { this.mapTypeParametersAsObject(node), new JContainer( this.prefix(node.getChildAt(node.getChildren().findIndex(n => n.pos === node.parameters.pos) - 1)), - node.parameters.map(p => this.rightPadded(this.visit(p), this.suffix(p))), + node.parameters.map(p => this.rightPadded(this.visit(p), this.suffix(p))) + .concat(node.parameters.hasTrailingComma ? this.rightPadded(this.newJEmpty(), this.prefix(this.findChildNode(node, ts.SyntaxKind.CloseParenToken)!)) : []), Markers.EMPTY), this.prefix(this.findChildNode(node, ts.SyntaxKind.EqualsGreaterThanToken)!), this.convert(node.type), @@ -1363,6 +1355,7 @@ export class JavaScriptParserVisitor { this.prefix(node), Markers.EMPTY, this.convert(node.exprName), + node.typeArguments ? this.mapTypeArguments(this.suffix(node.exprName), node.typeArguments) : null, this.mapType(node) ) } @@ -1899,36 +1892,19 @@ export class JavaScriptParserVisitor { if (ts.isIdentifier(node.expression) && !node.questionDotToken) { select = null; name = this.convert(node.expression); - } else if (ts.isPropertyAccessExpression(node.expression)) { - select = this.rightPadded( - node.expression.questionDotToken ? - new JS.Unary( - randomId(), - Space.EMPTY, - Markers.EMPTY, - this.leftPadded(this.suffix(node.expression.expression), JS.Unary.Type.QuestionDot), - this.visit(node.expression.expression), - this.mapType(node) - ) : - this.convert(node.expression.expression), - this.prefix(node.expression.getChildAt(1, this.sourceFile)) - ); - name = this.convert(node.expression.name); + } else if (node.questionDotToken) { + select = this.rightPadded(new JS.Unary( + randomId(), + Space.EMPTY, + Markers.EMPTY, + this.leftPadded(this.suffix(node.expression), JS.Unary.Type.QuestionDotWithDot), + this.visit(node.expression), + this.mapType(node) + ), + Space.EMPTY + ) } else { - if (node.questionDotToken) { - select = this.rightPadded(new JS.Unary( - randomId(), - Space.EMPTY, - Markers.EMPTY, - this.leftPadded(this.suffix(node.expression), JS.Unary.Type.QuestionDotWithDot), - this.visit(node.expression), - this.mapType(node) - ), - Space.EMPTY - ) - } else { - select = this.rightPadded(this.visit(node.expression), this.suffix(node.expression)) - } + select = this.rightPadded(this.visit(node.expression), this.suffix(node.expression)) } return new J.MethodInvocation( @@ -1975,7 +1951,7 @@ export class JavaScriptParserVisitor { Markers.EMPTY, this.rightPadded(this.visit(node.tag), this.suffix(node.tag)), node.typeArguments ? this.mapTypeArguments(Space.EMPTY, node.typeArguments) : null, - this.visit(node.template), + this.convert(node.template), this.mapType(node) ) } @@ -2561,8 +2537,8 @@ export class JavaScriptParserVisitor { } visitIfStatement(node: ts.IfStatement) { - const semicolonAfterThen = node.thenStatement.getLastToken()?.kind == ts.SyntaxKind.SemicolonToken; - const semicolonAfterElse = node.elseStatement?.getLastToken()?.kind == ts.SyntaxKind.SemicolonToken; + const semicolonAfterThen = (node.thenStatement?.kind != ts.SyntaxKind.IfStatement) && (node.thenStatement.getLastToken()?.kind == ts.SyntaxKind.SemicolonToken); + const semicolonAfterElse = (node.elseStatement?.kind != ts.SyntaxKind.IfStatement) && (node.elseStatement?.getLastToken()?.kind == ts.SyntaxKind.SemicolonToken); return new J.If( randomId(), this.prefix(node), @@ -2641,9 +2617,9 @@ export class JavaScriptParserVisitor { (ts.isVariableDeclarationList(node.initializer) ? this.rightPadded(this.visit(node.initializer), Space.EMPTY) : this.rightPadded(new ExpressionStatement(randomId(), this.visit(node.initializer)), this.suffix(node.initializer.getLastToken()!))) : this.rightPadded(this.newJEmpty(), this.suffix(this.findChildNode(node, ts.SyntaxKind.OpenParenToken)!))], // to handle for (/*_*/; ; ); - node.condition ? this.rightPadded(ts.isStatement(node.condition) ? this.visit(node.condition) : new ExpressionStatement(randomId(), this.visit(node.condition)), this.suffix(node.condition.getLastToken()!)) : + node.condition ? this.rightPadded(ts.isStatement(node.condition) ? this.visit(node.condition) : new ExpressionStatement(randomId(), this.visit(node.condition)), this.suffix(node.condition)) : this.rightPadded(this.newJEmpty(), this.suffix(this.findChildNode(node, ts.SyntaxKind.SemicolonToken)!)), // to handle for ( ;/*_*/; ); - [node.incrementor ? this.rightPadded(ts.isStatement(node.incrementor) ? this.visit(node.incrementor) : new ExpressionStatement(randomId(), this.visit(node.incrementor)), this.suffix(node.incrementor.getLastToken()!)) : + [node.incrementor ? this.rightPadded(ts.isStatement(node.incrementor) ? this.visit(node.incrementor) : new ExpressionStatement(randomId(), this.visit(node.incrementor)), this.suffix(node.incrementor)) : this.rightPadded(this.newJEmpty(this.prefix(this.findChildNode(node, ts.SyntaxKind.CloseParenToken)!)), Space.EMPTY)], // to handle for ( ; ;/*_*/); ), this.rightPadded( @@ -3435,7 +3411,7 @@ export class JavaScriptParserVisitor { this.prefix(node), Markers.EMPTY, [], - node.name ? this.convert(node.name) : this.mapIdentifier(node, ""), + node.name ? ts.isStringLiteral(node.name) ? this.mapIdentifier(node.name, node.name.getText()) : this.convert(node.name) : this.mapIdentifier(node, ""), node.initializer ? new J.NewClass( randomId(), this.suffix(node.name), @@ -3789,6 +3765,7 @@ export class JavaScriptParserVisitor { Markers.EMPTY, [], typeParameters.map(tp => this.rightPadded(this.visit(tp), this.suffix(tp))) + .concat(typeParameters.hasTrailingComma ? this.rightPadded(this.newJEmpty(), this.prefix(this.findChildNode(node, ts.SyntaxKind.GreaterThanToken)!)) : []), ); } diff --git a/openrewrite/src/javascript/remote/receiver.ts b/openrewrite/src/javascript/remote/receiver.ts index 7eeadb24..aeb2bffc 100644 --- a/openrewrite/src/javascript/remote/receiver.ts +++ b/openrewrite/src/javascript/remote/receiver.ts @@ -353,6 +353,7 @@ class Visitor extends JavaScriptVisitor { typeQuery = typeQuery.withPrefix(ctx.receiveNode(typeQuery.prefix, receiveSpace)!); typeQuery = typeQuery.withMarkers(ctx.receiveNode(typeQuery.markers, ctx.receiveMarkers)!); typeQuery = typeQuery.withTypeExpression(ctx.receiveNode(typeQuery.typeExpression, ctx.receiveTree)!); + typeQuery = typeQuery.padding.withTypeArguments(ctx.receiveNode(typeQuery.padding.typeArguments, receiveContainer)); typeQuery = typeQuery.withType(ctx.receiveValue(typeQuery.type, ValueType.Object)); return typeQuery; } @@ -1429,7 +1430,7 @@ class Factory implements ReceiverFactory { ctx.receiveValue(null, ValueType.UUID)!, ctx.receiveNode(null, receiveSpace)!, ctx.receiveNode(null, ctx.receiveMarkers)!, - ctx.receiveNode(null, ctx.receiveTree)!, + ctx.receiveNode(null, ctx.receiveTree)!, ctx.receiveNode>(null, receiveContainer), ctx.receiveValue(null, ValueType.Object) ); @@ -1614,7 +1615,7 @@ class Factory implements ReceiverFactory { ctx.receiveNode(null, ctx.receiveMarkers)!, ctx.receiveNode>(null, receiveRightPaddedTree), ctx.receiveNode>(null, receiveContainer), - ctx.receiveNode(null, ctx.receiveTree)!, + ctx.receiveNode(null, ctx.receiveTree)!, ctx.receiveValue(null, ValueType.Object) ); } @@ -1679,6 +1680,7 @@ class Factory implements ReceiverFactory { ctx.receiveNode(null, receiveSpace)!, ctx.receiveNode(null, ctx.receiveMarkers)!, ctx.receiveNode(null, ctx.receiveTree)!, + ctx.receiveNode>(null, receiveContainer), ctx.receiveValue(null, ValueType.Object) ); } diff --git a/openrewrite/src/javascript/remote/sender.ts b/openrewrite/src/javascript/remote/sender.ts index fa35c8b7..535bcde6 100644 --- a/openrewrite/src/javascript/remote/sender.ts +++ b/openrewrite/src/javascript/remote/sender.ts @@ -348,6 +348,7 @@ class Visitor extends JavaScriptVisitor { ctx.sendNode(typeQuery, v => v.prefix, Visitor.sendSpace); ctx.sendNode(typeQuery, v => v.markers, ctx.sendMarkers); ctx.sendNode(typeQuery, v => v.typeExpression, ctx.sendTree); + ctx.sendNode(typeQuery, v => v.padding.typeArguments, Visitor.sendContainer(ValueType.Tree)); ctx.sendTypedValue(typeQuery, v => v.type, ValueType.Object); return typeQuery; } diff --git a/openrewrite/src/javascript/tree/support_types.ts b/openrewrite/src/javascript/tree/support_types.ts index a857a89d..479f2d67 100644 --- a/openrewrite/src/javascript/tree/support_types.ts +++ b/openrewrite/src/javascript/tree/support_types.ts @@ -353,5 +353,6 @@ export namespace JsContainer { IMPORT_TYPE_TYPE_ARGUMENTS, NAMED_EXPORTS_ELEMENTS, MAPPED_TYPE_VALUE_TYPE, + TYPE_QUERY_TYPE_ARGUMENTS, } } diff --git a/openrewrite/src/javascript/tree/tree.ts b/openrewrite/src/javascript/tree/tree.ts index 3815e1f0..96bd95a2 100644 --- a/openrewrite/src/javascript/tree/tree.ts +++ b/openrewrite/src/javascript/tree/tree.ts @@ -805,7 +805,7 @@ export class Export extends JSMixin(Object) implements Statement { @LstType("org.openrewrite.javascript.tree.JS$ExpressionWithTypeArguments") export class ExpressionWithTypeArguments extends JSMixin(Object) implements TypeTree, Expression { - public constructor(id: UUID, prefix: Space, markers: Markers, clazz: NameTree, typeArguments: JContainer | null, _type: JavaType | null) { + public constructor(id: UUID, prefix: Space, markers: Markers, clazz: J, typeArguments: JContainer | null, _type: JavaType | null) { super(); this._id = id; this._prefix = prefix; @@ -845,13 +845,13 @@ export class ExpressionWithTypeArguments extends JSMixin(Object) implements Type return markers === this._markers ? this : new ExpressionWithTypeArguments(this._id, this._prefix, markers, this._clazz, this._typeArguments, this._type); } - private readonly _clazz: NameTree; + private readonly _clazz: J; - public get clazz(): NameTree { + public get clazz(): J { return this._clazz; } - public withClazz(clazz: NameTree): ExpressionWithTypeArguments { + public withClazz(clazz: J): ExpressionWithTypeArguments { return clazz === this._clazz ? this : new ExpressionWithTypeArguments(this._id, this._prefix, this._markers, clazz, this._typeArguments, this._type); } @@ -2384,7 +2384,7 @@ export namespace ScopedVariableDeclarations { @LstType("org.openrewrite.javascript.tree.JS$TaggedTemplateExpression") export class TaggedTemplateExpression extends JSMixin(Object) implements Statement, Expression { - public constructor(id: UUID, prefix: Space, markers: Markers, tag: JRightPadded | null, typeArguments: JContainer | null, templateExpression: TemplateExpression, _type: JavaType | null) { + public constructor(id: UUID, prefix: Space, markers: Markers, tag: JRightPadded | null, typeArguments: JContainer | null, templateExpression: Expression, _type: JavaType | null) { super(); this._id = id; this._prefix = prefix; @@ -2445,13 +2445,13 @@ export class TaggedTemplateExpression extends JSMixin(Object) implements Stateme return this.padding.withTypeArguments(JContainer.withElementsNullable(this._typeArguments, typeArguments)); } - private readonly _templateExpression: TemplateExpression; + private readonly _templateExpression: Expression; - public get templateExpression(): TemplateExpression { + public get templateExpression(): Expression { return this._templateExpression; } - public withTemplateExpression(templateExpression: TemplateExpression): TaggedTemplateExpression { + public withTemplateExpression(templateExpression: Expression): TaggedTemplateExpression { return templateExpression === this._templateExpression ? this : new TaggedTemplateExpression(this._id, this._prefix, this._markers, this._tag, this._typeArguments, templateExpression, this._type); } @@ -2915,12 +2915,13 @@ export class TypeOf extends JSMixin(Object) implements Expression { @LstType("org.openrewrite.javascript.tree.JS$TypeQuery") export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { - public constructor(id: UUID, prefix: Space, markers: Markers, typeExpression: TypeTree, _type: JavaType | null) { + public constructor(id: UUID, prefix: Space, markers: Markers, typeExpression: TypeTree, typeArguments: JContainer | null, _type: JavaType | null) { super(); this._id = id; this._prefix = prefix; this._markers = markers; this._typeExpression = typeExpression; + this._typeArguments = typeArguments; this._type = _type; } @@ -2931,7 +2932,7 @@ export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { } public withId(id: UUID): TypeQuery { - return id === this._id ? this : new TypeQuery(id, this._prefix, this._markers, this._typeExpression, this._type); + return id === this._id ? this : new TypeQuery(id, this._prefix, this._markers, this._typeExpression, this._typeArguments, this._type); } private readonly _prefix: Space; @@ -2941,7 +2942,7 @@ export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { } public withPrefix(prefix: Space): TypeQuery { - return prefix === this._prefix ? this : new TypeQuery(this._id, prefix, this._markers, this._typeExpression, this._type); + return prefix === this._prefix ? this : new TypeQuery(this._id, prefix, this._markers, this._typeExpression, this._typeArguments, this._type); } private readonly _markers: Markers; @@ -2951,7 +2952,7 @@ export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { } public withMarkers(markers: Markers): TypeQuery { - return markers === this._markers ? this : new TypeQuery(this._id, this._prefix, markers, this._typeExpression, this._type); + return markers === this._markers ? this : new TypeQuery(this._id, this._prefix, markers, this._typeExpression, this._typeArguments, this._type); } private readonly _typeExpression: TypeTree; @@ -2961,7 +2962,17 @@ export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { } public withTypeExpression(typeExpression: TypeTree): TypeQuery { - return typeExpression === this._typeExpression ? this : new TypeQuery(this._id, this._prefix, this._markers, typeExpression, this._type); + return typeExpression === this._typeExpression ? this : new TypeQuery(this._id, this._prefix, this._markers, typeExpression, this._typeArguments, this._type); + } + + private readonly _typeArguments: JContainer | null; + + public get typeArguments(): Expression[] | null { + return this._typeArguments === null ? null : this._typeArguments.elements; + } + + public withTypeArguments(typeArguments: Expression[] | null): TypeQuery { + return this.padding.withTypeArguments(JContainer.withElementsNullable(this._typeArguments, typeArguments)); } private readonly _type: JavaType | null; @@ -2971,13 +2982,25 @@ export class TypeQuery extends JSMixin(Object) implements Expression, TypeTree { } public withType(_type: JavaType | null): TypeQuery { - return _type === this._type ? this : new TypeQuery(this._id, this._prefix, this._markers, this._typeExpression, _type); + return _type === this._type ? this : new TypeQuery(this._id, this._prefix, this._markers, this._typeExpression, this._typeArguments, _type); } public acceptJavaScript

(v: JavaScriptVisitor

, p: P): J | null { return v.visitTypeQuery(this, p); } + get padding() { + const t = this; + return new class { + public get typeArguments(): JContainer | null { + return t._typeArguments; + } + public withTypeArguments(typeArguments: JContainer | null): TypeQuery { + return t._typeArguments === typeArguments ? t : new TypeQuery(t._id, t._prefix, t._markers, t._typeExpression, typeArguments, t._type); + } + } + } + } @LstType("org.openrewrite.javascript.tree.JS$TypeOperator") diff --git a/openrewrite/src/javascript/visitor.ts b/openrewrite/src/javascript/visitor.ts index 60aecc03..823c3b3b 100644 --- a/openrewrite/src/javascript/visitor.ts +++ b/openrewrite/src/javascript/visitor.ts @@ -473,6 +473,7 @@ export class JavaScriptVisitor

extends JavaVisitor

{ typeQuery = tempExpression as TypeQuery; typeQuery = typeQuery.withMarkers(this.visitMarkers(typeQuery.markers, p)); typeQuery = typeQuery.withTypeExpression(this.visitAndCast(typeQuery.typeExpression, p)!); + typeQuery = typeQuery.padding.withTypeArguments(this.visitJsContainer(typeQuery.padding.typeArguments, JsContainer.Location.TYPE_QUERY_TYPE_ARGUMENTS, p)); return typeQuery; } diff --git a/openrewrite/test/javascript/parser/arrow.test.ts b/openrewrite/test/javascript/parser/arrow.test.ts index b4ce0112..8484439c 100644 --- a/openrewrite/test/javascript/parser/arrow.test.ts +++ b/openrewrite/test/javascript/parser/arrow.test.ts @@ -191,4 +191,23 @@ describe('arrow mapping', () => { ); }); + test('arrow function with const type param', () => { + rewriteRun( + //language=typescript + typeScript(` + prop: ( + name: string, + schemas: S, + self: TestFunction< + A, + E, + R, + [{ [K in keyof S]: Schema.Schema.Type }, V.TaskContext> & V.TestContext] + >, + timeout?: number | V.TestOptions + ) => void + `) + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/call.test.ts b/openrewrite/test/javascript/parser/call.test.ts index c69bcda6..8dc2289a 100644 --- a/openrewrite/test/javascript/parser/call.test.ts +++ b/openrewrite/test/javascript/parser/call.test.ts @@ -156,4 +156,14 @@ describe('call mapping', () => { ); }); + // need a way to distinguish new class calls with empty braces and without braces + test.skip('call new expression without braces', () => { + rewriteRun( + //language=typescript + typeScript(` + var d = (new Date).getTime() + `) + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/enum.test.ts b/openrewrite/test/javascript/parser/enum.test.ts index cada08f9..fc43a887 100644 --- a/openrewrite/test/javascript/parser/enum.test.ts +++ b/openrewrite/test/javascript/parser/enum.test.ts @@ -179,4 +179,15 @@ describe('empty mapping', () => { `) ); }); + + test('enum with string literals', () => { + rewriteRun( + //language=typescript + typeScript(` + enum CustomizableCompilers { + /*a*/'typescript'/*b*/ = 'typescript' + } + `) + ); + }); }); diff --git a/openrewrite/test/javascript/parser/for.test.ts b/openrewrite/test/javascript/parser/for.test.ts index 37181572..0bd49cc1 100644 --- a/openrewrite/test/javascript/parser/for.test.ts +++ b/openrewrite/test/javascript/parser/for.test.ts @@ -219,4 +219,17 @@ describe('for mapping', () => { `) ); }); + + test('for with bool condition', () => { + rewriteRun( + //language=typescript + typeScript(` + let bit = true + for (let i = 0;/*a*/ bit/*b*/; ++i) { + bit = false; + } + `) + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/if.test.ts b/openrewrite/test/javascript/parser/if.test.ts index 3608aa1a..0b00eff0 100644 --- a/openrewrite/test/javascript/parser/if.test.ts +++ b/openrewrite/test/javascript/parser/if.test.ts @@ -10,16 +10,24 @@ describe('if mapping', () => { typeScript('if (true) console.log("foo");') ); }); + + test('simple with comments', () => { + rewriteRun( + //language=typescript + typeScript('/*a*/if /*b*/(/*c*/true/*d*/)/*e*/ console.log("foo")/*f*/;/*g*/') + ); + }); + test('braces', () => { rewriteRun( //language=typescript - typeScript('if (true) { console.log("foo"); }') + typeScript('if (true) /*a*/{/*b*/ console.log("foo")/*c*/; /*d*/}/*e*/') ); }); test('else', () => { rewriteRun( //language=typescript - typeScript('if (true) console.log("foo"); else console.log("bar");') + typeScript('if (true) console.log("foo"); /*a*/ else/*b*/ console.log("bar")/*c*/;/*d*/') ); }); @@ -34,4 +42,29 @@ describe('if mapping', () => { `) ); }); + + test('if-else-if with semicolon', () => { + rewriteRun( + //language=typescript + typeScript(` + if (false) + console.log("foo")/*b*/;/*c*/ + else /*d*/if (true) + console.log("bar")/*e*/;/*f*/ + `) + ); + }); + + test('if-if-else with semicolon', () => { + rewriteRun( + //language=typescript + typeScript(` + if (false) + /*a*/if (true) + console.log("foo")/*b*/;/*c*/ + else /*d*/if (true) + console.log("bar")/*e*/;/*f*/ + `) + ); + }); }); diff --git a/openrewrite/test/javascript/parser/import.test.ts b/openrewrite/test/javascript/parser/import.test.ts index fbcd0c25..017abe2f 100644 --- a/openrewrite/test/javascript/parser/import.test.ts +++ b/openrewrite/test/javascript/parser/import.test.ts @@ -99,6 +99,7 @@ describe('import mapping', () => { //language=typescript typeScript(` import foo from 'module-name' with { type: "json" }; + import Package from 'module-name' assert { type: "json" } `) ); }); diff --git a/openrewrite/test/javascript/parser/method.test.ts b/openrewrite/test/javascript/parser/method.test.ts index fb5b954a..7afc89be 100644 --- a/openrewrite/test/javascript/parser/method.test.ts +++ b/openrewrite/test/javascript/parser/method.test.ts @@ -199,4 +199,20 @@ describe('method mapping', () => { ); }); + test('method invocation with empty body', () => { + rewriteRun( + //language=typescript + typeScript(` + export class ResultLengthMismatch extends TypeIdError(SqlErrorTypeId, "ResultLengthMismatch")<{ + readonly expected: number + readonly actual: number + }> { + get message() { + return \`Expected \${this.expected} results but got \${this.actual}\` + } + } + `) + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/propertyAccess.test.ts b/openrewrite/test/javascript/parser/propertyAccess.test.ts index 926ae4f7..1d48debd 100644 --- a/openrewrite/test/javascript/parser/propertyAccess.test.ts +++ b/openrewrite/test/javascript/parser/propertyAccess.test.ts @@ -17,4 +17,33 @@ describe('property access mapping', () => { typeScript('foo . bar . baz') ); }); + + test('optional property last', () => { + rewriteRun( + //language=typescript + typeScript('options.onGroup?.(onGroupOptions)') + ); + }); + + test('optional property first', () => { + rewriteRun( + //language=typescript + typeScript('options?.onGroup(onGroupOptions)') + ); + }); + + test('optional property all', () => { + rewriteRun( + //language=typescript + typeScript('options?.onGroup?.(onGroupOptions)') + ); + }); + + test('no optional properties', () => { + rewriteRun( + //language=typescript + typeScript('options.onGroup(onGroupOptions)') + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/templateExpression.test.ts b/openrewrite/test/javascript/parser/templateExpression.test.ts index b0ce4632..9a70ddb7 100644 --- a/openrewrite/test/javascript/parser/templateExpression.test.ts +++ b/openrewrite/test/javascript/parser/templateExpression.test.ts @@ -14,6 +14,15 @@ describe('template expression mapping', () => { ); }); + test('simple template with literal', () => { + rewriteRun( + //language=typescript + typeScript(` + const v = \`\${42}\`; + `) + ); + }); + test('simple template with comments', () => { rewriteRun( //language=typescript @@ -128,6 +137,21 @@ describe('template expression mapping', () => { ); }); + test('template yield LiteralType ', () => { + rewriteRun( + //language=typescript + typeScript(` + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient + + const rows = yield* sql<{ table_name: string }>\`abc \` + + expect(rows).toEqual([{table_name: "test_creation"}]) + }).pipe(runTest({table: "test_creation"})) + `) + ); + }); + test('template LiteralType union', () => { rewriteRun( //language=typescript diff --git a/openrewrite/test/javascript/parser/typeAlias.test.ts b/openrewrite/test/javascript/parser/typeAlias.test.ts index 802676f2..4369dfb4 100644 --- a/openrewrite/test/javascript/parser/typeAlias.test.ts +++ b/openrewrite/test/javascript/parser/typeAlias.test.ts @@ -238,4 +238,27 @@ describe('type alias mapping', () => { `) ); }); + + test('trailing comma in params', () => { + rewriteRun( + //language=typescript + typeScript(` + type RichText = ( + overrides?: Partial/*a*/,/*b*/ + ) => RichTextField + `) + ); + }); + + test('trailing comma in type args', () => { + rewriteRun( + //language=typescript + typeScript(` + export type AfterReadRichTextHookArgs< + TValue = any, + > = {} + `) + ); + }); + }); diff --git a/openrewrite/test/javascript/parser/typeLiteral.test.ts b/openrewrite/test/javascript/parser/typeLiteral.test.ts index 7401bde9..bd8a041a 100644 --- a/openrewrite/test/javascript/parser/typeLiteral.test.ts +++ b/openrewrite/test/javascript/parser/typeLiteral.test.ts @@ -56,4 +56,15 @@ describe('type literal mapping', () => { `) ); }); + + test('with index signature and mapped type', () => { + rewriteRun( + //language=typescript + typeScript(` + export const struct: }>( + fields: R + ) => Semigroup<{ readonly [K in keyof R]: [R[K]] extends [Semigroup] ? A : never }> = product_.struct(Product) + `) + ); + }); }); diff --git a/openrewrite/test/javascript/parser/typeQuery.test.ts b/openrewrite/test/javascript/parser/typeQuery.test.ts index ed469b6c..a4394e52 100644 --- a/openrewrite/test/javascript/parser/typeQuery.test.ts +++ b/openrewrite/test/javascript/parser/typeQuery.test.ts @@ -112,4 +112,14 @@ describe('type-query operator mapping', () => { `) ); }); + + test('typeof with generics', () => { + rewriteRun( + //language=typescript + typeScript(` + type MyStructReturnType = S.Schema.Type/*b*/>> + `) + ); + }); + }); diff --git a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptReceiver.java b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptReceiver.java index 6443b7be..442961b9 100644 --- a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptReceiver.java +++ b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptReceiver.java @@ -432,6 +432,7 @@ public JS.TypeQuery visitTypeQuery(JS.TypeQuery typeQuery, ReceiverContext ctx) typeQuery = typeQuery.withPrefix(ctx.receiveNonNullNode(typeQuery.getPrefix(), JavaScriptReceiver::receiveSpace)); typeQuery = typeQuery.withMarkers(ctx.receiveNonNullNode(typeQuery.getMarkers(), ctx::receiveMarkers)); typeQuery = typeQuery.withTypeExpression(ctx.receiveNonNullNode(typeQuery.getTypeExpression(), ctx::receiveTree)); + typeQuery = typeQuery.getPadding().withTypeArguments(ctx.receiveNode(typeQuery.getPadding().getTypeArguments(), JavaScriptReceiver::receiveContainer)); typeQuery = typeQuery.withType(ctx.receiveValue(typeQuery.getType(), JavaType.class)); return typeQuery; } @@ -1967,6 +1968,7 @@ private static JS.TypeQuery createJSTypeQuery(ReceiverContext ctx) { ctx.receiveNonNullNode(null, JavaScriptReceiver::receiveSpace), ctx.receiveNonNullNode(null, ctx::receiveMarkers), ctx.receiveNonNullNode(null, ctx::receiveTree), + ctx.receiveNode(null, JavaScriptReceiver::receiveContainer), ctx.receiveValue(null, JavaType.class) ); } diff --git a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptSender.java b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptSender.java index 3d76d3fc..f0c292f8 100644 --- a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptSender.java +++ b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptSender.java @@ -414,6 +414,7 @@ public JS.TypeQuery visitTypeQuery(JS.TypeQuery typeQuery, SenderContext ctx) { ctx.sendNode(typeQuery, JS.TypeQuery::getPrefix, JavaScriptSender::sendSpace); ctx.sendNode(typeQuery, JS.TypeQuery::getMarkers, ctx::sendMarkers); ctx.sendNode(typeQuery, JS.TypeQuery::getTypeExpression, ctx::sendTree); + ctx.sendNode(typeQuery, e -> e.getPadding().getTypeArguments(), JavaScriptSender::sendContainer); ctx.sendTypedValue(typeQuery, JS.TypeQuery::getType); return typeQuery; } diff --git a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptValidator.java b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptValidator.java index c782f314..39aa9439 100644 --- a/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptValidator.java +++ b/rewrite-javascript-remote/src/main/java/org/openrewrite/javascript/remote/JavaScriptValidator.java @@ -125,7 +125,7 @@ public JS.ExpressionStatement visitExpressionStatement(JS.ExpressionStatement ex @Override public JS.ExpressionWithTypeArguments visitExpressionWithTypeArguments(JS.ExpressionWithTypeArguments expressionWithTypeArguments, P p) { - visitAndValidateNonNull(expressionWithTypeArguments.getClazz(), NameTree.class, p); + visitAndValidateNonNull(expressionWithTypeArguments.getClazz(), J.class, p); visitAndValidate(expressionWithTypeArguments.getTypeArguments(), Expression.class, p); return expressionWithTypeArguments; } @@ -245,7 +245,7 @@ public JS.StatementExpression visitStatementExpression(JS.StatementExpression st public JS.TaggedTemplateExpression visitTaggedTemplateExpression(JS.TaggedTemplateExpression taggedTemplateExpression, P p) { visitAndValidate(taggedTemplateExpression.getTag(), Expression.class, p); visitAndValidate(taggedTemplateExpression.getTypeArguments(), Expression.class, p); - visitAndValidateNonNull(taggedTemplateExpression.getTemplateExpression(), JS.TemplateExpression.class, p); + visitAndValidateNonNull(taggedTemplateExpression.getTemplateExpression(), Expression.class, p); return taggedTemplateExpression; } @@ -287,6 +287,7 @@ public JS.TypeOf visitTypeOf(JS.TypeOf typeOf, P p) { @Override public JS.TypeQuery visitTypeQuery(JS.TypeQuery typeQuery, P p) { visitAndValidateNonNull(typeQuery.getTypeExpression(), TypeTree.class, p); + visitAndValidate(typeQuery.getTypeArguments(), Expression.class, p); return typeQuery; } diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/JavaScriptVisitor.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/JavaScriptVisitor.java index e74b4989..d4a4780a 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/JavaScriptVisitor.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/JavaScriptVisitor.java @@ -840,6 +840,9 @@ public J visitTypeQuery(JS.TypeQuery typeQuery, P p) { t = (JS.TypeQuery) temp; } t = t.withTypeExpression(Objects.requireNonNull(visitAndCast(t.getTypeExpression(), p))); + if (t.getPadding().getTypeArguments() != null) { + t = t.getPadding().withTypeArguments(visitContainer(t.getPadding().getTypeArguments(), JsContainer.Location.TYPE_QUERY_TYPE_ARGUMENTS, p)); + } t = t.withType(visitType(t.getType(), p)); return t; } 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 53969677..208be2f4 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 @@ -577,6 +577,7 @@ public J visitTypeQuery(JS.TypeQuery typeQuery, PrintOutputCapture

p) { beforeSyntax(typeQuery, JsSpace.Location.TYPE_QUERY_PREFIX, p); p.append("typeof"); visit(typeQuery.getTypeExpression(), p); + visitContainer("<", typeQuery.getPadding().getTypeArguments(), JsContainer.Location.TYPE_QUERY_TYPE_ARGUMENTS, ",", ">", p); afterSyntax(typeQuery, p); return typeQuery; } @@ -835,8 +836,8 @@ public J visitTypeLiteral(JS.TypeLiteral tl, PrintOutputCapture

p) { public J visitIndexSignatureDeclaration(JS.IndexSignatureDeclaration isd, PrintOutputCapture

p) { beforeSyntax(isd, JsSpace.Location.INDEXED_SIGNATURE_DECLARATION_PREFIX, p); + isd.getModifiers().forEach(m -> delegate.visitModifier(m, p)); visitContainer("[", isd.getPadding().getParameters(), JsContainer.Location.INDEXED_SIGNATURE_DECLARATION_PARAMETERS, "", "]", p); - visitLeftPadded(":", isd.getPadding().getTypeExpression(), JsLeftPadded.Location.INDEXED_SIGNATURE_DECLARATION_TYPE_EXPRESSION, p); afterSyntax(isd, p); @@ -1309,6 +1310,7 @@ public J visitTypeCast(J.TypeCast typeCast, PrintOutputCapture

p) { public J visitTypeParameter(J.TypeParameter typeParameter, PrintOutputCapture

p) { beforeSyntax(typeParameter, Space.Location.TYPE_PARAMETERS_PREFIX, p); visit(typeParameter.getAnnotations(), p); + typeParameter.getModifiers().forEach(m -> delegate.visitModifier(m, p)); visit(typeParameter.getName(), p); JContainer bounds = typeParameter.getPadding().getBounds(); diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JS.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JS.java index 1ce1beb5..79df0807 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JS.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JS.java @@ -832,7 +832,7 @@ final class ExpressionWithTypeArguments implements JS, TypeTree, Expression { @With @Getter - NameTree clazz; + J clazz; @Nullable JContainer typeArguments; @@ -2371,7 +2371,7 @@ public TaggedTemplateExpression withTypeArguments(@Nullable List typ } @With - TemplateExpression templateExpression; + Expression templateExpression; @With @Nullable @@ -2747,22 +2747,43 @@ public CoordinateBuilder.Expression getCoordinates() { } } - @Getter @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @RequiredArgsConstructor - @With + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @Getter final class TypeQuery implements JS, Expression, TypeTree { + @Nullable + @NonFinal + transient WeakReference padding; + @EqualsAndHashCode.Include + @With UUID id; + @With Space prefix; + + @With Markers markers; + @With TypeTree typeExpression; @Nullable + JContainer typeArguments; + + public @Nullable List getTypeArguments() { + return typeArguments == null ? null : typeArguments.getElements(); + } + + public TypeQuery withTypeArguments(@Nullable List typeParameters) { + return getPadding().withTypeArguments(JContainer.withElementsNullable(this.typeArguments, typeParameters)); + } + + @Nullable + @With JavaType type; @Override @@ -2774,6 +2795,34 @@ public

J acceptJavaScript(JavaScriptVisitor

v, P p) { public CoordinateBuilder.Expression getCoordinates() { return new CoordinateBuilder.Expression(this); } + + public TypeQuery.Padding getPadding() { + TypeQuery.Padding p; + if (this.padding == null) { + p = new TypeQuery.Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new TypeQuery.Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final TypeQuery t; + + public @Nullable JContainer getTypeArguments() { + return t.typeArguments; + } + + public TypeQuery withTypeArguments(@Nullable JContainer typeArguments) { + return t.typeArguments == typeArguments ? t : new TypeQuery(t.id, t.prefix, t.markers, t.typeExpression, typeArguments, t.type); + } + } } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsContainer.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsContainer.java index 2f27c655..3e56f30b 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsContainer.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsContainer.java @@ -35,7 +35,8 @@ public enum Location { CONDITIONAL_TYPE_CONDITION(JsSpace.Location.CONDITIONAL_TYPE_CONDITION, JsRightPadded.Location.CONDITIONAL_TYPE_CONDITION), IMPORT_TYPE_TYPE_ARGUMENTS(JsSpace.Location.IMPORT_TYPE_TYPE_ARGUMENTS, JsRightPadded.Location.IMPORT_TYPE_TYPE_ARGUMENTS), NAMED_EXPORTS_ELEMENTS(JsSpace.Location.NAMED_EXPORTS_ELEMENTS_PREFIX, JsRightPadded.Location.NAMED_EXPORTS_ELEMENTS), - MAPPED_TYPE_VALUE_TYPE(JsSpace.Location.MAPPED_TYPE_VALUE_TYPE, JsRightPadded.Location.MAPPED_TYPE_VALUE_TYPE); + MAPPED_TYPE_VALUE_TYPE(JsSpace.Location.MAPPED_TYPE_VALUE_TYPE, JsRightPadded.Location.MAPPED_TYPE_VALUE_TYPE), + TYPE_QUERY_TYPE_ARGUMENTS(JsSpace.Location.TYPE_QUERY_TYPE_ARGUMENTS, JsRightPadded.Location.TYPE_QUERY_TYPE_ARGUMENTS); private final JsSpace.Location beforeLocation; private final JsRightPadded.Location elementLocation; diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsRightPadded.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsRightPadded.java index be7dfd33..e90fa073 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsRightPadded.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsRightPadded.java @@ -55,7 +55,8 @@ public enum Location { INDEXED_ACCESS_TYPE_INDEX_TYPE_ELEMENT(JsSpace.Location.INDEXED_ACCESS_TYPE_INDEX_TYPE_ELEMENT_SUFFIX), MAPPED_TYPE_VALUE_TYPE(JsSpace.Location.MAPPED_TYPE_VALUE_TYPE_SUFFIX), MAPPED_TYPE_KEYS_REMAPPING_TYPE_PARAMETER(JsSpace.Location.MAPPED_TYPE_KEYS_REMAPPING_TYPE_PARAMETER_SUFFIX), - MAPPED_TYPE_KEYS_REMAPPING_NAME_TYPE(JsSpace.Location.MAPPED_TYPE_KEYS_REMAPPING_NAME_TYPE_SUFFIX); + MAPPED_TYPE_KEYS_REMAPPING_NAME_TYPE(JsSpace.Location.MAPPED_TYPE_KEYS_REMAPPING_NAME_TYPE_SUFFIX), + TYPE_QUERY_TYPE_ARGUMENTS(JsSpace.Location.TYPE_QUERY_TYPE_ARGUMENTS_SUFFIX); private final JsSpace.Location afterLocation; diff --git a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsSpace.java b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsSpace.java index 6314cac0..05eb7010 100644 --- a/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsSpace.java +++ b/rewrite-javascript/src/main/java/org/openrewrite/javascript/tree/JsSpace.java @@ -165,6 +165,8 @@ public enum Location { TYPE_TREE_EXPRESSION_PREFIX, LAMBDA_ARROW_PREFIX, JS_YIELD_DELEGATED_PREFIX, - FUNCTION_TYPE_CONSTRUCTOR_PREFIX; + FUNCTION_TYPE_CONSTRUCTOR_PREFIX, + TYPE_QUERY_TYPE_ARGUMENTS, + TYPE_QUERY_TYPE_ARGUMENTS_SUFFIX; } }