Skip to content

Commit

Permalink
Map ts.NewExpression
Browse files Browse the repository at this point in the history
This includes handling for trailing commas.
  • Loading branch information
knutwannheden committed Sep 26, 2024
1 parent ffa7bfa commit af9442a
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 4 deletions.
31 changes: 30 additions & 1 deletion openrewrite/src/java/markers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {LstType, Marker, MarkerSymbol, UUID} from "../core";
import {Space} from "./tree";

@LstType("org.openrewrite.java.marker.Semicolon")
export class Semicolon implements Marker {
Expand All @@ -16,4 +17,32 @@ export class Semicolon implements Marker {
withId(id: UUID): Semicolon {
return id == this._id ? this : new Semicolon(id);
}
}
}

@LstType("org.openrewrite.java.marker.TrailingComma")
export class TrailingComma implements Marker {
[MarkerSymbol] = true;
private readonly _id: UUID;
private readonly _suffix: Space;

constructor(id: UUID, suffix: Space) {
this._id = id;
this._suffix = suffix;
}

get id() {
return this._id;
}

withId(id: UUID): TrailingComma {
return id == this._id ? this : new TrailingComma(id, this._suffix);
}

get suffix() {
return this._suffix;
}

withSuffix(suffix: Space): TrailingComma {
return suffix == this._suffix ? this : new TrailingComma(this._id, suffix);
}
}
6 changes: 6 additions & 0 deletions openrewrite/src/java/tree/support_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ export class JContainer<T> {
}
}
}

private static readonly EMPTY = new JContainer(Space.EMPTY, [], Markers.EMPTY);

static empty<T>(): JContainer<T> {
return JContainer.EMPTY as JContainer<T>;
}
}

export namespace Space {
Expand Down
55 changes: 52 additions & 3 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as ts from 'typescript';
import * as J from '../java/tree';
import {Comment, JavaType, JRightPadded, Space, TextComment} from '../java/tree';
import {Comment, Expression, JavaType, JContainer, JRightPadded, Space, TextComment} from '../java/tree';
import * as JS from './tree';
import {ExecutionContext, Markers, ParseError, Parser, ParserInput, randomId, SourceFile} from "../core";
import {Semicolon} from "../java";
import {Semicolon, TrailingComma} from "../java";
import {getNextSibling} from "./parserUtils";

export class JavaScriptParser extends Parser {

Expand Down Expand Up @@ -451,7 +452,17 @@ export class JavaScriptParserVisitor {
}

visitNewExpression(node: ts.NewExpression) {
return this.visitUnknown(node);
return new J.NewClass(
randomId(),
this.prefix(node),
Markers.EMPTY,
null,
Space.EMPTY,
this.visit(node.expression),
this.mapArguments(node.arguments ? node.getChildren().slice(2) : []),
null,
this.mapMethodType(node)
);
}

visitTaggedTemplateExpression(node: ts.TaggedTemplateExpression) {
Expand Down Expand Up @@ -1054,6 +1065,10 @@ export class JavaScriptParserVisitor {
// return Space.format(this.sourceFile.text, node.getFullStart(), node.getFullStart() + node.getLeadingTriviaWidth());
}

private suffix(node: ts.Node): Space {
return this.prefix(getNextSibling(node)!);
}

private mapType(node: ts.Expression): JavaType | null {
if (ts.isLiteralExpression(node)) {
if (ts.isNumericLiteral(node)) {
Expand All @@ -1069,6 +1084,40 @@ export class JavaScriptParserVisitor {
}
return JavaType.Unknown.INSTANCE;
}

private mapMethodType(node: ts.Node): JavaType.Method | null {
// FIXME JavaType.Method
return null;
}

private mapArguments(nodes: ts.Node[]): JContainer<Expression> {
if (nodes.length === 0) {
return JContainer.empty();
}
const prefix = this.prefix(nodes[0]);

let argList = nodes[1] as ts.SyntaxList;
let childCount = argList.getChildCount();

const args: JRightPadded<Expression>[] = [];
for (let i = 0; i < childCount - 1; i += 2){
// FIXME right padding and trailing comma
const last = i === childCount - 2;
args.push(this.rightPadded(
argList.getChildAt(i),
this.prefix(argList.getChildAt(i + 1)),
last ? Markers.EMPTY.withMarkers([new TrailingComma(randomId(), this.prefix(nodes[2]))]) : Markers.EMPTY
));
}
if ((childCount & 1) === 1) {
args.push(this.rightPadded(argList.getChildAt(childCount - 1), this.prefix(nodes[2])));
}
return new JContainer(
prefix,
args,
Markers.EMPTY
);
}
}

function prefixFromNode(node: ts.Node, sourceFile: ts.SourceFile): Space {
Expand Down
137 changes: 137 additions & 0 deletions openrewrite/src/javascript/parserUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as ts from "typescript";

export function getNextSibling(node: ts.Node): ts.Node | null {
const parent = node.parent;
if (!parent) {
return null;
}

const syntaxList = findContainingSyntaxList(node);

if (syntaxList) {
const children = syntaxList.getChildren();
const nodeIndex = children.indexOf(node);

if (nodeIndex === -1) {
throw new Error('Node not found among SyntaxList\'s children.');
}

// If the node is the last child in the SyntaxList, recursively check the parent's next sibling
if (nodeIndex === 0) {
const parentNextSibling = getNextSibling(parent);
if (!parentNextSibling) {
return null;
}

// Return the first child of the parent's next sibling
const parentSyntaxList = findContainingSyntaxList(parentNextSibling);
if (parentSyntaxList) {
const siblings = parentSyntaxList.getChildren();
return siblings[0] || null;
} else {
return parentNextSibling;
}
}

// Otherwise, return the next sibling in the SyntaxList
return children[nodeIndex + 1];
}

const parentChildren = parent.getChildren();
const nodeIndex = parentChildren.indexOf(node);

if (nodeIndex === -1) {
throw new Error('Node not found among parent\'s children.');
}

// If the node is the last child, recursively check the parent's next sibling
if (nodeIndex === parentChildren.length - 1) {
const parentNextSibling = getNextSibling(parent);
if (!parentNextSibling) {
return null;
}

// Return the last child of the parent's previous sibling
const siblings = parentNextSibling.getChildren();
return siblings[0] || null;
}

// Otherwise, return the next sibling
return parentChildren[nodeIndex + 1];
}

export function getPreviousSibling(node: ts.Node): ts.Node | null {
const parent = node.parent;
if (!parent) {
return null;
}

const syntaxList = findContainingSyntaxList(node);

if (syntaxList) {
const children = syntaxList.getChildren();
const nodeIndex = children.indexOf(node);

if (nodeIndex === -1) {
throw new Error('Node not found among SyntaxList\'s children.');
}

// If the node is the first child in the SyntaxList, recursively check the parent's previous sibling
if (nodeIndex === 0) {
const parentPreviousSibling = getPreviousSibling(parent);
if (!parentPreviousSibling) {
return null;
}

// Return the last child of the parent's previous sibling
const parentSyntaxList = findContainingSyntaxList(parentPreviousSibling);
if (parentSyntaxList) {
const siblings = parentSyntaxList.getChildren();
return siblings[siblings.length - 1] || null;
} else {
return parentPreviousSibling;
}
}

// Otherwise, return the previous sibling in the SyntaxList
return children[nodeIndex - 1];
}

const parentChildren = parent.getChildren();
const nodeIndex = parentChildren.indexOf(node);

if (nodeIndex === -1) {
throw new Error('Node not found among parent\'s children.');
}

// If the node is the first child, recursively check the parent's previous sibling
if (nodeIndex === 0) {
const parentPreviousSibling = getPreviousSibling(parent);
if (!parentPreviousSibling) {
return null;
}

// Return the last child of the parent's previous sibling
const siblings = parentPreviousSibling.getChildren();
return siblings[siblings.length - 1] || null;
}

// Otherwise, return the previous sibling
return parentChildren[nodeIndex - 1];
}

function findContainingSyntaxList(node: ts.Node): ts.SyntaxList | null {
const parent = node.parent;
if (!parent) {
return null;
}

const children = parent.getChildren();
for (const child of children) {
if (child.kind == ts.SyntaxKind.SyntaxList && child.getChildren().includes(node)) {
return child as ts.SyntaxList;
}
}

return null;
}
34 changes: 34 additions & 0 deletions openrewrite/test/javascript/parser/new.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {connect, disconnect, javaScript, rewriteRun} from '../testHarness';

describe('new mapping', () => {
beforeAll(() => connect());
afterAll(() => disconnect());

test('simple', () => {
rewriteRun(
//language=typescript
javaScript('new Uint8Array(1)')
);
});

test('space', () => {
rewriteRun(
//language=typescript
javaScript('new Uint8Array/*1*/(/*2*/1/*3*/)/*4*/')
);
});

test('multiple', () => {
rewriteRun(
//language=typescript
javaScript('new Date(2023, 9, 25, 10, 30, 15, 500)')
);
});

test('trailing comma', () => {
rewriteRun(
//language=typescript
javaScript('new Uint8Array(1 , )')
);
});
});

0 comments on commit af9442a

Please sign in to comment.