diff --git a/openrewrite/src/javascript/parser.ts b/openrewrite/src/javascript/parser.ts index 48432979..c42a5de7 100644 --- a/openrewrite/src/javascript/parser.ts +++ b/openrewrite/src/javascript/parser.ts @@ -893,7 +893,7 @@ export class JavaScriptParserVisitor { private mapTypeInfo(node: ts.MethodDeclaration | ts.PropertyDeclaration | ts.VariableDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.ArrowFunction | ts.CallSignatureDeclaration | ts.GetAccessorDeclaration - | ts.FunctionDeclaration | ts.ConstructSignatureDeclaration) { + | ts.FunctionDeclaration | ts.ConstructSignatureDeclaration | ts.FunctionExpression) { return node.type ? new JS.TypeInfo(randomId(), this.prefix(node.getChildAt(node.getChildren().indexOf(node.type) - 1)), Markers.EMPTY, this.visit(node.type)) : null; } @@ -1217,7 +1217,15 @@ export class JavaScriptParserVisitor { randomId(), this.prefix(node), Markers.EMPTY, - this.convert(node.expression), + node.questionDotToken ? + new JS.Unary( + randomId(), + Space.EMPTY, + Markers.EMPTY, + this.leftPadded(this.suffix(node.expression), JS.Unary.Type.QuestionDot), + this.visit(node.expression), + this.mapType(node) + ) : this.convert(node.expression), this.leftPadded(this.prefix(node.getChildAt(1, this.sourceFile)), this.convert(node.name)), this.mapType(node) ); @@ -1440,6 +1448,9 @@ export class JavaScriptParserVisitor { case ts.SyntaxKind.ExclamationEqualsEqualsToken: binaryOperator = JS.JsBinary.Type.IdentityNotEquals; break; + case ts.SyntaxKind.QuestionQuestionToken: + binaryOperator = JS.JsBinary.Type.QuestionQuestion; + break; } if (binaryOperator !== undefined) { @@ -1614,7 +1625,14 @@ export class JavaScriptParserVisitor { } visitNonNullExpression(node: ts.NonNullExpression) { - return this.visitUnknown(node); + return new JS.Unary( + randomId(), + Space.EMPTY, + Markers.EMPTY, + this.leftPadded(this.suffix(node.expression), JS.Unary.Type.Exclamation), + this.visit(node.expression), + this.mapType(node) + ) } visitMetaProperty(node: ts.MetaProperty) { diff --git a/openrewrite/src/javascript/tree/tree.ts b/openrewrite/src/javascript/tree/tree.ts index 395a58d2..52993bb2 100644 --- a/openrewrite/src/javascript/tree/tree.ts +++ b/openrewrite/src/javascript/tree/tree.ts @@ -1033,6 +1033,7 @@ export namespace JsBinary { IdentityEquals = 1, IdentityNotEquals = 2, In = 3, + QuestionQuestion = 4 } @@ -2143,7 +2144,8 @@ export namespace Unary { export enum Type { Spread = 0, Optional = 1, - + Exclamation = 2, + QuestionDot = 3, } } diff --git a/openrewrite/test/javascript/parser/expressionStatement.test.ts b/openrewrite/test/javascript/parser/expressionStatement.test.ts index caebd8d2..6618ded1 100644 --- a/openrewrite/test/javascript/parser/expressionStatement.test.ts +++ b/openrewrite/test/javascript/parser/expressionStatement.test.ts @@ -1,4 +1,4 @@ -import {connect, disconnect, rewriteRunWithOptions, typeScript} from '../testHarness'; +import {connect, disconnect, rewriteRun, rewriteRunWithOptions, typeScript} from '../testHarness'; describe('expression statement mapping', () => { beforeAll(() => connect()); @@ -6,21 +6,75 @@ describe('expression statement mapping', () => { test('literal with semicolon', () => { rewriteRunWithOptions( - {normalizeIndent: false}, - typeScript('1 ;') + {normalizeIndent: false}, + typeScript('1 ;') ); }); test('multiple', () => { rewriteRunWithOptions( - {normalizeIndent: false}, - typeScript( - //language=ts - ` - 1; // foo - // bar - /*baz*/ - 2;` - ) + {normalizeIndent: false}, + typeScript( + //language=ts + ` + 1; // foo + // bar + /*baz*/ + 2;` + ) + ); + }); + + test('simple non-null expression', () => { + rewriteRun( + //language=typescript + typeScript(` + const length = user ! . profile ! . username ! . length ; + `) + ); + }); + + test('simple question-dot expression', () => { + rewriteRun( + //language=typescript + typeScript(` + const length = user ?. profile ?. username ?. length ; + `) + ); + }); + + test('simple default expression', () => { + rewriteRun( + //language=typescript + typeScript(` + const length = user ?? 'default' ; + `) + ); + }); + + test('mixed expression with special tokens', () => { + rewriteRun( + //language=typescript + typeScript(` + class Profile { + username?: string; // Optional property + } + + class User { + profile?: Profile; // Optional property + } + + function getUser(id: number) : User | null { + if (id === 1) { + return new User(); + } + return null; + } + + const user = getUser(1); + const length = user ! . profile ?. username !. length /*test*/ ; + const username2 = getUser(1) ! . profile ?. username ; // test; + const username = user!.profile?.username ?? 'Guest' ; + `) ); }); }); diff --git a/openrewrite/test/javascript/parser/function.test.ts b/openrewrite/test/javascript/parser/function.test.ts index c6ce7a41..dd96e314 100644 --- a/openrewrite/test/javascript/parser/function.test.ts +++ b/openrewrite/test/javascript/parser/function.test.ts @@ -69,4 +69,32 @@ describe('function mapping', () => { `) ); }); + + test('function with return type', () => { + rewriteRun( + //language=typescript + typeScript('function f ( a : string ) : void {}') + ); + }); + + test('function type expressions', () => { + rewriteRun( + //language=typescript + typeScript('function greeter(fn: (a: string) => void) { fn("Hello, World"); }') + ); + }); + + test.skip('function expression', () => { + rewriteRun( + //language=typescript + typeScript('const greet = function(name: string): string { return name; }') + ); + }); + + test.skip('function expression with type parameter', () => { + rewriteRun( + //language=typescript + typeScript('const greet = function(name: T): number { return 1; }') + ); + }); }); 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 a9c36c64..fefcf6df 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 @@ -227,6 +227,9 @@ public J visitJsBinary(JS.JsBinary binary, PrintOutputCapture

p) { case In: keyword = "in"; break; + case QuestionQuestion: + keyword = "??"; + break; } visitSpace(binary.getPadding().getOperator().getBefore(), JsSpace.Location.BINARY_PREFIX, p); @@ -412,6 +415,16 @@ public J visitUnary(JS.Unary unary, PrintOutputCapture

p) { visitSpace(unary.getPadding().getOperator().getBefore(), Space.Location.UNARY_OPERATOR, p); p.append("?"); break; + case Exclamation: + visit(unary.getExpression(), p); + visitSpace(unary.getPadding().getOperator().getBefore(), Space.Location.UNARY_OPERATOR, p); + p.append("!"); + break; + case QuestionDot: + visit(unary.getExpression(), p); + visitSpace(unary.getPadding().getOperator().getBefore(), Space.Location.UNARY_OPERATOR, p); + p.append("?"); + break; default: break; } 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 9249c9c6..ed41cf75 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 @@ -950,7 +950,9 @@ public enum Type { As, IdentityEquals, IdentityNotEquals, - In + In, + QuestionQuestion + } public JS.JsBinary.Padding getPadding() { @@ -1931,7 +1933,8 @@ public List getSideEffects() { public enum Type { Spread, Optional, - ; + Exclamation, + QuestionDot; public boolean isModifying() { switch (this) {