diff --git a/packages/ts-morph/src/compiler/ast/base/TypeParameteredNode.ts b/packages/ts-morph/src/compiler/ast/base/TypeParameteredNode.ts index c4ea0ef7e..e30d17f71 100644 --- a/packages/ts-morph/src/compiler/ast/base/TypeParameteredNode.ts +++ b/packages/ts-morph/src/compiler/ast/base/TypeParameteredNode.ts @@ -104,6 +104,11 @@ export function TypeParameteredNode) { + if (node.getFirstChildByKind(SyntaxKind.OpenParenToken) != null) + return; + + const parameter = node._getNodeFromCompilerNode(node.compilerNode.parameters[0]); + const name = parameter.getNameNode(); + const text = parameter.getText(); + insertIntoParentTextRange({ + parent: node, + insertPos: parameter.getStart(), + newText: `(${text})`, + replacing: { textLength: text.length }, + customMappings: newParent => { + const newParameter = (newParent as ts.ArrowFunction).parameters[0]; + return [ + { currentNode: parameter, newNode: newParameter }, + { currentNode: name, newNode: newParameter.name }, + ]; + }, + }); +} diff --git a/packages/ts-morph/src/tests/compiler/ast/function/arrowFunctionTests.ts b/packages/ts-morph/src/tests/compiler/ast/function/arrowFunctionTests.ts index 2c3f9a226..5f4754b31 100644 --- a/packages/ts-morph/src/tests/compiler/ast/function/arrowFunctionTests.ts +++ b/packages/ts-morph/src/tests/compiler/ast/function/arrowFunctionTests.ts @@ -9,6 +9,75 @@ function getInfoFromTextWithExpression(text: string) { } describe("ArrowFunction", () => { + describe(nameof("addTypeParameters"), () => { + for ( + const [text, expectedText] of [ + ["(value) => value", "(value) => value"], + ["() => 1", "() => 1"], + ["async (value) => value", "async (value) => value"], + ["value => value", "(value) => value"], + ["async value => value", "async (value) => value"], + ["value /* comment */ => value", "(value) /* comment */ => value"], + ] + ) { + it(`should add type parameters to ${text}`, () => { + const { expression } = getInfoFromTextWithExpression(text); + const parameter = expression.getParameters()[0]; + const name = parameter?.getNameNode(); + const result = expression.addTypeParameters(["T"]); + expect(expression.getText()).to.equal(expectedText); + expect(result.map(p => p.getName())).to.deep.equal(["T"]); + expect(expression.getTypeParameters()[0]).to.equal(result[0]); + if (parameter != null) { + expect(expression.getParameters()[0]).to.equal(parameter); + expect(parameter.getNameNode()).to.equal(name); + expect(parameter.getText()).to.equal("value"); + } + }); + } + + for (const prefix of ["", "async "]) { + it(`should preserve a cached parameter name in ${prefix}value => value`, () => { + const { expression } = getInfoFromTextWithExpression(`${prefix}value => value`); + const name = expression.getParameters()[0].getNameNode().asKindOrThrow(SyntaxKind.Identifier); + expression.addTypeParameters(["T"]); + name.rename("item"); + expect(expression.getText()).to.equal(`${prefix}(item) => item`); + }); + } + + it("should disambiguate a single type parameter from JSX", () => { + const { descendant } = getInfoFromTextWithDescendant("value => value", SyntaxKind.ArrowFunction, { isJsx: true }); + descendant.addTypeParameters(["T"]); + expect(descendant.getText()).to.equal("(value) => value"); + expect(descendant.getTypeParameters()[0].getName()).to.equal("T"); + }); + + it("should add multiple type parameters in JSX", () => { + const { descendant } = getInfoFromTextWithDescendant("(value) => value", SyntaxKind.ArrowFunction, { isJsx: true }); + descendant.addTypeParameters(["T", "U extends T"]); + expect(descendant.getText()).to.equal("(value) => value"); + }); + + it("should append to existing type parameters", () => { + const { expression } = getInfoFromTextWithExpression("(value: T) => value"); + expression.addTypeParameters(["U extends T"]); + expect(expression.getText()).to.equal("(value: T) => value"); + }); + + it("should leave an unparenthesized parameter unchanged when no type parameters are added", () => { + const { expression } = getInfoFromTextWithExpression("value => value"); + expect(expression.addTypeParameters([])).to.deep.equal([]); + expect(expression.getText()).to.equal("value => value"); + }); + + it("should leave an unparenthesized parameter unchanged when the index is invalid", () => { + const { expression } = getInfoFromTextWithExpression("value => value"); + expect(() => expression.insertTypeParameter(1, "T")).to.throw(); + expect(expression.getText()).to.equal("value => value"); + }); + }); + describe(nameof("getEqualsGreaterThan"), () => { function doTest(text: string, expectedText: string) { const { expression } = getInfoFromTextWithExpression(text);