Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #remove on JsxElement and JsxSelfClosingElement #1371

Open
wants to merge 4 commits into
base: latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/ts-morph/src/compiler/ast/jsx/JsxElement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { errors, nameof, ts } from "@ts-morph/common";
import { insertIntoParentTextRange } from "../../../manipulation";
import { errors, nameof, SyntaxKind, ts } from "@ts-morph/common";
import { insertIntoParentTextRange, removeChildren } from "../../../manipulation";
import { JsxElementSpecificStructure, JsxElementStructure, StructureKind } from "../../../structures";
import { WriterFunction } from "../../../types";
import { printTextFromStringOrWriter } from "../../../utils";
Expand Down Expand Up @@ -104,6 +104,21 @@ export class JsxElement extends JsxElementBase<ts.JsxElement> {
delete structure.children;
return structure;
}

/**
* Removes the JSX element.
*/
remove() {
const parentKind = this.getParent()?.getKind()

if (!(parentKind === SyntaxKind.JsxElement || parentKind === SyntaxKind.JsxOpeningElement || parentKind === SyntaxKind.JsxFragment)) {
throw new errors.InvalidOperationError(`Error removing JsxElement: parent is ${this.getParent()?.getKindName() ?? '(no parent)'} and therefore the node cannot be removed. Only JsxElements with JsxElement/JsxOpeningElement/JsxFragment parent can be removed`)
}

return removeChildren({
children: [this],
})
}
}

function setText(element: JsxElement, newText: string) {
Expand Down
18 changes: 17 additions & 1 deletion packages/ts-morph/src/compiler/ast/jsx/JsxSelfClosingElement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ts } from "@ts-morph/common";
import { errors, ts, SyntaxKind } from "@ts-morph/common";
import { JsxSelfClosingElementSpecificStructure, JsxSelfClosingElementStructure, StructureKind } from "../../../structures";
import { removeChildren } from "../../../manipulation";
import { callBaseGetStructure } from "../callBaseGetStructure";
import { callBaseSet } from "../callBaseSet";
import { PrimaryExpression } from "../expression";
Expand All @@ -25,4 +26,19 @@ export class JsxSelfClosingElement extends JsxSelfClosingElementBase<ts.JsxSelfC
kind: StructureKind.JsxSelfClosingElement,
});
}

/**
* Removes the JSX self-closing element.
*/
remove() {
const parentKind = this.getParent()?.getKind()

if (!(parentKind === SyntaxKind.JsxElement || parentKind === SyntaxKind.JsxOpeningElement || parentKind === SyntaxKind.JsxFragment)) {
throw new errors.InvalidOperationError(`Error removing JsxSelfClosingElement: parent is ${this.getParent()?.getKindName() ?? '(no parent)'} and therefore the node cannot be removed. Only JsxSelfClosingElements with JsxElement/JsxOpeningElement/JsxFragment parent can be removed`)
}

return removeChildren({
children: [this],
})
}
}
32 changes: 31 additions & 1 deletion packages/ts-morph/src/tests/compiler/ast/jsx/jsxElementTests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { errors, nameof, SyntaxKind } from "@ts-morph/common";
import { expect } from "chai";
import { JsxElement } from "../../../../compiler";
import { JsxElement, JsxSelfClosingElement } from "../../../../compiler";
import { JsxAttributeStructure, JsxElementStructure, StructureKind } from "../../../../structures";
import { getInfoFromTextWithDescendant, OptionalKindAndTrivia, OptionalTrivia } from "../../testHelpers";

Expand Down Expand Up @@ -159,4 +159,34 @@ const v = <div>
});
});
});

describe(nameof<JsxElement>("remove"), () => {
function doRemove(text: string) {
const { descendant, sourceFile } = getInfo(text);
descendant.remove();
}

function doTestWithJsxElementChild(text: string, expected: string) {
const { descendant, sourceFile } = getInfo(text);
(descendant.getFirstDescendantByKind(SyntaxKind.JsxElement) as JsxElement).remove();
expect(sourceFile.getFullText()).to.equal(expected);
}

it("should not remove the root JsxElement", () => {
let error = null;

try {
doRemove(`var t = (<jsx></jsx>);`);
}
catch (err) {
error = err;
}

expect(error).to.be.instanceOf(errors.InvalidOperationError);
});

it("should remove the JsxElement child", () => {
doTestWithJsxElementChild(`var t = (<jsx><jsx2></jsx2></jsx>);`, `var t = (<jsx></jsx>);`);
Copy link
Owner

@dsherret dsherret Mar 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but it's not so simple unfortunately. Once spaces are added then it gets much more complicated because spaces have meaning in JSX. It's why I haven't had the time to add this functionality yet. For example:

it("should remove the JsxElement child when has spaces", () => {
      doTestWithJsxElementChild(
        `var t = (<jsx>
        <jsx2></jsx2>
      </jsx>);`,
        `var t = (<jsx></jsx>);`,
      );
    });

});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nameof, SyntaxKind } from "@ts-morph/common";
import { errors, nameof, SyntaxKind } from "@ts-morph/common";
import { expect } from "chai";
import { JsxSelfClosingElement } from "../../../../compiler";
import { JsxAttributeStructure, JsxSelfClosingElementStructure, StructureKind } from "../../../../structures";
Expand Down Expand Up @@ -167,4 +167,34 @@ describe("JsxSelfClosingElement", () => {
expect(descendant.getFullText()).to.equal(`<jsx // comment1\n {...props1} // comment2\n {...props2} />`);
});
});

describe(nameof<JsxSelfClosingElement>("remove"), () => {
function doRemove(text: string) {
const { descendant, sourceFile } = getInfo(text);
descendant.remove();
}

function doTestWithJsxSelfClosingElementChild(text: string, expected: string) {
const { descendant, sourceFile } = getInfo(text);
descendant.remove();
expect(sourceFile.getFullText()).to.equal(expected);
}

it("should not remove the root JsxSelfClosingElement", () => {
let error = null;

try {
doRemove(`var t = (<jsx />);`);
}
catch (err) {
error = err;
}

expect(error).to.be.instanceOf(errors.InvalidOperationError);
});

it("should remove the JsxSelfClosingElement child", () => {
doTestWithJsxSelfClosingElementChild(`var t = (<jsx><jsx2 /></jsx>);`, `var t = (<jsx></jsx>);`);
});
});
});