Repository: nicoespeon/abracadabra
Pull request #775 (2022-12-09 12:59:34)

Change signature:  Remove & Add parameters
Depend on https://github.com/nicoespeon/abracadabra/pull/774

## Changes

### New Design
![Untitled-2021-11-17-1443](https://user-images.githubusercontent.com/8685132/206705105-509fe470-81c6-450c-bc15-69286c0cb979.svg)
- Required param name and param value using `required` attribute and wrapped in a `form`
![image](https://user-images.githubusercontent.com/8685132/206705201-8da67ce5-c63f-41b5-81ee-f50708d0e088.png)


### Adding parameters
![Peek 2022-12-09 13-48](https://user-images.githubusercontent.com/8685132/206706106-9434584f-286c-43fb-ab07-d1cfb13d62f6.gif)

### Removing parameters
![Peek 2022-12-09 13-51](https://user-images.githubusercontent.com/8685132/206706610-2ab102ff-455e-43a8-b76a-8b80f791635c.gif)

## Combination of add + remove
![add-remove](https://user-images.githubusercontent.com/8685132/206706732-f929e4d3-f8c0-4e0a-a75c-21064578f575.gif)

### Combination of add + order change + remove
![new](https://user-images.githubusercontent.com/8685132/206707236-5b7758d7-776c-4be9-be53-03aee2ca2d6e.gif)

## Test changes

I divide `change-signature.test.ts` in two files. Why? Because eslint is trigger limit file lines size, so there are two test files:
- `change-signature.test.ts`: test for functions and references in same file
- `change-signature.modules.test.ts`: test for modules and function references
- Webview template (and code) has more unitary (_behaviour_) tests

nicoespeon: @11joselu could you rebase this PR now that #774 has been merged?

Patch:
diff --git a/src/editor/adapters/change-signature-webview/change-signature.html b/src/editor/adapters/change-signature-webview/change-signature.html
index 995ac9c7..4a6e1bc5 100644
--- a/src/editor/adapters/change-signature-webview/change-signature.html
+++ b/src/editor/adapters/change-signature-webview/change-signature.html
@@ -14,18 +14,36 @@
         padding: 8px;
       }
 
-      th:last-child {
-        border-top-color: transparent;
-        border-right-color: transparent;
+      th {
+        user-select: none;
       }
 
-      .up,
-      .down {
-        cursor: pointer;
+      th > span {
         display: inline-block;
-        width: 8px;
-        margin: 0 0.7rem;
-        font-size: 1.2rem;
+        min-width: 21px;
+        min-height: 21px;
+        font-size: 1.3rem;
+        cursor: pointer;
+        margin-right: 8px;
+      }
+
+      #remove {
+        position: relative;
+        top: -5px;
+      }
+
+      tbody tr {
+        cursor: pointer;
+      }
+
+      tbody td input {
+        max-width: 120px;
+        min-height: 20px;
+      }
+
+      input[disabled] {
+        background-color: #d1d1d1;
+        border: 1px solid #8b8b8b;
       }
 
       .up:after {
@@ -63,36 +81,62 @@
         cursor: pointer;
         color: white;
       }
+
+      button[disabled] {
+        background-color: #e2e2e2;
+        color: #b1b1b1;
+        cursor: not-allowed;
+      }
+
+      .param--selected {
+        background-color: rgba(100, 207, 255, 0.3);
+      }
+
+      .disabled {
+        opacity: 0.3;
+      }
     </style>
   </head>
 
   <body>
     <h4>Parameters</h4>
-    <table>
-      <thead>
+    <form id="form">
+      <table>
+        <thead>
+        <tr>
+          <th colspan="4">
+            <span id="add">+</span>
+            <span id="remove">⚊</span>
+            <span class="up" id="up"></span>
+            <span class="down" id="down"></span>
+          </th>
+        </tr>
         <tr>
-          <th>Name</th>
-          <th></th>
+          <th colspan="3" width="128">Name</th>
+          <th colspan="1">Value</th>
         </tr>
-      </thead>
+        </thead>
 
       <tbody id="params">
         {{tableContent}}
       </tbody>
       <tfoot>
         <tr>
-          <td colspan="2" style="text-align: center">
+          <td colspan="4" style="text-align: center">
             <button id="confirm">Confirm</button>
           </td>
         </tr>
-      </tfoot>
-    </table>
-
-    <div class="btn-wrapper"></div>
+        </tfoot>
+      </table>
+      <div class="btn-wrapper"></div>
+    </form>
 
     <script>
-      const vscode = acquireVsCodeApi();
       const startValues = document.querySelectorAll("#params .params-name");
+      const up = document.getElementById("up");
+      const down = document.getElementById("down");
+      const selectedClass = "param--selected";
+      const disabledClass = "disabled";
 
       function moveUp(element) {
         if (element.previousElementSibling)
@@ -107,31 +151,185 @@
           element.parentNode.insertBefore(element.nextElementSibling, element);
       }
 
-      document.querySelector("#params").addEventListener("click", function (e) {
-        if (e.target.className === "down")
-          moveDown(e.target.parentNode.parentNode);
-        else if (e.target.className === "up")
-          moveUp(e.target.parentNode.parentNode);
+      function validateArrowsState(el) {
+        if (!el.previousElementSibling) {
+          up.classList.add(disabledClass);
+        } else {
+          up.classList.remove(disabledClass);
+        }
+
+        if (!el.nextElementSibling) {
+          down.classList.add(disabledClass);
+        } else {
+          down.classList.remove(disabledClass);
+        }
+      }
+
+      function addTrClickEvents() {
+        const paramsTr = document.querySelectorAll("tbody tr");
+
+        function findParentTr(el) {
+          if (!el) return null;
+          if (el.nodeName === 'TR') return el;
+
+          return findParentTr(el.parentNode)
+        }
+
+        function handleClick(ev) {
+          ev.stopPropagation();
+          const el = findParentTr(ev.target);
+          if (!el) return;
+          paramsTr.forEach((el) => {
+            el.classList.remove(selectedClass);
+          });
+
+          el.classList.add(selectedClass);
+          validateArrowsState(el);
+        }
+
+        paramsTr.forEach((tr) => {
+          tr.removeEventListener('click', handleClick);
+          tr.addEventListener("click", handleClick);
+        });
+      }
+
+      function isNewParam(startAt) {
+        return startAt === -1;
+      }
+
+      function insert(arr, index, newItem) {
+        return [
+          // part of the array before the specified index
+          ...arr.slice(0, index),
+          // inserted item
+          newItem,
+          // part of the array after the specified index
+          ...arr.slice(index)
+        ];
+      }
+
+      addTrClickEvents();
+
+      up.addEventListener("click", (ev) => {
+        ev.stopPropagation();
+        if (ev.target.classList.contains("disable")) return;
+        const el = document.querySelector(`.${selectedClass}`);
+        if (!el) return;
+
+        moveUp(el);
+        validateArrowsState(el);
+      });
+
+      down.addEventListener("click", (ev) => {
+        ev.stopPropagation();
+        if (ev.target.classList.contains("disable")) return;
+        const el = document.querySelector(`.${selectedClass}`);
+        if (!el) return;
+
+        moveDown(el);
+        validateArrowsState(el);
+      });
+
+      document.getElementById("add").addEventListener("click", (ev) => {
+        ev.stopPropagation();
+
+        const tr = createParam();
+        addTrClickEvents();
+        tr.dispatchEvent(new Event('click'));
       });
 
-      document.querySelector("#confirm").addEventListener("click", () => {
+      document.getElementById("remove").addEventListener("click", (ev) => {
+        const el = document.querySelector(`.${selectedClass}`);
+        if (el) el.remove();
+      });
+
+      document.getElementById('form').addEventListener('submit', (ev) => {
+        ev.preventDefault();
+        const form = ev.target;
+        const isValid = form.checkValidity();
+
+        if (!isValid) {
+          const inputs = Array.from(document.querySelectorAll('input:not([disabled])'));
+          const input = inputs.find((input) => !input.value);
+          if (input) input.focus();
+          return;
+        }
+
         const tdsElements = document.querySelectorAll("#params .params-name");
         const tds = Array.from(tdsElements);
+        const startTdsValues =  Array.from(startValues);
 
-        const items = Array.from(startValues).map((item, index) => {
-          const endAt = tds.findIndex((td) => td === item);
+        let items = tds.map((item, index) => {
+          const startAt = Array.from(startValues).findIndex((td) => td === item);
+          let label = item.textContent;
+          let value = "";
 
-          return {
-            label: item.innerHTML,
-            startAt: index,
-            endAt: endAt
+          if (isNewParam(startAt)) {
+            // When user add new param we need inputs values
+            label = item.firstChild.value;
+            value = item.nextElementSibling.firstChild.value;
+          }
+
+          const result = {
+            label,
+            startAt,
+            endAt: index
           };
+
+          if (value) result.value = value;
+
+          return result;
+        });
+
+        startTdsValues.forEach((td, index) => {
+          const paramName = td.textContent;
+          const existInNewParams = items.find((item) => item.label === paramName);
+
+          if (!existInNewParams) {
+            items = insert(items, index, {
+              label: paramName,
+              startAt: index,
+              endAt: -1
+            });
+          }
         });
 
+        const confirmBtn = document.querySelector("#confirm");
+        confirmBtn.disabled = true;
+
+        const vscode = acquireVsCodeApi();
         vscode.postMessage({
           values: items
         });
       });
+
+
+      document.body.addEventListener("click", (evt) => {
+        if(evt.target.nodeName === "INPUT") return;
+
+        const el = document.querySelector(`.${selectedClass}`);
+        if (el) {
+          el.classList.remove(selectedClass);
+          up.classList.remove(disabledClass);
+          down.classList.remove(disabledClass);
+        }
+      });
+
+      function createParam() {
+        const template = `
+            <td colspan="3" class="params-name"><input class="input-param-name" required/></td>
+            <td colspan="1" class="params-value"><input class="input-param-value" required/></td>
+        `;
+
+        const tr = document.createElement("tr");
+        tr.classList.add(`param`);
+        tr.innerHTML = template;
+
+        document.querySelector("tbody").appendChild(tr);
+        tr.querySelector('.input-param-name').focus();
+
+        return tr;
+      }
     </script>
   </body>
 </html>
diff --git a/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.ts b/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.ts
index 2aa92594..732b158b 100644
--- a/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.ts
+++ b/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.ts
@@ -6,13 +6,10 @@ export function createChangeSignatureWebviewTemplate(
   const paramsTrValues = params.map((param) => {
     const name = param.label;
     return `
-      <tr>
-          <td class="params-name">${name}</td>
-          <td>
-            <span class="up"></span>
-            <span class="down"></span>
-          </td>
-        </tr>
+      <tr class="param">
+        <td colspan="3" class="params-name">${name}</td>
+        <td colspan="1" class="params-value"><input disabled/></td>
+      </tr>
     `;
   });
 
diff --git a/src/editor/adapters/vscode-editor.ts b/src/editor/adapters/vscode-editor.ts
index 9055f1c2..aec88c07 100644
--- a/src/editor/adapters/vscode-editor.ts
+++ b/src/editor/adapters/vscode-editor.ts
@@ -238,6 +238,7 @@ export class VSCodeEditor implements Editor {
           label: string;
           startAt: number;
           endAt: number;
+          value?: string;
         }[];
       }) => {
         const values = message.values;
@@ -247,7 +248,8 @@ export class VSCodeEditor implements Editor {
             label: result.label,
             value: {
               startAt: result.startAt,
-              endAt: result.endAt
+              endAt: result.endAt,
+              val: result.value
             }
           };
         });
diff --git a/src/editor/editor.ts b/src/editor/editor.ts
index d44bbf49..892fc1ef 100644
--- a/src/editor/editor.ts
+++ b/src/editor/editor.ts
@@ -61,6 +61,7 @@ export type SelectedPosition = Omit<
   Choice<{
     startAt: number;
     endAt: number;
+    val?: string;
   }>,
   "description" | "icon"
 >;
diff --git a/src/refactorings/change-signature/change-signature.ts b/src/refactorings/change-signature/change-signature.ts
index e9a13480..87aa855f 100644
--- a/src/refactorings/change-signature/change-signature.ts
+++ b/src/refactorings/change-signature/change-signature.ts
@@ -1,4 +1,5 @@
 import * as t from "../../ast";
+import { parse } from "../../ast";
 import { isFunctionDeclarationOrArrowFunction } from "../../ast/identity";
 import { Editor, ErrorReason, SelectedPosition } from "../../editor/editor";
 import { Path } from "../../editor/path";
@@ -111,29 +112,52 @@ function updateCode(
       const node = path.node;
 
       if (t.isCallExpression(node)) {
-        const args = node.arguments.slice();
+        const args = node.arguments.slice().filter((_param, index) => {
+          return !hasRemovedTheParameter(newPositions[index]);
+        });
+
         if (args.length) {
           newPositions.forEach((order) => {
-            const arg = node.arguments[order.value.startAt];
-            args[order.value.endAt] = arg;
+            if (isNewParameter(order)) {
+              // Convert to a valid code.
+              // Without that will trigger invalid "Missing semicolon (n, n)"
+              // That error occurs only for literal objects like: {id: 1, ...}
+              const fakedBlockCode = `const faked = ${order.value.val}`;
+              const parsed = parse(fakedBlockCode);
+              const node = parsed.program.body[0];
+
+              if (t.isVariableDeclaration(node)) {
+                const variableDeclarator = node.declarations[0];
+                if (variableDeclarator.init) {
+                  args[order.value.endAt] = variableDeclarator.init;
+                }
+              }
+            } else {
+              args[order.value.endAt] = node.arguments[order.value.startAt];
+            }
           });
         }
 
-        const newArgs = args.map((arg) => {
+        node.arguments = args.map((arg) => {
           if (arg) return arg;
 
           return t.identifier("undefined");
         });
-        node.arguments = newArgs;
       } else if (
         isFunctionDeclarationOrArrowFunction(node) ||
         t.isClassMethod(node)
       ) {
-        const params = node.params.slice();
+        const params = node.params.slice().filter((_param, index) => {
+          return !hasRemovedTheParameter(newPositions[index]);
+        });
+
         if (params.length) {
           newPositions.forEach((order) => {
-            const arg = node.params[order.value.startAt];
-            params[order.value.endAt] = arg;
+            if (isNewParameter(order)) {
+              params[order.value.endAt] = t.identifier(order.label);
+            } else {
+              params[order.value.endAt] = node.params[order.value.startAt];
+            }
           });
         }
 
@@ -264,3 +288,11 @@ function hasParameters(
 ) {
   return node.params.length > 0;
 }
+
+function isNewParameter(order: SelectedPosition) {
+  return order.value.startAt === -1;
+}
+
+function hasRemovedTheParameter(order: SelectedPosition) {
+  return order.value.endAt === -1;
+}


Test patch:
diff --git a/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.test.ts b/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.test.ts
index c3ed44a6..8dc0b6e3 100644
--- a/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.test.ts
+++ b/src/editor/adapters/change-signature-webview/createChangeSignatureWebviewTemplate.test.ts
@@ -5,7 +5,7 @@ import { createChangeSignatureWebviewTemplate } from "./createChangeSignatureWeb
 type AcquireVsCodeAPIPostMessage = Function | jest.Mock<void>;
 
 describe("Change signature Webview Content", () => {
-  it("Should render params labels", async () => {
+  it("Should render params labels", () => {
     const selections = [
       createSelectedPosition("paramA", 0),
       createSelectedPosition("paramB", 1)
@@ -19,6 +19,38 @@ describe("Change signature Webview Content", () => {
     expect(params[1].textContent).toBe("paramB");
   });
 
+  it("Should disable existing params inputs", () => {
+    const selections = [
+      createSelectedPosition("paramA", 0),
+      createSelectedPosition("paramB", 1)
+    ];
+
+    const document = render(loadHTML(selections), acquireVsCodeApi());
+
+    const inputs = document.querySelectorAll<HTMLInputElement>("td input");
+    expect(inputs).toHaveLength(2);
+    expect(inputs[0].disabled).toBeTruthy();
+    expect(inputs[1].disabled).toBeTruthy();
+  });
+
+  it("Should render arrow up", () => {
+    const selections = [createSelectedPosition("paramA", 0)];
+
+    const document = render(loadHTML(selections), acquireVsCodeApi());
+
+    const up = document.getElementById("up");
+    expect(up).not.toBeNull();
+  });
+
+  it("Should render arrow down", () => {
+    const selections = [createSelectedPosition("paramA", 0)];
+
+    const document = render(loadHTML(selections), acquireVsCodeApi());
+
+    const down = document.getElementById("down");
+    expect(down).not.toBeNull();
+  });
+
   describe("Params orders", () => {
     const postMessage = jest.fn();
     let document: Document;
@@ -31,7 +63,7 @@ describe("Change signature Webview Content", () => {
       document = render(loadHTML(selections), acquireVsCodeApi(postMessage));
     });
 
-    it("Should be able to confirm signature without any changes", async () => {
+    it("Should be able to confirm signature without any changes", () => {
       document.getElementById("confirm")?.click();
 
       expect(postMessage).toHaveBeenCalledWith({
@@ -42,48 +74,181 @@ describe("Change signature Webview Content", () => {
       });
     });
 
-    it("Should be able to move paramB as a first parameter", async () => {
-      const upButtons = document.querySelectorAll<HTMLElement>(".up");
-      upButtons[1].click();
+    it("Should be able to move paramB as a first parameter", () => {
+      selectParam(document, 1);
+      clickUp(document);
       document.getElementById("confirm")?.click();
 
       expect(postMessage).toHaveBeenCalledWith({
         values: [
-          { label: "paramA", startAt: 0, endAt: 1 },
-          { label: "paramB", startAt: 1, endAt: 0 }
+          { label: "paramB", startAt: 1, endAt: 0 },
+          { label: "paramA", startAt: 0, endAt: 1 }
         ]
       });
     });
 
-    it("Should be able to move paramA as last parameter", async () => {
-      const downButtons = document.querySelectorAll<HTMLElement>(".down");
-      downButtons[0].click();
+    it("Should be able to move paramA as last parameter", () => {
+      selectParam(document, 0);
+      clickDown(document);
       document.getElementById("confirm")?.click();
 
       expect(postMessage).toHaveBeenCalledWith({
         values: [
-          { label: "paramA", startAt: 0, endAt: 1 },
-          { label: "paramB", startAt: 1, endAt: 0 }
+          { label: "paramB", startAt: 1, endAt: 0 },
+          { label: "paramA", startAt: 0, endAt: 1 }
         ]
       });
     });
 
-    it("Should not change order if I move the last parameter down", async () => {
-      const downButtons = document.querySelectorAll<HTMLElement>(".down");
-      downButtons[1].click();
+    it("Should down button be disabled when I select the last parameter", () => {
+      selectParam(document, 1);
+      const downButton = document.getElementById("down") as HTMLSpanElement;
+
+      expect(downButton.classList.contains("disabled")).toBeTruthy();
+    });
+
+    it("Should up button be disabled when I select the first parameter", () => {
+      selectParam(document, 0);
+      const upButton = document.getElementById("up") as HTMLSpanElement;
+
+      expect(upButton.classList.contains("disabled")).toBeTruthy();
+    });
+
+    it("Should up button be disabled when I move paramB as a first parameter", () => {
+      selectParam(document, 1);
+      clickUp(document);
+
+      const up = document.getElementById("up") as HTMLSpanElement;
+      expect(up.classList.contains("disabled")).toBeTruthy();
+    });
+
+    it("Should down button be disabled when I move paramA as a last parameter", () => {
+      selectParam(document, 1);
+      clickDown(document);
+
+      const down = document.getElementById("down") as HTMLSpanElement;
+      expect(down.classList.contains("disabled")).toBeTruthy();
+    });
+
+    it("Should Confirm button be disabled after submit", () => {
+      const confirmBtn = document.getElementById(
+        "confirm"
+      ) as HTMLButtonElement;
+      confirmBtn.click();
+
+      expect(confirmBtn.disabled).toBeTruthy();
+    });
+  });
+
+  describe("Adding params", () => {
+    let postMessage: AcquireVsCodeAPIPostMessage;
+    let document: Document;
+
+    beforeEach(() => {
+      postMessage = jest.fn();
+      const selections = [
+        createSelectedPosition("paramA", 0),
+        createSelectedPosition("paramB", 1)
+      ];
+      document = render(loadHTML(selections), acquireVsCodeApi(postMessage));
+      const addBtn = document.getElementById("add") as HTMLElement;
+      addBtn.click();
+    });
+
+    it("Should be able to add new parameter", () => {
+      const inputLabel = document.querySelector(
+        ".input-param-name"
+      ) as HTMLInputElement;
+      inputLabel.value = "newParam";
+      const inputValue = document.querySelector(
+        ".input-param-value"
+      ) as HTMLInputElement;
+      inputValue.value = true.toString();
       document.getElementById("confirm")?.click();
 
       expect(postMessage).toHaveBeenCalledWith({
         values: [
           { label: "paramA", startAt: 0, endAt: 0 },
-          { label: "paramB", startAt: 1, endAt: 1 }
+          { label: "paramB", startAt: 1, endAt: 1 },
+          { label: "newParam", startAt: -1, endAt: 2, value: "true" }
         ]
       });
     });
 
-    it("Should not change order if I move the first parameter up", async () => {
-      const upButtons = document.querySelectorAll<HTMLElement>(".up");
-      upButtons[0].click();
+    it("Should param name input be focused when has empty value on submit", () => {
+      document.getElementById("confirm")?.click();
+
+      const inputLabel = document.querySelector(
+        ".input-param-name"
+      ) as HTMLInputElement;
+      const focusedElement = document.activeElement;
+      expect(inputLabel).toEqual(focusedElement);
+      expect(postMessage).not.toHaveBeenCalled();
+    });
+
+    it("Should param value input be focused when has empty value on submit", () => {
+      const inputLabel = document.querySelector(
+        ".input-param-name"
+      ) as HTMLInputElement;
+      inputLabel.value = "newParam";
+      document.getElementById("confirm")?.click();
+
+      const inputValue = document.querySelector(
+        ".input-param-value"
+      ) as HTMLInputElement;
+      const focusedElement = document.activeElement;
+      expect(inputValue).toEqual(focusedElement);
+      expect(postMessage).not.toHaveBeenCalled();
+    });
+
+    it("Should be able to move Up new parameter", () => {
+      clickUp(document);
+
+      const trs = document.querySelectorAll("tbody tr");
+      const newParamTr = trs[1];
+      expect(newParamTr.classList.contains("param--selected")).toBeTruthy();
+    });
+
+    it("Should be able to move Up then Down the new parameter", () => {
+      clickUp(document);
+      clickDown(document);
+
+      const trs = document.querySelectorAll("tbody tr");
+      const newParamTr = trs[2];
+      expect(newParamTr.classList.contains("param--selected")).toBeTruthy();
+    });
+
+    it("Should down button be disabled when user add new parameter because is the last param", () => {
+      const down = document.getElementById("down") as HTMLSpanElement;
+
+      expect(down.classList.contains("disabled")).toBeTruthy();
+    });
+
+    it("Should up button be enabled when user add new parameter because is the last param", () => {
+      const up = document.getElementById("up") as HTMLSpanElement;
+
+      expect(up.classList.contains("disabled")).toBeFalsy();
+    });
+  });
+
+  describe("Removing param", () => {
+    let postMessage: AcquireVsCodeAPIPostMessage;
+    let document: Document;
+
+    beforeEach(() => {
+      postMessage = jest.fn();
+      const selections = [
+        createSelectedPosition("paramA", 0),
+        createSelectedPosition("paramB", 1)
+      ];
+      document = render(loadHTML(selections), acquireVsCodeApi(postMessage));
+    });
+
+    it("Should be able to remove paramB parameter", () => {
+      const addBtn = document.getElementById("add") as HTMLElement;
+      addBtn.click();
+      const removeBtn = document.getElementById("remove") as HTMLElement;
+      removeBtn.click();
       document.getElementById("confirm")?.click();
 
       expect(postMessage).toHaveBeenCalledWith({
@@ -93,6 +258,57 @@ describe("Change signature Webview Content", () => {
         ]
       });
     });
+
+    it("Should be able to add a new parameter and remove it", () => {
+      selectParam(document, 1);
+
+      const removeBtn = document.getElementById("remove") as HTMLElement;
+      removeBtn.click();
+      document.getElementById("confirm")?.click();
+
+      expect(postMessage).toHaveBeenCalledWith({
+        values: [
+          { label: "paramA", startAt: 0, endAt: 0 },
+          { label: "paramB", startAt: 1, endAt: -1 }
+        ]
+      });
+    });
+  });
+
+  it("Should be able to remove second parameter and add new one", () => {
+    const postMessage = jest.fn();
+    const selections = [
+      createSelectedPosition("paramA", 0),
+      createSelectedPosition("paramB", 1)
+    ];
+    const document = render(
+      loadHTML(selections),
+      acquireVsCodeApi(postMessage)
+    );
+
+    selectParam(document, 1);
+    const removeBtn = document.getElementById("remove") as HTMLElement;
+    removeBtn.click();
+    const addBtn = document.getElementById("add") as HTMLElement;
+    addBtn.click();
+    const inputLabel = document.querySelector(
+      ".input-param-name"
+    ) as HTMLInputElement;
+    inputLabel.value = "newParam";
+    const inputValue = document.querySelector(
+      ".input-param-value"
+    ) as HTMLInputElement;
+    inputValue.value = true.toString();
+
+    document.getElementById("confirm")?.click();
+
+    expect(postMessage).toHaveBeenCalledWith({
+      values: [
+        { label: "paramA", startAt: 0, endAt: 0 },
+        { label: "paramB", startAt: 1, endAt: -1 },
+        { label: "newParam", startAt: -1, endAt: 1, value: "true" }
+      ]
+    });
   });
 });
 
@@ -134,3 +350,18 @@ function acquireVsCodeApi(postMessage: AcquireVsCodeAPIPostMessage = () => {}) {
     };
   };
 }
+
+function clickUp(document: Document) {
+  const up = document.getElementById("up") as HTMLSpanElement;
+  up.click();
+}
+
+function clickDown(document: Document) {
+  const down = document.getElementById("down") as HTMLSpanElement;
+  down.click();
+}
+
+function selectParam(document: Document, paramIndex: number) {
+  const paramsTr = document.querySelectorAll<HTMLTableRowElement>(".param");
+  paramsTr[paramIndex].click();
+}
diff --git a/src/refactorings/change-signature/change-signature.modules.test.ts b/src/refactorings/change-signature/change-signature.modules.test.ts
new file mode 100644
index 00000000..ee683266
--- /dev/null
+++ b/src/refactorings/change-signature/change-signature.modules.test.ts
@@ -0,0 +1,634 @@
+import { AbsolutePath } from "../../editor/path";
+import { testEach } from "../../tests-helpers";
+import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
+import { changeSignature } from "./change-signature";
+import { Code, SelectedPosition } from "../../editor/editor";
+
+type TestSample = {
+  code: Code;
+  path: AbsolutePath;
+};
+
+describe("Change Signature: Modules", () => {
+  const addModule = new AbsolutePath("/temp/add.ts");
+  const anotherModule = new AbsolutePath("/temp/anotherModule");
+
+  testEach<{
+    setup: {
+      currentFile: TestSample;
+      otherFiles: TestSample[];
+    };
+    expected: {
+      currentFile: TestSample;
+      otherFiles: TestSample[];
+    };
+  }>(
+    "should change signature of function with a reference in modules",
+    [
+      {
+        description: "that import the function",
+        setup: {
+          currentFile: {
+            code: `export function [cursor]add(a, b) {
+              return a + b;
+            }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {add} from './module';
+                add(1, 2)
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        },
+        expected: {
+          currentFile: {
+            code: `export function add(b, a) {
+              return a + b;
+            }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {add} from './module';
+                add(2, 1)
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(b, a);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        }
+      },
+      {
+        description: "that import arrow function",
+        setup: {
+          currentFile: {
+            code: `export const add = [cursor](a, b) => {
+              return a + b;
+            }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {add} from './module';
+                add(1, 2)
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        },
+        expected: {
+          currentFile: {
+            code: `export const add = (b, a) => {
+              return a + b;
+            }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {add} from './module';
+                add(2, 1)
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(b, a);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        }
+      },
+      {
+        description: "that import a class and use a method",
+        setup: {
+          currentFile: {
+            code: `export class Maths {
+                [cursor]add(a, b) {
+                  return a + b;
+                }
+              }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(1, 2);
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(a, b);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        },
+        expected: {
+          currentFile: {
+            code: `export class Maths {
+                add(b, a) {
+                  return a + b;
+                }
+              }`,
+            path: new AbsolutePath("/temp/module.ts")
+          },
+          otherFiles: [
+            {
+              code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(2, 1);
+              `,
+              path: addModule
+            },
+            {
+              code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(b, a);
+                }
+              `,
+              path: anotherModule
+            }
+          ]
+        }
+      }
+    ],
+    async ({ setup, expected }) => {
+      const editor = new InMemoryEditor(setup.currentFile.code);
+      await editor.writeIn(setup.currentFile.path, editor.code);
+      editor.saveUserChoices(userChangePositionOf(0, 1));
+      editor.saveUserChoices(userChangePositionOf(1, 0));
+      await saveOtherFiles(setup, editor);
+
+      await changeSignature(editor);
+
+      const extracted = await editor.codeOf(setup.currentFile.path);
+      expect(extracted).toBe(expected.currentFile.code);
+      await validateOutput(expected, editor);
+    }
+  );
+
+  describe("Adding parameter", () => {
+    testEach<{
+      setup: {
+        currentFile: TestSample;
+        otherFiles: TestSample[];
+      };
+      expected: {
+        currentFile: TestSample;
+        otherFiles: TestSample[];
+      };
+    }>(
+      "should change signature of function with a reference in modules changing order and adding new one",
+      [
+        {
+          description: "that import the function",
+          setup: {
+            currentFile: {
+              code: `export function [cursor]add(a, b) {
+              return a + b;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1, 2)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export function add(b, a, newParameter) {
+              return a + b;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(2, 1, 100)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(b, a, 100);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        },
+        {
+          description: "that import arrow function",
+          setup: {
+            currentFile: {
+              code: `export const add = [cursor](a, b) => {
+              return a + b;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1, 2)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export const add = (b, a, newParameter) => {
+              return a + b;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(2, 1, 100)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(b, a, 100);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        },
+        {
+          description: "that import a class and use a method",
+          setup: {
+            currentFile: {
+              code: `export class Maths {
+                [cursor]add(a, b) {
+                  return a + b;
+                }
+              }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(1, 2);
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export class Maths {
+                add(b, a, newParameter) {
+                  return a + b;
+                }
+              }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(2, 1, 100);
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(b, a, 100);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        }
+      ],
+      async ({ setup, expected }) => {
+        const editor = new InMemoryEditor(setup.currentFile.code);
+        await editor.writeIn(setup.currentFile.path, editor.code);
+        editor.saveUserChoices(userChangePositionOf(0, 1));
+        editor.saveUserChoices(userChangePositionOf(1, 0));
+        editor.saveUserChoices(
+          userChangePositionOf(-1, 2, "newParameter", "100")
+        );
+        await saveOtherFiles(setup, editor);
+
+        await changeSignature(editor);
+
+        const extracted = await editor.codeOf(setup.currentFile.path);
+        expect(extracted).toBe(expected.currentFile.code);
+        await validateOutput(expected, editor);
+      }
+    );
+  });
+
+  describe("Removing parameter", () => {
+    testEach<{
+      setup: {
+        currentFile: TestSample;
+        otherFiles: TestSample[];
+      };
+      expected: {
+        currentFile: TestSample;
+        otherFiles: TestSample[];
+      };
+    }>(
+      "should change signature of function with a reference in modules",
+      [
+        {
+          description: "that import the function",
+          setup: {
+            currentFile: {
+              code: `export function [cursor]add(a, b) {
+              return a;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1, 2)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export function add(a) {
+              return a;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        },
+        {
+          description: "that import arrow function",
+          setup: {
+            currentFile: {
+              code: `export const add = [cursor](a, b) => {
+              return a;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1, 2)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export const add = a => {
+              return a;
+            }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {add} from './module';
+                add(1)
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  return add(a);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        },
+        {
+          description: "that import a class and use a method",
+          setup: {
+            currentFile: {
+              code: `export class Maths {
+                [cursor]add(a) {
+                  return a;
+                }
+              }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(1, 2);
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(a, b);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          },
+          expected: {
+            currentFile: {
+              code: `export class Maths {
+                add(a) {
+                  return a;
+                }
+              }`,
+              path: new AbsolutePath("/temp/module.ts")
+            },
+            otherFiles: [
+              {
+                code: `import {Maths} from './module';
+                const maths = new Maths();
+                maths.add(1);
+              `,
+                path: addModule
+              },
+              {
+                code: `import {add} from './anotherModule';
+                export const calculateAdd = (a, b) => {
+                  const maths = new Maths();
+                  return maths.add(a);
+                }
+              `,
+                path: anotherModule
+              }
+            ]
+          }
+        }
+      ],
+      async ({ setup, expected }) => {
+        const editor = new InMemoryEditor(setup.currentFile.code);
+        await editor.writeIn(setup.currentFile.path, editor.code);
+        editor.saveUserChoices(userChangePositionOf(0, 0));
+        editor.saveUserChoices(userChangePositionOf(1, -1));
+        await saveOtherFiles(setup, editor);
+
+        await changeSignature(editor);
+
+        const extracted = await editor.codeOf(setup.currentFile.path);
+        expect(extracted).toBe(expected.currentFile.code);
+        await validateOutput(expected, editor);
+      }
+    );
+  });
+});
+
+function validateOutput(
+  expected: { currentFile: TestSample; otherFiles: TestSample[] },
+  editor: InMemoryEditor
+) {
+  const promises = expected.otherFiles.map(async (file) => {
+    const extracted = await editor.codeOf(file.path);
+    expect(extracted).toBe(file.code);
+  });
+
+  return Promise.all(promises);
+}
+
+function saveOtherFiles(
+  setup: {
+    currentFile: TestSample;
+    otherFiles: TestSample[];
+  },
+  editor: InMemoryEditor
+) {
+  const promises = setup.otherFiles.map(async (file) => {
+    await editor.writeIn(file.path, file.code);
+  });
+
+  return Promise.all(promises);
+}
+
+function userChangePositionOf(
+  startAt: number,
+  endAt: number,
+  label = "irrelevant",
+  value?: string
+): SelectedPosition {
+  const result: SelectedPosition = {
+    label,
+    value: {
+      startAt,
+      endAt
+    }
+  };
+
+  if (value) result.value.val = value;
+
+  return result;
+}
diff --git a/src/refactorings/change-signature/change-signature.test.ts b/src/refactorings/change-signature/change-signature.test.ts
index 65f3a7e6..7362e4ec 100644
--- a/src/refactorings/change-signature/change-signature.test.ts
+++ b/src/refactorings/change-signature/change-signature.test.ts
@@ -1,16 +1,16 @@
 import * as t from "../../ast";
 import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
-import { AbsolutePath, Code, ErrorReason } from "../../editor/editor";
+import {
+  AbsolutePath,
+  Code,
+  ErrorReason,
+  SelectedPosition
+} from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import { testEach } from "../../tests-helpers";
 
 import { changeSignature, createVisitor } from "./change-signature";
 
-type TestSample = {
-  code: Code;
-  path: AbsolutePath;
-};
-
 describe("Change Signature", () => {
   testEach<{
     code: Code;
@@ -299,200 +299,6 @@ describe("Change Signature", () => {
     }
   );
 
-  describe("Modules", () => {
-    const addModule = new AbsolutePath("/temp/add.ts");
-    const anotherModule = new AbsolutePath("/temp/anotherModule");
-
-    testEach<{
-      setup: {
-        currentFile: TestSample;
-        otherFiles: TestSample[];
-      };
-      expected: {
-        currentFile: TestSample;
-        otherFiles: TestSample[];
-      };
-    }>(
-      "should change signature of function with a reference in modules",
-      [
-        {
-          description: "that import the function",
-          setup: {
-            currentFile: {
-              code: `export function [cursor]add(a, b) {
-              return a + b;
-            }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {add} from './module';
-                add(1, 2)
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  return add(a, b);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          },
-          expected: {
-            currentFile: {
-              code: `export function add(b, a) {
-              return a + b;
-            }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {add} from './module';
-                add(2, 1)
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  return add(b, a);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          }
-        },
-        {
-          description: "that import arrow function",
-          setup: {
-            currentFile: {
-              code: `export const add = [cursor](a, b) => {
-              return a + b;
-            }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {add} from './module';
-                add(1, 2)
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  return add(a, b);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          },
-          expected: {
-            currentFile: {
-              code: `export const add = (b, a) => {
-              return a + b;
-            }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {add} from './module';
-                add(2, 1)
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  return add(b, a);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          }
-        },
-        {
-          description: "that import a class and use a method",
-          setup: {
-            currentFile: {
-              code: `export class Maths {
-                [cursor]add(a, b) {
-                  return a + b;
-                }
-              }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {Maths} from './module';
-                const maths = new Maths();
-                maths.add(1, 2);
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  const maths = new Maths();
-                  return maths.add(a, b);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          },
-          expected: {
-            currentFile: {
-              code: `export class Maths {
-                add(b, a) {
-                  return a + b;
-                }
-              }`,
-              path: new AbsolutePath("/temp/module.ts")
-            },
-            otherFiles: [
-              {
-                code: `import {Maths} from './module';
-                const maths = new Maths();
-                maths.add(2, 1);
-              `,
-                path: addModule
-              },
-              {
-                code: `import {add} from './anotherModule';
-                export const calculateAdd = (a, b) => {
-                  const maths = new Maths();
-                  return maths.add(b, a);
-                }
-              `,
-                path: anotherModule
-              }
-            ]
-          }
-        }
-      ],
-      async ({ setup, expected }) => {
-        const editor = new InMemoryEditor(setup.currentFile.code);
-        await editor.writeIn(setup.currentFile.path, editor.code);
-        editor.saveUserChoices(userChangePositionOf(0, 1));
-        editor.saveUserChoices(userChangePositionOf(1, 0));
-        await saveOtherFiles(setup, editor);
-
-        await changeSignature(editor);
-
-        const extracted = await editor.codeOf(setup.currentFile.path);
-        expect(extracted).toBe(expected.currentFile.code);
-        await validateOutput(expected, editor);
-      }
-    );
-  });
-
   it("should show an error message if refactoring can't be made because rest param should be the last", async () => {
     const code = `
     function [cursor]aFn(a, ...args) {
@@ -512,7 +318,7 @@ describe("Change Signature", () => {
     expect(editor.showError).toBeCalledWith(ErrorReason.CantChangeSignature);
   });
 
-  it("Should order correclty for complex parameters with defaults values", async () => {
+  it("Should order correctly for complex parameters with defaults values", async () => {
     const { setup, expected } = {
       setup: {
         currentFile: `function [cursor]add(a, str, {item}, [value = 1]) {
@@ -582,40 +388,358 @@ describe("Change Signature", () => {
       expect(canConvert).toBeFalsy();
     }
   );
-});
 
-function validateOutput(
-  expected: { currentFile: TestSample; otherFiles: TestSample[] },
-  editor: InMemoryEditor
-) {
-  const promises = expected.otherFiles.map(async (file) => {
-    const extracted = await editor.codeOf(file.path);
-    expect(extracted).toBe(file.code);
+  describe("Adding new parameter", () => {
+    testEach<{
+      code: Code;
+      expected: Code;
+      newValue: string;
+    }>(
+      "In same file",
+      [
+        {
+          description: "is able to add <boolean> parameter in a function",
+          newValue: "true",
+          code: `function [cursor]add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, "years");`,
+          expected: `function add(a, b, newParam) {
+            console.log(a, b)
+          }
+
+          add(7, "years", true);`
+        },
+        {
+          description: "is able to add <number> parameter in a function",
+          newValue: "120",
+          code: `function [cursor]add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, "years");`,
+          expected: `function add(a, b, newParam) {
+            console.log(a, b)
+          }
+
+          add(7, "years", 120);`
+        },
+        {
+          description: "is able to add <array> parameter in a function",
+          newValue: "[1, 2, 3]",
+          code: `function [cursor]add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, "years");`,
+          expected: `function add(a, b, newParam) {
+            console.log(a, b)
+          }
+
+          add(7, "years", [1, 2, 3]);`
+        },
+        {
+          description:
+            "is able to add <literal object> parameter in a function",
+          newValue: "{ id: 1, name: 'Abracadabra' }",
+          code: `function [cursor]add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, "years");`,
+          expected: `function add(a, b, newParam) {
+            console.log(a, b)
+          }
+
+          add(7, "years", { id: 1, name: 'Abracadabra' });`
+        },
+        {
+          description:
+            "is able to add <instance class> parameter in a function",
+          newValue: "new AbsolutePath('/temp/')",
+          code: `function [cursor]add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, "years");`,
+          expected: `function add(a, b, newParam) {
+            console.log(a, b)
+          }
+
+          add(7, "years", new AbsolutePath('/temp/'));`
+        },
+        {
+          description: "is able to add <boolean> parameter in a class method",
+          newValue: "true",
+          code: `class Math {
+            [cursor]add(a, b) {
+              console.log(a, b)
+            }
+          }
+
+          math.add(7, "years");`,
+          expected: `class Math {
+            add(a, b, newParam) {
+              console.log(a, b)
+            }
+          }
+
+          math.add(7, "years", true);`
+        },
+        {
+          description: "is able to add <array> parameter in a class method",
+          newValue: "[1, 2, 3]",
+          code: `class Math {
+            [cursor]add(a, b) {
+              console.log(a, b)
+            }
+          }
+
+          math.add(7, "years");`,
+          expected: `class Math {
+            add(a, b, newParam) {
+              console.log(a, b)
+            }
+          }
+
+          math.add(7, "years", [1, 2, 3]);`
+        },
+        {
+          description: "is able to add <array> parameter in an arrow function",
+          newValue: "[true]",
+          code: `const add = [cursor](a, b) => {
+            return a + b;
+          }
+
+          add(7, "years");`,
+          expected: `const add = (a, b, newParam) => {
+            return a + b;
+          }
+
+          add(7, "years", [true]);`
+        }
+      ],
+      async ({ code, expected, newValue }) => {
+        const path = new AbsolutePath("/temp/file.ts");
+        const editor = new InMemoryEditor(code);
+        await editor.writeIn(path, editor.code);
+        editor.saveUserChoices(userChangePositionOf(0, 0));
+        editor.saveUserChoices(userChangePositionOf(1, 1));
+        editor.saveUserChoices(
+          userChangePositionOf(-1, 2, "newParam", newValue)
+        );
+
+        await changeSignature(editor);
+
+        const extracted = await editor.codeOf(path);
+        expect(extracted).toBe(expected);
+      }
+    );
   });
 
-  return Promise.all(promises);
-}
+  describe("Removing a parameter", () => {
+    testEach<{
+      code: Code;
+      expected: Code;
+    }>(
+      "In same file",
+      [
+        {
+          description: "is able to remove <boolean> parameter in a function",
+          code: `function [cursor]add(a, b) {
+            console.log(a)
+          }
+
+          add(7, true);`,
+          expected: `function add(a) {
+            console.log(a)
+          }
+
+          add(7);`
+        },
+        {
+          description:
+            "is able to remove <array> parameter with default value in a function",
+          code: `function [cursor]add(a, b = []) {
+            console.log(a)
+          }
 
-function saveOtherFiles(
-  setup: {
-    currentFile: TestSample;
-    otherFiles: TestSample[];
-  },
-  editor: InMemoryEditor
-) {
-  const promises = setup.otherFiles.map(async (file) => {
-    await editor.writeIn(file.path, file.code);
+          add(7);`,
+          expected: `function add(a) {
+            console.log(a)
+          }
+
+          add(7);`
+        },
+        {
+          description:
+            "is able to remove <literal object> parameter in a function",
+          code: `function [cursor]add(a, b) {
+            console.log(a)
+          }
+
+          add(7, { id: 1, name: 'Abracadabra' });`,
+          expected: `function add(a) {
+            console.log(a)
+          }
+
+          add(7);`
+        },
+        {
+          description:
+            "is able to remove <instance class> parameter in a function",
+          code: `function [cursor]add(a, b) {
+            console.log(a)
+          }
+
+          add(7, new AbsolutePath('/temp/'));`,
+          expected: `function add(a) {
+            console.log(a)
+          }
+
+          add(7);`
+        },
+        {
+          description:
+            "is able to remove <boolean> parameter with default value in a class method",
+          code: `class Math {
+            [cursor]add(a, b = true) {
+              console.log(a)
+            }
+          }
+
+          math.add(7, "years");`,
+          expected: `class Math {
+            add(a) {
+              console.log(a)
+            }
+          }
+
+          math.add(7);`
+        },
+        {
+          description:
+            "is able to remove <array> parameter in an arrow function",
+          code: `const add = [cursor](a, b) => {
+            return a;
+          }
+
+          add(7, []);`,
+          expected: `const add = a => {
+            return a;
+          }
+
+          add(7);`
+        }
+      ],
+      async ({ code, expected }) => {
+        const path = new AbsolutePath("/temp/file.ts");
+        const editor = new InMemoryEditor(code);
+        await editor.writeIn(path, editor.code);
+        editor.saveUserChoices(userChangePositionOf(0, 0));
+        editor.saveUserChoices(userChangePositionOf(1, -1));
+
+        await changeSignature(editor);
+
+        const extracted = await editor.codeOf(path);
+        expect(extracted).toBe(expected);
+      }
+    );
+
+    it("Should be able to remove first parameter of a function with multiples parameters", async () => {
+      const code = `function [cursor]add(a, b) {
+            console.log(b)
+          }
+
+          add(7, { id: 1, name: 'Abracadabra' });`;
+      const expected = `function add(b) {
+            console.log(b)
+          }
+
+          add({ id: 1, name: 'Abracadabra' });`;
+
+      const path = new AbsolutePath("/temp/file.ts");
+      const editor = new InMemoryEditor(code);
+      await editor.writeIn(path, editor.code);
+      editor.saveUserChoices(userChangePositionOf(0, -1));
+      editor.saveUserChoices(userChangePositionOf(1, 0));
+
+      await changeSignature(editor);
+
+      const extracted = await editor.codeOf(path);
+      expect(extracted).toBe(expected);
+    });
+
+    it("Should be able to remove multiple parameters", async () => {
+      const code = `function [cursor]add(a, b, paramOne, paramTwo) {
+            console.log(a, b)
+          }
+
+          add(7, 8, -9, -99);`;
+      const expected = `function add(a, b) {
+            console.log(a, b)
+          }
+
+          add(7, 8);`;
+
+      const path = new AbsolutePath("/temp/file.ts");
+      const editor = new InMemoryEditor(code);
+      await editor.writeIn(path, editor.code);
+      editor.saveUserChoices(userChangePositionOf(0, 0));
+      editor.saveUserChoices(userChangePositionOf(1, 1));
+      editor.saveUserChoices(userChangePositionOf(2, -1));
+      editor.saveUserChoices(userChangePositionOf(3, -1));
+
+      await changeSignature(editor);
+
+      const extracted = await editor.codeOf(path);
+      expect(extracted).toBe(expected);
+    });
   });
 
-  return Promise.all(promises);
-}
+  it("Should be able to combine add new parameters and remove some", async () => {
+    const code = `function [cursor]add(a, b, c) {
+            console.log(b)
+          }
+
+          add(7, { id: 1, name: 'Abracadabra' }, [1]);`;
+    const expected = `function add(b, newParam) {
+            console.log(b)
+          }
 
-function userChangePositionOf(startAt: number, endAt: number) {
-  return {
-    label: "irrelevant",
+          add({ id: 1, name: 'Abracadabra' }, true);`;
+
+    const path = new AbsolutePath("/temp/file.ts");
+    const editor = new InMemoryEditor(code);
+    await editor.writeIn(path, editor.code);
+    editor.saveUserChoices(userChangePositionOf(0, -1, "a"));
+    editor.saveUserChoices(userChangePositionOf(1, 0, "b"));
+    editor.saveUserChoices(userChangePositionOf(2, -1, "c"));
+    editor.saveUserChoices(userChangePositionOf(-1, 1, "newParam", "true"));
+
+    await changeSignature(editor);
+
+    const extracted = await editor.codeOf(path);
+    expect(extracted).toBe(expected);
+  });
+});
+
+function userChangePositionOf(
+  startAt: number,
+  endAt: number,
+  label = "irrelevant",
+  value?: string
+): SelectedPosition {
+  const result: SelectedPosition = {
+    label,
     value: {
       startAt,
       endAt
     }
   };
+
+  if (value) result.value.val = value;
+
+  return result;
 }